From fd743e8230e03d451a1670554b0fca121ccf2059 Mon Sep 17 00:00:00 2001 From: Frederic Dartayre <30438455+fdartayre@users.noreply.github.com> Date: Wed, 22 Apr 2020 21:03:43 +0200 Subject: [PATCH 001/116] Fix script.cache.max_size typo in Cisco module docs (#17856) Fix a typo in the Filebeat Cisco module docs. The setting is script.cache.max_size and not script.cache_max_size From 61fe9fc533bc86af0cb0486a3740bf68d96556d5 Mon Sep 17 00:00:00 2001 From: Andrew Kroh Date: Wed, 22 Apr 2020 16:46:30 -0400 Subject: [PATCH 002/116] Fix script.cache.max_size typo in Cisco module docs (#17912) Fix a typo in the Filebeat Cisco module docs. The setting is script.cache.max_size and not script.cache_max_size. Fixes #17856 Co-authored-by: Frederic Dartayre <30438455+fdartayre@users.noreply.github.com> --- filebeat/docs/modules/cisco.asciidoc | 2 +- x-pack/filebeat/module/cisco/_meta/docs.asciidoc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/filebeat/docs/modules/cisco.asciidoc b/filebeat/docs/modules/cisco.asciidoc index 14d571e6172..e252aacbf68 100644 --- a/filebeat/docs/modules/cisco.asciidoc +++ b/filebeat/docs/modules/cisco.asciidoc @@ -294,7 +294,7 @@ parameters on your Elasticsearch cluster: - {ref}/circuit-breaker.html#script-compilation-circuit-breaker[script.max_compilations_rate]: Increase to at least `100/5m`. -- {ref}/modules-scripting-using.html#modules-scripting-using-caching[script.cache_max_size]: +- {ref}/modules-scripting-using.html#modules-scripting-using-caching[script.cache.max_size]: Increase to at least `200` if using both filesets or other script-heavy modules. [float] diff --git a/x-pack/filebeat/module/cisco/_meta/docs.asciidoc b/x-pack/filebeat/module/cisco/_meta/docs.asciidoc index f1a40037f6e..b72070d4918 100644 --- a/x-pack/filebeat/module/cisco/_meta/docs.asciidoc +++ b/x-pack/filebeat/module/cisco/_meta/docs.asciidoc @@ -289,7 +289,7 @@ parameters on your Elasticsearch cluster: - {ref}/circuit-breaker.html#script-compilation-circuit-breaker[script.max_compilations_rate]: Increase to at least `100/5m`. -- {ref}/modules-scripting-using.html#modules-scripting-using-caching[script.cache_max_size]: +- {ref}/modules-scripting-using.html#modules-scripting-using-caching[script.cache.max_size]: Increase to at least `200` if using both filesets or other script-heavy modules. [float] From 07d0c8c1b4fe16da1bdf474ae3cf6eb8b1cb2b89 Mon Sep 17 00:00:00 2001 From: Adrian Serrano Date: Thu, 23 Apr 2020 10:35:21 +0200 Subject: [PATCH 003/116] Fix setup.dashboards.index not working (#17749) Due to type casting nightmare, the setting of `setup.dashboards.index` configuration option to replace the index name in use for dashboards and index pattern wasn't being honored. Fixes #14019 --- CHANGELOG.next.asciidoc | 1 + libbeat/dashboards/kibana_loader.go | 14 ++- libbeat/dashboards/modify_json.go | 52 +++++++--- libbeat/dashboards/modify_json_test.go | 134 +++++++++++++++++++++++++ 4 files changed, 183 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 71aae915595..8076c533689 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -76,6 +76,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Fix building on FreeBSD by removing build flags from `add_cloudfoundry_metadata` processor. {pull}17486[17486] - Do not rotate log files on startup when interval is configured and rotateonstartup is disabled. {pull}17613[17613] - Fix goroutine leak and Elasticsearch output file descriptor leak when output reloading is in use. {issue}10491[10491] {pull}17381[17381] +- Fix `setup.dashboards.index` setting not working. {pull}17749[17749] *Auditbeat* diff --git a/libbeat/dashboards/kibana_loader.go b/libbeat/dashboards/kibana_loader.go index 93dd0e5dc0e..1733f94750c 100644 --- a/libbeat/dashboards/kibana_loader.go +++ b/libbeat/dashboards/kibana_loader.go @@ -25,6 +25,9 @@ import ( "net/url" "time" + "github.com/joeshaw/multierror" + "github.com/pkg/errors" + "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/libbeat/kibana" "github.com/elastic/beats/v7/libbeat/logp" @@ -101,11 +104,18 @@ func (loader KibanaLoader) ImportIndexFile(file string) error { // ImportIndex imports the passed index pattern to Kibana func (loader KibanaLoader) ImportIndex(pattern common.MapStr) error { + var errs multierror.Errors + params := url.Values{} params.Set("force", "true") //overwrite the existing dashboards - indexContent := ReplaceIndexInIndexPattern(loader.config.Index, pattern) - return loader.client.ImportJSON(importAPI, params, indexContent) + if err := ReplaceIndexInIndexPattern(loader.config.Index, pattern); err != nil { + errs = append(errs, errors.Wrapf(err, "error setting index '%s' in index pattern", loader.config.Index)) + } + if err := loader.client.ImportJSON(importAPI, params, pattern); err != nil { + errs = append(errs, errors.Wrap(err, "error loading index pattern")) + } + return errs.Err() } // ImportDashboard imports the dashboard file diff --git a/libbeat/dashboards/modify_json.go b/libbeat/dashboards/modify_json.go index c8b1c79da6b..2e0c48e38b0 100644 --- a/libbeat/dashboards/modify_json.go +++ b/libbeat/dashboards/modify_json.go @@ -22,6 +22,8 @@ import ( "encoding/json" "fmt" + "github.com/pkg/errors" + "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/libbeat/logp" ) @@ -41,32 +43,50 @@ type JSONFormat struct { Objects []JSONObject `json:"objects"` } -func ReplaceIndexInIndexPattern(index string, content common.MapStr) common.MapStr { +func ReplaceIndexInIndexPattern(index string, content common.MapStr) (err error) { if index == "" { - return content + return nil } - objects, ok := content["objects"].([]interface{}) + list, ok := content["objects"] if !ok { - return content + return errors.New("empty index pattern") } - // change index pattern name - for i, object := range objects { - objectMap, ok := object.(map[string]interface{}) - if !ok { - continue + updateObject := func(obj common.MapStr) { + // This uses Put instead of DeepUpdate to avoid modifying types for + // inner objects. (DeepUpdate will replace maps with MapStr). + obj.Put("id", index) + // Only overwrite title if it exists. + if _, err := obj.GetValue("attributes.title"); err == nil { + obj.Put("attributes.title", index) } + } - objectMap["id"] = index - if attributes, ok := objectMap["attributes"].(map[string]interface{}); ok { - attributes["title"] = index + switch v := list.(type) { + case []interface{}: + for _, objIf := range v { + switch obj := objIf.(type) { + case common.MapStr: + updateObject(obj) + case map[string]interface{}: + updateObject(obj) + default: + return errors.Errorf("index pattern object has unexpected type %T", v) + } } - objects[i] = objectMap + case []map[string]interface{}: + for _, obj := range v { + updateObject(obj) + } + case []common.MapStr: + for _, obj := range v { + updateObject(obj) + } + default: + return errors.Errorf("index pattern objects have unexpected type %T", v) } - content["objects"] = objects - - return content + return nil } func replaceIndexInSearchObject(index string, savedObject string) (string, error) { diff --git a/libbeat/dashboards/modify_json_test.go b/libbeat/dashboards/modify_json_test.go index 08fedac4df3..a7424414b53 100644 --- a/libbeat/dashboards/modify_json_test.go +++ b/libbeat/dashboards/modify_json_test.go @@ -111,3 +111,137 @@ func TestReplaceIndexInDashboardObject(t *testing.T) { assert.Equal(t, test.expected, result) } } + +func TestReplaceIndexInIndexPattern(t *testing.T) { + // Test that replacing of index name in index pattern works no matter + // what the inner types are (MapStr, map[string]interface{} or interface{}). + // Also ensures that the inner types are not modified after replacement. + tests := []struct { + title string + input common.MapStr + index string + expected common.MapStr + }{ + { + title: "Replace in []interface(map).map", + input: common.MapStr{"objects": []interface{}{map[string]interface{}{ + "id": "phonybeat-*", + "type": "index-pattern", + "attributes": map[string]interface{}{ + "title": "phonybeat-*", + "timeFieldName": "@timestamp", + }}}}, + index: "otherindex-*", + expected: common.MapStr{"objects": []interface{}{map[string]interface{}{ + "id": "otherindex-*", + "type": "index-pattern", + "attributes": map[string]interface{}{ + "title": "otherindex-*", + "timeFieldName": "@timestamp", + }}}}, + }, + { + title: "Replace in []interface(map).mapstr", + input: common.MapStr{"objects": []interface{}{map[string]interface{}{ + "id": "phonybeat-*", + "type": "index-pattern", + "attributes": common.MapStr{ + "title": "phonybeat-*", + "timeFieldName": "@timestamp", + }}}}, + index: "otherindex-*", + expected: common.MapStr{"objects": []interface{}{map[string]interface{}{ + "id": "otherindex-*", + "type": "index-pattern", + "attributes": common.MapStr{ + "title": "otherindex-*", + "timeFieldName": "@timestamp", + }}}}, + }, + { + title: "Replace in []map.mapstr", + input: common.MapStr{"objects": []map[string]interface{}{{ + "id": "phonybeat-*", + "type": "index-pattern", + "attributes": common.MapStr{ + "title": "phonybeat-*", + "timeFieldName": "@timestamp", + }}}}, + index: "otherindex-*", + expected: common.MapStr{"objects": []map[string]interface{}{{ + "id": "otherindex-*", + "type": "index-pattern", + "attributes": common.MapStr{ + "title": "otherindex-*", + "timeFieldName": "@timestamp", + }}}}, + }, + { + title: "Replace in []mapstr.mapstr", + input: common.MapStr{"objects": []common.MapStr{{ + "id": "phonybeat-*", + "type": "index-pattern", + "attributes": common.MapStr{ + "title": "phonybeat-*", + "timeFieldName": "@timestamp", + }}}}, + index: "otherindex-*", + expected: common.MapStr{"objects": []common.MapStr{{ + "id": "otherindex-*", + "type": "index-pattern", + "attributes": common.MapStr{ + "title": "otherindex-*", + "timeFieldName": "@timestamp", + }}}}, + }, + { + title: "Replace in []mapstr.interface(mapstr)", + input: common.MapStr{"objects": []common.MapStr{{ + "id": "phonybeat-*", + "type": "index-pattern", + "attributes": interface{}(common.MapStr{ + "title": "phonybeat-*", + "timeFieldName": "@timestamp", + })}}}, + index: "otherindex-*", + expected: common.MapStr{"objects": []common.MapStr{{ + "id": "otherindex-*", + "type": "index-pattern", + "attributes": interface{}(common.MapStr{ + "title": "otherindex-*", + "timeFieldName": "@timestamp", + })}}}, + }, + { + title: "Do not create missing attributes", + input: common.MapStr{"objects": []common.MapStr{{ + "id": "phonybeat-*", + "type": "index-pattern", + }}}, + index: "otherindex-*", + expected: common.MapStr{"objects": []common.MapStr{{ + "id": "otherindex-*", + "type": "index-pattern", + }}}, + }, + { + title: "Create missing id", + input: common.MapStr{"objects": []common.MapStr{{ + "type": "index-pattern", + }}}, + index: "otherindex-*", + expected: common.MapStr{"objects": []common.MapStr{{ + "id": "otherindex-*", + "type": "index-pattern", + }}}, + }, + } + + for _, test := range tests { + t.Run(test.title, func(t *testing.T) { + err := ReplaceIndexInIndexPattern(test.index, test.input) + assert.NoError(t, err) + assert.Equal(t, test.expected, test.input) + }) + } +} From be3ec38a550cdcd527d387d2d59572a10939213d Mon Sep 17 00:00:00 2001 From: Ivan Fernandez Calvo Date: Thu, 23 Apr 2020 11:11:35 +0200 Subject: [PATCH 004/116] fix: mount Docker credentials (#17798) * fix: mount Docker credentials * Apply suggestions from code review * Apply suggestions from code review --- Jenkinsfile | 12 +++++------- x-pack/metricbeat/docker-compose.yml | 1 + 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 4480a2af642..aa2efab218c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -647,14 +647,8 @@ def withBeatsEnv(boolean archive, Closure body) { ]) { deleteDir() unstash 'source' - if(os == 'linux'){ + if(isDockerInstalled()){ dockerLogin(secret: "${DOCKERELASTIC_SECRET}", registry: "${DOCKER_REGISTRY}") - // FIXME workaround until we fix the packer cache - // Retry to avoid DDoS detection from the server - retry(3) { - sleep randomNumber(min: 2, max: 5) - sh 'docker pull docker.elastic.co/observability-ci/database-enterprise:12.2.0.1' - } } dir("${env.BASE_DIR}") { sh(label: "Install Go ${GO_VERSION}", script: ".ci/scripts/install-go.sh") @@ -954,3 +948,7 @@ def setGitConfig(){ fi ''') } + +def isDockerInstalled(){ + return sh(label: 'check for Docker', script: 'command -v docker', returnStatus: true) +} diff --git a/x-pack/metricbeat/docker-compose.yml b/x-pack/metricbeat/docker-compose.yml index c89b558605d..83ac016ffd7 100644 --- a/x-pack/metricbeat/docker-compose.yml +++ b/x-pack/metricbeat/docker-compose.yml @@ -10,6 +10,7 @@ services: volumes: - ${PWD}/../..:/go/src/github.com/elastic/beats/ - /var/run/docker.sock:/var/run/docker.sock + - ${HOME}/.docker:/root/.docker:ro network_mode: host command: make From 3940cc0f5f5a3ab77d6b7376cacd5177fcaf6515 Mon Sep 17 00:00:00 2001 From: Mariana Dima Date: Thu, 23 Apr 2020 12:53:29 +0200 Subject: [PATCH 005/116] Metricbeat: Move windows/perfmon metricset to GA (#17879) * move perfmon metricset to GA * update changelog --- CHANGELOG.next.asciidoc | 1 + metricbeat/docs/modules/windows/perfmon.asciidoc | 2 -- metricbeat/docs/modules_list.asciidoc | 2 +- metricbeat/module/windows/fields.go | 2 +- metricbeat/module/windows/perfmon/_meta/fields.yml | 2 +- metricbeat/module/windows/perfmon/perfmon.go | 3 --- 6 files changed, 4 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 8076c533689..d3fbeb5e862 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -342,6 +342,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Reference kubernetes manifests mount data directory from the host when running metricbeat as daemonset, so data persist between executions in the same node. {pull}17429[17429] - Add more detailed error messages, system tests and small refactoring to the service metricset in windows. {pull}17725[17725] - Stack Monitoring modules now auto-configure required metricsets when `xpack.enabled: true` is set. {issue}16471[[16471] {pull}17609[17609] +- Move the perfmon metricset to GA. {issue}16608[16608] {pull}17879[17879] *Packetbeat* diff --git a/metricbeat/docs/modules/windows/perfmon.asciidoc b/metricbeat/docs/modules/windows/perfmon.asciidoc index f3f53901205..20df688c5eb 100644 --- a/metricbeat/docs/modules/windows/perfmon.asciidoc +++ b/metricbeat/docs/modules/windows/perfmon.asciidoc @@ -5,8 +5,6 @@ This file is generated! See scripts/mage/docs_collector.go [[metricbeat-metricset-windows-perfmon]] === Windows perfmon metricset -beta[] - include::../../../module/windows/perfmon/_meta/docs.asciidoc[] diff --git a/metricbeat/docs/modules_list.asciidoc b/metricbeat/docs/modules_list.asciidoc index b324627d173..6bdb22404f5 100644 --- a/metricbeat/docs/modules_list.asciidoc +++ b/metricbeat/docs/modules_list.asciidoc @@ -264,7 +264,7 @@ This file is generated! See scripts/mage/docs_collector.go |<> |<> |<> |image:./images/icon-yes.png[Prebuilt dashboards are available] | -.2+| .2+| |<> beta[] +.2+| .2+| |<> |<> |<> |image:./images/icon-yes.png[Prebuilt dashboards are available] | .3+| .3+| |<> diff --git a/metricbeat/module/windows/fields.go b/metricbeat/module/windows/fields.go index 1087083cc13..8b21143cdb4 100644 --- a/metricbeat/module/windows/fields.go +++ b/metricbeat/module/windows/fields.go @@ -32,5 +32,5 @@ func init() { // AssetWindows returns asset data. // This is the base64 encoded gzipped contents of ../metricbeat/module/windows. func AssetWindows() string { - return "eJysVlFv2zYQfvevOOSlQJEYa/s0PxTw4mXzsKRFk8IYEMCiqZN1C0WqvKMdA/vxAyXZkW05dozqoVB51Hffd99HM1fwhKsBLMmmbsk9ACExOICLSb1y0QNIkbWnUsjZAXzuAQDcujQYhMx5mGw+5dx5mWpnM5oPIFOGsQfg0aBiHMBc9QAyQpPyoAK5AqsKbDePj6zKuNm7UDYrHf23gdpgJfqscHaz3gUYnw2tGYpqrXc2q5996F0SLzTIsiircQugZvKEq6Xz6VZlq+t/WyWAcYMFC2UC9ju6FSieNPff9993NHSzf1HLVqFemtb1zDh1sDwtVFmSnTd7L95fHCb+eYf4bUWrps3gUYK3mL4I2DOP0S9oa2jd5r3SNWkwEtDOiiLLIDkCi5LA7cCum3G/KxVz9arNLaPTHdGHTQbAZ1WU8Xjlk093f93oD8NyZ8er8wQYQrD0IyCMR5WWSlqtow9jAWJQkCvOwWVVsVA6J4vvGP74Ph6Bsmlc3sNtMCpNHf60Bcd/z5E8Qc0L/Ta5Dy/yTqGWEpdGraZnU2yS8fsCrcC1Mwa1OP92zg2RitbaibVNr0tgUb4+eG8QcMoUI24oK5hdRtWG0jHTzOD6tCqPkAyDuEIJ6eRyDzX5zTlJLiEZEauZwTS+3yoblEkuq6Al9ysWLJKTJJ/r2d0DDL8//Pnl2/jhn8e/nVbmfu9H5IQZDbV2wUrtWLApeljmpHNQmwD6YPmIlFJJfraS68HjZHw3+jK5f+RqcJ8+PvJC546lj88IV0/Q1gdXb/ztuAnGrOBHUIYywrQiC+KqKGRkECRXAhTJFGiF2xnZt5+sNiElOwfl56H64LjP8pNTrbQEZWrk01N97ayQDWTnXbH+qgJXpfq1zvW3YG2zeB/junl31eW4CXz8P6bHEo/PFP9OSn/iNG6cf2m/udpAcuJ4KcTBoPfOQ2xbO73JNZbOC+9BLnO09eGMLosDbtTG0RLDkoyBGVbYc7QYr/qdu3UPs8UhWIO8FTIovVtQGm1aL11xiZoy0q0vj53BAzeycXZ+6Oh9+OXXj2fMe52K7nkrZqdJSTxq3uko9ut4dIR9KIUK7Be7dhzUkDlfKBlAGryKZHfKZMsg0/WmgowhRu1sutvg9Jv4HTcsoTEHUyC7hd3v/R8AAP//KJpZdg==" + return "eJysVl1v2zYUffevuMhLgSIx1vZpfiiQxcvmYUmLJoUxIIDFkFfWXShS5b20Y2A/fqAkO5Y/YscoHwKFpM49555D0RfwhIsBzMkZP+cegJBYHMDZuJk56wEYZB2oEvJuAJ97AAA33kSLkPsA49WrXPggE+1dTtMB5Moy9gACWlSMA5iqHkBOaA0PapALcKrE9eJpyKJKm4OPVTuzo34XaB2swpCX3q3mdwGm0aW1HDtLNWMbeJPCCwlyLMpp7AA0PJ5wMffBdFY6Vf/rLAGMWiyYKRuxv6NaiRJIc/99//2Ogv7xX9TSWWimJs16br3auzwpVVWRm7Z7z96f7Sf+eYP4TU2roc0QUGJwaF4EbFnHGGbUadpu616pmrUYGWjvRJFjkAKBRUnk9bgui3H/YCa2bV4z2myI3m8yAD6rskqHqxh/uv3rWn+4rDZ2vNpPgEuIjn5EhNGw1lJLa3T0YSRADAoKxQX4vF4slS7I4TuGP76PhqCcSdNbuC1GrWmHP+uC099TJI9R80y/Te79i7xjqBniyqrF5GSKbTJ+n6ETuPLWohYf3s65JVLTWjqxtOl1CSwqNAfvDQKO6WLCjVUNs8mo3lB5Znq0uDytKiBkl1F8qYR0dr6Fmv3mvWTnkA2J1aNFk55vlIvKZud10LK7BQuW2VGST/Xs9h4uv9//+eXb6P6fh7+9VvZu6yNyRI8utfbRSeNYdAYDzAvSBahVAEN0fEBKpaQ4WcnV4GE8uh1+Gd89cN24Tx8feKYLz9LHZ4SLJ1jXBxdv/HZcR2sX8CMqSzmhqcmC+DoKOVkEKZQAJTIlOuH1jGzbT07baMhNQYVprF847LP85FQrLVHZBvn4VF95J+QiuemuWH9Vkeul5rHJ9bfoXDt5l+K6evb15bgKfPofzaHE4zOlX0nmJ3bj2oeX8qurDaQgTpdCagyG4AOkso3Tq1xj5YPwFuS8QNcczuSyeOBWbWotMczJWnjEGnuKDtNVv3G3bmGucYjOIndCBlXwMzLJpuXUBVeoKSe99uahM7jnRrbeTfcdvQ+//PrxhH4vU7G734rZa1KSjlrwOon9OhoeYB8roRL75aYdezXkPpRKBmBiUInsxjK5Kspkuakka4lRe2c2Cxx/E7/jliW05qABch3sfu//AAAA//8t/Vii" } diff --git a/metricbeat/module/windows/perfmon/_meta/fields.yml b/metricbeat/module/windows/perfmon/_meta/fields.yml index 9e07225e17d..177af776297 100644 --- a/metricbeat/module/windows/perfmon/_meta/fields.yml +++ b/metricbeat/module/windows/perfmon/_meta/fields.yml @@ -1,6 +1,6 @@ - name: perfmon type: group - release: beta + release: ga description: > perfmon fields: diff --git a/metricbeat/module/windows/perfmon/perfmon.go b/metricbeat/module/windows/perfmon/perfmon.go index c0490a34430..7f4712a5f3b 100644 --- a/metricbeat/module/windows/perfmon/perfmon.go +++ b/metricbeat/module/windows/perfmon/perfmon.go @@ -24,7 +24,6 @@ import ( "github.com/pkg/errors" - "github.com/elastic/beats/v7/libbeat/common/cfgwarn" "github.com/elastic/beats/v7/libbeat/logp" "github.com/elastic/beats/v7/metricbeat/mb" ) @@ -43,8 +42,6 @@ type MetricSet struct { // New create a new instance of the MetricSet. func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - cfgwarn.Beta("The perfmon metricset is beta") - var config Config if err := base.Module().UnpackConfig(&config); err != nil { return nil, err From cd5fefdccccf9529c056fb4be303827b2984d4a7 Mon Sep 17 00:00:00 2001 From: Pier-Hugues Pellerin Date: Thu, 23 Apr 2020 08:23:13 -0400 Subject: [PATCH 006/116] [Elastic Agent] Enable more inputs in Filebeat. (#17909) * [Elastic Agent] Enable more inputs in Filebeat. Enable S3, Azureeventhub, cloudfoundry, httpjson, netflow, o365audit We were missing a number of x-pack specific inputs --- x-pack/elastic-agent/CHANGELOG.asciidoc | 1 + x-pack/elastic-agent/pkg/agent/program/supported.go | 2 +- x-pack/elastic-agent/spec/filebeat.yml | 8 ++++++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/x-pack/elastic-agent/CHANGELOG.asciidoc b/x-pack/elastic-agent/CHANGELOG.asciidoc index b81acebc9e8..1271df44646 100644 --- a/x-pack/elastic-agent/CHANGELOG.asciidoc +++ b/x-pack/elastic-agent/CHANGELOG.asciidoc @@ -37,3 +37,4 @@ - Expose stream.* variables in events {pull}17468[17468] - Monitoring configuration reloadable {pull}17855[17855] - Pack ECS metadata to request payload send to fleet {pull}17894[17894] +- Enable Filebeat input: S3, Azureeventhub, cloudfoundry, httpjson, netflow, o365audit. {pull}17909[17909] diff --git a/x-pack/elastic-agent/pkg/agent/program/supported.go b/x-pack/elastic-agent/pkg/agent/program/supported.go index fe62d0afc28..b8b5ddd3a40 100644 --- a/x-pack/elastic-agent/pkg/agent/program/supported.go +++ b/x-pack/elastic-agent/pkg/agent/program/supported.go @@ -19,7 +19,7 @@ func init() { // Packed Files // spec/filebeat.yml // spec/metricbeat.yml - unpacked := packer.MustUnpack("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") + unpacked := packer.MustUnpack("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") SupportedMap = make(map[string]bool) for f, v := range unpacked { diff --git a/x-pack/elastic-agent/spec/filebeat.yml b/x-pack/elastic-agent/spec/filebeat.yml index 232ac9b12bb..3c1adf7d7a9 100644 --- a/x-pack/elastic-agent/spec/filebeat.yml +++ b/x-pack/elastic-agent/spec/filebeat.yml @@ -60,6 +60,14 @@ rules: - docker - redis - syslog + - s3 + - netflow + - httpjson + - o365audit + - azureeventhub + - cloudfoundry + - googlepubsub + - kafka - filter_values: selector: inputs From f1ce8e14b4730ee6e3c16c7b0d9faf1ae55c2a14 Mon Sep 17 00:00:00 2001 From: Andrew Kroh Date: Thu, 23 Apr 2020 08:45:45 -0400 Subject: [PATCH 007/116] Update Platform selector in packaging job (#17908) Specify each target platform that we want a package for. This works around an issue with filters mixing adds ('!') and removes ('+') and the default set that gets applied in this case. --- .ci/packaging.groovy | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/.ci/packaging.groovy b/.ci/packaging.groovy index e35a574c5fa..36734003aba 100644 --- a/.ci/packaging.groovy +++ b/.ci/packaging.groovy @@ -76,7 +76,19 @@ pipeline { } environment { HOME = "${env.WORKSPACE}" - PLATFORMS = "!darwin +linux/armv7 +linux/ppc64le +linux/s390x +linux/mips64" + PLATFORMS = [ + '+all', + 'linux/amd64', + 'linux/386', + 'linux/arm64', + 'linux/armv7', + 'linux/ppc64le', + 'linux/mips64', + 'linux/s390x', + 'windows/amd64', + 'windows/386', + (params.macos ? '' : 'darwin/amd64'), + ].join(' ') } steps { release() @@ -94,7 +106,10 @@ pipeline { } environment { HOME = "${env.WORKSPACE}" - PLATFORMS = "!defaults +darwin/amd64" + PLATFORMS = [ + '+all', + 'darwin/amd64', + ].join(' ') } steps { withMacOSEnv(){ From c46268a08b5e0564e179968b8759b590eaf84e24 Mon Sep 17 00:00:00 2001 From: Chris Mark Date: Thu, 23 Apr 2020 16:10:33 +0300 Subject: [PATCH 008/116] Improve Openshift documentation (#17867) --- deploy/kubernetes/metricbeat-kubernetes.yaml | 5 ++-- .../metricbeat-daemonset-configmap.yaml | 5 ++-- .../docs/running-on-kubernetes.asciidoc | 30 ++++++++++++++++++- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/deploy/kubernetes/metricbeat-kubernetes.yaml b/deploy/kubernetes/metricbeat-kubernetes.yaml index f5550f6ecbe..179f33089e1 100644 --- a/deploy/kubernetes/metricbeat-kubernetes.yaml +++ b/deploy/kubernetes/metricbeat-kubernetes.yaml @@ -79,10 +79,11 @@ data: hosts: ["https://${NODE_NAME}:10250"] bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token ssl.verification_mode: "none" - # If using Red Hat OpenShift remove ssl.verification_mode entry and - # uncomment these settings: + # If there is a CA bundle that contains the issuer of the certificate used in the Kubelet API, + # remove ssl.verification_mode entry and use the CA, for instance: #ssl.certificate_authorities: #- /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt + # Currently `proxy` metricset is not supported on Openshift, comment out section - module: kubernetes metricsets: - proxy diff --git a/deploy/kubernetes/metricbeat/metricbeat-daemonset-configmap.yaml b/deploy/kubernetes/metricbeat/metricbeat-daemonset-configmap.yaml index 8760c3eaa0a..a244dda551a 100644 --- a/deploy/kubernetes/metricbeat/metricbeat-daemonset-configmap.yaml +++ b/deploy/kubernetes/metricbeat/metricbeat-daemonset-configmap.yaml @@ -79,10 +79,11 @@ data: hosts: ["https://${NODE_NAME}:10250"] bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token ssl.verification_mode: "none" - # If using Red Hat OpenShift remove ssl.verification_mode entry and - # uncomment these settings: + # If there is a CA bundle that contains the issuer of the certificate used in the Kubelet API, + # remove ssl.verification_mode entry and use the CA, for instance: #ssl.certificate_authorities: #- /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt + # Currently `proxy` metricset is not supported on Openshift, comment out section - module: kubernetes metricsets: - proxy diff --git a/metricbeat/docs/running-on-kubernetes.asciidoc b/metricbeat/docs/running-on-kubernetes.asciidoc index dfa6cbb25d4..2d757906110 100644 --- a/metricbeat/docs/running-on-kubernetes.asciidoc +++ b/metricbeat/docs/running-on-kubernetes.asciidoc @@ -85,6 +85,15 @@ spec: If you are using Red Hat OpenShift, you need to specify additional settings in the manifest file and enable the container to run as privileged. +. Modify the `DaemonSet` container spec in the manifest file: ++ +[source,yaml] +----- + securityContext: + runAsUser: 0 + privileged: true +----- + . In the manifest file, edit the `metricbeat-daemonset-modules` ConfigMap, and specify the following settings under `kubernetes.yml` in the `data` section: + @@ -103,7 +112,26 @@ specify the following settings under `kubernetes.yml` in the `data` section: hosts: ["https://${NODE_NAME}:10250"] bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token ssl.certificate_authorities: - - /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt + - /path/to/kubelet-service-ca.crt +----- +NOTE: `kubelet-service-ca.crt` can be any CA bundle that contains the issuer of the certificate used in the Kubelet API. +According to each specific installation of Openshift this can be found either in `secrets` or in `configmaps`. +In some installations it can be available as part of the service account secret, in +`/var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt`. +In case of using Openshift installer[https://github.com/openshift/installer/blob/master/docs/user/gcp/install.md] +for GCP then the following `configmap` can be mounted in Metricbeat Pod and use `ca-bundle.crt` +in `ssl.certificate_authorities`: ++ +[source,shell] +----- +Name: kubelet-serving-ca +Namespace: openshift-kube-apiserver +Labels: +Annotations: + +Data +==== +ca-bundle.crt: ----- . Under the `metricbeat` ClusterRole, add the following resources: From 93c3d1591eba2d35a5015751894cf8bd21dbc5e8 Mon Sep 17 00:00:00 2001 From: Lee Hinman <57081003+leehinman@users.noreply.github.com> Date: Thu, 23 Apr 2020 09:09:32 -0500 Subject: [PATCH 009/116] Improve ECS categorization field mappings for nginx module (#17844) - access + event.kind + event.category + event.type + event.outcome + lowercase http.request.method + improve grok to not populate empty fields + related.ip + related.users - error + event.kind + event.category + event.outcome - ingress_controller + event.kind + event.category + event.type + event.outcome + lowercase http.request.method + improve grok to not populate empty fields + related.ip + related.users Closes #16174 --- CHANGELOG.next.asciidoc | 1 + .../module/nginx/access/ingest/default.json | 150 ---------- .../module/nginx/access/ingest/pipeline.yml | 167 +++++++++++ filebeat/module/nginx/access/manifest.yml | 2 +- .../access/test/access.log-expected.json | 178 +++++++++--- .../test/test-with-host.log-expected.json | 152 +++++++--- .../nginx/access/test/test.log-expected.json | 147 +++++++--- .../module/nginx/error/ingest/pipeline.json | 47 --- .../module/nginx/error/ingest/pipeline.yml | 51 ++++ filebeat/module/nginx/error/manifest.yml | 2 +- .../nginx/error/test/error.log-expected.json | 28 ++ .../ingress_controller/ingest/pipeline.json | 151 ---------- .../ingress_controller/ingest/pipeline.yml | 172 +++++++++++ .../nginx/ingress_controller/manifest.yml | 2 +- .../test/test.log-expected.json | 267 +++++++++++++----- 15 files changed, 992 insertions(+), 525 deletions(-) delete mode 100644 filebeat/module/nginx/access/ingest/default.json create mode 100644 filebeat/module/nginx/access/ingest/pipeline.yml delete mode 100644 filebeat/module/nginx/error/ingest/pipeline.json create mode 100644 filebeat/module/nginx/error/ingest/pipeline.yml delete mode 100644 filebeat/module/nginx/ingress_controller/ingest/pipeline.json create mode 100644 filebeat/module/nginx/ingress_controller/ingest/pipeline.yml diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index d3fbeb5e862..e33820df243 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -269,6 +269,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Enhance `elasticsearch/slowlog` fileset to handle ECS-compatible logs emitted by Elasticsearch. {issue}17715[17715] {pull}17729[17729] - Improve ECS categorization field mappings in misp module. {issue}16026[16026] {pull}17344[17344] - Added Unix stream socket support as an input source and a syslog input source. {pull}17492[17492] +- Improve ECS categorization field mappings for nginx module. {issue}16174[16174] {pull}17844[17844] *Heartbeat* diff --git a/filebeat/module/nginx/access/ingest/default.json b/filebeat/module/nginx/access/ingest/default.json deleted file mode 100644 index 04efd885e69..00000000000 --- a/filebeat/module/nginx/access/ingest/default.json +++ /dev/null @@ -1,150 +0,0 @@ -{ - "description": "Pipeline for parsing Nginx access logs. Requires the geoip and user_agent plugins.", - "processors": [ - { - "grok": { - "field": "message", - "patterns": [ - "(%{NGINX_HOST} )?\"?(?:%{NGINX_ADDRESS_LIST:nginx.access.remote_ip_list}|%{NOTSPACE:source.address}) - %{DATA:user.name} \\[%{HTTPDATE:nginx.access.time}\\] \"%{DATA:nginx.access.info}\" %{NUMBER:http.response.status_code:long} %{NUMBER:http.response.body.bytes:long} \"%{DATA:http.request.referrer}\" \"%{DATA:user_agent.original}\"" - ], - "pattern_definitions": { - "NGINX_HOST": "(?:%{IP:destination.ip}|%{NGINX_NOTSEPARATOR:destination.domain})(:%{NUMBER:destination.port})?", - "NGINX_NOTSEPARATOR": "[^\t ,:]+", - "NGINX_ADDRESS_LIST": "(?:%{IP}|%{WORD})(\"?,?\\s*(?:%{IP}|%{WORD}))*" - }, - "ignore_missing": true - } - }, - { - "grok": { - "field": "nginx.access.info", - "patterns": [ - "%{WORD:http.request.method} %{DATA:url.original} HTTP/%{NUMBER:http.version}", - "" - ], - "ignore_missing": true - } - }, - { - "remove": { - "field": "nginx.access.info" - } - }, - { - "split": { - "field": "nginx.access.remote_ip_list", - "separator": "\"?,?\\s+", - "ignore_missing": true - } - }, - { - "split": { - "field": "nginx.access.origin", - "separator": "\"?,?\\s+", - "ignore_missing": true - } - }, - { - "set": { - "field": "source.address", - "if": "ctx.source?.address == null", - "value": "" - } - }, - { - "script": { - "if": "ctx.nginx?.access?.remote_ip_list != null && ctx.nginx.access.remote_ip_list.length > 0", - "lang": "painless", - "source": "boolean isPrivate(def dot, def ip) { try { StringTokenizer tok = new StringTokenizer(ip, dot); int firstByte = Integer.parseInt(tok.nextToken()); int secondByte = Integer.parseInt(tok.nextToken()); if (firstByte == 10) { return true; } if (firstByte == 192 && secondByte == 168) { return true; } if (firstByte == 172 && secondByte >= 16 && secondByte <= 31) { return true; } if (firstByte == 127) { return true; } return false; } catch (Exception e) { return false; } } try { ctx.source.address = null; if (ctx.nginx.access.remote_ip_list == null) { return; } def found = false; for (def item : ctx.nginx.access.remote_ip_list) { if (!isPrivate(params.dot, item)) { ctx.source.address = item; found = true; break; } } if (!found) { ctx.source.address = ctx.nginx.access.remote_ip_list[0]; }} catch (Exception e) { ctx.source.address = null; }", - "params": { - "dot": "." - } - } - }, - { - "remove": { - "field": "source.address", - "if": "ctx.source.address == null" - } - }, - { - "grok": { - "field": "source.address", - "patterns": ["^%{IP:source.ip}$"], - "ignore_failure": true - } - }, - { - "remove": { - "field": "message" - } - }, - { - "rename": { - "field": "@timestamp", - "target_field": "event.created" - } - }, - { - "date": { - "field": "nginx.access.time", - "target_field": "@timestamp", - "formats": [ - "dd/MMM/yyyy:H:m:s Z" - ], - "on_failure": [{"append": {"field": "error.message", "value": "{{ _ingest.on_failure_message }}"}}] - } - }, - { - "remove": { - "field": "nginx.access.time" - } - }, - { - "user_agent": { - "field": "user_agent.original" - } - }, - { - "geoip": { - "field": "source.ip", - "target_field": "source.geo", - "ignore_missing": true - } - }, - { - "geoip": { - "database_file": "GeoLite2-ASN.mmdb", - "field": "source.ip", - "target_field": "source.as", - "properties": [ - "asn", - "organization_name" - ], - "ignore_missing": true - } - }, - { - "rename": { - "field": "source.as.asn", - "target_field": "source.as.number", - "ignore_missing": true - } - }, - { - "rename": { - "field": "source.as.organization_name", - "target_field": "source.as.organization.name", - "ignore_missing": true - } - } - ], - "on_failure": [ - { - "set": { - "field": "error.message", - "value": "{{ _ingest.on_failure_message }}" - } - } - ] -} diff --git a/filebeat/module/nginx/access/ingest/pipeline.yml b/filebeat/module/nginx/access/ingest/pipeline.yml new file mode 100644 index 00000000000..3a41265875b --- /dev/null +++ b/filebeat/module/nginx/access/ingest/pipeline.yml @@ -0,0 +1,167 @@ +description: Pipeline for parsing Nginx access logs. Requires the geoip and user_agent + plugins. +processors: +- grok: + field: message + patterns: + - (%{NGINX_HOST} )?"?(?:%{NGINX_ADDRESS_LIST:nginx.access.remote_ip_list}|%{NOTSPACE:source.address}) + - (-|%{DATA:user.name}) \[%{HTTPDATE:nginx.access.time}\] "%{DATA:nginx.access.info}" + %{NUMBER:http.response.status_code:long} %{NUMBER:http.response.body.bytes:long} + "(-|%{DATA:http.request.referrer})" "(-|%{DATA:user_agent.original})" + pattern_definitions: + NGINX_HOST: (?:%{IP:destination.ip}|%{NGINX_NOTSEPARATOR:destination.domain})(:%{NUMBER:destination.port})? + NGINX_NOTSEPARATOR: "[^\t ,:]+" + NGINX_ADDRESS_LIST: (?:%{IP}|%{WORD})("?,?\s*(?:%{IP}|%{WORD}))* + ignore_missing: true +- grok: + field: nginx.access.info + patterns: + - '%{WORD:http.request.method} %{DATA:url.original} HTTP/%{NUMBER:http.version}' + - "" + ignore_missing: true +- remove: + field: nginx.access.info +- split: + field: nginx.access.remote_ip_list + separator: '"?,?\s+' + ignore_missing: true +- split: + field: nginx.access.origin + separator: '"?,?\s+' + ignore_missing: true +- set: + field: source.address + if: ctx.source?.address == null + value: "" +- script: + if: ctx.nginx?.access?.remote_ip_list != null && ctx.nginx.access.remote_ip_list.length > 0 + lang: painless + source: >- + boolean isPrivate(def dot, def ip) { + try { + StringTokenizer tok = new StringTokenizer(ip, dot); + int firstByte = Integer.parseInt(tok.nextToken()); + int secondByte = Integer.parseInt(tok.nextToken()); + if (firstByte == 10) { + return true; + } + if (firstByte == 192 && secondByte == 168) { + return true; + } + if (firstByte == 172 && secondByte >= 16 && secondByte <= 31) { + return true; + } + if (firstByte == 127) { + return true; + } + return false; + } + catch (Exception e) { + return false; + } + } + try { + ctx.source.address = null; + if (ctx.nginx.access.remote_ip_list == null) { + return; + } + def found = false; + for (def item : ctx.nginx.access.remote_ip_list) { + if (!isPrivate(params.dot, item)) { + ctx.source.address = item; + found = true; + break; + } + } + if (!found) { + ctx.source.address = ctx.nginx.access.remote_ip_list[0]; + } + } + catch (Exception e) { + ctx.source.address = null; + } + params: + dot: . +- remove: + field: source.address + if: ctx.source.address == null +- grok: + field: source.address + patterns: + - ^%{IP:source.ip}$ + ignore_failure: true +- remove: + field: message +- rename: + field: '@timestamp' + target_field: event.created +- date: + field: nginx.access.time + target_field: '@timestamp' + formats: + - dd/MMM/yyyy:H:m:s Z + on_failure: + - append: + field: error.message + value: '{{ _ingest.on_failure_message }}' +- remove: + field: nginx.access.time +- user_agent: + field: user_agent.original + ignore_missing: true +- geoip: + field: source.ip + target_field: source.geo + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: source.ip + target_field: source.as + properties: + - asn + - organization_name + ignore_missing: true +- rename: + field: source.as.asn + target_field: source.as.number + ignore_missing: true +- rename: + field: source.as.organization_name + target_field: source.as.organization.name + ignore_missing: true +- set: + field: event.kind + value: event +- append: + field: event.category + value: web +- append: + field: event.type + value: access +- set: + field: event.outcome + value: success + if: "ctx?.http?.response?.status_code != null && ctx.http.response.status_code < 400" +- set: + field: event.outcome + value: failure + if: "ctx?.http?.response?.status_code != null && ctx.http.response.status_code >= 400" +- lowercase: + field: http.request.method + ignore_missing: true +- append: + field: related.ip + value: "{{source.ip}}" + if: "ctx?.source?.ip != null" +- append: + field: related.ip + value: "{{destination.ip}}" + if: "ctx?.destination?.ip != null" +- append: + field: related.user + value: "{{user.name}}" + if: "ctx?.user?.name != null" +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/filebeat/module/nginx/access/manifest.yml b/filebeat/module/nginx/access/manifest.yml index d3d6211dccc..8d04dd32e5c 100644 --- a/filebeat/module/nginx/access/manifest.yml +++ b/filebeat/module/nginx/access/manifest.yml @@ -9,7 +9,7 @@ var: os.windows: - c:/programdata/nginx/logs/*access.log* -ingest_pipeline: ingest/default.json +ingest_pipeline: ingest/pipeline.yml input: config/nginx-access.yml requires.processors: diff --git a/filebeat/module/nginx/access/test/access.log-expected.json b/filebeat/module/nginx/access/test/access.log-expected.json index a121dd67613..12c94f2996d 100644 --- a/filebeat/module/nginx/access/test/access.log-expected.json +++ b/filebeat/module/nginx/access/test/access.log-expected.json @@ -1,12 +1,19 @@ [ { "@timestamp": "2016-10-25T12:49:33.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.access", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "access" + ], "fileset.name": "access", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 612, "http.response.status_code": 200, "http.version": "1.1", @@ -15,6 +22,9 @@ "nginx.access.remote_ip_list": [ "77.179.66.156" ], + "related.ip": [ + "77.179.66.156" + ], "service.type": "nginx", "source.address": "77.179.66.156", "source.as.number": 6805, @@ -28,7 +38,6 @@ "source.geo.region_name": "Rheinland-Pfalz", "source.ip": "77.179.66.156", "url.original": "/", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Chrome", "user_agent.original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.59 Safari/537.36", @@ -39,11 +48,19 @@ }, { "@timestamp": "2016-10-25T12:49:34.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.access", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "failure", "event.timezone": "-02:00", + "event.type": [ + "access" + ], "fileset.name": "access", - "http.request.method": "GET", + "http.request.method": "get", "http.request.referrer": "http://localhost:8080/", "http.response.body.bytes": 571, "http.response.status_code": 404, @@ -53,6 +70,9 @@ "nginx.access.remote_ip_list": [ "77.179.66.156" ], + "related.ip": [ + "77.179.66.156" + ], "service.type": "nginx", "source.address": "77.179.66.156", "source.as.number": 6805, @@ -66,7 +86,6 @@ "source.geo.region_name": "Rheinland-Pfalz", "source.ip": "77.179.66.156", "url.original": "/favicon.ico", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Chrome", "user_agent.original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.59 Safari/537.36", @@ -77,12 +96,19 @@ }, { "@timestamp": "2016-10-25T12:50:44.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.access", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "failure", "event.timezone": "-02:00", + "event.type": [ + "access" + ], "fileset.name": "access", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 571, "http.response.status_code": 404, "http.version": "1.1", @@ -91,6 +117,9 @@ "nginx.access.remote_ip_list": [ "77.179.66.156" ], + "related.ip": [ + "77.179.66.156" + ], "service.type": "nginx", "source.address": "77.179.66.156", "source.as.number": 6805, @@ -104,7 +133,6 @@ "source.geo.region_name": "Rheinland-Pfalz", "source.ip": "77.179.66.156", "url.original": "/adsasd", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Chrome", "user_agent.original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.59 Safari/537.36", @@ -115,12 +143,19 @@ }, { "@timestamp": "2016-12-07T09:34:43.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.access", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "access" + ], "fileset.name": "access", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 612, "http.response.status_code": 200, "http.version": "1.1", @@ -129,6 +164,9 @@ "nginx.access.remote_ip_list": [ "77.179.66.156" ], + "related.ip": [ + "77.179.66.156" + ], "service.type": "nginx", "source.address": "77.179.66.156", "source.as.number": 6805, @@ -142,7 +180,6 @@ "source.geo.region_name": "Rheinland-Pfalz", "source.ip": "77.179.66.156", "url.original": "/", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Chrome", "user_agent.original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.36", @@ -153,11 +190,19 @@ }, { "@timestamp": "2016-12-07T09:34:43.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.access", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "failure", "event.timezone": "-02:00", + "event.type": [ + "access" + ], "fileset.name": "access", - "http.request.method": "GET", + "http.request.method": "get", "http.request.referrer": "http://localhost:8080/", "http.response.body.bytes": 571, "http.response.status_code": 404, @@ -167,6 +212,9 @@ "nginx.access.remote_ip_list": [ "77.179.66.156" ], + "related.ip": [ + "77.179.66.156" + ], "service.type": "nginx", "source.address": "77.179.66.156", "source.as.number": 6805, @@ -180,7 +228,6 @@ "source.geo.region_name": "Rheinland-Pfalz", "source.ip": "77.179.66.156", "url.original": "/favicon.ico", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Chrome", "user_agent.original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.36", @@ -191,12 +238,19 @@ }, { "@timestamp": "2016-12-07T09:43:18.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.access", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "failure", "event.timezone": "-02:00", + "event.type": [ + "access" + ], "fileset.name": "access", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 571, "http.response.status_code": 404, "http.version": "1.1", @@ -205,6 +259,9 @@ "nginx.access.remote_ip_list": [ "77.179.66.156" ], + "related.ip": [ + "77.179.66.156" + ], "service.type": "nginx", "source.address": "77.179.66.156", "source.as.number": 6805, @@ -218,7 +275,6 @@ "source.geo.region_name": "Rheinland-Pfalz", "source.ip": "77.179.66.156", "url.original": "/test", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Chrome", "user_agent.original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.36", @@ -229,12 +285,19 @@ }, { "@timestamp": "2016-12-07T09:43:21.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.access", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "failure", "event.timezone": "-02:00", + "event.type": [ + "access" + ], "fileset.name": "access", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 571, "http.response.status_code": 404, "http.version": "1.1", @@ -243,6 +306,9 @@ "nginx.access.remote_ip_list": [ "77.179.66.156" ], + "related.ip": [ + "77.179.66.156" + ], "service.type": "nginx", "source.address": "77.179.66.156", "source.as.number": 6805, @@ -256,7 +322,6 @@ "source.geo.region_name": "Rheinland-Pfalz", "source.ip": "77.179.66.156", "url.original": "/test", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Chrome", "user_agent.original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.36", @@ -267,12 +332,19 @@ }, { "@timestamp": "2016-12-07T09:43:23.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.access", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "failure", "event.timezone": "-02:00", + "event.type": [ + "access" + ], "fileset.name": "access", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 571, "http.response.status_code": 404, "http.version": "1.1", @@ -281,6 +353,9 @@ "nginx.access.remote_ip_list": [ "77.179.66.156" ], + "related.ip": [ + "77.179.66.156" + ], "service.type": "nginx", "source.address": "77.179.66.156", "source.as.number": 6805, @@ -294,7 +369,6 @@ "source.geo.region_name": "Rheinland-Pfalz", "source.ip": "77.179.66.156", "url.original": "/test1", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Chrome", "user_agent.original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.36", @@ -305,12 +379,19 @@ }, { "@timestamp": "2016-12-07T10:04:37.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.access", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "failure", "event.timezone": "-02:00", + "event.type": [ + "access" + ], "fileset.name": "access", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 571, "http.response.status_code": 404, "http.version": "1.1", @@ -319,11 +400,13 @@ "nginx.access.remote_ip_list": [ "127.0.0.1" ], + "related.ip": [ + "127.0.0.1" + ], "service.type": "nginx", "source.address": "127.0.0.1", "source.ip": "127.0.0.1", "url.original": "/test1", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Chrome", "user_agent.original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.36", @@ -334,12 +417,19 @@ }, { "@timestamp": "2016-12-07T10:04:58.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.access", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "access" + ], "fileset.name": "access", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 0, "http.response.status_code": 304, "http.version": "1.1", @@ -348,11 +438,13 @@ "nginx.access.remote_ip_list": [ "127.0.0.1" ], + "related.ip": [ + "127.0.0.1" + ], "service.type": "nginx", "source.address": "127.0.0.1", "source.ip": "127.0.0.1", "url.original": "/", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Firefox", "user_agent.original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:49.0) Gecko/20100101 Firefox/49.0", @@ -363,12 +455,19 @@ }, { "@timestamp": "2016-12-07T10:04:59.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.access", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "access" + ], "fileset.name": "access", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 0, "http.response.status_code": 304, "http.version": "1.1", @@ -377,11 +476,13 @@ "nginx.access.remote_ip_list": [ "127.0.0.1" ], + "related.ip": [ + "127.0.0.1" + ], "service.type": "nginx", "source.address": "127.0.0.1", "source.ip": "127.0.0.1", "url.original": "/", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Firefox", "user_agent.original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:49.0) Gecko/20100101 Firefox/49.0", @@ -392,12 +493,19 @@ }, { "@timestamp": "2016-12-07T10:05:07.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.access", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "failure", "event.timezone": "-02:00", + "event.type": [ + "access" + ], "fileset.name": "access", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 169, "http.response.status_code": 404, "http.version": "1.1", @@ -406,11 +514,13 @@ "nginx.access.remote_ip_list": [ "127.0.0.1" ], + "related.ip": [ + "127.0.0.1" + ], "service.type": "nginx", "source.address": "127.0.0.1", "source.ip": "127.0.0.1", "url.original": "/taga", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Firefox", "user_agent.original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:49.0) Gecko/20100101 Firefox/49.0", diff --git a/filebeat/module/nginx/access/test/test-with-host.log-expected.json b/filebeat/module/nginx/access/test/test-with-host.log-expected.json index 38695946ca5..a641922d139 100644 --- a/filebeat/module/nginx/access/test/test-with-host.log-expected.json +++ b/filebeat/module/nginx/access/test/test-with-host.log-expected.json @@ -2,12 +2,19 @@ { "@timestamp": "2016-12-07T10:05:07.000Z", "destination.domain": "example.com", + "event.category": [ + "web" + ], "event.dataset": "nginx.access", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "access" + ], "fileset.name": "access", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 571, "http.response.status_code": 200, "http.version": "1.1", @@ -18,11 +25,13 @@ "10.0.0.1", "127.0.0.1" ], + "related.ip": [ + "10.0.0.2" + ], "service.type": "nginx", "source.address": "10.0.0.2", "source.ip": "10.0.0.2", "url.original": "/ocelot", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Firefox", "user_agent.original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:49.0) Gecko/20100101 Firefox/49.0", @@ -34,12 +43,19 @@ { "@timestamp": "2017-05-29T19:02:48.000Z", "destination.domain": "example.com", + "event.category": [ + "web" + ], "event.dataset": "nginx.access", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "failure", "event.timezone": "-02:00", + "event.type": [ + "access" + ], "fileset.name": "access", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 612, "http.response.status_code": 404, "http.version": "1.1", @@ -48,11 +64,13 @@ "nginx.access.remote_ip_list": [ "172.17.0.1" ], + "related.ip": [ + "172.17.0.1" + ], "service.type": "nginx", "source.address": "172.17.0.1", "source.ip": "172.17.0.1", "url.original": "/stringpatch", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Firefox Alpha", "user_agent.original": "Mozilla/5.0 (Windows NT 6.1; rv:15.0) Gecko/20120716 Firefox/15.0a2", @@ -64,12 +82,19 @@ { "@timestamp": "2016-12-07T10:05:07.000Z", "destination.domain": "example.com", + "event.category": [ + "web" + ], "event.dataset": "nginx.access", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "access" + ], "fileset.name": "access", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 571, "http.response.status_code": 200, "http.version": "1.1", @@ -80,6 +105,9 @@ "10.0.0.1", "85.181.35.98" ], + "related.ip": [ + "85.181.35.98" + ], "service.type": "nginx", "source.address": "85.181.35.98", "source.as.number": 6805, @@ -93,7 +121,6 @@ "source.geo.region_name": "Land Berlin", "source.ip": "85.181.35.98", "url.original": "/ocelot", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Firefox", "user_agent.original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:49.0) Gecko/20100101 Firefox/49.0", @@ -106,12 +133,19 @@ "@timestamp": "2016-12-07T10:05:07.000Z", "destination.domain": "example.com", "destination.port": "80", + "event.category": [ + "web" + ], "event.dataset": "nginx.access", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "access" + ], "fileset.name": "access", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 571, "http.response.status_code": 200, "http.version": "1.1", @@ -120,6 +154,9 @@ "nginx.access.remote_ip_list": [ "85.181.35.98" ], + "related.ip": [ + "85.181.35.98" + ], "service.type": "nginx", "source.address": "85.181.35.98", "source.as.number": 6805, @@ -133,7 +170,6 @@ "source.geo.region_name": "Land Berlin", "source.ip": "85.181.35.98", "url.original": "/ocelot", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Chrome", "user_agent.original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", @@ -146,12 +182,19 @@ "@timestamp": "2016-01-22T13:18:29.000Z", "destination.domain": "example.com", "destination.port": "80", + "event.category": [ + "web" + ], "event.dataset": "nginx.access", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "access" + ], "fileset.name": "access", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 25507, "http.response.status_code": 200, "http.version": "1.1", @@ -163,6 +206,9 @@ "204.246.1.1", "10.2.1.185" ], + "related.ip": [ + "199.96.1.1" + ], "service.type": "nginx", "source.address": "199.96.1.1", "source.as.number": 19065, @@ -176,7 +222,6 @@ "source.geo.region_name": "Illinois", "source.ip": "199.96.1.1", "url.original": "/assets/xxxx?q=100", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Other", "user_agent.original": "Amazon CloudFront" @@ -184,12 +229,19 @@ { "@timestamp": "2016-12-30T06:47:09.000Z", "destination.ip": "1.2.3.4", + "event.category": [ + "web" + ], "event.dataset": "nginx.access", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "failure", "event.timezone": "-02:00", + "event.type": [ + "access" + ], "fileset.name": "access", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 8571, "http.response.status_code": 404, "http.version": "1.1", @@ -200,6 +252,10 @@ "10.225.192.17", "10.2.2.121" ], + "related.ip": [ + "2a03:0000:10ff:f00f:0000:0000:0:8000", + "1.2.3.4" + ], "service.type": "nginx", "source.address": "2a03:0000:10ff:f00f:0000:0000:0:8000", "source.geo.continent_name": "Europe", @@ -208,7 +264,6 @@ "source.geo.location.lon": -8.0, "source.ip": "2a03:0000:10ff:f00f:0000:0000:0:8000", "url.original": "/test.html", - "user.name": "-", "user_agent.device.name": "Spider", "user_agent.name": "Facebot", "user_agent.original": "Mozilla/5.0 (compatible; Facebot 1.0; https://developers.facebook.com/docs/sharing/webmasters/crawler)", @@ -218,11 +273,18 @@ "@timestamp": "2018-04-12T07:48:40.000Z", "destination.ip": "1.2.3.4", "destination.port": "80", + "event.category": [ + "web" + ], "event.dataset": "nginx.access", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "failure", "event.timezone": "-02:00", + "event.type": [ + "access" + ], "fileset.name": "access", - "http.request.referrer": "-", "http.response.body.bytes": 0, "http.response.status_code": 400, "input.type": "log", @@ -230,43 +292,53 @@ "nginx.access.remote_ip_list": [ "127.0.0.1" ], + "related.ip": [ + "127.0.0.1", + "1.2.3.4" + ], "service.type": "nginx", "source.address": "127.0.0.1", - "source.ip": "127.0.0.1", - "user.name": "-", - "user_agent.device.name": "Other", - "user_agent.name": "Other", - "user_agent.original": "-" + "source.ip": "127.0.0.1" }, { "@timestamp": "2019-02-26T14:39:42.000Z", "destination.domain": "example.com", "destination.port": "80", + "event.category": [ + "web" + ], "event.dataset": "nginx.access", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "failure", "event.timezone": "-02:00", + "event.type": [ + "access" + ], "fileset.name": "access", - "http.request.referrer": "-", "http.response.body.bytes": 173, "http.response.status_code": 400, "input.type": "log", "log.offset": 1269, "service.type": "nginx", - "source.address": "unix:", - "user.name": "-", - "user_agent.device.name": "Other", - "user_agent.name": "Other", - "user_agent.original": "-" + "source.address": "unix:" }, { "@timestamp": "2017-05-29T19:02:48.000Z", "destination.ip": "1.2.3.4", + "event.category": [ + "web" + ], "event.dataset": "nginx.access", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "access" + ], "fileset.name": "access", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 612, "http.response.status_code": 200, "http.version": "1.1", @@ -275,10 +347,12 @@ "nginx.access.remote_ip_list": [ "localhost" ], + "related.ip": [ + "1.2.3.4" + ], "service.type": "nginx", "source.address": "localhost", "url.original": "/test2", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Firefox Alpha", "user_agent.original": "Mozilla/5.0 (Windows NT 6.1; rv:15.0) Gecko/20120716 Firefox/15.0a2", @@ -290,12 +364,19 @@ { "@timestamp": "2017-05-29T19:02:48.000Z", "destination.domain": "example.com", + "event.category": [ + "web" + ], "event.dataset": "nginx.access", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "access" + ], "fileset.name": "access", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 612, "http.response.status_code": 200, "http.version": "1.1", @@ -308,7 +389,6 @@ "service.type": "nginx", "source.address": "localhost", "url.original": "/test2", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Firefox Alpha", "user_agent.original": "Mozilla/5.0 (Windows NT 6.1; rv:15.0) Gecko/20120716 Firefox/15.0a2", diff --git a/filebeat/module/nginx/access/test/test.log-expected.json b/filebeat/module/nginx/access/test/test.log-expected.json index 247b7a12e21..22959d1a8be 100644 --- a/filebeat/module/nginx/access/test/test.log-expected.json +++ b/filebeat/module/nginx/access/test/test.log-expected.json @@ -1,12 +1,19 @@ [ { "@timestamp": "2016-12-07T10:05:07.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.access", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "access" + ], "fileset.name": "access", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 571, "http.response.status_code": 200, "http.version": "1.1", @@ -17,11 +24,13 @@ "10.0.0.1", "127.0.0.1" ], + "related.ip": [ + "10.0.0.2" + ], "service.type": "nginx", "source.address": "10.0.0.2", "source.ip": "10.0.0.2", "url.original": "/ocelot", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Firefox", "user_agent.original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:49.0) Gecko/20100101 Firefox/49.0", @@ -32,12 +41,19 @@ }, { "@timestamp": "2017-05-29T19:02:48.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.access", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "failure", "event.timezone": "-02:00", + "event.type": [ + "access" + ], "fileset.name": "access", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 612, "http.response.status_code": 404, "http.version": "1.1", @@ -46,11 +62,13 @@ "nginx.access.remote_ip_list": [ "172.17.0.1" ], + "related.ip": [ + "172.17.0.1" + ], "service.type": "nginx", "source.address": "172.17.0.1", "source.ip": "172.17.0.1", "url.original": "/stringpatch", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Firefox Alpha", "user_agent.original": "Mozilla/5.0 (Windows NT 6.1; rv:15.0) Gecko/20120716 Firefox/15.0a2", @@ -61,12 +79,19 @@ }, { "@timestamp": "2016-12-07T10:05:07.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.access", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "access" + ], "fileset.name": "access", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 571, "http.response.status_code": 200, "http.version": "1.1", @@ -77,6 +102,9 @@ "10.0.0.1", "85.181.35.98" ], + "related.ip": [ + "85.181.35.98" + ], "service.type": "nginx", "source.address": "85.181.35.98", "source.as.number": 6805, @@ -90,7 +118,6 @@ "source.geo.region_name": "Land Berlin", "source.ip": "85.181.35.98", "url.original": "/ocelot", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Firefox", "user_agent.original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:49.0) Gecko/20100101 Firefox/49.0", @@ -101,12 +128,19 @@ }, { "@timestamp": "2016-12-07T10:05:07.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.access", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "access" + ], "fileset.name": "access", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 571, "http.response.status_code": 200, "http.version": "1.1", @@ -115,6 +149,9 @@ "nginx.access.remote_ip_list": [ "85.181.35.98" ], + "related.ip": [ + "85.181.35.98" + ], "service.type": "nginx", "source.address": "85.181.35.98", "source.as.number": 6805, @@ -128,7 +165,6 @@ "source.geo.region_name": "Land Berlin", "source.ip": "85.181.35.98", "url.original": "/ocelot", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Chrome", "user_agent.original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", @@ -139,12 +175,19 @@ }, { "@timestamp": "2016-01-22T13:18:29.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.access", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "access" + ], "fileset.name": "access", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 25507, "http.response.status_code": 200, "http.version": "1.1", @@ -156,6 +199,9 @@ "204.246.1.1", "10.2.1.185" ], + "related.ip": [ + "199.96.1.1" + ], "service.type": "nginx", "source.address": "199.96.1.1", "source.as.number": 19065, @@ -169,19 +215,25 @@ "source.geo.region_name": "Illinois", "source.ip": "199.96.1.1", "url.original": "/assets/xxxx?q=100", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Other", "user_agent.original": "Amazon CloudFront" }, { "@timestamp": "2016-12-30T06:47:09.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.access", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "failure", "event.timezone": "-02:00", + "event.type": [ + "access" + ], "fileset.name": "access", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 8571, "http.response.status_code": 404, "http.version": "1.1", @@ -192,6 +244,9 @@ "10.225.192.17", "10.2.2.121" ], + "related.ip": [ + "2a03:0000:10ff:f00f:0000:0000:0:8000" + ], "service.type": "nginx", "source.address": "2a03:0000:10ff:f00f:0000:0000:0:8000", "source.geo.continent_name": "Europe", @@ -200,7 +255,6 @@ "source.geo.location.lon": -8.0, "source.ip": "2a03:0000:10ff:f00f:0000:0000:0:8000", "url.original": "/test.html", - "user.name": "-", "user_agent.device.name": "Spider", "user_agent.name": "Facebot", "user_agent.original": "Mozilla/5.0 (compatible; Facebot 1.0; https://developers.facebook.com/docs/sharing/webmasters/crawler)", @@ -208,11 +262,18 @@ }, { "@timestamp": "2018-04-12T07:48:40.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.access", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "failure", "event.timezone": "-02:00", + "event.type": [ + "access" + ], "fileset.name": "access", - "http.request.referrer": "-", "http.response.body.bytes": 0, "http.response.status_code": 400, "input.type": "log", @@ -220,40 +281,49 @@ "nginx.access.remote_ip_list": [ "127.0.0.1" ], + "related.ip": [ + "127.0.0.1" + ], "service.type": "nginx", "source.address": "127.0.0.1", - "source.ip": "127.0.0.1", - "user.name": "-", - "user_agent.device.name": "Other", - "user_agent.name": "Other", - "user_agent.original": "-" + "source.ip": "127.0.0.1" }, { "@timestamp": "2019-02-26T14:39:42.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.access", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "failure", "event.timezone": "-02:00", + "event.type": [ + "access" + ], "fileset.name": "access", - "http.request.referrer": "-", "http.response.body.bytes": 173, "http.response.status_code": 400, "input.type": "log", "log.offset": 1184, "service.type": "nginx", - "source.address": "unix:", - "user.name": "-", - "user_agent.device.name": "Other", - "user_agent.name": "Other", - "user_agent.original": "-" + "source.address": "unix:" }, { "@timestamp": "2017-05-29T19:02:48.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.access", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "access" + ], "fileset.name": "access", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 612, "http.response.status_code": 200, "http.version": "1.1", @@ -265,7 +335,6 @@ "service.type": "nginx", "source.address": "localhost", "url.original": "/test2", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Firefox Alpha", "user_agent.original": "Mozilla/5.0 (Windows NT 6.1; rv:15.0) Gecko/20120716 Firefox/15.0a2", @@ -276,12 +345,19 @@ }, { "@timestamp": "2017-05-29T19:02:48.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.access", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "access" + ], "fileset.name": "access", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 612, "http.response.status_code": 200, "http.version": "1.1", @@ -294,7 +370,6 @@ "service.type": "nginx", "source.address": "localhost", "url.original": "/test2", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Firefox Alpha", "user_agent.original": "Mozilla/5.0 (Windows NT 6.1; rv:15.0) Gecko/20120716 Firefox/15.0a2", diff --git a/filebeat/module/nginx/error/ingest/pipeline.json b/filebeat/module/nginx/error/ingest/pipeline.json deleted file mode 100644 index 473fa087922..00000000000 --- a/filebeat/module/nginx/error/ingest/pipeline.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "description": "Pipeline for parsing the Nginx error logs", - "processors": [{ - "grok": { - "field": "message", - "patterns": [ - "%{DATA:nginx.error.time} \\[%{DATA:log.level}\\] %{NUMBER:process.pid:long}#%{NUMBER:process.thread.id:long}: (\\*%{NUMBER:nginx.error.connection_id:long} )?%{GREEDYMULTILINE:message}" - ], - "pattern_definitions": { - "GREEDYMULTILINE":"(.|\n|\t)*" - }, - "ignore_missing": true - } - }, { - "rename": { - "field": "@timestamp", - "target_field": "event.created" - } - }, { - "date": { - "if": "ctx.event.timezone == null", - "field": "nginx.error.time", - "target_field": "@timestamp", - "formats": ["yyyy/MM/dd H:m:s"], - "on_failure": [{"append": {"field": "error.message", "value": "{{ _ingest.on_failure_message }}"}}] - } - }, { - "date": { - "if": "ctx.event.timezone != null", - "field": "nginx.error.time", - "target_field": "@timestamp", - "formats": ["yyyy/MM/dd H:m:s"], - "timezone": "{{ event.timezone }}", - "on_failure": [{"append": {"field": "error.message", "value": "{{ _ingest.on_failure_message }}"}}] - } - }, { - "remove": { - "field": "nginx.error.time" - } - }], - "on_failure" : [{ - "set" : { - "field" : "error.message", - "value" : "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/filebeat/module/nginx/error/ingest/pipeline.yml b/filebeat/module/nginx/error/ingest/pipeline.yml new file mode 100644 index 00000000000..5a33c34710c --- /dev/null +++ b/filebeat/module/nginx/error/ingest/pipeline.yml @@ -0,0 +1,51 @@ +description: Pipeline for parsing the Nginx error logs +processors: +- grok: + field: message + patterns: + - '%{DATA:nginx.error.time} \[%{DATA:log.level}\] %{NUMBER:process.pid:long}#%{NUMBER:process.thread.id:long}: + (\*%{NUMBER:nginx.error.connection_id:long} )?%{GREEDYMULTILINE:message}' + pattern_definitions: + GREEDYMULTILINE: |- + (.| + | )* + ignore_missing: true +- rename: + field: '@timestamp' + target_field: event.created +- date: + if: ctx.event.timezone == null + field: nginx.error.time + target_field: '@timestamp' + formats: + - yyyy/MM/dd H:m:s + on_failure: + - append: + field: error.message + value: '{{ _ingest.on_failure_message }}' +- date: + if: ctx.event.timezone != null + field: nginx.error.time + target_field: '@timestamp' + formats: + - yyyy/MM/dd H:m:s + timezone: '{{ event.timezone }}' + on_failure: + - append: + field: error.message + value: '{{ _ingest.on_failure_message }}' +- remove: + field: nginx.error.time +- set: + field: event.kind + value: event +- append: + field: event.category + value: web +- append: + field: event.type + value: error +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/filebeat/module/nginx/error/manifest.yml b/filebeat/module/nginx/error/manifest.yml index 641ec771bbb..b83c154693d 100644 --- a/filebeat/module/nginx/error/manifest.yml +++ b/filebeat/module/nginx/error/manifest.yml @@ -9,5 +9,5 @@ var: os.windows: - c:/programdata/nginx/logs/error.log* -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/nginx-error.yml diff --git a/filebeat/module/nginx/error/test/error.log-expected.json b/filebeat/module/nginx/error/test/error.log-expected.json index 6252e87d66b..8896a490705 100644 --- a/filebeat/module/nginx/error/test/error.log-expected.json +++ b/filebeat/module/nginx/error/test/error.log-expected.json @@ -1,9 +1,16 @@ [ { "@timestamp": "2016-10-25T14:49:34.000-02:00", + "event.category": [ + "web" + ], "event.dataset": "nginx.error", + "event.kind": "event", "event.module": "nginx", "event.timezone": "-02:00", + "event.type": [ + "error" + ], "fileset.name": "error", "input.type": "log", "log.level": "error", @@ -16,9 +23,16 @@ }, { "@timestamp": "2016-10-25T14:50:44.000-02:00", + "event.category": [ + "web" + ], "event.dataset": "nginx.error", + "event.kind": "event", "event.module": "nginx", "event.timezone": "-02:00", + "event.type": [ + "error" + ], "fileset.name": "error", "input.type": "log", "log.level": "error", @@ -31,9 +45,16 @@ }, { "@timestamp": "2019-10-30T23:26:34.000-02:00", + "event.category": [ + "web" + ], "event.dataset": "nginx.error", + "event.kind": "event", "event.module": "nginx", "event.timezone": "-02:00", + "event.type": [ + "error" + ], "fileset.name": "error", "input.type": "log", "log.flags": [ @@ -49,9 +70,16 @@ }, { "@timestamp": "2019-11-05T14:50:44.000-02:00", + "event.category": [ + "web" + ], "event.dataset": "nginx.error", + "event.kind": "event", "event.module": "nginx", "event.timezone": "-02:00", + "event.type": [ + "error" + ], "fileset.name": "error", "input.type": "log", "log.level": "error", diff --git a/filebeat/module/nginx/ingress_controller/ingest/pipeline.json b/filebeat/module/nginx/ingress_controller/ingest/pipeline.json deleted file mode 100644 index e660f22f022..00000000000 --- a/filebeat/module/nginx/ingress_controller/ingest/pipeline.json +++ /dev/null @@ -1,151 +0,0 @@ -{ - "description": "Pipeline for parsing Nginx ingress controller access logs. Requires the geoip and user_agent plugins.", - "processors": [ - { - "grok": { - "field": "message", - "patterns": [ - "(%{NGINX_HOST} )?\"?(?:%{NGINX_ADDRESS_LIST:nginx.ingress_controller.remote_ip_list}|%{NOTSPACE:source.address}) - %{DATA:user.name} \\[%{HTTPDATE:nginx.ingress_controller.time}\\] \"%{DATA:nginx.ingress_controller.info}\" %{NUMBER:http.response.status_code:long} %{NUMBER:http.response.body.bytes:long} \"%{DATA:http.request.referrer}\" \"%{DATA:user_agent.original}\" %{NUMBER:nginx.ingress_controller.http.request.length:long} %{NUMBER:nginx.ingress_controller.http.request.time:double} \\[%{DATA:nginx.ingress_controller.upstream.name}\\] \\[%{DATA:nginx.ingress_controller.upstream.alternative_name}\\] (%{UPSTREAM_ADDRESS}|-) (%{NUMBER:nginx.ingress_controller.upstream.response.length:long}|-) (%{NUMBER:nginx.ingress_controller.upstream.response.time:double}|-) (%{NUMBER:nginx.ingress_controller.upstream.response.status_code:long}|-) %{GREEDYDATA:nginx.ingress_controller.http.request.id}" - ], - "pattern_definitions": { - "NGINX_HOST": "(?:%{IP:destination.ip}|%{NGINX_NOTSEPARATOR:destination.domain})(:%{NUMBER:destination.port})?", - "NGINX_NOTSEPARATOR": "[^\t ,:]+", - "NGINX_ADDRESS_LIST": "(?:%{IP}|%{WORD})(\"?,?\\s*(?:%{IP}|%{WORD}))*", - "UPSTREAM_ADDRESS": "%{IP:nginx.ingress_controller.upstream.ip}(:%{NUMBER:nginx.ingress_controller.upstream.port})?" - }, - "ignore_missing": true - } - }, - { - "grok": { - "field": "nginx.ingress_controller.info", - "patterns": [ - "%{WORD:http.request.method} %{DATA:url.original} HTTP/%{NUMBER:http.version}", - "" - ], - "ignore_missing": true - } - }, - { - "remove": { - "field": "nginx.ingress_controller.info" - } - }, - { - "split": { - "field": "nginx.ingress_controller.remote_ip_list", - "separator": "\"?,?\\s+", - "ignore_missing": true - } - }, - { - "split": { - "field": "nginx.ingress_controller.origin", - "separator": "\"?,?\\s+", - "ignore_missing": true - } - }, - { - "set": { - "field": "source.address", - "if": "ctx.source?.address == null", - "value": "" - } - }, - { - "script": { - "if": "ctx.nginx?.access?.remote_ip_list != null && ctx.nginx.ingress_controller.remote_ip_list.length > 0", - "lang": "painless", - "source": "boolean isPrivate(def dot, def ip) { try { StringTokenizer tok = new StringTokenizer(ip, dot); int firstByte = Integer.parseInt(tok.nextToken()); int secondByte = Integer.parseInt(tok.nextToken()); if (firstByte == 10) { return true; } if (firstByte == 192 && secondByte == 168) { return true; } if (firstByte == 172 && secondByte >= 16 && secondByte <= 31) { return true; } if (firstByte == 127) { return true; } return false; } catch (Exception e) { return false; } } try { ctx.source.address = null; if (ctx.nginx.ingress_controller.remote_ip_list == null) { return; } def found = false; for (def item : ctx.nginx.ingress_controller.remote_ip_list) { if (!isPrivate(params.dot, item)) { ctx.source.address = item; found = true; break; } } if (!found) { ctx.source.address = ctx.nginx.ingress_controller.remote_ip_list[0]; }} catch (Exception e) { ctx.source.address = null; }", - "params": { - "dot": "." - } - } - }, - { - "remove": { - "field": "source.address", - "if": "ctx.source.address == null" - } - }, - { - "grok": { - "field": "source.address", - "patterns": ["^%{IP:source.ip}$"], - "ignore_failure": true - } - }, - { - "remove": { - "field": "message" - } - }, - { - "rename": { - "field": "@timestamp", - "target_field": "event.created" - } - }, - { - "date": { - "field": "nginx.ingress_controller.time", - "target_field": "@timestamp", - "formats": [ - "dd/MMM/yyyy:H:m:s Z" - ], - "on_failure": [{"append": {"field": "error.message", "value": "{{ _ingest.on_failure_message }}"}}] - } - }, - { - "remove": { - "field": "nginx.ingress_controller.time" - } - }, - { - "user_agent": { - "field": "user_agent.original" - } - }, - { - "geoip": { - "field": "source.ip", - "target_field": "source.geo", - "ignore_missing": true - } - }, - { - "geoip": { - "database_file": "GeoLite2-ASN.mmdb", - "field": "source.ip", - "target_field": "source.as", - "properties": [ - "asn", - "organization_name" - ], - "ignore_missing": true - } - }, - { - "rename": { - "field": "source.as.asn", - "target_field": "source.as.number", - "ignore_missing": true - } - }, - { - "rename": { - "field": "source.as.organization_name", - "target_field": "source.as.organization.name", - "ignore_missing": true - } - } - ], - "on_failure": [ - { - "set": { - "field": "error.message", - "value": "{{ _ingest.on_failure_message }}" - } - } - ] -} diff --git a/filebeat/module/nginx/ingress_controller/ingest/pipeline.yml b/filebeat/module/nginx/ingress_controller/ingest/pipeline.yml new file mode 100644 index 00000000000..9721be136e3 --- /dev/null +++ b/filebeat/module/nginx/ingress_controller/ingest/pipeline.yml @@ -0,0 +1,172 @@ +description: Pipeline for parsing Nginx ingress controller access logs. Requires the + geoip and user_agent plugins. +processors: +- grok: + field: message + patterns: + - (%{NGINX_HOST} )?"?(?:%{NGINX_ADDRESS_LIST:nginx.ingress_controller.remote_ip_list}|%{NOTSPACE:source.address}) + - (-|%{DATA:user.name}) \[%{HTTPDATE:nginx.ingress_controller.time}\] "%{DATA:nginx.ingress_controller.info}" + %{NUMBER:http.response.status_code:long} %{NUMBER:http.response.body.bytes:long} + "(-|%{DATA:http.request.referrer})" "(-|%{DATA:user_agent.original})" %{NUMBER:nginx.ingress_controller.http.request.length:long} + %{NUMBER:nginx.ingress_controller.http.request.time:double} \[%{DATA:nginx.ingress_controller.upstream.name}\] + \[%{DATA:nginx.ingress_controller.upstream.alternative_name}\] (%{UPSTREAM_ADDRESS}|-) + (%{NUMBER:nginx.ingress_controller.upstream.response.length:long}|-) (%{NUMBER:nginx.ingress_controller.upstream.response.time:double}|-) + (%{NUMBER:nginx.ingress_controller.upstream.response.status_code:long}|-) %{GREEDYDATA:nginx.ingress_controller.http.request.id} + pattern_definitions: + NGINX_HOST: (?:%{IP:destination.ip}|%{NGINX_NOTSEPARATOR:destination.domain})(:%{NUMBER:destination.port})? + NGINX_NOTSEPARATOR: "[^\t ,:]+" + NGINX_ADDRESS_LIST: (?:%{IP}|%{WORD})("?,?\s*(?:%{IP}|%{WORD}))* + UPSTREAM_ADDRESS: '%{IP:nginx.ingress_controller.upstream.ip}(:%{NUMBER:nginx.ingress_controller.upstream.port})?' + ignore_missing: true +- grok: + field: nginx.ingress_controller.info + patterns: + - '%{WORD:http.request.method} %{DATA:url.original} HTTP/%{NUMBER:http.version}' + - "" + ignore_missing: true +- remove: + field: nginx.ingress_controller.info +- split: + field: nginx.ingress_controller.remote_ip_list + separator: '"?,?\s+' + ignore_missing: true +- split: + field: nginx.ingress_controller.origin + separator: '"?,?\s+' + ignore_missing: true +- set: + field: source.address + if: ctx.source?.address == null + value: "" +- script: + if: ctx.nginx?.access?.remote_ip_list != null && ctx.nginx.ingress_controller.remote_ip_list.length > 0 + lang: painless + source: >- + boolean isPrivate(def dot, def ip) { + try { + StringTokenizer tok = new StringTokenizer(ip, dot); + int firstByte = Integer.parseInt(tok.nextToken()); + int secondByte = Integer.parseInt(tok.nextToken()); + if (firstByte == 10) { + return true; + } + if (firstByte == 192 && secondByte == 168) { + return true; + } + if (firstByte == 172 && secondByte >= 16 && secondByte <= 31) { + return true; + } + if (firstByte == 127) { + return true; + } + return false; + } + catch (Exception e) { + return false; + } + } + try { + ctx.source.address = null; + if (ctx.nginx.ingress_controller.remote_ip_list == null) { + return; + } + def found = false; + for (def item : ctx.nginx.ingress_controller.remote_ip_list) { + if (!isPrivate(params.dot, item)) { + ctx.source.address = item; + found = true; + break; + } + } + if (!found) { + ctx.source.address = ctx.nginx.ingress_controller.remote_ip_list[0]; + } + } + catch (Exception e) { + ctx.source.address = null; + } + params: + dot: . +- remove: + field: source.address + if: ctx.source.address == null +- grok: + field: source.address + patterns: + - ^%{IP:source.ip}$ + ignore_failure: true +- remove: + field: message +- rename: + field: '@timestamp' + target_field: event.created +- date: + field: nginx.ingress_controller.time + target_field: '@timestamp' + formats: + - dd/MMM/yyyy:H:m:s Z + on_failure: + - append: + field: error.message + value: '{{ _ingest.on_failure_message }}' +- remove: + field: nginx.ingress_controller.time +- user_agent: + field: user_agent.original + ignore_missing: true +- geoip: + field: source.ip + target_field: source.geo + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: source.ip + target_field: source.as + properties: + - asn + - organization_name + ignore_missing: true +- rename: + field: source.as.asn + target_field: source.as.number + ignore_missing: true +- rename: + field: source.as.organization_name + target_field: source.as.organization.name + ignore_missing: true +- set: + field: event.kind + value: event +- append: + field: event.category + value: web +- append: + field: event.type + value: info +- set: + field: event.outcome + value: success + if: "ctx?.http?.response?.status_code != null && ctx.http.response.status_code < 400" +- set: + field: event.outcome + value: failure + if: "ctx?.http?.response?.status_code != null && ctx.http.response.status_code >= 400" +- lowercase: + field: http.request.method + ignore_missing: true +- append: + field: related.ip + value: "{{source.ip}}" + if: "ctx?.source?.ip != null" +- append: + field: related.ip + value: "{{destination.ip}}" + if: "ctx?.destination?.ip != null" +- append: + field: related.user + value: "{{user.name}}" + if: "ctx?.user?.name != null" +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/filebeat/module/nginx/ingress_controller/manifest.yml b/filebeat/module/nginx/ingress_controller/manifest.yml index 0f51e4d5c04..326beb11461 100644 --- a/filebeat/module/nginx/ingress_controller/manifest.yml +++ b/filebeat/module/nginx/ingress_controller/manifest.yml @@ -9,7 +9,7 @@ var: os.windows: - c:/programdata/nginx/logs/*access.log* -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/ingress_controller.yml requires.processors: diff --git a/filebeat/module/nginx/ingress_controller/test/test.log-expected.json b/filebeat/module/nginx/ingress_controller/test/test.log-expected.json index 2dc9d1afbce..a2bf0f6c6e0 100644 --- a/filebeat/module/nginx/ingress_controller/test/test.log-expected.json +++ b/filebeat/module/nginx/ingress_controller/test/test.log-expected.json @@ -1,12 +1,19 @@ [ { "@timestamp": "2020-02-07T11:48:51.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.ingress_controller", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "info" + ], "fileset.name": "ingress_controller", - "http.request.method": "POST", - "http.request.referrer": "-", + "http.request.method": "post", "http.response.body.bytes": 59, "http.response.status_code": 200, "http.version": "1.1", @@ -28,7 +35,6 @@ "service.type": "nginx", "source.address": "", "url.original": "/products", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "curl", "user_agent.original": "curl/7.54.0", @@ -36,12 +42,19 @@ }, { "@timestamp": "2020-02-07T11:49:15.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.ingress_controller", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "info" + ], "fileset.name": "ingress_controller", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 59, "http.response.status_code": 200, "http.version": "1.1", @@ -63,7 +76,6 @@ "service.type": "nginx", "source.address": "", "url.original": "/products/42", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "curl", "user_agent.original": "curl/7.54.0", @@ -71,12 +83,19 @@ }, { "@timestamp": "2020-02-07T11:49:30.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.ingress_controller", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "info" + ], "fileset.name": "ingress_controller", - "http.request.method": "DELETE", - "http.request.referrer": "-", + "http.request.method": "delete", "http.response.body.bytes": 59, "http.response.status_code": 200, "http.version": "1.1", @@ -98,7 +117,6 @@ "service.type": "nginx", "source.address": "", "url.original": "/products/42", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "curl", "user_agent.original": "curl/7.54.0", @@ -106,12 +124,19 @@ }, { "@timestamp": "2020-02-07T11:49:43.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.ingress_controller", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "info" + ], "fileset.name": "ingress_controller", - "http.request.method": "PATCH", - "http.request.referrer": "-", + "http.request.method": "patch", "http.response.body.bytes": 59, "http.response.status_code": 200, "http.version": "1.1", @@ -133,7 +158,6 @@ "service.type": "nginx", "source.address": "", "url.original": "/products/42", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "curl", "user_agent.original": "curl/7.54.0", @@ -141,12 +165,19 @@ }, { "@timestamp": "2020-02-07T11:49:50.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.ingress_controller", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "failure", "event.timezone": "-02:00", + "event.type": [ + "info" + ], "fileset.name": "ingress_controller", - "http.request.method": "PATCHp", - "http.request.referrer": "-", + "http.request.method": "patchp", "http.response.body.bytes": 163, "http.response.status_code": 400, "http.version": "1.1", @@ -162,20 +193,23 @@ "nginx.ingress_controller.upstream.name": "", "service.type": "nginx", "source.address": "", - "url.original": "/products/42", - "user.name": "-", - "user_agent.device.name": "Other", - "user_agent.name": "Other", - "user_agent.original": "-" + "url.original": "/products/42" }, { "@timestamp": "2020-02-07T11:50:09.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.ingress_controller", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "failure", "event.timezone": "-02:00", + "event.type": [ + "info" + ], "fileset.name": "ingress_controller", "http.request.method": "geti", - "http.request.referrer": "-", "http.response.body.bytes": 163, "http.response.status_code": 400, "http.version": "1.1", @@ -191,20 +225,23 @@ "nginx.ingress_controller.upstream.name": "", "service.type": "nginx", "source.address": "", - "url.original": "/products/42", - "user.name": "-", - "user_agent.device.name": "Other", - "user_agent.name": "Other", - "user_agent.original": "-" + "url.original": "/products/42" }, { "@timestamp": "2020-02-07T11:55:05.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.ingress_controller", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "info" + ], "fileset.name": "ingress_controller", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 59, "http.response.status_code": 200, "http.version": "1.1", @@ -226,7 +263,6 @@ "service.type": "nginx", "source.address": "", "url.original": "/products/42", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Wget", "user_agent.original": "Wget/1.20.3 (darwin18.6.0)", @@ -234,12 +270,19 @@ }, { "@timestamp": "2020-02-07T11:55:57.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.ingress_controller", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "info" + ], "fileset.name": "ingress_controller", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 59, "http.response.status_code": 200, "http.version": "1.1", @@ -261,7 +304,6 @@ "service.type": "nginx", "source.address": "", "url.original": "/products/42", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Chrome", "user_agent.original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36", @@ -272,11 +314,19 @@ }, { "@timestamp": "2020-02-07T11:55:57.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.ingress_controller", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "info" + ], "fileset.name": "ingress_controller", - "http.request.method": "GET", + "http.request.method": "get", "http.request.referrer": "http://hello-world.info/products/42", "http.response.body.bytes": 59, "http.response.status_code": 200, @@ -299,7 +349,6 @@ "service.type": "nginx", "source.address": "", "url.original": "/favicon.ico", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Chrome", "user_agent.original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36", @@ -310,12 +359,19 @@ }, { "@timestamp": "2020-02-07T11:56:24.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.ingress_controller", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "info" + ], "fileset.name": "ingress_controller", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 61, "http.response.status_code": 200, "http.version": "1.1", @@ -337,7 +393,6 @@ "service.type": "nginx", "source.address": "", "url.original": "/v2", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Chrome", "user_agent.original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36", @@ -348,11 +403,19 @@ }, { "@timestamp": "2020-02-07T11:56:24.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.ingress_controller", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "info" + ], "fileset.name": "ingress_controller", - "http.request.method": "GET", + "http.request.method": "get", "http.request.referrer": "http://hello-world.info/v2", "http.response.body.bytes": 59, "http.response.status_code": 200, @@ -375,7 +438,6 @@ "service.type": "nginx", "source.address": "", "url.original": "/favicon.ico", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Chrome", "user_agent.original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36", @@ -386,12 +448,19 @@ }, { "@timestamp": "2020-02-07T11:56:36.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.ingress_controller", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "info" + ], "fileset.name": "ingress_controller", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 59, "http.response.status_code": 200, "http.version": "1.1", @@ -413,7 +482,6 @@ "service.type": "nginx", "source.address": "", "url.original": "/products/42", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Safari", "user_agent.original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Safari/605.1.15", @@ -424,11 +492,19 @@ }, { "@timestamp": "2020-02-07T11:56:36.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.ingress_controller", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "info" + ], "fileset.name": "ingress_controller", - "http.request.method": "GET", + "http.request.method": "get", "http.request.referrer": "http://hello-world.info/products/42", "http.response.body.bytes": 59, "http.response.status_code": 200, @@ -451,7 +527,6 @@ "service.type": "nginx", "source.address": "", "url.original": "/favicon.ico", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Safari", "user_agent.original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Safari/605.1.15", @@ -462,12 +537,19 @@ }, { "@timestamp": "2020-02-07T11:56:54.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.ingress_controller", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "info" + ], "fileset.name": "ingress_controller", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 59, "http.response.status_code": 200, "http.version": "1.1", @@ -489,7 +571,6 @@ "service.type": "nginx", "source.address": "", "url.original": "/products/42", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Safari", "user_agent.original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Safari/605.1.15", @@ -500,12 +581,19 @@ }, { "@timestamp": "2020-02-07T11:56:54.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.ingress_controller", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "info" + ], "fileset.name": "ingress_controller", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 59, "http.response.status_code": 200, "http.version": "1.1", @@ -527,7 +615,6 @@ "service.type": "nginx", "source.address": "", "url.original": "/", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Safari", "user_agent.original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Safari/605.1.15", @@ -538,11 +625,19 @@ }, { "@timestamp": "2020-02-07T11:56:54.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.ingress_controller", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "info" + ], "fileset.name": "ingress_controller", - "http.request.method": "GET", + "http.request.method": "get", "http.request.referrer": "http://hello-world.info/", "http.response.body.bytes": 59, "http.response.status_code": 200, @@ -565,7 +660,6 @@ "service.type": "nginx", "source.address": "", "url.original": "/favicon.ico", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Safari", "user_agent.original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Safari/605.1.15", @@ -576,12 +670,19 @@ }, { "@timestamp": "2020-02-07T11:56:56.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.ingress_controller", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "info" + ], "fileset.name": "ingress_controller", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 61, "http.response.status_code": 200, "http.version": "1.1", @@ -603,7 +704,6 @@ "service.type": "nginx", "source.address": "", "url.original": "/v2", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Safari", "user_agent.original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Safari/605.1.15", @@ -614,11 +714,19 @@ }, { "@timestamp": "2020-02-07T11:56:56.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.ingress_controller", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "info" + ], "fileset.name": "ingress_controller", - "http.request.method": "GET", + "http.request.method": "get", "http.request.referrer": "http://hello-world.info/v2", "http.response.body.bytes": 59, "http.response.status_code": 200, @@ -641,7 +749,6 @@ "service.type": "nginx", "source.address": "", "url.original": "/favicon.ico", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Safari", "user_agent.original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Safari/605.1.15", @@ -652,12 +759,19 @@ }, { "@timestamp": "2020-02-07T12:00:28.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.ingress_controller", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "info" + ], "fileset.name": "ingress_controller", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 59, "http.response.status_code": 200, "http.version": "1.1", @@ -679,7 +793,6 @@ "service.type": "nginx", "source.address": "", "url.original": "/products/42?address=delhi+technological+university", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Python Requests", "user_agent.original": "python-requests/2.22.0", @@ -687,12 +800,19 @@ }, { "@timestamp": "2020-02-07T12:02:38.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.ingress_controller", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "info" + ], "fileset.name": "ingress_controller", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 61, "http.response.status_code": 200, "http.version": "1.1", @@ -714,7 +834,6 @@ "service.type": "nginx", "source.address": "", "url.original": "/v2", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Firefox", "user_agent.original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:72.0) Gecko/20100101 Firefox/72.0", @@ -725,12 +844,19 @@ }, { "@timestamp": "2020-02-07T12:02:38.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.ingress_controller", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "info" + ], "fileset.name": "ingress_controller", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 59, "http.response.status_code": 200, "http.version": "1.1", @@ -752,7 +878,6 @@ "service.type": "nginx", "source.address": "", "url.original": "/favicon.ico", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Firefox", "user_agent.original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:72.0) Gecko/20100101 Firefox/72.0", @@ -763,12 +888,19 @@ }, { "@timestamp": "2020-02-07T12:02:42.000Z", + "event.category": [ + "web" + ], "event.dataset": "nginx.ingress_controller", + "event.kind": "event", "event.module": "nginx", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "info" + ], "fileset.name": "ingress_controller", - "http.request.method": "GET", - "http.request.referrer": "-", + "http.request.method": "get", "http.response.body.bytes": 61, "http.response.status_code": 200, "http.version": "1.1", @@ -790,7 +922,6 @@ "service.type": "nginx", "source.address": "", "url.original": "/v2/some", - "user.name": "-", "user_agent.device.name": "Other", "user_agent.name": "Firefox", "user_agent.original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:72.0) Gecko/20100101 Firefox/72.0", From da87ae2f78401f74a5524699125e6c06a12bd520 Mon Sep 17 00:00:00 2001 From: Lee Hinman <57081003+leehinman@users.noreply.github.com> Date: Thu, 23 Apr 2020 09:27:51 -0500 Subject: [PATCH 010/116] Improve ECS categorization field mappings in postgresql module (#17914) - convert pipeline to yml - event.kind - event.category - event.type - related.user Closes #16177 --- CHANGELOG.next.asciidoc | 1 + .../postgresql/log/ingest/pipeline.json | 49 -- .../module/postgresql/log/ingest/pipeline.yml | 57 ++ filebeat/module/postgresql/log/manifest.yml | 2 +- .../test/postgresql-11.4.log-expected.json | 204 ++++++ ...-9.6-debian-with-slowlog.log-expected.json | 182 ++++- ...ostgresql-9.6-multi-core.log-expected.json | 71 ++ ...gresql-9.6-new-timestamp.log-expected.json | 41 ++ ...esql-query-steps-slowlog.log-expected.json | 20 + .../postgresql-ubuntu-9.5.log-expected.json | 660 ++++++++++++++++++ 10 files changed, 1235 insertions(+), 52 deletions(-) delete mode 100644 filebeat/module/postgresql/log/ingest/pipeline.json create mode 100644 filebeat/module/postgresql/log/ingest/pipeline.yml diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index e33820df243..e2f62c51b33 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -270,6 +270,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Improve ECS categorization field mappings in misp module. {issue}16026[16026] {pull}17344[17344] - Added Unix stream socket support as an input source and a syslog input source. {pull}17492[17492] - Improve ECS categorization field mappings for nginx module. {issue}16174[16174] {pull}17844[17844] +- Improve ECS categorization field mappings in postgresql module. {issue}16177[16177] {pull}17914[17914] *Heartbeat* diff --git a/filebeat/module/postgresql/log/ingest/pipeline.json b/filebeat/module/postgresql/log/ingest/pipeline.json deleted file mode 100644 index 1bed827739d..00000000000 --- a/filebeat/module/postgresql/log/ingest/pipeline.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "description": "Pipeline for parsing PostgreSQL logs.", - "processors": [ - { - "grok": { - "field": "message", - "ignore_missing": true, - "patterns": [ - "^%{DATETIME:postgresql.log.timestamp} \\[%{NUMBER:process.pid:long}(-%{BASE16FLOAT:postgresql.log.core_id:long})?\\] ((\\[%{USERNAME:user.name}\\]@\\[%{POSTGRESQL_DB_NAME:postgresql.log.database}\\]|%{USERNAME:user.name}@%{POSTGRESQL_DB_NAME:postgresql.log.database}) )?%{WORD:log.level}: (?:%{NUMBER:postgresql.log.error.code:long}|%{SPACE})(duration: %{NUMBER:temp.duration:float} ms %{POSTGRESQL_QUERY_STEP}: %{GREEDYDATA:postgresql.log.query}|: %{GREEDYDATA:message}|%{GREEDYDATA:message})" - ], - "pattern_definitions": { - "DATETIME": "[-0-9]+ %{TIME} %{WORD:event.timezone}", - "GREEDYDATA": "(.|\n|\t)*", - "POSTGRESQL_DB_NAME": "[a-zA-Z0-9_]+[a-zA-Z0-9_\\$]*", - "POSTGRESQL_QUERY_STEP": "%{WORD:postgresql.log.query_step}(?: | %{WORD:postgresql.log.query_name})?" - } - } - }, - { - "date": { - "field": "postgresql.log.timestamp", - "target_field": "@timestamp", - "formats": [ - "yyyy-MM-dd HH:mm:ss.SSS zz", "yyyy-MM-dd HH:mm:ss zz" - ] - } - }, { - "script": { - "lang": "painless", - "source": "ctx.event.duration = Math.round(ctx.temp.duration * params.scale)", - "params": { "scale": 1000000 }, - "if": "ctx.temp?.duration != null" - } - }, { - "remove": { - "field": "temp.duration", - "ignore_missing": true - } - } - ], - "on_failure": [ - { - "set": { - "field": "error.message", - "value": "{{ _ingest.on_failure_message }}" - } - } - ] -} diff --git a/filebeat/module/postgresql/log/ingest/pipeline.yml b/filebeat/module/postgresql/log/ingest/pipeline.yml new file mode 100644 index 00000000000..bd7fbd69e7d --- /dev/null +++ b/filebeat/module/postgresql/log/ingest/pipeline.yml @@ -0,0 +1,57 @@ +description: Pipeline for parsing PostgreSQL logs. +processors: +- grok: + field: message + ignore_missing: true + patterns: + - '^%{DATETIME:postgresql.log.timestamp} \[%{NUMBER:process.pid:long}(-%{BASE16FLOAT:postgresql.log.core_id:long})?\] + ((\[%{USERNAME:user.name}\]@\[%{POSTGRESQL_DB_NAME:postgresql.log.database}\]|%{USERNAME:user.name}@%{POSTGRESQL_DB_NAME:postgresql.log.database}) + )?%{WORD:log.level}: (?:%{NUMBER:postgresql.log.error.code:long}|%{SPACE})(duration: + %{NUMBER:temp.duration:float} ms %{POSTGRESQL_QUERY_STEP}: %{GREEDYDATA:postgresql.log.query}|: + %{GREEDYDATA:message}|%{GREEDYDATA:message})' + pattern_definitions: + DATETIME: '[-0-9]+ %{TIME} %{WORD:event.timezone}' + GREEDYDATA: |- + (.| + | )* + POSTGRESQL_DB_NAME: '[a-zA-Z0-9_]+[a-zA-Z0-9_\$]*' + POSTGRESQL_QUERY_STEP: '%{WORD:postgresql.log.query_step}(?: | %{WORD:postgresql.log.query_name})?' +- date: + field: postgresql.log.timestamp + target_field: '@timestamp' + formats: + - yyyy-MM-dd HH:mm:ss.SSS zz + - yyyy-MM-dd HH:mm:ss zz +- script: + lang: painless + source: ctx.event.duration = Math.round(ctx.temp.duration * params.scale) + params: + scale: 1000000 + if: ctx.temp?.duration != null +- remove: + field: temp.duration + ignore_missing: true +- set: + field: event.kind + value: event +- append: + field: event.category + value: + - database +- append: + field: event.type + value: + - info +- append: + field: event.type + value: + - error + if: "ctx?.postgresql?.log?.error?.code != null && ctx.postgresql.log.error.code >= 02000" +- append: + field: related.user + value: "{{user.name}}" + if: "ctx?.user?.name != null" +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/filebeat/module/postgresql/log/manifest.yml b/filebeat/module/postgresql/log/manifest.yml index e5ab4a9a69c..ade6e2899de 100644 --- a/filebeat/module/postgresql/log/manifest.yml +++ b/filebeat/module/postgresql/log/manifest.yml @@ -9,5 +9,5 @@ var: os.windows: - "c:/Program Files/PostgreSQL/*/logs/*.log*" -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/log.yml diff --git a/filebeat/module/postgresql/log/test/postgresql-11.4.log-expected.json b/filebeat/module/postgresql/log/test/postgresql-11.4.log-expected.json index 2c347c87c6a..2d95ce2fd0e 100644 --- a/filebeat/module/postgresql/log/test/postgresql-11.4.log-expected.json +++ b/filebeat/module/postgresql/log/test/postgresql-11.4.log-expected.json @@ -1,9 +1,16 @@ [ { "@timestamp": "2019-07-23T12:06:24.406Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -16,9 +23,16 @@ }, { "@timestamp": "2019-07-23T12:06:24.406Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOCATION", @@ -30,9 +44,16 @@ }, { "@timestamp": "2019-07-23T12:06:24.478Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -45,9 +66,16 @@ }, { "@timestamp": "2019-07-23T12:06:24.478Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOCATION", @@ -59,9 +87,16 @@ }, { "@timestamp": "2019-07-23T12:06:24.485Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -74,9 +109,16 @@ }, { "@timestamp": "2019-07-23T12:06:24.485Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOCATION", @@ -88,9 +130,16 @@ }, { "@timestamp": "2019-07-23T12:06:24.485Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -103,9 +152,16 @@ }, { "@timestamp": "2019-07-23T12:06:24.485Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOCATION", @@ -117,9 +173,16 @@ }, { "@timestamp": "2019-07-23T12:06:24.485Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -132,9 +195,16 @@ }, { "@timestamp": "2019-07-23T12:06:24.485Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOCATION", @@ -146,9 +216,16 @@ }, { "@timestamp": "2019-07-23T12:06:24.507Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -161,9 +238,16 @@ }, { "@timestamp": "2019-07-23T12:06:24.507Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOCATION", @@ -175,9 +259,16 @@ }, { "@timestamp": "2019-07-23T12:06:30.536Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -190,9 +281,16 @@ }, { "@timestamp": "2019-07-23T12:06:30.536Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOCATION", @@ -204,9 +302,16 @@ }, { "@timestamp": "2019-07-23T12:06:30.537Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -219,9 +324,16 @@ }, { "@timestamp": "2019-07-23T12:06:30.537Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOCATION", @@ -233,9 +345,16 @@ }, { "@timestamp": "2019-07-23T12:06:33.732Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -248,9 +367,16 @@ }, { "@timestamp": "2019-07-23T12:06:33.732Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOCATION", @@ -262,9 +388,17 @@ }, { "@timestamp": "2019-07-23T12:06:33.732Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info", + "error" + ], "fileset.name": "log", "input.type": "log", "log.level": "ERROR", @@ -277,9 +411,16 @@ }, { "@timestamp": "2019-07-23T12:06:33.732Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOCATION", @@ -291,9 +432,16 @@ }, { "@timestamp": "2019-07-23T12:06:33.732Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "STATEMENT", @@ -305,9 +453,16 @@ }, { "@timestamp": "2019-07-23T12:06:34.877Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -320,9 +475,16 @@ }, { "@timestamp": "2019-07-23T12:06:34.877Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOCATION", @@ -334,9 +496,16 @@ }, { "@timestamp": "2019-07-23T12:06:34.878Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -349,9 +518,16 @@ }, { "@timestamp": "2019-07-23T12:06:34.878Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOCATION", @@ -363,9 +539,16 @@ }, { "@timestamp": "2019-07-23T12:09:57.563Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -378,9 +561,16 @@ }, { "@timestamp": "2019-07-23T12:09:57.563Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOCATION", @@ -392,9 +582,16 @@ }, { "@timestamp": "2019-07-23T12:09:57.565Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -407,9 +604,16 @@ }, { "@timestamp": "2019-07-23T12:09:57.565Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOCATION", diff --git a/filebeat/module/postgresql/log/test/postgresql-9.6-debian-with-slowlog.log-expected.json b/filebeat/module/postgresql/log/test/postgresql-9.6-debian-with-slowlog.log-expected.json index 201c50cb0b7..280547f6b29 100644 --- a/filebeat/module/postgresql/log/test/postgresql-9.6-debian-with-slowlog.log-expected.json +++ b/filebeat/module/postgresql/log/test/postgresql-9.6-debian-with-slowlog.log-expected.json @@ -1,9 +1,16 @@ [ { "@timestamp": "2017-07-31T11:36:42.585Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -15,9 +22,16 @@ }, { "@timestamp": "2017-07-31T11:36:42.605Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -29,9 +43,16 @@ }, { "@timestamp": "2017-07-31T11:36:42.615Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -43,9 +64,16 @@ }, { "@timestamp": "2017-07-31T11:36:42.616Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -57,9 +85,16 @@ }, { "@timestamp": "2017-07-31T11:36:42.956Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -68,15 +103,25 @@ "postgresql.log.database": "unknown", "postgresql.log.timestamp": "2017-07-31 13:36:42.956 CEST", "process.pid": 4980, + "related.user": [ + "unknown" + ], "service.type": "postgresql", "user.name": "unknown" }, { "@timestamp": "2017-07-31T11:36:43.557Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", "event.duration": 37118000, + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.flags": [ @@ -90,15 +135,25 @@ "postgresql.log.query_step": "statement", "postgresql.log.timestamp": "2017-07-31 13:36:43.557 CEST", "process.pid": 4983, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-07-31T11:36:44.104Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", "event.duration": 2895000, + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.flags": [ @@ -112,15 +167,25 @@ "postgresql.log.query_step": "statement", "postgresql.log.timestamp": "2017-07-31 13:36:44.104 CEST", "process.pid": 4986, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-07-31T11:36:44.642Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", "event.duration": 2809000, + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.flags": [ @@ -134,14 +199,24 @@ "postgresql.log.query_step": "statement", "postgresql.log.timestamp": "2017-07-31 13:36:44.642 CEST", "process.pid": 4989, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-07-31T11:39:16.249Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "FATAL", @@ -150,14 +225,24 @@ "postgresql.log.database": "users", "postgresql.log.timestamp": "2017-07-31 13:39:16.249 CEST", "process.pid": 5407, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-07-31T11:39:17.945Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "FATAL", @@ -166,15 +251,25 @@ "postgresql.log.database": "user", "postgresql.log.timestamp": "2017-07-31 13:39:17.945 CEST", "process.pid": 5500, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-07-31T11:39:21.025Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", "event.duration": 37598000, + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.flags": [ @@ -188,15 +283,25 @@ "postgresql.log.query_step": "statement", "postgresql.log.timestamp": "2017-07-31 13:39:21.025 CEST", "process.pid": 5404, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-07-31T11:39:31.619Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", "event.duration": 9482000, + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -207,15 +312,25 @@ "postgresql.log.query_step": "statement", "postgresql.log.timestamp": "2017-07-31 13:39:31.619 CEST", "process.pid": 5502, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-07-31T11:39:40.147Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", "event.duration": 765000, + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -226,15 +341,25 @@ "postgresql.log.query_step": "statement", "postgresql.log.timestamp": "2017-07-31 13:39:40.147 CEST", "process.pid": 5502, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-07-31T11:40:54.310Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", - "event.duration": 26082001, + "event.duration": 26082000, + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.flags": [ @@ -248,15 +373,25 @@ "postgresql.log.query_step": "statement", "postgresql.log.timestamp": "2017-07-31 13:40:54.310 CEST", "process.pid": 5502, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-07-31T11:43:22.645Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", - "event.duration": 36161999, + "event.duration": 36162000, + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -267,15 +402,25 @@ "postgresql.log.query_step": "statement", "postgresql.log.timestamp": "2017-07-31 13:43:22.645 CEST", "process.pid": 5502, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-07-31T11:46:02.670Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", "event.duration": 10540000, + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -286,15 +431,25 @@ "postgresql.log.query_step": "statement", "postgresql.log.timestamp": "2017-07-31 13:46:02.670 CEST", "process.pid": 5502, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-07-31T11:46:23.016Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", "event.duration": 5156000, + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -305,15 +460,25 @@ "postgresql.log.query_step": "statement", "postgresql.log.timestamp": "2017-07-31 13:46:23.016 CEST", "process.pid": 5502, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-07-31T11:46:55.637Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", "event.duration": 25871000, + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -324,15 +489,25 @@ "postgresql.log.query_step": "statement", "postgresql.log.timestamp": "2017-07-31 13:46:55.637 CEST", "process.pid": 5502, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2019-05-06T19:00:04.511Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", "event.duration": 753000, + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.flags": [ @@ -346,6 +521,9 @@ "postgresql.log.query_step": "statement", "postgresql.log.timestamp": "2019-05-06 19:00:04.511 UTC", "process.pid": 913763, + "related.user": [ + "elastic" + ], "service.type": "postgresql", "user.name": "elastic" } diff --git a/filebeat/module/postgresql/log/test/postgresql-9.6-multi-core.log-expected.json b/filebeat/module/postgresql/log/test/postgresql-9.6-multi-core.log-expected.json index dbd1e12dd49..76f1bd2f065 100644 --- a/filebeat/module/postgresql/log/test/postgresql-9.6-multi-core.log-expected.json +++ b/filebeat/module/postgresql/log/test/postgresql-9.6-multi-core.log-expected.json @@ -1,9 +1,16 @@ [ { "@timestamp": "2017-04-03T20:32:14.322Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -13,14 +20,24 @@ "postgresql.log.database": "unknown", "postgresql.log.timestamp": "2017-04-03 22:32:14.322 CEST", "process.pid": 12975, + "related.user": [ + "unknown" + ], "service.type": "postgresql", "user.name": "unknown" }, { "@timestamp": "2017-04-03T20:32:14.322Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "FATAL", @@ -30,15 +47,25 @@ "postgresql.log.database": "user", "postgresql.log.timestamp": "2017-04-03 22:32:14.322 CEST", "process.pid": 5404, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-04-03T20:35:22.389Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", "event.duration": 37598000, + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.flags": [ @@ -53,14 +80,24 @@ "postgresql.log.query_step": "statement", "postgresql.log.timestamp": "2017-04-03 22:35:22.389 CEST", "process.pid": 5404, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-07-31T17:36:43.557Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "EST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -73,9 +110,16 @@ }, { "@timestamp": "2017-07-31T17:36:44.227Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "EST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -88,9 +132,16 @@ }, { "@timestamp": "2017-07-31T17:46:02.670Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "EST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "HINT", @@ -103,9 +154,16 @@ }, { "@timestamp": "2017-07-31T17:46:23.016Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "EST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "FATAL", @@ -115,14 +173,24 @@ "postgresql.log.database": "postgres", "postgresql.log.timestamp": "2017-07-31 13:46:23.016 EST", "process.pid": 768, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-07-31T17:46:55.637Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "EST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "FATAL", @@ -132,6 +200,9 @@ "postgresql.log.database": "postgres", "postgresql.log.timestamp": "2017-07-31 13:46:55.637 EST", "process.pid": 771, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" } diff --git a/filebeat/module/postgresql/log/test/postgresql-9.6-new-timestamp.log-expected.json b/filebeat/module/postgresql/log/test/postgresql-9.6-new-timestamp.log-expected.json index 9737568df83..9a1d8b1b5fa 100644 --- a/filebeat/module/postgresql/log/test/postgresql-9.6-new-timestamp.log-expected.json +++ b/filebeat/module/postgresql/log/test/postgresql-9.6-new-timestamp.log-expected.json @@ -1,9 +1,16 @@ [ { "@timestamp": "2017-07-31T17:36:43.000Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "EST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -16,9 +23,16 @@ }, { "@timestamp": "2017-07-31T17:36:44.000Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "EST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -31,9 +45,16 @@ }, { "@timestamp": "2017-07-31T17:46:02.000Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "EST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "HINT", @@ -46,9 +67,16 @@ }, { "@timestamp": "2017-07-31T17:46:23.000Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "EST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "FATAL", @@ -58,14 +86,24 @@ "postgresql.log.database": "postgres", "postgresql.log.timestamp": "2017-07-31 13:46:23 EST", "process.pid": 768, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-07-31T17:46:55.000Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "EST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "FATAL", @@ -75,6 +113,9 @@ "postgresql.log.database": "postgres", "postgresql.log.timestamp": "2017-07-31 13:46:55 EST", "process.pid": 771, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" } diff --git a/filebeat/module/postgresql/log/test/postgresql-query-steps-slowlog.log-expected.json b/filebeat/module/postgresql/log/test/postgresql-query-steps-slowlog.log-expected.json index 273499e8634..cec040589ab 100644 --- a/filebeat/module/postgresql/log/test/postgresql-query-steps-slowlog.log-expected.json +++ b/filebeat/module/postgresql/log/test/postgresql-query-steps-slowlog.log-expected.json @@ -1,10 +1,17 @@ [ { "@timestamp": "2019-09-04T13:52:38.004Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", "event.duration": 12437000, + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -15,15 +22,25 @@ "postgresql.log.query_step": "parse", "postgresql.log.timestamp": "2019-09-04 15:52:38.004 CEST", "process.pid": 31136, + "related.user": [ + "user" + ], "service.type": "postgresql", "user.name": "user" }, { "@timestamp": "2019-09-04T13:52:38.004Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", "event.duration": 12437000, + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.flags": [ @@ -38,6 +55,9 @@ "postgresql.log.query_step": "execute", "postgresql.log.timestamp": "2019-09-04 15:52:38.004 CEST", "process.pid": 31136, + "related.user": [ + "user" + ], "service.type": "postgresql", "user.name": "user" } diff --git a/filebeat/module/postgresql/log/test/postgresql-ubuntu-9.5.log-expected.json b/filebeat/module/postgresql/log/test/postgresql-ubuntu-9.5.log-expected.json index 0d1b3df95b5..f1248d53e45 100644 --- a/filebeat/module/postgresql/log/test/postgresql-ubuntu-9.5.log-expected.json +++ b/filebeat/module/postgresql/log/test/postgresql-ubuntu-9.5.log-expected.json @@ -1,9 +1,16 @@ [ { "@timestamp": "2017-04-03T20:32:14.322Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -12,14 +19,24 @@ "postgresql.log.database": "mydb", "postgresql.log.timestamp": "2017-04-03 22:32:14.322 CEST", "process.pid": 31225, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-04-03T20:32:14.322Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -28,14 +45,24 @@ "postgresql.log.database": "mydb", "postgresql.log.timestamp": "2017-04-03 22:32:14.322 CEST", "process.pid": 31225, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-04-03T20:35:22.389Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -44,14 +71,24 @@ "postgresql.log.database": "mydb", "postgresql.log.timestamp": "2017-04-03 22:35:22.389 CEST", "process.pid": 3474, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-04-03T20:36:56.464Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -60,14 +97,24 @@ "postgresql.log.database": "mydb", "postgresql.log.timestamp": "2017-04-03 22:36:56.464 CEST", "process.pid": 3525, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-04-03T20:37:12.961Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -76,14 +123,24 @@ "postgresql.log.database": "mydb", "postgresql.log.timestamp": "2017-04-03 22:37:12.961 CEST", "process.pid": 3570, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-04-07T19:05:28.549Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -92,14 +149,24 @@ "postgresql.log.database": "mydb", "postgresql.log.timestamp": "2017-04-07 21:05:28.549 CEST", "process.pid": 21483, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-04-07T19:09:41.345Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -108,14 +175,24 @@ "postgresql.log.database": "mydb", "postgresql.log.timestamp": "2017-04-07 21:09:41.345 CEST", "process.pid": 21597, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-04-07T20:45:30.218Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "ERROR", @@ -124,14 +201,24 @@ "postgresql.log.database": "mydb", "postgresql.log.timestamp": "2017-04-07 22:45:30.218 CEST", "process.pid": 22603, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-04-07T20:45:30.218Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "HINT", @@ -140,14 +227,24 @@ "postgresql.log.database": "mydb", "postgresql.log.timestamp": "2017-04-07 22:45:30.218 CEST", "process.pid": 22603, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-04-07T20:45:30.218Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "STATEMENT", @@ -156,14 +253,24 @@ "postgresql.log.database": "mydb", "postgresql.log.timestamp": "2017-04-07 22:45:30.218 CEST", "process.pid": 22603, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-04-07T20:46:09.751Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "ERROR", @@ -172,14 +279,24 @@ "postgresql.log.database": "mydb", "postgresql.log.timestamp": "2017-04-07 22:46:09.751 CEST", "process.pid": 22608, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-04-07T20:46:09.751Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "STATEMENT", @@ -188,14 +305,24 @@ "postgresql.log.database": "mydb", "postgresql.log.timestamp": "2017-04-07 22:46:09.751 CEST", "process.pid": 22608, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-04-07T21:02:51.199Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -204,14 +331,24 @@ "postgresql.log.database": "mydb", "postgresql.log.timestamp": "2017-04-07 23:02:51.199 CEST", "process.pid": 24341, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-04-07T21:02:51.199Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -220,14 +357,24 @@ "postgresql.log.database": "mydb", "postgresql.log.timestamp": "2017-04-07 23:02:51.199 CEST", "process.pid": 24341, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-04-07T21:04:36.087Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "ERROR", @@ -236,14 +383,24 @@ "postgresql.log.database": "mydb", "postgresql.log.timestamp": "2017-04-07 23:04:36.087 CEST", "process.pid": 20730, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-04-07T21:04:36.087Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "STATEMENT", @@ -252,14 +409,24 @@ "postgresql.log.database": "mydb", "postgresql.log.timestamp": "2017-04-07 23:04:36.087 CEST", "process.pid": 20730, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-04-07T21:04:51.462Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "ERROR", @@ -268,14 +435,24 @@ "postgresql.log.database": "mydb", "postgresql.log.timestamp": "2017-04-07 23:04:51.462 CEST", "process.pid": 20730, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-04-07T21:04:51.462Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "STATEMENT", @@ -284,14 +461,24 @@ "postgresql.log.database": "mydb", "postgresql.log.timestamp": "2017-04-07 23:04:51.462 CEST", "process.pid": 20730, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-04-07T21:05:06.217Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "ERROR", @@ -300,14 +487,24 @@ "postgresql.log.database": "mydb", "postgresql.log.timestamp": "2017-04-07 23:05:06.217 CEST", "process.pid": 20730, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-04-07T21:05:06.217Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "STATEMENT", @@ -316,14 +513,24 @@ "postgresql.log.database": "mydb", "postgresql.log.timestamp": "2017-04-07 23:05:06.217 CEST", "process.pid": 20730, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-04-07T21:05:18.295Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "ERROR", @@ -332,14 +539,24 @@ "postgresql.log.database": "mydb", "postgresql.log.timestamp": "2017-04-07 23:05:18.295 CEST", "process.pid": 20730, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-04-07T21:05:18.295Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "STATEMENT", @@ -348,14 +565,24 @@ "postgresql.log.database": "mydb", "postgresql.log.timestamp": "2017-04-07 23:05:18.295 CEST", "process.pid": 20730, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-04-07T21:13:47.505Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -364,14 +591,24 @@ "postgresql.log.database": "mydb", "postgresql.log.timestamp": "2017-04-07 23:13:47.505 CEST", "process.pid": 24489, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-04-07T21:13:47.505Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -380,14 +617,24 @@ "postgresql.log.database": "mydb", "postgresql.log.timestamp": "2017-04-07 23:13:47.505 CEST", "process.pid": 24489, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-04-08T10:32:51.056Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "ERROR", @@ -396,14 +643,24 @@ "postgresql.log.database": "mydb", "postgresql.log.timestamp": "2017-04-08 12:32:51.056 CEST", "process.pid": 20730, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-04-08T10:32:51.056Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "DETAIL", @@ -412,14 +669,24 @@ "postgresql.log.database": "mydb", "postgresql.log.timestamp": "2017-04-08 12:32:51.056 CEST", "process.pid": 20730, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-04-08T10:32:51.056Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "STATEMENT", @@ -428,14 +695,24 @@ "postgresql.log.database": "mydb", "postgresql.log.timestamp": "2017-04-08 12:32:51.056 CEST", "process.pid": 20730, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-04-08T19:54:37.443Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -444,14 +721,24 @@ "postgresql.log.database": "mydb", "postgresql.log.timestamp": "2017-04-08 21:54:37.443 CEST", "process.pid": 30630, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-04-08T19:54:37.468Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -460,14 +747,24 @@ "postgresql.log.database": "mydb", "postgresql.log.timestamp": "2017-04-08 21:54:37.468 CEST", "process.pid": 30502, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-04-08T19:54:37.618Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -479,9 +776,16 @@ }, { "@timestamp": "2017-04-08T19:54:37.618Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -493,9 +797,16 @@ }, { "@timestamp": "2017-04-08T19:54:37.618Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -507,9 +818,16 @@ }, { "@timestamp": "2017-04-08T19:54:37.622Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -521,9 +839,16 @@ }, { "@timestamp": "2017-04-08T19:54:37.644Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -535,9 +860,16 @@ }, { "@timestamp": "2017-04-08T19:56:02.932Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -549,9 +881,16 @@ }, { "@timestamp": "2017-04-08T19:56:02.944Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -563,9 +902,16 @@ }, { "@timestamp": "2017-04-08T19:56:02.946Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -577,9 +923,16 @@ }, { "@timestamp": "2017-04-08T19:56:02.947Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -591,9 +944,16 @@ }, { "@timestamp": "2017-04-08T19:56:03.362Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -602,14 +962,24 @@ "postgresql.log.database": "unknown", "postgresql.log.timestamp": "2017-04-08 21:56:03.362 CEST", "process.pid": 891, + "related.user": [ + "unknown" + ], "service.type": "postgresql", "user.name": "unknown" }, { "@timestamp": "2017-05-27T14:07:53.007Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -621,9 +991,16 @@ }, { "@timestamp": "2017-05-27T14:07:53.010Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -635,9 +1012,16 @@ }, { "@timestamp": "2017-05-27T14:07:53.015Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -649,9 +1033,16 @@ }, { "@timestamp": "2017-05-27T14:07:53.016Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -663,9 +1054,16 @@ }, { "@timestamp": "2017-05-27T14:07:53.463Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -674,14 +1072,24 @@ "postgresql.log.database": "unknown", "postgresql.log.timestamp": "2017-05-27 14:07:53.463 UTC", "process.pid": 32573, + "related.user": [ + "unknown" + ], "service.type": "postgresql", "user.name": "unknown" }, { "@timestamp": "2017-05-27T14:08:13.661Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "FATAL", @@ -690,14 +1098,24 @@ "postgresql.log.database": "mydb", "postgresql.log.timestamp": "2017-05-27 14:08:13.661 UTC", "process.pid": 1308, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-05-27T14:59:26.553Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -706,14 +1124,24 @@ "postgresql.log.database": "mydb", "postgresql.log.timestamp": "2017-05-27 14:59:26.553 UTC", "process.pid": 1994, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-05-27T14:59:26.555Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "UTC", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -722,14 +1150,24 @@ "postgresql.log.database": "mydb", "postgresql.log.timestamp": "2017-05-27 14:59:26.555 UTC", "process.pid": 1989, + "related.user": [ + "postgres" + ], "service.type": "postgresql", "user.name": "postgres" }, { "@timestamp": "2017-06-06T05:54:13.753Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -741,9 +1179,16 @@ }, { "@timestamp": "2017-06-06T05:54:13.753Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -755,9 +1200,16 @@ }, { "@timestamp": "2017-06-06T05:54:13.753Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -769,9 +1221,16 @@ }, { "@timestamp": "2017-06-06T05:54:13.755Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -783,9 +1242,16 @@ }, { "@timestamp": "2017-06-06T05:54:13.816Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -797,9 +1263,16 @@ }, { "@timestamp": "2017-06-06T05:55:39.725Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -811,9 +1284,16 @@ }, { "@timestamp": "2017-06-06T05:55:39.736Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -825,9 +1305,16 @@ }, { "@timestamp": "2017-06-06T05:55:39.739Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -839,9 +1326,16 @@ }, { "@timestamp": "2017-06-06T05:55:39.739Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -853,9 +1347,16 @@ }, { "@timestamp": "2017-06-06T05:55:40.155Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -864,14 +1365,24 @@ "postgresql.log.database": "unknown", "postgresql.log.timestamp": "2017-06-06 07:55:40.155 CEST", "process.pid": 12975, + "related.user": [ + "unknown" + ], "service.type": "postgresql", "user.name": "unknown" }, { "@timestamp": "2017-06-06T05:55:40.156Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -880,14 +1391,24 @@ "postgresql.log.database": "unknown", "postgresql.log.timestamp": "2017-06-06 07:55:40.156 CEST", "process.pid": 12975, + "related.user": [ + "unknown" + ], "service.type": "postgresql", "user.name": "unknown" }, { "@timestamp": "2017-06-10T17:37:30.681Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -899,9 +1420,16 @@ }, { "@timestamp": "2017-06-10T17:37:30.695Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -913,9 +1441,16 @@ }, { "@timestamp": "2017-06-10T17:37:30.702Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -927,9 +1462,16 @@ }, { "@timestamp": "2017-06-10T17:37:30.702Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -941,9 +1483,16 @@ }, { "@timestamp": "2017-06-10T17:37:31.104Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -952,14 +1501,24 @@ "postgresql.log.database": "unknown", "postgresql.log.timestamp": "2017-06-10 19:37:31.104 CEST", "process.pid": 17404, + "related.user": [ + "unknown" + ], "service.type": "postgresql", "user.name": "unknown" }, { "@timestamp": "2017-06-10T18:27:55.911Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -971,9 +1530,16 @@ }, { "@timestamp": "2017-06-10T18:27:55.911Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -985,9 +1551,16 @@ }, { "@timestamp": "2017-06-10T18:27:55.911Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -999,9 +1572,16 @@ }, { "@timestamp": "2017-06-10T18:27:55.914Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -1013,9 +1593,16 @@ }, { "@timestamp": "2017-06-10T18:27:55.973Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -1027,9 +1614,16 @@ }, { "@timestamp": "2017-06-10T18:27:57.022Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -1041,9 +1635,16 @@ }, { "@timestamp": "2017-06-10T18:27:57.032Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -1055,9 +1656,16 @@ }, { "@timestamp": "2017-06-10T18:27:57.035Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -1069,9 +1677,16 @@ }, { "@timestamp": "2017-06-10T18:27:57.035Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -1083,9 +1698,16 @@ }, { "@timestamp": "2017-06-10T18:27:57.475Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -1094,14 +1716,24 @@ "postgresql.log.database": "unknown", "postgresql.log.timestamp": "2017-06-10 20:27:57.475 CEST", "process.pid": 24496, + "related.user": [ + "unknown" + ], "service.type": "postgresql", "user.name": "unknown" }, { "@timestamp": "2017-06-17T14:58:03.937Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -1113,9 +1745,16 @@ }, { "@timestamp": "2017-06-17T14:58:03.937Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -1127,9 +1766,16 @@ }, { "@timestamp": "2017-06-17T14:58:03.938Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -1141,9 +1787,16 @@ }, { "@timestamp": "2017-06-17T14:58:03.940Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", @@ -1155,9 +1808,16 @@ }, { "@timestamp": "2017-06-17T14:58:04.040Z", + "event.category": [ + "database" + ], "event.dataset": "postgresql.log", + "event.kind": "event", "event.module": "postgresql", "event.timezone": "CEST", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "LOG", From e1744413f069d50778e508a04dfcf51047e195ad Mon Sep 17 00:00:00 2001 From: Lee Hinman <57081003+leehinman@users.noreply.github.com> Date: Thu, 23 Apr 2020 09:44:35 -0500 Subject: [PATCH 011/116] Improve ECS field mappings in panw module (#17910) - panw.panos.action - event.outcome, limit to succes/failure - event.kind - event.category, make array - event.type, make array - rule.name - related.user Closes #16025 --- CHANGELOG.next.asciidoc | 2 +- x-pack/filebeat/module/panw/fields.go | 2 +- .../module/panw/panos/_meta/fields.yml | 4 + .../module/panw/panos/config/input.yml | 4 +- .../module/panw/panos/ingest/pipeline.yml | 100 +- .../test/pan_inc_other.log-expected.json | 19 +- .../test/pan_inc_threat.log-expected.json | 1797 ++++++++++++++-- .../test/pan_inc_traffic.log-expected.json | 1888 +++++++++++++++-- .../panw/panos/test/threat.log-expected.json | 1064 ++++++++-- .../panw/panos/test/traffic.log-expected.json | 1492 +++++++++++-- 10 files changed, 5603 insertions(+), 769 deletions(-) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index e2f62c51b33..141a852c5a7 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -20,7 +20,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - File integrity dataset (macOS): Replace unnecessary `file.origin.raw` (type keyword) with `file.origin.text` (type `text`). {issue}12423[12423] {pull}15630[15630] *Filebeat* - +- Improve ECS field mappings in panw module. event.outcome now only contains success/failure per ECS specification. {issue}16025[16025] {pull}17910[17910] *Heartbeat* diff --git a/x-pack/filebeat/module/panw/fields.go b/x-pack/filebeat/module/panw/fields.go index 8d877ad9d7e..5864f7597ab 100644 --- a/x-pack/filebeat/module/panw/fields.go +++ b/x-pack/filebeat/module/panw/fields.go @@ -19,5 +19,5 @@ func init() { // AssetPanw returns asset data. // This is the base64 encoded gzipped contents of module/panw. func AssetPanw() string { - return "eJzMmM9u4zYQxu95irm5BeLcesmhQNpFgABpauw26HFBUyOJNcXRDkdxtU+/IC3bWpu2tLbXiG6STH6/+fNRpKewwPYeauWWNwBixOLmLkOv2dRiyN3D7zcAAH9R1liEnBhmyhI8WCF4QVkSLzz8Mnt4mf796dcbgNygzfx9HDQFp6rttOGStsZ7KJiaunuSEAvXY5wHcqYKpMQ4B1SR4q77UV8KdvTIbx+nVI9Jf6dPHOUTQa9iBkuFv+uP3cPqkXFj0aN8L9XhLbBdEmc7744xAsCLqhAoj4xhcpBSCVRKdIkZSGk8ePTekLtLAnlqWGOSZy9dwzRd0oQA/xd0WcQSqqcW39B2YkDz/1DL3c7oVNr6pF/J7XIO5W4Ecbg+rbCCQFfvQ1nr8xgnyLnaS95loTYqP0Dm1G6DwdGijiSakZfpy8M/6zKqLGP0/hZMvn4U3hoPNXJOXGG2z3i4zr3MpgDXARx4OYJ/P4KnWQpws4oQp/K4BrHkisuhBLEtTNKqGXoxToV5r+TXnuK7M+2HHtv7cm6f7D3at1/Vvof7z88z8qCVB8w80kMHYkq7eoSvB519HtcIi7vV3uJK9u7UTrR2rVX92aQ8cgEPzZReoIBWtTSM8PQh+keBlIzqUBbhTBONaWhNVdU4I2069DHhj0xBuP5cq8UMWFpOS+XLzaY0dNhvU2nq7Z74QGPlxl5rkxekTmypENxp/fKHcYpb2GRn3SkrGo9OAu8cQTll26+Yrsu8jbH8a2z2aDiM4zejMbWcpIuczH3D9kqpb9iemHmtBAvi9ue4+TH2a6zH68fn8LWRiY/srx+fN9rpVTuMXRfkNo55Q86MFiAXb2OFlcvA+OQEaKREhkmlrNGGGj+5hUnBql0qxsktEMNkjs4UbjJkIkvLfdufcXh7CrsDpyy4pkI2GkyGTkxukGMXo9Ll/n4hfY7DLw06jZ9dU82Rk4yJ79oA4DMVgE647ZPFE6bxYJxmrNAJZp28GGVtoo6vznxpcBuSpSIiDcTULfaMRw6pJ+U9dB3xqnOC1JiPyw7UJdsg8d/CTiNEm/8AXbj5qXybrPXJvgUAAP//KvmK+g==" + return "eJzMmM9u4zYQxu95irm5BeLcesmhQNpFgABpauw26HFBUyOJNcXRDkdxtU+/IC3LWouOtE42iG6STH6/+fNRpJewwfYaauW2FwBixGJ/l6HXbGox5K7h9wsAgL8oayxCTgwrZQlurBA8oGyJNx5+Wd08LP/+9OsFQG7QZv46DlqCU9Vh2nBJW+M1FExN3T1JiIXrNs4DOVMFUmKcA6pIcdX9aCgFR3rkD49Tqs9Jf6dPHOUTQe9iBkuFvxqOHWENyLix6FG+l+rwNthuibOjd88xAsCDqhAoj4xhcpBSCVRKdIkZSGk8ePTekLtKAnlqWGOSZ5SuaZouaUKA/wu6LGIJ1UuLT2g7MaD1f6jl6mh0Km1D0q/kjjmncjeDOFyfdlhBoKv3qawNeYwT5FyNkve6UL3KD5A5ddxg8GxRZxKtyMvy4eaffRlVljF6fwkm3z8Kb42HGjknrjAbM56u8yCzKcB9ACdezuAfR3C3SgH2qwhxKo97EEuueD2UIHaASVo1Qy/GqTDvG/l1oPjuTPthwPa+nDske4/2HVZ16OHh85cZedLKE2ae6aETMaVdPcPXk85+GdcMi7vd3uKN7N2pnWntWqv6s0l55BU8tFJ6gwJa1dIwwt2H6B8FUjKqU1mEF5poTkNrqqrGGWnToc8Jf2YKwvXnXi1mwNJ2WSpf9pvS0GG/LaWpD3viE42VG/tWm7wgdWZLheDO65c/jFPcQp+dfafsaDw6CbxrBOWUbb9iui7rNsbyr7HZreEwjp+MxtRyki5yMvcN2zdKfcP2zMxrJVgQtz/HzbexX2M9Hj/eh6+NLHxkf/x432unV+0wdl+QyzjmCTkzWoBcvI0VVi4D45MToJESGRaVskYbavziEhYFq3arGBeXQAyLNTpTuMWUiSxtx7Z/weHtLuwOnLLgmgrZaDAZOjG5QY5djEqX4/1C+hyHXxp0Gj+7plojJxkT37UJwHsqAJ1wOySLJ0zjwTjNWKETzDp5McraRB0fnfnS4CEkS0VEmoipW+wZnzmknpX30HXEu84JUnM+LkdQr9kGif8Wjhoh2vwH6MLNT+Xrs5YiGwIpffK8ModlOYK5iROCqA26nqD3yLcAAAD//yC/rB0=" } diff --git a/x-pack/filebeat/module/panw/panos/_meta/fields.yml b/x-pack/filebeat/module/panw/panos/_meta/fields.yml index 14920667ca6..a5900461f08 100644 --- a/x-pack/filebeat/module/panw/panos/_meta/fields.yml +++ b/x-pack/filebeat/module/panw/panos/_meta/fields.yml @@ -127,3 +127,7 @@ type: keyword description: > Palo Alto Networks name for the threat. + - name: action + type: keyword + description: >- + Action taken for the session. diff --git a/x-pack/filebeat/module/panw/panos/config/input.yml b/x-pack/filebeat/module/panw/panos/config/input.yml index 7998f04511a..929237b99af 100644 --- a/x-pack/filebeat/module/panw/panos/config/input.yml +++ b/x-pack/filebeat/module/panw/panos/config/input.yml @@ -70,7 +70,7 @@ processors: destination.nat.port: 27 _temp_.labels: 28 network.transport: 29 - event.outcome: 30 + panw.panos.action: 30 network.bytes: 31 client.bytes: 32 destination.bytes: 32 @@ -123,7 +123,7 @@ processors: destination.nat.port: 27 _temp_.labels: 28 network.transport: 29 - event.outcome: 30 + panw.panos.action: 30 panw.panos.threat.resource: 31 url.original: 31 panw.panos.threat.name: 32 diff --git a/x-pack/filebeat/module/panw/panos/ingest/pipeline.yml b/x-pack/filebeat/module/panw/panos/ingest/pipeline.yml index 135d90a04dc..1c2c912bd87 100644 --- a/x-pack/filebeat/module/panw/panos/ingest/pipeline.yml +++ b/x-pack/filebeat/module/panw/panos/ingest/pipeline.yml @@ -175,34 +175,82 @@ processors: # Set event.category depending on log type. - set: + field: event.kind + value: event + if: 'ctx?._temp_?.message_type == "TRAFFIC"' + - append: field: event.category - value: network_traffic + value: + - network_traffic + - network if: 'ctx?._temp_?.message_type == "TRAFFIC"' - set: + field: event.kind + value: alert + if: 'ctx?._temp_?.message_type == "THREAT"' + - append: field: event.category - value: security_threat + value: + - security_threat + - intrusion_detection + - network if: 'ctx?._temp_?.message_type == "THREAT"' - - drop: if: 'ctx?.event?.category == null' + - append: + field: event.type + value: allowed + if: "ctx?.panw?.panos?.action != null && ['alert', 'allow', 'continue'].contains(ctx.panw.panos.action)" + - append: + field: event.type + value: denied + if: "ctx?.panw?.panos?.action != null && ['deny', 'drop', 'reset-client', 'reset-server', 'reset-both', 'block-url', 'block-ip', 'random-drop', 'sinkhole', 'block'].contains(ctx.panw.panos.action)" + - set: + field: event.outcome + value: success + # event.action for traffic logs. - set: field: event.action value: flow_started if: 'ctx?._temp_?.message_subtype == "start"' + - append: + field: event.type + value: + - start + - connection + if: 'ctx?._temp_?.message_subtype == "start"' - set: field: event.action value: flow_terminated if: 'ctx?._temp_?.message_subtype == "end"' + - append: + field: event.type + value: + - end + - connection + if: 'ctx?._temp_?.message_subtype == "end"' - set: field: event.action value: flow_dropped if: 'ctx?._temp_?.message_subtype == "drop"' + - append: + field: event.type + value: + - denied + - connection + if: 'ctx?._temp_?.message_subtype == "drop"' - set: field: event.action value: flow_denied if: 'ctx?._temp_?.message_subtype == "deny"' + - append: + field: event.type + value: + - denied + - connection + if: 'ctx?._temp_?.message_subtype == "deny"' # event.action for threat logs. - set: @@ -276,21 +324,21 @@ processors: # Normalize event.outcome. # These values appear in the TRAFFIC docs but look like a mistake. - set: - field: event.outcome + field: panw.panos.action value: 'drop-icmp' - if: 'ctx?.event?.outcome == "drop icmp" || ctx?.event?.outcome == "drop ICMP"' + if: 'ctx?.panw?.panos?.action == "drop icmp" || ctx?.panw?.panos?.action == "drop ICMP"' - set: - field: event.outcome + field: panw.panos.action value: 'reset-both' - if: 'ctx?.event?.outcome == "reset both"' + if: 'ctx?.panw?.panos?.action == "reset both"' - set: - field: event.outcome + field: panw.panos.action value: 'reset-client' - if: 'ctx?.event?.outcome == "reset client"' + if: 'ctx?.panw?.panos?.action == "reset client"' - set: - field: event.outcome + field: panw.panos.action value: 'reset-server' - if: 'ctx?.event?.outcome == "reset server"' + if: 'ctx?.panw?.panos?.action == "reset server"' # Build related.ip array from src/dest/NAT IPs. - append: @@ -391,6 +439,36 @@ processors: value: 'URL-filtering' if: 'ctx?.panw?.panos?.threat?.id == "9999"' + - set: + field: rule.name + value: "{{panw.panos.ruleset}}" + if: "ctx?.panw?.panos?.ruleset != null" + + - append: + field: related.user + value: "{{client.user.name}}" + if: "ctx?.client?.user?.name != null" + + - append: + field: related.user + value: "{{source.user.name}}" + if: "ctx?.source?.user?.name != null" + + - append: + field: related.user + value: "{{server.user.name}}" + if: "ctx?.server?.user?.name != null" + + - append: + field: related.user + value: "{{destination.user.name}}" + if: "ctx?.destination?.user?.name != null" + + - append: + field: related.hash + value: "{{panw.panos.file.hash}}" + if: "ctx?.panw?.panos?.file?.hash != null" + # Remove temporary fields. - remove: field: diff --git a/x-pack/filebeat/module/panw/panos/test/pan_inc_other.log-expected.json b/x-pack/filebeat/module/panw/panos/test/pan_inc_other.log-expected.json index e94019b5a55..5b43295399c 100644 --- a/x-pack/filebeat/module/panw/panos/test/pan_inc_other.log-expected.json +++ b/x-pack/filebeat/module/panw/panos/test/pan_inc_other.log-expected.json @@ -23,14 +23,23 @@ "destination.packets": 1, "destination.port": 80, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:56.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:56.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -44,6 +53,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -62,6 +72,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "204.232.231.46", "server.packets": 1, diff --git a/x-pack/filebeat/module/panw/panos/test/pan_inc_threat.log-expected.json b/x-pack/filebeat/module/panw/panos/test/pan_inc_threat.log-expected.json index ecf18d56eb3..f6ca00ac200 100644 --- a/x-pack/filebeat/module/panw/panos/test/pan_inc_threat.log-expected.json +++ b/x-pack/filebeat/module/panw/panos/test/pan_inc_threat.log-expected.json @@ -20,12 +20,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -38,6 +46,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -58,6 +67,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -94,12 +108,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -112,6 +134,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -132,6 +155,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -168,12 +196,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -186,6 +222,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -206,6 +243,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -242,12 +284,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -260,6 +310,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -280,6 +331,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -316,12 +372,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -334,6 +398,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -354,6 +419,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -390,12 +460,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -408,6 +486,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -428,6 +507,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -464,12 +548,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -482,6 +574,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -502,6 +595,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -538,12 +636,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -556,6 +662,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -576,6 +683,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -612,12 +724,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -630,6 +750,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -650,6 +771,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -686,12 +812,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -704,6 +838,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -724,6 +859,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -760,12 +900,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -778,6 +926,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -798,6 +947,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -834,12 +988,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -852,6 +1014,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -872,6 +1035,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -908,12 +1076,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -926,6 +1102,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -946,6 +1123,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -982,12 +1164,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -999,6 +1189,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -1019,6 +1210,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -1055,12 +1251,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -1073,6 +1277,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -1093,6 +1298,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -1129,12 +1339,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -1147,6 +1365,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -1167,6 +1386,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -1200,12 +1424,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -1217,6 +1449,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -1237,6 +1470,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "78.159.99.224", "server.port": 80, "service.type": "panw", @@ -1273,12 +1511,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -1291,6 +1537,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -1311,6 +1558,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -1347,12 +1599,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -1365,6 +1625,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -1385,6 +1646,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -1421,12 +1687,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -1439,6 +1713,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -1459,6 +1734,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -1495,12 +1775,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -1513,6 +1801,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -1533,6 +1822,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -1569,12 +1863,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -1587,6 +1889,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -1607,6 +1910,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -1643,12 +1951,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -1661,6 +1977,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -1681,6 +1998,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -1717,12 +2039,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -1735,6 +2065,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -1755,6 +2086,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -1791,12 +2127,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -1809,6 +2153,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -1829,6 +2174,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -1865,12 +2215,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -1883,6 +2241,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -1903,6 +2262,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -1939,12 +2303,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -1957,6 +2329,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -1977,6 +2350,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -2013,12 +2391,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -2031,6 +2417,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -2051,6 +2438,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -2087,12 +2479,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -2105,6 +2505,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -2125,6 +2526,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -2161,12 +2567,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -2179,6 +2593,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -2199,6 +2614,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -2235,12 +2655,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -2253,6 +2681,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -2273,6 +2702,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -2309,12 +2743,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -2327,6 +2769,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -2347,6 +2790,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -2383,12 +2831,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -2401,6 +2857,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -2421,6 +2878,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -2454,12 +2916,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -2471,6 +2941,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -2491,6 +2962,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "69.43.161.167", "server.port": 80, "service.type": "panw", @@ -2524,12 +3000,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -2541,6 +3025,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -2561,6 +3046,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "202.31.187.154", "server.port": 80, "service.type": "panw", @@ -2594,12 +3084,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -2611,6 +3109,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -2631,6 +3130,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "89.111.176.67", "server.port": 80, "service.type": "panw", @@ -2667,12 +3171,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -2684,6 +3196,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -2704,6 +3217,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -2737,12 +3255,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -2754,6 +3280,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -2774,6 +3301,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "208.73.210.29", "server.port": 80, "service.type": "panw", @@ -2807,12 +3339,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -2824,6 +3364,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -2844,6 +3385,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "208.73.210.29", "server.port": 80, "service.type": "panw", @@ -2880,12 +3426,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -2897,6 +3451,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -2917,6 +3472,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -2950,12 +3510,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -2967,6 +3535,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -2987,6 +3556,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "208.73.210.29", "server.port": 80, "service.type": "panw", @@ -3020,12 +3594,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -3037,6 +3619,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -3057,6 +3640,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "89.108.64.156", "server.port": 80, "service.type": "panw", @@ -3090,12 +3678,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -3107,6 +3703,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -3127,6 +3724,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "89.108.64.156", "server.port": 80, "service.type": "panw", @@ -3154,10 +3756,15 @@ "destination.port": 58849, "destination.user.name": "crusher", "event.action": "spyware_detected", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "drop-all-packets", + "event.outcome": "success", "event.severity": 1, "event.timezone": "-02:00", "fileset.name": "panos", @@ -3171,6 +3778,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "drop-all-packets", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -3191,6 +3799,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "192.168.0.2", "server.port": 58849, "server.user.name": "crusher", @@ -3236,12 +3849,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -3253,6 +3874,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -3273,6 +3895,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "216.8.179.25", "server.port": 80, "service.type": "panw", @@ -3306,12 +3933,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -3323,6 +3958,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -3343,6 +3979,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "69.43.161.154", "server.port": 80, "service.type": "panw", @@ -3376,12 +4017,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -3393,6 +4042,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -3413,6 +4063,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "208.91.196.252", "server.port": 80, "service.type": "panw", @@ -3446,12 +4101,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -3463,6 +4126,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -3483,6 +4147,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "208.73.210.29", "server.port": 80, "service.type": "panw", @@ -3519,12 +4188,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -3536,6 +4213,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -3556,6 +4234,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -3592,12 +4275,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -3609,6 +4300,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -3629,6 +4321,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -3665,12 +4362,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -3682,6 +4387,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -3702,6 +4408,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -3738,12 +4449,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -3755,6 +4474,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "1606001116", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -3775,6 +4495,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -3811,12 +4536,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -3828,6 +4561,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "1606001116", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -3848,6 +4582,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -3875,12 +4614,20 @@ "destination.port": 54431, "destination.user.name": "crusher", "event.action": "file_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "deny", + "event.outcome": "success", "event.severity": 4, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -3892,6 +4639,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "1606001116", + "panw.panos.action": "deny", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -3912,6 +4660,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "192.168.0.2", "server.port": 54431, "server.user.name": "crusher", @@ -3957,12 +4710,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -3974,6 +4735,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "1606001116", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -3994,6 +4756,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -4021,12 +4788,20 @@ "destination.port": 61220, "destination.user.name": "crusher", "event.action": "file_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "deny", + "event.outcome": "success", "event.severity": 4, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -4038,6 +4813,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "deny", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -4058,6 +4834,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "192.168.0.2", "server.port": 61220, "server.user.name": "crusher", @@ -4094,12 +4875,20 @@ "destination.port": 61726, "destination.user.name": "crusher", "event.action": "file_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "deny", + "event.outcome": "success", "event.severity": 4, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -4111,6 +4900,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "deny", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -4131,6 +4921,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "192.168.0.2", "server.port": 61726, "server.user.name": "crusher", @@ -4175,12 +4970,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -4192,6 +4995,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -4212,6 +5016,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -4239,12 +5048,20 @@ "destination.port": 60212, "destination.user.name": "crusher", "event.action": "file_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "deny", + "event.outcome": "success", "event.severity": 4, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -4256,6 +5073,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "deny", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -4276,6 +5094,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "192.168.0.2", "server.port": 60212, "server.user.name": "crusher", @@ -4309,12 +5132,20 @@ "destination.port": 60392, "destination.user.name": "crusher", "event.action": "file_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "deny", + "event.outcome": "success", "event.severity": 4, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -4326,6 +5157,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "deny", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -4346,6 +5178,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "192.168.0.2", "server.port": 60392, "server.user.name": "crusher", @@ -4388,12 +5225,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -4405,6 +5250,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -4425,6 +5271,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "213.180.199.61", "server.port": 80, "service.type": "panw", @@ -4458,12 +5309,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -4475,6 +5334,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -4495,6 +5355,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "213.180.199.61", "server.port": 80, "service.type": "panw", @@ -4528,12 +5393,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -4545,6 +5418,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -4565,6 +5439,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "213.180.199.61", "server.port": 80, "service.type": "panw", @@ -4592,12 +5471,20 @@ "destination.port": 54431, "destination.user.name": "crusher", "event.action": "file_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "deny", + "event.outcome": "success", "event.severity": 4, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -4609,6 +5496,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "deny", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -4629,6 +5517,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "192.168.0.2", "server.port": 54431, "server.user.name": "crusher", @@ -4674,12 +5567,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -4691,6 +5592,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -4711,6 +5613,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.ip": "204.232.231.46", "server.port": 80, "service.type": "panw", @@ -4747,12 +5654,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "data_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -4764,6 +5679,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -4784,6 +5700,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "jordy", + "jordy" + ], + "rule.name": "rule1", "server.ip": "207.46.140.46", "server.port": 80, "service.type": "panw", @@ -4811,12 +5732,20 @@ "destination.port": 1039, "destination.user.name": "jordy", "event.action": "data_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -4828,6 +5757,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -4848,6 +5778,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "jordy", + "jordy" + ], + "rule.name": "rule1", "server.ip": "192.168.0.6", "server.port": 1039, "server.user.name": "jordy", @@ -4884,12 +5819,20 @@ "destination.port": 1064, "destination.user.name": "jordy", "event.action": "data_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -4901,6 +5844,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -4921,6 +5865,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "jordy", + "jordy" + ], + "rule.name": "rule1", "server.ip": "192.168.0.6", "server.port": 1064, "server.user.name": "jordy", @@ -4966,12 +5915,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "data_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -4983,6 +5940,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -5003,6 +5961,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "jordy", + "jordy" + ], + "rule.name": "rule1", "server.ip": "65.54.71.11", "server.port": 80, "service.type": "panw", @@ -5030,12 +5993,20 @@ "destination.port": 1071, "destination.user.name": "jordy", "event.action": "data_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -5047,6 +6018,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -5067,6 +6039,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "jordy", + "jordy" + ], + "rule.name": "rule1", "server.ip": "192.168.0.6", "server.port": 1071, "server.user.name": "jordy", @@ -5106,12 +6083,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "data_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -5123,6 +6108,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -5143,6 +6129,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "picard", + "picard" + ], + "rule.name": "rule1", "server.ip": "208.85.40.48", "server.port": 80, "service.type": "panw", @@ -5170,12 +6161,20 @@ "destination.port": 57876, "destination.user.name": "picard", "event.action": "data_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "reset-both", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -5187,6 +6186,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "reset-both", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -5207,6 +6207,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "picard", + "picard" + ], + "rule.name": "rule1", "server.ip": "192.168.0.2", "server.port": 57876, "server.user.name": "picard", @@ -5240,12 +6245,20 @@ "destination.port": 1082, "destination.user.name": "jordy", "event.action": "file_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "deny", + "event.outcome": "success", "event.severity": 4, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -5257,6 +6270,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "deny", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -5277,6 +6291,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "jordy", + "jordy" + ], + "rule.name": "rule1", "server.ip": "192.168.0.6", "server.port": 1082, "server.user.name": "jordy", @@ -5313,12 +6332,20 @@ "destination.port": 50986, "destination.user.name": "picard", "event.action": "data_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "reset-both", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -5330,6 +6357,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "reset-both", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -5350,6 +6378,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "picard", + "picard" + ], + "rule.name": "rule1", "server.ip": "192.168.0.2", "server.port": 50986, "server.user.name": "picard", @@ -5383,12 +6416,20 @@ "destination.port": 51716, "destination.user.name": "picard", "event.action": "data_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "reset-both", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -5400,6 +6441,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "reset-both", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -5420,6 +6462,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "picard", + "picard" + ], + "rule.name": "rule1", "server.ip": "192.168.0.2", "server.port": 51716, "server.user.name": "picard", @@ -5453,12 +6500,20 @@ "destination.port": 52119, "destination.user.name": "picard", "event.action": "data_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "reset-both", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -5470,6 +6525,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "reset-both", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -5490,6 +6546,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "picard", + "picard" + ], + "rule.name": "rule1", "server.ip": "192.168.0.2", "server.port": 52119, "server.user.name": "picard", @@ -5523,12 +6584,20 @@ "destination.port": 52411, "destination.user.name": "picard", "event.action": "data_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "reset-both", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -5540,6 +6609,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "reset-both", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -5560,6 +6630,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "picard", + "picard" + ], + "rule.name": "rule1", "server.ip": "192.168.0.2", "server.port": 52411, "server.user.name": "picard", @@ -5599,12 +6674,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "data_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -5616,6 +6699,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -5636,6 +6720,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "picard", + "picard" + ], + "rule.name": "rule1", "server.ip": "74.125.239.6", "server.port": 80, "service.type": "panw", @@ -5663,12 +6752,20 @@ "destination.port": 53026, "destination.user.name": "picard", "event.action": "data_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "reset-both", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -5680,6 +6777,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "reset-both", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -5700,6 +6798,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "picard", + "picard" + ], + "rule.name": "rule1", "server.ip": "192.168.0.2", "server.port": 53026, "server.user.name": "picard", @@ -5733,12 +6836,20 @@ "destination.port": 53809, "destination.user.name": "picard", "event.action": "data_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -5750,6 +6861,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -5770,6 +6882,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "picard", + "picard" + ], + "rule.name": "rule1", "server.ip": "192.168.0.2", "server.port": 53809, "server.user.name": "picard", @@ -5803,12 +6920,20 @@ "destination.port": 55912, "destination.user.name": "picard", "event.action": "data_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -5820,6 +6945,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -5840,6 +6966,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "picard", + "picard" + ], + "rule.name": "rule1", "server.ip": "192.168.0.2", "server.port": 55912, "server.user.name": "picard", @@ -5873,12 +7004,20 @@ "destination.port": 55916, "destination.user.name": "picard", "event.action": "data_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -5890,6 +7029,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -5910,6 +7050,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "picard", + "picard" + ], + "rule.name": "rule1", "server.ip": "192.168.0.2", "server.port": 55916, "server.user.name": "picard", @@ -5943,12 +7088,20 @@ "destination.port": 1046, "destination.user.name": "jordy", "event.action": "data_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "reset-both", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -5960,6 +7113,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "reset-both", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -5980,6 +7134,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "jordy", + "jordy" + ], + "rule.name": "rule1", "server.ip": "192.168.0.6", "server.port": 1046, "server.user.name": "jordy", @@ -6016,12 +7175,20 @@ "destination.port": 61734, "destination.user.name": "jordy", "event.action": "data_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "reset-both", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -6033,6 +7200,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "reset-both", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -6053,6 +7221,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "jordy", + "jordy" + ], + "rule.name": "rule1", "server.ip": "192.168.0.2", "server.port": 61734, "server.user.name": "jordy", @@ -6086,12 +7259,20 @@ "destination.port": 62292, "destination.user.name": "jordy", "event.action": "data_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "reset-both", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -6103,6 +7284,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "reset-both", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -6123,6 +7305,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "jordy", + "jordy" + ], + "rule.name": "rule1", "server.ip": "192.168.0.2", "server.port": 62292, "server.user.name": "jordy", @@ -6156,12 +7343,20 @@ "destination.port": 64669, "destination.user.name": "jordy", "event.action": "data_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -6173,6 +7368,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -6193,6 +7389,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "jordy", + "jordy" + ], + "rule.name": "rule1", "server.ip": "192.168.0.2", "server.port": 64669, "server.user.name": "jordy", @@ -6229,12 +7430,20 @@ "destination.port": 65265, "destination.user.name": "picard", "event.action": "data_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "reset-both", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -6246,6 +7455,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "reset-both", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -6266,6 +7476,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "picard", + "picard" + ], + "rule.name": "rule1", "server.ip": "192.168.0.2", "server.port": 65265, "server.user.name": "picard", @@ -6299,12 +7514,20 @@ "destination.port": 64979, "destination.user.name": "picard", "event.action": "data_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -6316,6 +7539,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -6336,6 +7560,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "picard", + "picard" + ], + "rule.name": "rule1", "server.ip": "192.168.0.2", "server.port": 64979, "server.user.name": "picard", @@ -6369,12 +7598,20 @@ "destination.port": 49432, "destination.user.name": "picard", "event.action": "data_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -6386,6 +7623,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -6406,6 +7644,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "picard", + "picard" + ], + "rule.name": "rule1", "server.ip": "192.168.0.2", "server.port": 49432, "server.user.name": "picard", @@ -6442,12 +7685,20 @@ "destination.port": 49722, "destination.user.name": "picard", "event.action": "data_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "reset-both", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -6459,6 +7710,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "reset-both", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -6479,6 +7731,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "picard", + "picard" + ], + "rule.name": "rule1", "server.ip": "192.168.0.2", "server.port": 49722, "server.user.name": "picard", @@ -6518,12 +7775,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "data_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -6535,6 +7800,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -6555,6 +7821,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "picard", + "picard" + ], + "rule.name": "rule1", "server.ip": "74.125.224.201", "server.port": 80, "service.type": "panw", @@ -6582,12 +7853,20 @@ "destination.port": 50108, "destination.user.name": "picard", "event.action": "data_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "reset-both", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -6599,6 +7878,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "reset-both", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -6619,6 +7899,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "picard", + "picard" + ], + "rule.name": "rule1", "server.ip": "192.168.0.2", "server.port": 50108, "server.user.name": "picard", @@ -6652,12 +7937,20 @@ "destination.port": 50387, "destination.user.name": "picard", "event.action": "data_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "reset-both", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -6669,6 +7962,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "reset-both", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -6689,6 +7983,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "picard", + "picard" + ], + "rule.name": "rule1", "server.ip": "192.168.0.2", "server.port": 50387, "server.user.name": "picard", @@ -6728,12 +8027,20 @@ "destination.nat.port": 0, "destination.port": 80, "event.action": "data_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -6745,6 +8052,7 @@ "network.direction": "inbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -6765,6 +8073,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "jordy", + "jordy" + ], + "rule.name": "rule1", "server.ip": "208.85.40.48", "server.port": 80, "service.type": "panw", @@ -6792,12 +8105,20 @@ "destination.port": 60005, "destination.user.name": "jordy", "event.action": "data_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "reset-both", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -6809,6 +8130,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "reset-both", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -6829,6 +8151,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "jordy", + "jordy" + ], + "rule.name": "rule1", "server.ip": "192.168.0.2", "server.port": 60005, "server.user.name": "jordy", @@ -6862,12 +8189,20 @@ "destination.port": 60443, "destination.user.name": "jordy", "event.action": "data_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "reset-both", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -6879,6 +8214,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "reset-both", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -6899,6 +8235,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "jordy", + "jordy" + ], + "rule.name": "rule1", "server.ip": "192.168.0.2", "server.port": 60443, "server.user.name": "jordy", @@ -6932,12 +8273,20 @@ "destination.port": 60822, "destination.user.name": "jordy", "event.action": "data_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "reset-both", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -6949,6 +8298,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "reset-both", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -6969,6 +8319,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "jordy", + "jordy" + ], + "rule.name": "rule1", "server.ip": "192.168.0.2", "server.port": 60822, "server.user.name": "jordy", @@ -7002,12 +8357,20 @@ "destination.port": 61105, "destination.user.name": "jordy", "event.action": "data_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "reset-both", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -7019,6 +8382,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "reset-both", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -7039,6 +8403,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "jordy", + "jordy" + ], + "rule.name": "rule1", "server.ip": "192.168.0.2", "server.port": 61105, "server.user.name": "jordy", @@ -7072,12 +8441,20 @@ "destination.port": 60782, "destination.user.name": "jordy", "event.action": "data_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "alert", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -7089,6 +8466,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "alert", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -7109,6 +8487,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "jordy", + "jordy" + ], + "rule.name": "rule1", "server.ip": "192.168.0.2", "server.port": 60782, "server.user.name": "jordy", @@ -7142,12 +8525,20 @@ "destination.port": 61470, "destination.user.name": "jordy", "event.action": "data_match", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "reset-both", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -7159,6 +8550,7 @@ "network.direction": "outbound", "network.transport": "tcp", "observer.serial_number": "01606001116", + "panw.panos.action": "reset-both", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -7179,6 +8571,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "jordy", + "jordy" + ], + "rule.name": "rule1", "server.ip": "192.168.0.2", "server.port": 61470, "server.user.name": "jordy", diff --git a/x-pack/filebeat/module/panw/panos/test/pan_inc_traffic.log-expected.json b/x-pack/filebeat/module/panw/panos/test/pan_inc_traffic.log-expected.json index 4565c577acd..c285f88d43d 100644 --- a/x-pack/filebeat/module/panw/panos/test/pan_inc_traffic.log-expected.json +++ b/x-pack/filebeat/module/panw/panos/test/pan_inc_traffic.log-expected.json @@ -23,14 +23,23 @@ "destination.packets": 1, "destination.port": 80, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:59.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:59.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -44,6 +53,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -62,6 +72,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "204.232.231.46", "server.packets": 1, @@ -100,14 +115,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:58.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:58.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -121,6 +145,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -139,6 +164,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "205.171.2.25", "server.packets": 1, @@ -177,14 +207,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:58.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:58.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -198,6 +237,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -216,6 +256,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "205.171.2.25", "server.packets": 1, @@ -257,14 +302,23 @@ "destination.packets": 1, "destination.port": 80, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:58.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:58.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -278,6 +332,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -296,6 +351,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "204.232.231.46", "server.packets": 1, @@ -337,14 +397,23 @@ "destination.packets": 1, "destination.port": 80, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:58.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:58.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -358,6 +427,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -376,6 +446,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "204.232.231.46", "server.packets": 1, @@ -414,14 +489,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:58.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:58.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -435,6 +519,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -453,6 +538,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "205.171.2.25", "server.packets": 1, @@ -491,14 +581,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:58.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:58.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -512,6 +611,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -530,6 +630,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "205.171.2.25", "server.packets": 1, @@ -571,14 +676,23 @@ "destination.packets": 6, "destination.port": 80, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 1000000000, "event.end": "2012-04-10T04:39:28.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:27.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -592,6 +706,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -610,6 +725,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 806, "server.ip": "204.232.231.46", "server.packets": 6, @@ -651,14 +771,23 @@ "destination.packets": 6, "destination.port": 80, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:28.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:28.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -672,6 +801,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -690,6 +820,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 806, "server.ip": "204.232.231.46", "server.packets": 6, @@ -731,14 +866,23 @@ "destination.packets": 6, "destination.port": 80, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 1000000000, "event.end": "2012-04-10T04:39:28.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:27.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -752,6 +896,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -770,6 +915,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 806, "server.ip": "204.232.231.46", "server.packets": 6, @@ -811,14 +961,23 @@ "destination.packets": 1, "destination.port": 80, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:58.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:58.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -832,6 +991,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -850,6 +1010,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "204.232.231.46", "server.packets": 1, @@ -891,14 +1056,23 @@ "destination.packets": 1, "destination.port": 80, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:57.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:57.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -912,6 +1086,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -930,6 +1105,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "204.232.231.46", "server.packets": 1, @@ -971,14 +1151,23 @@ "destination.packets": 1, "destination.port": 80, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:57.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:57.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -992,6 +1181,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -1010,6 +1200,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "204.232.231.46", "server.packets": 1, @@ -1051,14 +1246,23 @@ "destination.packets": 1, "destination.port": 80, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:57.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:57.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -1072,6 +1276,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -1090,6 +1295,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "204.232.231.46", "server.packets": 1, @@ -1131,14 +1341,23 @@ "destination.packets": 6, "destination.port": 80, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:27.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:27.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -1152,6 +1371,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -1170,6 +1390,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 806, "server.ip": "204.232.231.46", "server.packets": 6, @@ -1211,14 +1436,23 @@ "destination.packets": 6, "destination.port": 80, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 1000000000, "event.end": "2012-04-10T04:39:27.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:26.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -1232,6 +1466,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -1250,6 +1485,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 806, "server.ip": "204.232.231.46", "server.packets": 6, @@ -1291,14 +1531,23 @@ "destination.packets": 18, "destination.port": 80, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 512000000000, "event.end": "2012-04-10T04:38:26.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:29:54.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -1312,6 +1561,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -1330,6 +1580,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 551, "server.ip": "204.232.231.46", "server.packets": 18, @@ -1371,14 +1626,23 @@ "destination.packets": 1, "destination.port": 80, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:56.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:56.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -1392,6 +1656,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -1410,6 +1675,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "204.232.231.46", "server.packets": 1, @@ -1451,14 +1721,23 @@ "destination.packets": 1, "destination.port": 80, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:56.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:56.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -1472,6 +1751,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -1490,6 +1770,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "204.232.231.46", "server.packets": 1, @@ -1528,14 +1813,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:56.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:56.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -1549,6 +1843,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -1567,6 +1862,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "205.171.2.25", "server.packets": 1, @@ -1605,14 +1905,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:56.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:56.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -1626,6 +1935,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -1644,6 +1954,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "205.171.2.25", "server.packets": 1, @@ -1685,14 +2000,23 @@ "destination.packets": 1, "destination.port": 80, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:56.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:56.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -1706,6 +2030,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -1724,6 +2049,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "204.232.231.46", "server.packets": 1, @@ -1762,14 +2092,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:26.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:26.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -1783,6 +2122,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -1801,6 +2141,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 98, "server.ip": "205.171.2.25", "server.packets": 1, @@ -1842,14 +2187,23 @@ "destination.packets": 6, "destination.port": 80, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:26.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:26.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -1863,6 +2217,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -1881,6 +2236,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 806, "server.ip": "204.232.231.46", "server.packets": 6, @@ -1922,14 +2282,23 @@ "destination.packets": 6, "destination.port": 80, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:26.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:26.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -1943,6 +2312,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -1961,6 +2331,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 806, "server.ip": "204.232.231.46", "server.packets": 6, @@ -2002,14 +2377,23 @@ "destination.packets": 1, "destination.port": 80, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:56.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:56.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -2023,6 +2407,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -2041,6 +2426,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "204.232.231.46", "server.packets": 1, @@ -2079,14 +2469,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:55.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:55.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -2100,6 +2499,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -2118,6 +2518,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "205.171.2.25", "server.packets": 1, @@ -2156,14 +2561,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:55.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:55.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -2177,6 +2591,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -2195,6 +2610,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "205.171.2.25", "server.packets": 1, @@ -2236,14 +2656,23 @@ "destination.packets": 8, "destination.port": 13069, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 125000000000, "event.end": "2012-04-10T04:39:55.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:37:50.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -2257,6 +2686,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -2275,6 +2705,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 504, "server.ip": "98.149.55.63", "server.packets": 8, @@ -2316,14 +2751,23 @@ "destination.packets": 1, "destination.port": 80, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:55.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:55.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -2337,6 +2781,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -2355,6 +2800,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "204.232.231.46", "server.packets": 1, @@ -2393,14 +2843,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:55.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:55.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -2414,6 +2873,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -2432,6 +2892,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "205.171.2.25", "server.packets": 1, @@ -2473,14 +2938,23 @@ "destination.packets": 10, "destination.port": 80, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 1000000000, "event.end": "2012-04-10T04:39:25.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:24.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -2494,6 +2968,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -2512,6 +2987,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 9130, "server.ip": "212.48.10.58", "server.packets": 10, @@ -2553,14 +3033,23 @@ "destination.packets": 1, "destination.port": 80, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:55.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:55.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -2574,6 +3063,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -2592,6 +3082,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "204.232.231.46", "server.packets": 1, @@ -2630,14 +3125,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:54.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:54.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -2651,6 +3155,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -2669,6 +3174,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "205.171.2.25", "server.packets": 1, @@ -2707,14 +3217,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:54.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:54.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -2728,6 +3247,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -2746,6 +3266,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "205.171.2.25", "server.packets": 1, @@ -2787,14 +3312,23 @@ "destination.packets": 1, "destination.port": 80, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:54.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:54.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -2808,6 +3342,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -2826,6 +3361,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "204.232.231.46", "server.packets": 1, @@ -2867,14 +3407,23 @@ "destination.packets": 1, "destination.port": 80, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:54.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:54.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -2888,6 +3437,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -2906,6 +3456,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "204.232.231.46", "server.packets": 1, @@ -2944,14 +3499,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:54.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:54.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -2965,6 +3529,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -2983,6 +3548,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "205.171.2.25", "server.packets": 1, @@ -3021,14 +3591,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:54.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:54.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -3042,6 +3621,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -3060,6 +3640,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "205.171.2.25", "server.packets": 1, @@ -3097,14 +3682,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:24.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:24.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "log.offset": 14217, @@ -3117,6 +3711,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -3135,6 +3730,7 @@ "0.0.0.0", "0.0.0.0" ], + "rule.name": "rule1", "server.bytes": 111, "server.ip": "8.8.8.8", "server.packets": 1, @@ -3172,14 +3768,23 @@ "destination.packets": 6, "destination.port": 80, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 1000000000, "event.end": "2012-04-10T04:39:24.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:23.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -3193,6 +3798,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -3211,6 +3817,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 906, "server.ip": "62.211.68.12", "server.packets": 6, @@ -3251,14 +3862,23 @@ "destination.packets": 10, "destination.port": 443, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:24.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:24.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "log.offset": 14933, @@ -3271,6 +3891,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -3289,6 +3910,7 @@ "0.0.0.0", "0.0.0.0" ], + "rule.name": "rule1", "server.bytes": 5013, "server.ip": "50.19.102.116", "server.packets": 10, @@ -3329,14 +3951,23 @@ "destination.packets": 1, "destination.port": 40026, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:24.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:24.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -3350,6 +3981,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -3368,6 +4000,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 99, "server.ip": "65.55.223.19", "server.packets": 1, @@ -3409,14 +4046,23 @@ "destination.packets": 1, "destination.port": 40029, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:24.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:24.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -3430,6 +4076,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -3448,6 +4095,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 902, "server.ip": "65.55.223.24", "server.packets": 1, @@ -3485,14 +4137,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:24.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:24.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "log.offset": 16061, @@ -3505,6 +4166,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -3523,6 +4185,7 @@ "0.0.0.0", "0.0.0.0" ], + "rule.name": "rule1", "server.bytes": 141, "server.ip": "8.8.8.8", "server.packets": 1, @@ -3563,14 +4226,23 @@ "destination.packets": 1, "destination.port": 80, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:54.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:54.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -3584,6 +4256,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -3602,6 +4275,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "204.232.231.46", "server.packets": 1, @@ -3640,14 +4318,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:53.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:53.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -3661,6 +4348,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -3679,6 +4367,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "205.171.2.25", "server.packets": 1, @@ -3720,14 +4413,23 @@ "destination.packets": 1, "destination.port": 80, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:53.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:53.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -3741,6 +4443,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -3759,6 +4462,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "204.232.231.46", "server.packets": 1, @@ -3797,14 +4505,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:53.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:53.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -3818,6 +4535,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -3836,6 +4554,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "205.171.2.25", "server.packets": 1, @@ -3874,14 +4597,23 @@ "destination.packets": 2, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 1000000000, "event.end": "2012-04-10T04:39:23.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:22.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -3895,6 +4627,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -3913,6 +4646,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 316, "server.ip": "205.171.2.25", "server.packets": 2, @@ -3951,14 +4689,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:23.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:23.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -3972,6 +4719,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -3990,6 +4738,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 121, "server.ip": "205.171.2.25", "server.packets": 1, @@ -4028,14 +4781,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:23.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:23.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -4049,6 +4811,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -4067,6 +4830,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 169, "server.ip": "205.171.2.25", "server.packets": 1, @@ -4105,14 +4873,23 @@ "destination.packets": 6, "destination.port": 80, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:23.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:23.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -4126,6 +4903,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -4144,6 +4922,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 954, "server.ip": "62.211.68.12", "server.packets": 6, @@ -4185,14 +4968,23 @@ "destination.packets": 12, "destination.port": 80, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 2000000000, "event.end": "2012-04-10T04:39:23.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:21.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -4206,6 +4998,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -4224,6 +5017,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 9130, "server.ip": "212.48.10.58", "server.packets": 12, @@ -4265,14 +5063,23 @@ "destination.packets": 18, "destination.port": 80, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 512000000000, "event.end": "2012-04-10T04:38:23.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:29:51.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -4286,6 +5093,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -4304,6 +5112,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 555, "server.ip": "204.232.231.46", "server.packets": 18, @@ -4342,14 +5155,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:53.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:53.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -4363,6 +5185,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -4381,6 +5204,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "205.171.2.25", "server.packets": 1, @@ -4422,14 +5250,23 @@ "destination.packets": 1, "destination.port": 80, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:53.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:53.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -4443,6 +5280,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -4461,6 +5299,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "204.232.231.46", "server.packets": 1, @@ -4499,14 +5342,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:52.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:52.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -4520,6 +5372,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -4538,6 +5391,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "205.171.2.25", "server.packets": 1, @@ -4576,14 +5434,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:52.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:52.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -4597,6 +5464,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -4615,6 +5483,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "205.171.2.25", "server.packets": 1, @@ -4656,14 +5529,23 @@ "destination.packets": 1, "destination.port": 40043, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:52.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:52.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -4677,6 +5559,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -4695,6 +5578,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "65.55.223.31", "server.packets": 1, @@ -4736,14 +5624,23 @@ "destination.packets": 1, "destination.port": 80, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:52.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:52.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -4757,6 +5654,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -4775,6 +5673,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "204.232.231.46", "server.packets": 1, @@ -4813,14 +5716,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:52.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:52.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -4834,6 +5746,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -4852,6 +5765,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "205.171.2.25", "server.packets": 1, @@ -4890,14 +5808,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:52.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:52.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -4911,6 +5838,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -4929,6 +5857,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "205.171.2.25", "server.packets": 1, @@ -4967,14 +5900,23 @@ "destination.packets": 6, "destination.port": 80, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 1000000000, "event.end": "2012-04-10T04:39:22.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:21.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -4988,6 +5930,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -5006,6 +5949,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 906, "server.ip": "62.211.68.12", "server.packets": 6, @@ -5044,14 +5992,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:22.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:22.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -5065,6 +6022,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -5083,6 +6041,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 163, "server.ip": "205.171.2.25", "server.packets": 1, @@ -5121,14 +6084,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:51.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:51.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -5142,6 +6114,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -5160,6 +6133,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "205.171.2.25", "server.packets": 1, @@ -5198,14 +6176,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:51.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:51.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -5219,6 +6206,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -5237,6 +6225,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "205.171.2.25", "server.packets": 1, @@ -5278,14 +6271,23 @@ "destination.packets": 1, "destination.port": 80, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:51.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:51.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -5299,6 +6301,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -5317,6 +6320,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "204.232.231.46", "server.packets": 1, @@ -5355,14 +6363,23 @@ "destination.packets": 6, "destination.port": 80, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 1000000000, "event.end": "2012-04-10T04:39:21.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:20.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -5376,6 +6393,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -5394,6 +6412,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 922, "server.ip": "62.211.68.12", "server.packets": 6, @@ -5435,14 +6458,23 @@ "destination.packets": 1, "destination.port": 80, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:51.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:51.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -5456,6 +6488,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -5474,6 +6507,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "204.232.231.46", "server.packets": 1, @@ -5512,14 +6550,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:50.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:50.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -5533,6 +6580,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -5551,6 +6599,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "205.171.2.25", "server.packets": 1, @@ -5589,14 +6642,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:50.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:50.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -5610,6 +6672,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -5628,6 +6691,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "205.171.2.25", "server.packets": 1, @@ -5669,14 +6737,23 @@ "destination.packets": 1, "destination.port": 80, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:50.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:50.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -5690,6 +6767,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -5708,6 +6786,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "204.232.231.46", "server.packets": 1, @@ -5746,14 +6829,23 @@ "destination.packets": 17, "destination.port": 80, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:20.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:20.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -5767,6 +6859,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -5785,6 +6878,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 26786, "server.ip": "8.5.1.1", "server.packets": 17, @@ -5823,14 +6921,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:50.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:50.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -5844,6 +6951,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -5862,6 +6970,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "205.171.2.25", "server.packets": 1, @@ -5900,14 +7013,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:50.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:50.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -5921,6 +7043,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -5939,6 +7062,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "205.171.2.25", "server.packets": 1, @@ -5980,14 +7108,23 @@ "destination.packets": 1, "destination.port": 80, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:50.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:50.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -6001,6 +7138,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -6019,6 +7157,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "204.232.231.46", "server.packets": 1, @@ -6051,14 +7194,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:20.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:20.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -6072,6 +7224,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -6090,6 +7243,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 169, "server.ip": "192.168.0.1", "server.packets": 1, @@ -6131,14 +7289,23 @@ "destination.packets": 12, "destination.port": 80, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 3000000000, "event.end": "2012-04-10T04:39:20.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:17.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -6152,6 +7319,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -6170,6 +7338,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 9064, "server.ip": "212.48.10.58", "server.packets": 12, @@ -6211,14 +7384,23 @@ "destination.packets": 12, "destination.port": 80, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 7000000000, "event.end": "2012-04-10T04:39:20.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:13.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -6232,6 +7414,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -6250,6 +7433,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 9124, "server.ip": "212.48.10.58", "server.packets": 12, @@ -6282,14 +7470,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:20.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:20.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -6303,6 +7500,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -6321,6 +7519,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 137, "server.ip": "192.168.0.1", "server.packets": 1, @@ -6353,14 +7556,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:20.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:20.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -6374,6 +7586,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -6392,6 +7605,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 93, "server.ip": "192.168.0.1", "server.packets": 1, @@ -6433,14 +7651,23 @@ "destination.packets": 1, "destination.port": 80, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:49.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:49.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -6454,6 +7681,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -6472,6 +7700,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "204.232.231.46", "server.packets": 1, @@ -6510,14 +7743,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:49.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:49.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -6531,6 +7773,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -6549,6 +7792,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "205.171.2.25", "server.packets": 1, @@ -6587,14 +7835,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:49.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:49.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -6608,6 +7865,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -6626,6 +7884,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "205.171.2.25", "server.packets": 1, @@ -6667,14 +7930,23 @@ "destination.packets": 1, "destination.port": 80, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:49.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:49.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -6688,6 +7960,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -6706,6 +7979,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "204.232.231.46", "server.packets": 1, @@ -6744,14 +8022,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:49.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:49.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -6765,6 +8052,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -6783,6 +8071,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "205.171.2.25", "server.packets": 1, @@ -6815,14 +8108,23 @@ "destination.packets": 2, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 1000000000, "event.end": "2012-04-10T04:39:19.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:18.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -6836,6 +8138,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -6854,6 +8157,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "192.168.0.1", "server.packets": 2, @@ -6892,14 +8200,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:49.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:49.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -6913,6 +8230,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -6931,6 +8249,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "205.171.2.25", "server.packets": 1, @@ -6972,14 +8295,23 @@ "destination.packets": 1, "destination.port": 80, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:48.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:48.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -6993,6 +8325,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -7011,6 +8344,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "204.232.231.46", "server.packets": 1, @@ -7049,14 +8387,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:48.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:48.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -7070,6 +8417,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -7088,6 +8436,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "205.171.2.25", "server.packets": 1, @@ -7126,14 +8479,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:48.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:48.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -7147,6 +8509,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -7165,6 +8528,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "205.171.2.25", "server.packets": 1, @@ -7203,14 +8571,23 @@ "destination.packets": 6, "destination.port": 80, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 1000000000, "event.end": "2012-04-10T04:39:18.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:17.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -7224,6 +8601,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -7242,6 +8620,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 906, "server.ip": "62.211.68.12", "server.packets": 6, @@ -7283,14 +8666,23 @@ "destination.packets": 1, "destination.port": 80, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:48.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:48.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -7304,6 +8696,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -7322,6 +8715,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "204.232.231.46", "server.packets": 1, @@ -7363,14 +8761,23 @@ "destination.packets": 1, "destination.port": 80, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:48.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:48.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -7384,6 +8791,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -7402,6 +8810,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "204.232.231.46", "server.packets": 1, @@ -7443,14 +8856,23 @@ "destination.packets": 1, "destination.port": 80, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:47.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:47.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -7464,6 +8886,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -7482,6 +8905,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "204.232.231.46", "server.packets": 1, @@ -7514,14 +8942,23 @@ "destination.packets": 2, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 1000000000, "event.end": "2012-04-10T04:39:17.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:16.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -7535,6 +8972,7 @@ "network.transport": "udp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -7553,6 +8991,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "192.168.0.1", "server.packets": 2, @@ -7594,14 +9037,23 @@ "destination.packets": 3, "destination.port": 80, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:47.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:47.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -7615,6 +9067,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -7633,6 +9086,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 78, "server.ip": "204.232.231.46", "server.packets": 3, @@ -7674,14 +9132,23 @@ "destination.packets": 3, "destination.port": 80, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:47.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:47.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -7695,6 +9162,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -7713,6 +9181,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 78, "server.ip": "204.232.231.46", "server.packets": 3, @@ -7754,14 +9227,23 @@ "destination.packets": 1, "destination.port": 80, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2012-04-10T04:39:46.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2012-04-10T04:39:46.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.captive_portal": true, @@ -7775,6 +9257,7 @@ "network.transport": "tcp", "network.type": "ipv4", "observer.serial_number": "01606001116", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "0.0.0.0", "panw.panos.destination.nat.port": 0, @@ -7793,6 +9276,11 @@ "0.0.0.0", "0.0.0.0" ], + "related.user": [ + "crusher", + "crusher" + ], + "rule.name": "rule1", "server.bytes": 0, "server.ip": "204.232.231.46", "server.packets": 1, diff --git a/x-pack/filebeat/module/panw/panos/test/threat.log-expected.json b/x-pack/filebeat/module/panw/panos/test/threat.log-expected.json index c8c9082e093..c17fcbee131 100644 --- a/x-pack/filebeat/module/panw/panos/test/threat.log-expected.json +++ b/x-pack/filebeat/module/panw/panos/test/threat.log-expected.json @@ -16,12 +16,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -38,6 +46,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "152.195.55.192", "panw.panos.destination.nat.port": 443, @@ -59,6 +68,7 @@ "192.168.1.63", "152.195.55.192" ], + "rule.name": "new_outbound_from_trust", "server.ip": "152.195.55.192", "server.port": 443, "service.type": "panw", @@ -90,12 +100,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -112,6 +130,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "152.195.55.192", "panw.panos.destination.nat.port": 443, @@ -133,6 +152,7 @@ "192.168.1.63", "152.195.55.192" ], + "rule.name": "new_outbound_from_trust", "server.ip": "152.195.55.192", "server.port": 443, "service.type": "panw", @@ -164,12 +184,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -186,6 +214,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "152.195.55.192", "panw.panos.destination.nat.port": 443, @@ -207,6 +236,7 @@ "192.168.1.63", "152.195.55.192" ], + "rule.name": "new_outbound_from_trust", "server.ip": "152.195.55.192", "server.port": 443, "service.type": "panw", @@ -238,12 +268,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -260,6 +298,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "152.195.55.192", "panw.panos.destination.nat.port": 443, @@ -281,6 +320,7 @@ "192.168.1.63", "152.195.55.192" ], + "rule.name": "new_outbound_from_trust", "server.ip": "152.195.55.192", "server.port": 443, "service.type": "panw", @@ -312,12 +352,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -334,6 +382,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "152.195.55.192", "panw.panos.destination.nat.port": 443, @@ -355,6 +404,7 @@ "192.168.1.63", "152.195.55.192" ], + "rule.name": "new_outbound_from_trust", "server.ip": "152.195.55.192", "server.port": 443, "service.type": "panw", @@ -386,12 +436,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -408,6 +466,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "152.195.55.192", "panw.panos.destination.nat.port": 443, @@ -429,6 +488,7 @@ "192.168.1.63", "152.195.55.192" ], + "rule.name": "new_outbound_from_trust", "server.ip": "152.195.55.192", "server.port": 443, "service.type": "panw", @@ -460,12 +520,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -482,6 +550,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "152.195.55.192", "panw.panos.destination.nat.port": 443, @@ -503,6 +572,7 @@ "192.168.1.63", "152.195.55.192" ], + "rule.name": "new_outbound_from_trust", "server.ip": "152.195.55.192", "server.port": 443, "service.type": "panw", @@ -534,12 +604,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -556,6 +634,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "152.195.55.192", "panw.panos.destination.nat.port": 443, @@ -577,6 +656,7 @@ "192.168.1.63", "152.195.55.192" ], + "rule.name": "new_outbound_from_trust", "server.ip": "152.195.55.192", "server.port": 443, "service.type": "panw", @@ -608,12 +688,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -630,6 +718,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "152.195.55.192", "panw.panos.destination.nat.port": 443, @@ -651,6 +740,7 @@ "192.168.1.63", "152.195.55.192" ], + "rule.name": "new_outbound_from_trust", "server.ip": "152.195.55.192", "server.port": 443, "service.type": "panw", @@ -682,12 +772,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -704,6 +802,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "152.195.55.192", "panw.panos.destination.nat.port": 443, @@ -725,6 +824,7 @@ "192.168.1.63", "152.195.55.192" ], + "rule.name": "new_outbound_from_trust", "server.ip": "152.195.55.192", "server.port": 443, "service.type": "panw", @@ -756,12 +856,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -778,6 +886,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "152.195.55.192", "panw.panos.destination.nat.port": 443, @@ -799,6 +908,7 @@ "192.168.1.63", "152.195.55.192" ], + "rule.name": "new_outbound_from_trust", "server.ip": "152.195.55.192", "server.port": 443, "service.type": "panw", @@ -830,12 +940,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -852,6 +970,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "152.195.55.192", "panw.panos.destination.nat.port": 443, @@ -873,6 +992,7 @@ "192.168.1.63", "152.195.55.192" ], + "rule.name": "new_outbound_from_trust", "server.ip": "152.195.55.192", "server.port": 443, "service.type": "panw", @@ -904,12 +1024,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -926,6 +1054,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "152.195.55.192", "panw.panos.destination.nat.port": 443, @@ -947,6 +1076,7 @@ "192.168.1.63", "152.195.55.192" ], + "rule.name": "new_outbound_from_trust", "server.ip": "152.195.55.192", "server.port": 443, "service.type": "panw", @@ -978,12 +1108,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -1000,6 +1138,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "152.195.55.192", "panw.panos.destination.nat.port": 443, @@ -1021,6 +1160,7 @@ "192.168.1.63", "152.195.55.192" ], + "rule.name": "new_outbound_from_trust", "server.ip": "152.195.55.192", "server.port": 443, "service.type": "panw", @@ -1052,12 +1192,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -1074,6 +1222,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "152.195.55.192", "panw.panos.destination.nat.port": 443, @@ -1095,6 +1244,7 @@ "192.168.1.63", "152.195.55.192" ], + "rule.name": "new_outbound_from_trust", "server.ip": "152.195.55.192", "server.port": 443, "service.type": "panw", @@ -1126,12 +1276,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -1148,6 +1306,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "152.195.55.192", "panw.panos.destination.nat.port": 443, @@ -1169,6 +1328,7 @@ "192.168.1.63", "152.195.55.192" ], + "rule.name": "new_outbound_from_trust", "server.ip": "152.195.55.192", "server.port": 443, "service.type": "panw", @@ -1200,12 +1360,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -1222,6 +1390,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "152.195.55.192", "panw.panos.destination.nat.port": 443, @@ -1243,6 +1412,7 @@ "192.168.1.63", "152.195.55.192" ], + "rule.name": "new_outbound_from_trust", "server.ip": "152.195.55.192", "server.port": 443, "service.type": "panw", @@ -1274,12 +1444,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -1296,6 +1474,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "152.195.55.192", "panw.panos.destination.nat.port": 443, @@ -1317,6 +1496,7 @@ "192.168.1.63", "152.195.55.192" ], + "rule.name": "new_outbound_from_trust", "server.ip": "152.195.55.192", "server.port": 443, "service.type": "panw", @@ -1348,12 +1528,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -1370,6 +1558,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "152.195.55.192", "panw.panos.destination.nat.port": 443, @@ -1391,6 +1580,7 @@ "192.168.1.63", "152.195.55.192" ], + "rule.name": "new_outbound_from_trust", "server.ip": "152.195.55.192", "server.port": 443, "service.type": "panw", @@ -1422,12 +1612,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -1444,6 +1642,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "152.195.55.192", "panw.panos.destination.nat.port": 443, @@ -1465,6 +1664,7 @@ "192.168.1.63", "152.195.55.192" ], + "rule.name": "new_outbound_from_trust", "server.ip": "152.195.55.192", "server.port": 443, "service.type": "panw", @@ -1496,12 +1696,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -1518,6 +1726,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "23.72.137.131", "panw.panos.destination.nat.port": 443, @@ -1539,6 +1748,7 @@ "192.168.1.63", "23.72.137.131" ], + "rule.name": "new_outbound_from_trust", "server.ip": "23.72.137.131", "server.port": 443, "service.type": "panw", @@ -1570,12 +1780,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -1592,6 +1810,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "152.195.55.192", "panw.panos.destination.nat.port": 443, @@ -1613,6 +1832,7 @@ "192.168.1.63", "152.195.55.192" ], + "rule.name": "new_outbound_from_trust", "server.ip": "152.195.55.192", "server.port": 443, "service.type": "panw", @@ -1644,12 +1864,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -1666,6 +1894,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "152.195.55.192", "panw.panos.destination.nat.port": 443, @@ -1687,6 +1916,7 @@ "192.168.1.63", "152.195.55.192" ], + "rule.name": "new_outbound_from_trust", "server.ip": "152.195.55.192", "server.port": 443, "service.type": "panw", @@ -1718,12 +1948,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -1740,6 +1978,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "152.195.55.192", "panw.panos.destination.nat.port": 443, @@ -1761,6 +2000,7 @@ "192.168.1.63", "152.195.55.192" ], + "rule.name": "new_outbound_from_trust", "server.ip": "152.195.55.192", "server.port": 443, "service.type": "panw", @@ -1792,12 +2032,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -1814,6 +2062,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "152.195.55.192", "panw.panos.destination.nat.port": 443, @@ -1835,6 +2084,7 @@ "192.168.1.63", "152.195.55.192" ], + "rule.name": "new_outbound_from_trust", "server.ip": "152.195.55.192", "server.port": 443, "service.type": "panw", @@ -1866,12 +2116,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -1888,6 +2146,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "152.195.55.192", "panw.panos.destination.nat.port": 443, @@ -1909,6 +2168,7 @@ "192.168.1.63", "152.195.55.192" ], + "rule.name": "new_outbound_from_trust", "server.ip": "152.195.55.192", "server.port": 443, "service.type": "panw", @@ -1940,12 +2200,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -1962,6 +2230,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "152.195.55.192", "panw.panos.destination.nat.port": 443, @@ -1983,6 +2252,7 @@ "192.168.1.63", "152.195.55.192" ], + "rule.name": "new_outbound_from_trust", "server.ip": "152.195.55.192", "server.port": 443, "service.type": "panw", @@ -2014,12 +2284,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -2036,6 +2314,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "152.195.55.192", "panw.panos.destination.nat.port": 443, @@ -2057,6 +2336,7 @@ "192.168.1.63", "152.195.55.192" ], + "rule.name": "new_outbound_from_trust", "server.ip": "152.195.55.192", "server.port": 443, "service.type": "panw", @@ -2088,12 +2368,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -2110,6 +2398,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "152.195.55.192", "panw.panos.destination.nat.port": 443, @@ -2131,6 +2420,7 @@ "192.168.1.63", "152.195.55.192" ], + "rule.name": "new_outbound_from_trust", "server.ip": "152.195.55.192", "server.port": 443, "service.type": "panw", @@ -2162,12 +2452,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -2184,6 +2482,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "152.195.55.192", "panw.panos.destination.nat.port": 443, @@ -2205,6 +2504,7 @@ "192.168.1.63", "152.195.55.192" ], + "rule.name": "new_outbound_from_trust", "server.ip": "152.195.55.192", "server.port": 443, "service.type": "panw", @@ -2236,12 +2536,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -2258,6 +2566,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "152.195.55.192", "panw.panos.destination.nat.port": 443, @@ -2279,6 +2588,7 @@ "192.168.1.63", "152.195.55.192" ], + "rule.name": "new_outbound_from_trust", "server.ip": "152.195.55.192", "server.port": 443, "service.type": "panw", @@ -2310,12 +2620,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -2332,6 +2650,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "152.195.55.192", "panw.panos.destination.nat.port": 443, @@ -2353,6 +2672,7 @@ "192.168.1.63", "152.195.55.192" ], + "rule.name": "new_outbound_from_trust", "server.ip": "152.195.55.192", "server.port": 443, "service.type": "panw", @@ -2384,12 +2704,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -2406,6 +2734,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "152.195.55.192", "panw.panos.destination.nat.port": 443, @@ -2427,6 +2756,7 @@ "192.168.1.63", "152.195.55.192" ], + "rule.name": "new_outbound_from_trust", "server.ip": "152.195.55.192", "server.port": 443, "service.type": "panw", @@ -2458,12 +2788,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -2480,6 +2818,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "152.195.55.192", "panw.panos.destination.nat.port": 443, @@ -2501,6 +2840,7 @@ "192.168.1.63", "152.195.55.192" ], + "rule.name": "new_outbound_from_trust", "server.ip": "152.195.55.192", "server.port": 443, "service.type": "panw", @@ -2532,12 +2872,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -2554,6 +2902,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "152.195.55.192", "panw.panos.destination.nat.port": 443, @@ -2575,6 +2924,7 @@ "192.168.1.63", "152.195.55.192" ], + "rule.name": "new_outbound_from_trust", "server.ip": "152.195.55.192", "server.port": 443, "service.type": "panw", @@ -2606,12 +2956,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -2628,6 +2986,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "151.101.2.2", "panw.panos.destination.nat.port": 443, @@ -2649,6 +3008,7 @@ "192.168.1.63", "151.101.2.2" ], + "rule.name": "new_outbound_from_trust", "server.ip": "151.101.2.2", "server.port": 443, "service.type": "panw", @@ -2683,12 +3043,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -2705,6 +3073,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "54.192.7.152", "panw.panos.destination.nat.port": 443, @@ -2726,6 +3095,7 @@ "192.168.1.63", "54.192.7.152" ], + "rule.name": "new_outbound_from_trust", "server.ip": "54.192.7.152", "server.port": 443, "service.type": "panw", @@ -2760,12 +3130,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -2782,6 +3160,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "52.4.120.175", "panw.panos.destination.nat.port": 443, @@ -2803,6 +3182,7 @@ "192.168.1.63", "52.4.120.175" ], + "rule.name": "new_outbound_from_trust", "server.ip": "52.4.120.175", "server.port": 443, "service.type": "panw", @@ -2837,12 +3217,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -2859,6 +3247,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "52.4.120.175", "panw.panos.destination.nat.port": 443, @@ -2880,6 +3269,7 @@ "192.168.1.63", "52.4.120.175" ], + "rule.name": "new_outbound_from_trust", "server.ip": "52.4.120.175", "server.port": 443, "service.type": "panw", @@ -2914,12 +3304,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -2936,6 +3334,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "52.4.120.175", "panw.panos.destination.nat.port": 443, @@ -2957,6 +3356,7 @@ "192.168.1.63", "52.4.120.175" ], + "rule.name": "new_outbound_from_trust", "server.ip": "52.4.120.175", "server.port": 443, "service.type": "panw", @@ -2991,12 +3391,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -3013,6 +3421,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "52.4.120.175", "panw.panos.destination.nat.port": 443, @@ -3034,6 +3443,7 @@ "192.168.1.63", "52.4.120.175" ], + "rule.name": "new_outbound_from_trust", "server.ip": "52.4.120.175", "server.port": 443, "service.type": "panw", @@ -3068,12 +3478,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -3090,6 +3508,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "52.4.120.175", "panw.panos.destination.nat.port": 443, @@ -3111,6 +3530,7 @@ "192.168.1.63", "52.4.120.175" ], + "rule.name": "new_outbound_from_trust", "server.ip": "52.4.120.175", "server.port": 443, "service.type": "panw", @@ -3145,12 +3565,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -3167,6 +3595,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "52.4.120.175", "panw.panos.destination.nat.port": 443, @@ -3188,6 +3617,7 @@ "192.168.1.63", "52.4.120.175" ], + "rule.name": "new_outbound_from_trust", "server.ip": "52.4.120.175", "server.port": 443, "service.type": "panw", @@ -3222,12 +3652,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -3244,6 +3682,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "52.4.120.175", "panw.panos.destination.nat.port": 443, @@ -3265,6 +3704,7 @@ "192.168.1.63", "52.4.120.175" ], + "rule.name": "new_outbound_from_trust", "server.ip": "52.4.120.175", "server.port": 443, "service.type": "panw", @@ -3299,12 +3739,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -3321,6 +3769,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "52.4.120.175", "panw.panos.destination.nat.port": 443, @@ -3342,6 +3791,7 @@ "192.168.1.63", "52.4.120.175" ], + "rule.name": "new_outbound_from_trust", "server.ip": "52.4.120.175", "server.port": 443, "service.type": "panw", @@ -3376,12 +3826,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -3398,6 +3856,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "52.4.120.175", "panw.panos.destination.nat.port": 443, @@ -3419,6 +3878,7 @@ "192.168.1.63", "52.4.120.175" ], + "rule.name": "new_outbound_from_trust", "server.ip": "52.4.120.175", "server.port": 443, "service.type": "panw", @@ -3453,12 +3913,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -3475,6 +3943,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "52.4.120.175", "panw.panos.destination.nat.port": 443, @@ -3496,6 +3965,7 @@ "192.168.1.63", "52.4.120.175" ], + "rule.name": "new_outbound_from_trust", "server.ip": "52.4.120.175", "server.port": 443, "service.type": "panw", @@ -3530,12 +4000,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -3552,6 +4030,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "52.4.120.175", "panw.panos.destination.nat.port": 443, @@ -3573,6 +4052,7 @@ "192.168.1.63", "52.4.120.175" ], + "rule.name": "new_outbound_from_trust", "server.ip": "52.4.120.175", "server.port": 443, "service.type": "panw", @@ -3607,12 +4087,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -3629,6 +4117,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "52.4.120.175", "panw.panos.destination.nat.port": 443, @@ -3650,6 +4139,7 @@ "192.168.1.63", "52.4.120.175" ], + "rule.name": "new_outbound_from_trust", "server.ip": "52.4.120.175", "server.port": 443, "service.type": "panw", @@ -3684,12 +4174,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -3706,6 +4204,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "216.58.194.98", "panw.panos.destination.nat.port": 443, @@ -3727,6 +4226,7 @@ "192.168.1.63", "216.58.194.98" ], + "rule.name": "new_outbound_from_trust", "server.ip": "216.58.194.98", "server.port": 443, "service.type": "panw", @@ -3758,12 +4258,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -3780,6 +4288,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "23.72.145.245", "panw.panos.destination.nat.port": 443, @@ -3801,6 +4310,7 @@ "192.168.1.63", "23.72.145.245" ], + "rule.name": "new_outbound_from_trust", "server.ip": "23.72.145.245", "server.port": 443, "service.type": "panw", @@ -3832,12 +4342,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -3854,6 +4372,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "23.72.145.245", "panw.panos.destination.nat.port": 443, @@ -3875,6 +4394,7 @@ "192.168.1.63", "23.72.145.245" ], + "rule.name": "new_outbound_from_trust", "server.ip": "23.72.145.245", "server.port": 443, "service.type": "panw", @@ -3906,12 +4426,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -3928,6 +4456,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "23.72.145.245", "panw.panos.destination.nat.port": 443, @@ -3949,6 +4478,7 @@ "192.168.1.63", "23.72.145.245" ], + "rule.name": "new_outbound_from_trust", "server.ip": "23.72.145.245", "server.port": 443, "service.type": "panw", @@ -3980,12 +4510,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -4002,6 +4540,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "23.72.145.245", "panw.panos.destination.nat.port": 443, @@ -4023,6 +4562,7 @@ "192.168.1.63", "23.72.145.245" ], + "rule.name": "new_outbound_from_trust", "server.ip": "23.72.145.245", "server.port": 443, "service.type": "panw", @@ -4054,12 +4594,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -4076,6 +4624,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "23.72.145.245", "panw.panos.destination.nat.port": 443, @@ -4097,6 +4646,7 @@ "192.168.1.63", "23.72.145.245" ], + "rule.name": "new_outbound_from_trust", "server.ip": "23.72.145.245", "server.port": 443, "service.type": "panw", @@ -4128,12 +4678,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -4150,6 +4708,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "23.72.145.245", "panw.panos.destination.nat.port": 443, @@ -4171,6 +4730,7 @@ "192.168.1.63", "23.72.145.245" ], + "rule.name": "new_outbound_from_trust", "server.ip": "23.72.145.245", "server.port": 443, "service.type": "panw", @@ -4202,12 +4762,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -4224,6 +4792,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "23.72.145.245", "panw.panos.destination.nat.port": 443, @@ -4245,6 +4814,7 @@ "192.168.1.63", "23.72.145.245" ], + "rule.name": "new_outbound_from_trust", "server.ip": "23.72.145.245", "server.port": 443, "service.type": "panw", @@ -4276,12 +4846,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -4298,6 +4876,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "23.72.145.245", "panw.panos.destination.nat.port": 443, @@ -4319,6 +4898,7 @@ "192.168.1.63", "23.72.145.245" ], + "rule.name": "new_outbound_from_trust", "server.ip": "23.72.145.245", "server.port": 443, "service.type": "panw", @@ -4350,12 +4930,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -4372,6 +4960,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "23.72.145.245", "panw.panos.destination.nat.port": 443, @@ -4393,6 +4982,7 @@ "192.168.1.63", "23.72.145.245" ], + "rule.name": "new_outbound_from_trust", "server.ip": "23.72.145.245", "server.port": 443, "service.type": "panw", @@ -4424,12 +5014,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -4446,6 +5044,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "23.72.145.245", "panw.panos.destination.nat.port": 443, @@ -4467,6 +5066,7 @@ "192.168.1.63", "23.72.145.245" ], + "rule.name": "new_outbound_from_trust", "server.ip": "23.72.145.245", "server.port": 443, "service.type": "panw", @@ -4501,12 +5101,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -4523,6 +5131,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "54.209.101.70", "panw.panos.destination.nat.port": 443, @@ -4544,6 +5153,7 @@ "192.168.1.63", "54.209.101.70" ], + "rule.name": "new_outbound_from_trust", "server.ip": "54.209.101.70", "server.port": 443, "service.type": "panw", @@ -4578,12 +5188,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -4600,6 +5218,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "54.209.101.70", "panw.panos.destination.nat.port": 443, @@ -4621,6 +5240,7 @@ "192.168.1.63", "54.209.101.70" ], + "rule.name": "new_outbound_from_trust", "server.ip": "54.209.101.70", "server.port": 443, "service.type": "panw", @@ -4655,12 +5275,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -4677,6 +5305,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "54.209.101.70", "panw.panos.destination.nat.port": 443, @@ -4698,6 +5327,7 @@ "192.168.1.63", "54.209.101.70" ], + "rule.name": "new_outbound_from_trust", "server.ip": "54.209.101.70", "server.port": 443, "service.type": "panw", @@ -4732,12 +5362,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -4754,6 +5392,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "54.209.101.70", "panw.panos.destination.nat.port": 443, @@ -4775,6 +5414,7 @@ "192.168.1.63", "54.209.101.70" ], + "rule.name": "new_outbound_from_trust", "server.ip": "54.209.101.70", "server.port": 443, "service.type": "panw", @@ -4809,12 +5449,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -4831,6 +5479,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "54.209.101.70", "panw.panos.destination.nat.port": 443, @@ -4852,6 +5501,7 @@ "192.168.1.63", "54.209.101.70" ], + "rule.name": "new_outbound_from_trust", "server.ip": "54.209.101.70", "server.port": 443, "service.type": "panw", @@ -4886,12 +5536,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -4908,6 +5566,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "54.209.101.70", "panw.panos.destination.nat.port": 443, @@ -4929,6 +5588,7 @@ "192.168.1.63", "54.209.101.70" ], + "rule.name": "new_outbound_from_trust", "server.ip": "54.209.101.70", "server.port": 443, "service.type": "panw", @@ -4963,12 +5623,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -4985,6 +5653,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "54.209.101.70", "panw.panos.destination.nat.port": 443, @@ -5006,6 +5675,7 @@ "192.168.1.63", "54.209.101.70" ], + "rule.name": "new_outbound_from_trust", "server.ip": "54.209.101.70", "server.port": 443, "service.type": "panw", @@ -5040,12 +5710,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -5062,6 +5740,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "54.209.101.70", "panw.panos.destination.nat.port": 443, @@ -5083,6 +5762,7 @@ "192.168.1.63", "54.209.101.70" ], + "rule.name": "new_outbound_from_trust", "server.ip": "54.209.101.70", "server.port": 443, "service.type": "panw", @@ -5117,12 +5797,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -5139,6 +5827,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "54.209.101.70", "panw.panos.destination.nat.port": 443, @@ -5160,6 +5849,7 @@ "192.168.1.63", "54.209.101.70" ], + "rule.name": "new_outbound_from_trust", "server.ip": "54.209.101.70", "server.port": 443, "service.type": "panw", @@ -5194,12 +5884,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -5216,6 +5914,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "54.209.101.70", "panw.panos.destination.nat.port": 443, @@ -5237,6 +5936,7 @@ "192.168.1.63", "54.209.101.70" ], + "rule.name": "new_outbound_from_trust", "server.ip": "54.209.101.70", "server.port": 443, "service.type": "panw", @@ -5271,12 +5971,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -5293,6 +6001,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "54.209.101.70", "panw.panos.destination.nat.port": 443, @@ -5314,6 +6023,7 @@ "192.168.1.63", "54.209.101.70" ], + "rule.name": "new_outbound_from_trust", "server.ip": "54.209.101.70", "server.port": 443, "service.type": "panw", @@ -5348,12 +6058,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -5370,6 +6088,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "54.209.101.70", "panw.panos.destination.nat.port": 443, @@ -5391,6 +6110,7 @@ "192.168.1.63", "54.209.101.70" ], + "rule.name": "new_outbound_from_trust", "server.ip": "54.209.101.70", "server.port": 443, "service.type": "panw", @@ -5425,12 +6145,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -5447,6 +6175,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "54.209.101.70", "panw.panos.destination.nat.port": 443, @@ -5468,6 +6197,7 @@ "192.168.1.63", "54.209.101.70" ], + "rule.name": "new_outbound_from_trust", "server.ip": "54.209.101.70", "server.port": 443, "service.type": "panw", @@ -5502,12 +6232,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -5524,6 +6262,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "54.209.101.70", "panw.panos.destination.nat.port": 443, @@ -5545,6 +6284,7 @@ "192.168.1.63", "54.209.101.70" ], + "rule.name": "new_outbound_from_trust", "server.ip": "54.209.101.70", "server.port": 443, "service.type": "panw", @@ -5579,12 +6319,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -5601,6 +6349,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "54.209.101.70", "panw.panos.destination.nat.port": 443, @@ -5622,6 +6371,7 @@ "192.168.1.63", "54.209.101.70" ], + "rule.name": "new_outbound_from_trust", "server.ip": "54.209.101.70", "server.port": 443, "service.type": "panw", @@ -5656,12 +6406,20 @@ "destination.nat.port": 443, "destination.port": 443, "event.action": "url_filtering", - "event.category": "security_threat", + "event.category": [ + "security_threat", + "intrusion_detection", + "network" + ], "event.dataset": "panw.panos", + "event.kind": "alert", "event.module": "panw", - "event.outcome": "block-url", + "event.outcome": "success", "event.severity": 5, "event.timezone": "-02:00", + "event.type": [ + "denied" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -5678,6 +6436,7 @@ "network.transport": "tcp", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "block-url", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "54.209.101.70", "panw.panos.destination.nat.port": 443, @@ -5699,6 +6458,7 @@ "192.168.1.63", "54.209.101.70" ], + "rule.name": "new_outbound_from_trust", "server.ip": "54.209.101.70", "server.port": 443, "service.type": "panw", diff --git a/x-pack/filebeat/module/panw/panos/test/traffic.log-expected.json b/x-pack/filebeat/module/panw/panos/test/traffic.log-expected.json index 563290f9dba..9e1333f9fb8 100644 --- a/x-pack/filebeat/module/panw/panos/test/traffic.log-expected.json +++ b/x-pack/filebeat/module/panw/panos/test/traffic.log-expected.json @@ -19,14 +19,23 @@ "destination.packets": 16, "destination.port": 443, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 586000000000, "event.end": "2018-11-30T16:08:50.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T15:59:04.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -44,6 +53,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "184.51.253.152", "panw.panos.destination.nat.port": 443, @@ -63,6 +73,7 @@ "192.168.1.63", "184.51.253.152" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 5976, "server.ip": "184.51.253.152", "server.packets": 16, @@ -99,14 +110,23 @@ "destination.packets": 6, "destination.port": 0, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:08:55.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:08:55.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -124,6 +144,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 0, @@ -143,6 +164,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 588, "server.ip": "8.8.8.8", "server.packets": 6, @@ -182,14 +204,23 @@ "destination.packets": 6, "destination.port": 80, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 1000000000, "event.end": "2018-11-30T16:08:52.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:08:51.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -207,6 +238,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "17.253.3.202", "panw.panos.destination.nat.port": 80, @@ -226,6 +258,7 @@ "192.168.1.63", "17.253.3.202" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 1035, "server.ip": "17.253.3.202", "server.packets": 6, @@ -262,14 +295,23 @@ "destination.packets": 6, "destination.port": 0, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:01.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:01.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -287,6 +329,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 0, @@ -306,6 +349,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 588, "server.ip": "8.8.8.8", "server.packets": 6, @@ -345,14 +389,23 @@ "destination.packets": 5, "destination.port": 443, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:07:13.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:07:13.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -370,6 +423,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "216.58.194.99", "panw.panos.destination.nat.port": 443, @@ -389,6 +443,7 @@ "192.168.1.63", "216.58.194.99" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 1613, "server.ip": "216.58.194.99", "server.packets": 5, @@ -425,14 +480,23 @@ "destination.packets": 62, "destination.port": 443, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 85000000000, "event.end": "2018-11-30T16:08:58.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:07:33.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -450,6 +514,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "209.234.224.22", "panw.panos.destination.nat.port": 443, @@ -469,6 +534,7 @@ "192.168.1.63", "209.234.224.22" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 21111, "server.ip": "209.234.224.22", "server.packets": 62, @@ -505,14 +571,23 @@ "destination.packets": 6, "destination.port": 0, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:07.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:07.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -530,6 +605,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 0, @@ -549,6 +625,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 588, "server.ip": "8.8.8.8", "server.packets": 6, @@ -585,14 +662,23 @@ "destination.packets": 7, "destination.port": 443, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 15000000000, "event.end": "2018-11-30T16:07:19.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:07:04.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -610,6 +696,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "172.217.2.238", "panw.panos.destination.nat.port": 443, @@ -629,6 +716,7 @@ "192.168.1.63", "172.217.2.238" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 3732, "server.ip": "172.217.2.238", "server.packets": 7, @@ -665,14 +753,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:08:50.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:08:50.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -690,6 +787,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -709,6 +807,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 221, "server.ip": "8.8.8.8", "server.packets": 1, @@ -745,14 +844,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:08:51.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:08:51.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -770,6 +878,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -789,6 +898,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 221, "server.ip": "8.8.8.8", "server.packets": 1, @@ -825,14 +935,23 @@ "destination.packets": 16, "destination.port": 443, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 593000000000, "event.end": "2018-11-30T16:08:52.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T15:58:59.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -850,6 +969,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "17.249.60.78", "panw.panos.destination.nat.port": 443, @@ -869,6 +989,7 @@ "192.168.1.63", "17.249.60.78" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 5469, "server.ip": "17.249.60.78", "server.packets": 16, @@ -905,14 +1026,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:08:52.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:08:52.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -930,6 +1060,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -949,6 +1080,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 224, "server.ip": "8.8.8.8", "server.packets": 1, @@ -985,14 +1117,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:08:52.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:08:52.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -1010,6 +1151,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -1029,6 +1171,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 117, "server.ip": "8.8.8.8", "server.packets": 1, @@ -1065,14 +1208,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:08:52.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:08:52.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -1090,6 +1242,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -1109,6 +1262,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 307, "server.ip": "8.8.8.8", "server.packets": 1, @@ -1145,14 +1299,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:08:52.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:08:52.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -1170,6 +1333,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -1189,6 +1353,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 365, "server.ip": "8.8.8.8", "server.packets": 1, @@ -1225,14 +1390,23 @@ "destination.packets": 6, "destination.port": 0, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:13.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:13.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -1250,6 +1424,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 0, @@ -1269,6 +1444,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 588, "server.ip": "8.8.8.8", "server.packets": 6, @@ -1305,14 +1481,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 1000000000, "event.end": "2018-11-30T16:08:55.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:08:54.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -1330,6 +1515,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -1349,6 +1535,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 161, "server.ip": "8.8.8.8", "server.packets": 1, @@ -1385,14 +1572,23 @@ "destination.packets": 14, "destination.port": 443, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 17000000000, "event.end": "2018-11-30T16:09:11.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:08:54.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -1410,6 +1606,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "98.138.49.44", "panw.panos.destination.nat.port": 443, @@ -1429,6 +1626,7 @@ "192.168.1.63", "98.138.49.44" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 7805, "server.ip": "98.138.49.44", "server.packets": 14, @@ -1465,14 +1663,23 @@ "destination.packets": 13, "destination.port": 443, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 17000000000, "event.end": "2018-11-30T16:09:11.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:08:54.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -1490,6 +1697,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "72.30.3.43", "panw.panos.destination.nat.port": 443, @@ -1509,6 +1717,7 @@ "192.168.1.63", "72.30.3.43" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 6106, "server.ip": "72.30.3.43", "server.packets": 13, @@ -1545,14 +1754,23 @@ "destination.packets": 2, "destination.port": 0, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:15.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:15.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -1570,6 +1788,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 0, @@ -1589,6 +1808,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 196, "server.ip": "8.8.8.8", "server.packets": 2, @@ -1625,14 +1845,23 @@ "destination.packets": 19, "destination.port": 80, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 116000000000, "event.end": "2018-11-30T16:09:12.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:07:16.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -1650,6 +1879,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "172.217.9.142", "panw.panos.destination.nat.port": 80, @@ -1669,6 +1899,7 @@ "192.168.1.63", "172.217.9.142" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 3245, "server.ip": "172.217.9.142", "server.packets": 19, @@ -1705,14 +1936,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:08:57.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:08:57.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -1730,6 +1970,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -1749,6 +1990,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 179, "server.ip": "8.8.8.8", "server.packets": 1, @@ -1788,14 +2030,23 @@ "destination.packets": 13, "destination.port": 443, "event.action": "flow_started", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:13.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:13.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "start", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -1813,6 +2064,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "54.84.80.198", "panw.panos.destination.nat.port": 443, @@ -1832,6 +2084,7 @@ "192.168.1.63", "54.84.80.198" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 4537, "server.ip": "54.84.80.198", "server.packets": 13, @@ -1869,14 +2122,23 @@ "destination.packets": 8, "destination.port": 4282, "event.action": "flow_dropped", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 13000000000, "event.end": "2018-11-30T16:09:25.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:12.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "denied", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -1894,6 +2156,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "199.167.55.52", "panw.panos.destination.nat.port": 4282, @@ -1913,6 +2176,7 @@ "192.168.1.63", "199.167.55.52" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 0, "server.ip": "199.167.55.52", "server.packets": 8, @@ -1949,14 +2213,23 @@ "destination.packets": 6, "destination.port": 0, "event.action": "flow_denied", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:19.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:19.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "denied", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -1974,6 +2247,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 0, @@ -1993,6 +2267,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 588, "server.ip": "8.8.8.8", "server.packets": 6, @@ -2028,14 +2303,21 @@ "destination.nat.port": 53, "destination.packets": 1, "destination.port": 53, - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:02.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:02.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -2053,6 +2335,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -2072,6 +2355,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 130, "server.ip": "8.8.8.8", "server.packets": 1, @@ -2107,14 +2391,21 @@ "destination.nat.port": 443, "destination.packets": 6, "destination.port": 443, - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 15000000000, "event.end": "2018-11-30T16:07:35.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:07:20.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -2132,6 +2423,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "172.217.9.142", "panw.panos.destination.nat.port": 443, @@ -2151,6 +2443,7 @@ "192.168.1.63", "172.217.9.142" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 1991, "server.ip": "172.217.9.142", "server.packets": 6, @@ -2187,14 +2480,23 @@ "destination.packets": 8, "destination.port": 443, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:21.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:21.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -2212,6 +2514,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "151.101.2.2", "panw.panos.destination.nat.port": 443, @@ -2231,6 +2534,7 @@ "192.168.1.63", "151.101.2.2" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 523, "server.ip": "151.101.2.2", "server.packets": 8, @@ -2270,14 +2574,23 @@ "destination.packets": 5, "destination.port": 443, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:07:36.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:07:36.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -2295,6 +2608,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "216.58.194.66", "panw.panos.destination.nat.port": 443, @@ -2314,6 +2628,7 @@ "192.168.1.63", "216.58.194.66" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 2428, "server.ip": "216.58.194.66", "server.packets": 5, @@ -2350,14 +2665,23 @@ "destination.packets": 6, "destination.port": 0, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:25.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:25.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -2375,6 +2699,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 0, @@ -2394,6 +2719,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 588, "server.ip": "8.8.8.8", "server.packets": 6, @@ -2430,14 +2756,23 @@ "destination.packets": 2, "destination.port": 0, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:25.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:25.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -2455,6 +2790,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 0, @@ -2474,6 +2810,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 196, "server.ip": "8.8.8.8", "server.packets": 2, @@ -2510,14 +2847,23 @@ "destination.packets": 12, "destination.port": 443, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:22.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:22.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -2535,6 +2881,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "184.51.253.193", "panw.panos.destination.nat.port": 443, @@ -2554,6 +2901,7 @@ "192.168.1.63", "184.51.253.193" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 5003, "server.ip": "184.51.253.193", "server.packets": 12, @@ -2590,14 +2938,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:08.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:08.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -2615,6 +2972,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -2634,6 +2992,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 171, "server.ip": "8.8.8.8", "server.packets": 1, @@ -2671,14 +3030,23 @@ "destination.packets": 1, "destination.port": 4282, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:33.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:33.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -2696,6 +3064,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "199.167.55.52", "panw.panos.destination.nat.port": 4282, @@ -2715,6 +3084,7 @@ "192.168.1.63", "199.167.55.52" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 0, "server.ip": "199.167.55.52", "server.packets": 1, @@ -2754,14 +3124,23 @@ "destination.packets": 11, "destination.port": 17472, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:25.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:25.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -2779,6 +3158,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "199.167.52.219", "panw.panos.destination.nat.port": 17472, @@ -2798,6 +3178,7 @@ "192.168.1.63", "199.167.52.219" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 2316, "server.ip": "199.167.52.219", "server.packets": 11, @@ -2837,14 +3218,23 @@ "destination.packets": 19, "destination.port": 443, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 4000000000, "event.end": "2018-11-30T16:09:25.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:21.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -2862,6 +3252,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "52.71.117.196", "panw.panos.destination.nat.port": 443, @@ -2881,6 +3272,7 @@ "192.168.1.63", "52.71.117.196" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 13966, "server.ip": "52.71.117.196", "server.packets": 19, @@ -2917,14 +3309,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:12.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:12.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -2942,6 +3343,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -2961,6 +3363,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 244, "server.ip": "8.8.8.8", "server.packets": 1, @@ -2997,14 +3400,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:12.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:12.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -3022,6 +3434,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -3041,6 +3454,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 205, "server.ip": "8.8.8.8", "server.packets": 1, @@ -3080,14 +3494,23 @@ "destination.packets": 24, "destination.port": 443, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 8000000000, "event.end": "2018-11-30T16:09:27.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:19.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -3105,6 +3528,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "35.186.194.41", "panw.panos.destination.nat.port": 443, @@ -3124,6 +3548,7 @@ "192.168.1.63", "35.186.194.41" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 2302, "server.ip": "35.186.194.41", "server.packets": 24, @@ -3159,14 +3584,23 @@ "destination.packets": 63, "destination.port": 443, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 8000000000, "event.end": "2018-11-30T16:09:27.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:19.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -3184,6 +3618,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "35.201.124.9", "panw.panos.destination.nat.port": 443, @@ -3203,6 +3638,7 @@ "192.168.1.63", "35.201.124.9" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 6757, "server.ip": "35.201.124.9", "server.packets": 63, @@ -3242,14 +3678,23 @@ "destination.packets": 17, "destination.port": 443, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 6000000000, "event.end": "2018-11-30T16:09:27.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:21.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -3267,6 +3712,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "100.24.131.237", "panw.panos.destination.nat.port": 443, @@ -3286,6 +3732,7 @@ "192.168.1.63", "100.24.131.237" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 9007, "server.ip": "100.24.131.237", "server.packets": 17, @@ -3322,14 +3769,23 @@ "destination.packets": 8, "destination.port": 443, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 13000000000, "event.end": "2018-11-30T16:09:27.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:14.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -3347,6 +3803,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "184.51.252.247", "panw.panos.destination.nat.port": 443, @@ -3366,6 +3823,7 @@ "192.168.1.63", "184.51.252.247" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 661, "server.ip": "184.51.252.247", "server.packets": 8, @@ -3405,14 +3863,23 @@ "destination.packets": 15, "destination.port": 443, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 8000000000, "event.end": "2018-11-30T16:09:27.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:19.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -3430,6 +3897,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "35.190.88.148", "panw.panos.destination.nat.port": 443, @@ -3449,6 +3917,7 @@ "192.168.1.63", "35.190.88.148" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 11136, "server.ip": "35.190.88.148", "server.packets": 15, @@ -3488,14 +3957,23 @@ "destination.packets": 15, "destination.port": 443, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 8000000000, "event.end": "2018-11-30T16:09:27.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:19.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -3513,6 +3991,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "35.186.243.83", "panw.panos.destination.nat.port": 443, @@ -3532,6 +4011,7 @@ "192.168.1.63", "35.186.243.83" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 11136, "server.ip": "35.186.243.83", "server.packets": 15, @@ -3568,14 +4048,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:12.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:12.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -3593,6 +4082,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -3612,6 +4102,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 182, "server.ip": "8.8.8.8", "server.packets": 1, @@ -3648,14 +4139,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:12.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:12.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -3673,6 +4173,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -3692,6 +4193,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 90, "server.ip": "8.8.8.8", "server.packets": 1, @@ -3731,14 +4233,23 @@ "destination.packets": 17, "destination.port": 443, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 6000000000, "event.end": "2018-11-30T16:09:27.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:21.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -3756,6 +4267,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "100.24.165.74", "panw.panos.destination.nat.port": 443, @@ -3775,6 +4287,7 @@ "192.168.1.63", "100.24.165.74" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 6669, "server.ip": "100.24.165.74", "server.packets": 17, @@ -3811,14 +4324,23 @@ "destination.packets": 8, "destination.port": 443, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 13000000000, "event.end": "2018-11-30T16:09:27.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:14.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -3836,6 +4358,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "184.51.252.247", "panw.panos.destination.nat.port": 443, @@ -3855,6 +4378,7 @@ "192.168.1.63", "184.51.252.247" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 661, "server.ip": "184.51.252.247", "server.packets": 8, @@ -3890,14 +4414,23 @@ "destination.packets": 15, "destination.port": 443, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 8000000000, "event.end": "2018-11-30T16:09:27.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:19.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -3915,6 +4448,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "35.201.94.140", "panw.panos.destination.nat.port": 443, @@ -3934,6 +4468,7 @@ "192.168.1.63", "35.201.94.140" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 11136, "server.ip": "35.201.94.140", "server.packets": 15, @@ -3970,14 +4505,23 @@ "destination.packets": 6, "destination.port": 0, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:31.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:31.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -3995,6 +4539,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 0, @@ -4012,6 +4557,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 588, "server.ip": "8.8.8.8", "server.packets": 6, @@ -4048,14 +4594,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:13.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:13.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -4073,6 +4628,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -4092,6 +4648,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 144, "server.ip": "8.8.8.8", "server.packets": 1, @@ -4128,14 +4685,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:13.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:13.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -4153,6 +4719,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -4172,6 +4739,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 206, "server.ip": "8.8.8.8", "server.packets": 1, @@ -4208,14 +4776,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:13.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:13.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -4233,6 +4810,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -4252,6 +4830,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 206, "server.ip": "8.8.8.8", "server.packets": 1, @@ -4288,14 +4867,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:13.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:13.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -4313,6 +4901,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -4332,6 +4921,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 169, "server.ip": "8.8.8.8", "server.packets": 1, @@ -4368,14 +4958,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:13.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:13.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -4393,6 +4992,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -4412,6 +5012,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 132, "server.ip": "8.8.8.8", "server.packets": 1, @@ -4448,14 +5049,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:13.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:13.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -4473,6 +5083,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -4492,6 +5103,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 127, "server.ip": "8.8.8.8", "server.packets": 1, @@ -4528,14 +5140,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:13.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:13.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -4553,6 +5174,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -4572,6 +5194,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 105, "server.ip": "8.8.8.8", "server.packets": 1, @@ -4608,14 +5231,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:13.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:13.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -4633,6 +5265,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -4652,6 +5285,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 172, "server.ip": "8.8.8.8", "server.packets": 1, @@ -4688,14 +5322,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:13.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:13.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -4713,6 +5356,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -4732,6 +5376,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 134, "server.ip": "8.8.8.8", "server.packets": 1, @@ -4768,14 +5413,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:13.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:13.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -4793,6 +5447,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -4812,6 +5467,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 179, "server.ip": "8.8.8.8", "server.packets": 1, @@ -4848,14 +5504,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:13.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:13.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -4873,6 +5538,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -4892,6 +5558,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 218, "server.ip": "8.8.8.8", "server.packets": 1, @@ -4928,14 +5595,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:13.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:13.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -4953,6 +5629,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -4972,6 +5649,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 172, "server.ip": "8.8.8.8", "server.packets": 1, @@ -5008,14 +5686,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:13.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:13.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -5033,6 +5720,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -5052,6 +5740,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 305, "server.ip": "8.8.8.8", "server.packets": 1, @@ -5091,14 +5780,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:14.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:14.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -5116,6 +5814,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "66.28.0.45", "panw.panos.destination.nat.port": 53, @@ -5135,6 +5834,7 @@ "192.168.1.63", "66.28.0.45" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 527, "server.ip": "66.28.0.45", "server.packets": 1, @@ -5171,14 +5871,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:14.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:14.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -5196,6 +5905,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -5215,6 +5925,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 153, "server.ip": "8.8.8.8", "server.packets": 1, @@ -5251,14 +5962,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:14.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:14.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -5276,6 +5996,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -5295,6 +6016,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 169, "server.ip": "8.8.8.8", "server.packets": 1, @@ -5331,14 +6053,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:14.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:14.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -5356,6 +6087,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -5375,6 +6107,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 128, "server.ip": "8.8.8.8", "server.packets": 1, @@ -5411,14 +6144,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:14.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:14.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -5436,6 +6178,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -5455,6 +6198,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 181, "server.ip": "8.8.8.8", "server.packets": 1, @@ -5491,14 +6235,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:14.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:14.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -5516,6 +6269,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -5535,6 +6289,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 121, "server.ip": "8.8.8.8", "server.packets": 1, @@ -5574,14 +6329,23 @@ "destination.packets": 6, "destination.port": 80, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:29.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:29.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -5599,6 +6363,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "23.52.174.25", "panw.panos.destination.nat.port": 80, @@ -5618,6 +6383,7 @@ "192.168.1.63", "23.52.174.25" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 1246, "server.ip": "23.52.174.25", "server.packets": 6, @@ -5654,14 +6420,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 1000000000, "event.end": "2018-11-30T16:09:14.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:13.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -5679,6 +6454,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -5698,6 +6474,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 315, "server.ip": "8.8.8.8", "server.packets": 1, @@ -5734,14 +6511,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:14.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:14.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -5759,6 +6545,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -5778,6 +6565,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 130, "server.ip": "8.8.8.8", "server.packets": 1, @@ -5817,14 +6605,23 @@ "destination.packets": 5, "destination.port": 443, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 12000000000, "event.end": "2018-11-30T16:09:29.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:17.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -5842,6 +6639,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "54.230.5.228", "panw.panos.destination.nat.port": 443, @@ -5861,6 +6659,7 @@ "192.168.1.63", "54.230.5.228" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 288, "server.ip": "54.230.5.228", "server.packets": 5, @@ -5897,14 +6696,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:14.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:14.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -5922,6 +6730,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -5941,6 +6750,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 149, "server.ip": "8.8.8.8", "server.packets": 1, @@ -5977,14 +6787,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:15.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:15.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -6002,6 +6821,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -6021,6 +6841,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 202, "server.ip": "8.8.8.8", "server.packets": 1, @@ -6057,14 +6878,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:15.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:15.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -6082,6 +6912,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -6101,6 +6932,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 195, "server.ip": "8.8.8.8", "server.packets": 1, @@ -6137,14 +6969,23 @@ "destination.packets": 1, "destination.port": 123, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:15.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:15.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -6162,6 +7003,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "208.83.246.20", "panw.panos.destination.nat.port": 123, @@ -6181,6 +7023,7 @@ "192.168.1.63", "208.83.246.20" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 90, "server.ip": "208.83.246.20", "server.packets": 1, @@ -6217,14 +7060,22 @@ "destination.packets": 2, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:16.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "drop-icmp", + "event.outcome": "success", "event.start": "2018-11-30T16:09:16.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -6242,6 +7093,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "drop-icmp", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -6261,6 +7113,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 192, "server.ip": "8.8.8.8", "server.packets": 2, @@ -6297,14 +7150,22 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:16.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "reset-client", + "event.outcome": "success", "event.start": "2018-11-30T16:09:16.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -6322,6 +7183,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "reset-client", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -6341,6 +7203,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 208, "server.ip": "8.8.8.8", "server.packets": 1, @@ -6377,14 +7240,22 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:16.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "reset-server", + "event.outcome": "success", "event.start": "2018-11-30T16:09:16.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -6402,6 +7273,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "reset-server", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -6421,6 +7293,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 100, "server.ip": "8.8.8.8", "server.packets": 1, @@ -6459,14 +7332,22 @@ "destination.packets": 13, "destination.port": 443, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 10000000000, "event.end": "2018-11-30T16:09:31.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "reset-both", + "event.outcome": "success", "event.start": "2018-11-30T16:09:21.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -6484,6 +7365,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "reset-both", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "35.185.88.112", "panw.panos.destination.nat.port": 443, @@ -6503,6 +7385,7 @@ "192.168.1.63", "35.185.88.112" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 7237, "server.ip": "35.185.88.112", "server.packets": 13, @@ -6539,14 +7422,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:16.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:16.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -6564,6 +7456,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -6583,6 +7476,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 109, "server.ip": "8.8.8.8", "server.packets": 1, @@ -6619,14 +7513,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:16.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:16.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -6644,6 +7547,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -6663,6 +7567,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 116, "server.ip": "8.8.8.8", "server.packets": 1, @@ -6699,14 +7604,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:16.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:16.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -6724,6 +7638,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -6743,6 +7658,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 96, "server.ip": "8.8.8.8", "server.packets": 1, @@ -6782,14 +7698,23 @@ "destination.packets": 8, "destination.port": 443, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 11000000000, "event.end": "2018-11-30T16:09:32.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:21.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -6807,6 +7732,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "50.19.85.24", "panw.panos.destination.nat.port": 443, @@ -6826,6 +7752,7 @@ "192.168.1.63", "50.19.85.24" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 654, "server.ip": "50.19.85.24", "server.packets": 8, @@ -6865,14 +7792,23 @@ "destination.packets": 8, "destination.port": 443, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 11000000000, "event.end": "2018-11-30T16:09:32.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:21.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -6890,6 +7826,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "50.19.85.24", "panw.panos.destination.nat.port": 443, @@ -6909,6 +7846,7 @@ "192.168.1.63", "50.19.85.24" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 654, "server.ip": "50.19.85.24", "server.packets": 8, @@ -6948,14 +7886,23 @@ "destination.packets": 8, "destination.port": 443, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 11000000000, "event.end": "2018-11-30T16:09:32.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:21.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -6973,6 +7920,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "50.19.85.24", "panw.panos.destination.nat.port": 443, @@ -6992,6 +7940,7 @@ "192.168.1.63", "50.19.85.24" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 654, "server.ip": "50.19.85.24", "server.packets": 8, @@ -7028,14 +7977,23 @@ "destination.packets": 12, "destination.port": 443, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 11000000000, "event.end": "2018-11-30T16:09:32.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:21.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -7053,6 +8011,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "104.254.150.9", "panw.panos.destination.nat.port": 443, @@ -7072,6 +8031,7 @@ "192.168.1.63", "104.254.150.9" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 7820, "server.ip": "104.254.150.9", "server.packets": 12, @@ -7111,14 +8071,23 @@ "destination.packets": 8, "destination.port": 443, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 11000000000, "event.end": "2018-11-30T16:09:32.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:21.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -7136,6 +8105,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "50.19.85.24", "panw.panos.destination.nat.port": 443, @@ -7155,6 +8125,7 @@ "192.168.1.63", "50.19.85.24" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 654, "server.ip": "50.19.85.24", "server.packets": 8, @@ -7194,14 +8165,23 @@ "destination.packets": 4, "destination.port": 443, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 12000000000, "event.end": "2018-11-30T16:09:32.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:20.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -7219,6 +8199,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "52.0.218.108", "panw.panos.destination.nat.port": 443, @@ -7238,6 +8219,7 @@ "192.168.1.63", "52.0.218.108" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 214, "server.ip": "52.0.218.108", "server.packets": 4, @@ -7277,14 +8259,23 @@ "destination.packets": 4, "destination.port": 443, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 12000000000, "event.end": "2018-11-30T16:09:32.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:20.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -7302,6 +8293,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "52.6.117.19", "panw.panos.destination.nat.port": 443, @@ -7321,6 +8313,7 @@ "192.168.1.63", "52.6.117.19" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 214, "server.ip": "52.6.117.19", "server.packets": 4, @@ -7360,14 +8353,23 @@ "destination.packets": 4, "destination.port": 443, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 12000000000, "event.end": "2018-11-30T16:09:32.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:20.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -7385,6 +8387,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "34.238.96.22", "panw.panos.destination.nat.port": 443, @@ -7404,6 +8407,7 @@ "192.168.1.63", "34.238.96.22" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 214, "server.ip": "34.238.96.22", "server.packets": 4, @@ -7443,14 +8447,23 @@ "destination.packets": 4, "destination.port": 443, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 12000000000, "event.end": "2018-11-30T16:09:32.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:20.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -7468,6 +8481,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "130.211.47.17", "panw.panos.destination.nat.port": 443, @@ -7487,6 +8501,7 @@ "192.168.1.63", "130.211.47.17" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 280, "server.ip": "130.211.47.17", "server.packets": 4, @@ -7523,14 +8538,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:18.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:18.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -7548,6 +8572,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -7567,6 +8592,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 172, "server.ip": "8.8.8.8", "server.packets": 1, @@ -7603,14 +8629,23 @@ "destination.packets": 6, "destination.port": 0, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:37.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:37.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -7628,6 +8663,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 0, @@ -7647,6 +8683,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 588, "server.ip": "8.8.8.8", "server.packets": 6, @@ -7683,14 +8720,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:19.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:19.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -7708,6 +8754,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -7727,6 +8774,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 94, "server.ip": "8.8.8.8", "server.packets": 1, @@ -7763,14 +8811,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:19.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:19.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -7788,6 +8845,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -7807,6 +8865,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 170, "server.ip": "8.8.8.8", "server.packets": 1, @@ -7843,14 +8902,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:19.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:19.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -7868,6 +8936,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -7887,6 +8956,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 94, "server.ip": "8.8.8.8", "server.packets": 1, @@ -7923,14 +8993,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:19.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:19.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -7948,6 +9027,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -7967,6 +9047,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 94, "server.ip": "8.8.8.8", "server.packets": 1, @@ -8003,14 +9084,23 @@ "destination.packets": 1, "destination.port": 53, "event.action": "flow_terminated", - "event.category": "network_traffic", + "event.category": [ + "network_traffic", + "network" + ], "event.dataset": "panw.panos", "event.duration": 0, "event.end": "2018-11-30T16:09:19.000-02:00", + "event.kind": "event", "event.module": "panw", - "event.outcome": "allow", + "event.outcome": "success", "event.start": "2018-11-30T16:09:19.000-02:00", "event.timezone": "-02:00", + "event.type": [ + "allowed", + "end", + "connection" + ], "fileset.name": "panos", "input.type": "log", "labels.nat_translated": true, @@ -8028,6 +9118,7 @@ "network.type": "ipv4", "observer.hostname": "PA-220", "observer.serial_number": "012801096514", + "panw.panos.action": "allow", "panw.panos.destination.interface": "ethernet1/1", "panw.panos.destination.nat.ip": "8.8.8.8", "panw.panos.destination.nat.port": 53, @@ -8047,6 +9138,7 @@ "192.168.1.63", "8.8.8.8" ], + "rule.name": "new_outbound_from_trust", "server.bytes": 166, "server.ip": "8.8.8.8", "server.packets": 1, From 29ecd726eff10c984095dc68eca715e3d41a73f3 Mon Sep 17 00:00:00 2001 From: Lee Hinman <57081003+leehinman@users.noreply.github.com> Date: Thu, 23 Apr 2020 10:03:51 -0500 Subject: [PATCH 012/116] Improve ECS categorization field mappings in rabbitmq module (#17916) - event.kind Closes #16178 --- CHANGELOG.next.asciidoc | 1 + .../module/rabbitmq/log/ingest/pipeline.yml | 3 +++ .../rabbitmq/log/test/test.log-expected.json | 25 +++++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 141a852c5a7..b55ad88642f 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -271,6 +271,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Added Unix stream socket support as an input source and a syslog input source. {pull}17492[17492] - Improve ECS categorization field mappings for nginx module. {issue}16174[16174] {pull}17844[17844] - Improve ECS categorization field mappings in postgresql module. {issue}16177[16177] {pull}17914[17914] +- Improve ECS categorization field mappings in rabbitmq module. {issue}16178[16178] {pull}17916[17916] *Heartbeat* diff --git a/x-pack/filebeat/module/rabbitmq/log/ingest/pipeline.yml b/x-pack/filebeat/module/rabbitmq/log/ingest/pipeline.yml index b6bc5f57f63..58097c578d8 100644 --- a/x-pack/filebeat/module/rabbitmq/log/ingest/pipeline.yml +++ b/x-pack/filebeat/module/rabbitmq/log/ingest/pipeline.yml @@ -26,6 +26,9 @@ processors: - remove: field: - timestamp +- set: + field: event.kind + value: event on_failure: - set: field: error.message diff --git a/x-pack/filebeat/module/rabbitmq/log/test/test.log-expected.json b/x-pack/filebeat/module/rabbitmq/log/test/test.log-expected.json index 747b866dabe..0bdae14b894 100644 --- a/x-pack/filebeat/module/rabbitmq/log/test/test.log-expected.json +++ b/x-pack/filebeat/module/rabbitmq/log/test/test.log-expected.json @@ -2,6 +2,7 @@ { "@timestamp": "2019-04-03T11:13:15.076-02:00", "event.dataset": "rabbitmq.log", + "event.kind": "event", "event.module": "rabbitmq", "event.timezone": "-02:00", "fileset.name": "log", @@ -15,6 +16,7 @@ { "@timestamp": "2019-04-03T11:13:15.510-02:00", "event.dataset": "rabbitmq.log", + "event.kind": "event", "event.module": "rabbitmq", "event.timezone": "-02:00", "fileset.name": "log", @@ -31,6 +33,7 @@ { "@timestamp": "2019-04-03T11:13:15.512-02:00", "event.dataset": "rabbitmq.log", + "event.kind": "event", "event.module": "rabbitmq", "event.timezone": "-02:00", "fileset.name": "log", @@ -47,6 +50,7 @@ { "@timestamp": "2019-04-12T10:00:53.458-02:00", "event.dataset": "rabbitmq.log", + "event.kind": "event", "event.module": "rabbitmq", "event.timezone": "-02:00", "fileset.name": "log", @@ -60,6 +64,7 @@ { "@timestamp": "2019-04-12T10:00:53.550-02:00", "event.dataset": "rabbitmq.log", + "event.kind": "event", "event.module": "rabbitmq", "event.timezone": "-02:00", "fileset.name": "log", @@ -76,6 +81,7 @@ { "@timestamp": "2019-04-12T10:00:53.550-02:00", "event.dataset": "rabbitmq.log", + "event.kind": "event", "event.module": "rabbitmq", "event.timezone": "-02:00", "fileset.name": "log", @@ -89,6 +95,7 @@ { "@timestamp": "2019-04-12T10:00:54.553-02:00", "event.dataset": "rabbitmq.log", + "event.kind": "event", "event.module": "rabbitmq", "event.timezone": "-02:00", "fileset.name": "log", @@ -102,6 +109,7 @@ { "@timestamp": "2019-04-12T10:00:54.555-02:00", "event.dataset": "rabbitmq.log", + "event.kind": "event", "event.module": "rabbitmq", "event.timezone": "-02:00", "fileset.name": "log", @@ -115,6 +123,7 @@ { "@timestamp": "2019-04-12T10:00:54.567-02:00", "event.dataset": "rabbitmq.log", + "event.kind": "event", "event.module": "rabbitmq", "event.timezone": "-02:00", "fileset.name": "log", @@ -128,6 +137,7 @@ { "@timestamp": "2019-04-12T10:00:54.567-02:00", "event.dataset": "rabbitmq.log", + "event.kind": "event", "event.module": "rabbitmq", "event.timezone": "-02:00", "fileset.name": "log", @@ -141,6 +151,7 @@ { "@timestamp": "2019-04-12T10:00:54.568-02:00", "event.dataset": "rabbitmq.log", + "event.kind": "event", "event.module": "rabbitmq", "event.timezone": "-02:00", "fileset.name": "log", @@ -154,6 +165,7 @@ { "@timestamp": "2019-04-12T10:00:54.569-02:00", "event.dataset": "rabbitmq.log", + "event.kind": "event", "event.module": "rabbitmq", "event.timezone": "-02:00", "fileset.name": "log", @@ -167,6 +179,7 @@ { "@timestamp": "2019-04-12T10:00:54.579-02:00", "event.dataset": "rabbitmq.log", + "event.kind": "event", "event.module": "rabbitmq", "event.timezone": "-02:00", "fileset.name": "log", @@ -180,6 +193,7 @@ { "@timestamp": "2019-04-12T10:00:54.588-02:00", "event.dataset": "rabbitmq.log", + "event.kind": "event", "event.module": "rabbitmq", "event.timezone": "-02:00", "fileset.name": "log", @@ -193,6 +207,7 @@ { "@timestamp": "2019-04-12T10:00:54.589-02:00", "event.dataset": "rabbitmq.log", + "event.kind": "event", "event.module": "rabbitmq", "event.timezone": "-02:00", "fileset.name": "log", @@ -206,6 +221,7 @@ { "@timestamp": "2019-04-12T10:00:54.598-02:00", "event.dataset": "rabbitmq.log", + "event.kind": "event", "event.module": "rabbitmq", "event.timezone": "-02:00", "fileset.name": "log", @@ -219,6 +235,7 @@ { "@timestamp": "2019-04-12T10:00:54.606-02:00", "event.dataset": "rabbitmq.log", + "event.kind": "event", "event.module": "rabbitmq", "event.timezone": "-02:00", "fileset.name": "log", @@ -232,6 +249,7 @@ { "@timestamp": "2019-04-12T10:00:54.615-02:00", "event.dataset": "rabbitmq.log", + "event.kind": "event", "event.module": "rabbitmq", "event.timezone": "-02:00", "fileset.name": "log", @@ -245,6 +263,7 @@ { "@timestamp": "2019-04-12T10:00:54.615-02:00", "event.dataset": "rabbitmq.log", + "event.kind": "event", "event.module": "rabbitmq", "event.timezone": "-02:00", "fileset.name": "log", @@ -261,6 +280,7 @@ { "@timestamp": "2019-04-12T10:01:01.031-02:00", "event.dataset": "rabbitmq.log", + "event.kind": "event", "event.module": "rabbitmq", "event.timezone": "-02:00", "fileset.name": "log", @@ -277,6 +297,7 @@ { "@timestamp": "2019-04-12T10:11:15.094-02:00", "event.dataset": "rabbitmq.log", + "event.kind": "event", "event.module": "rabbitmq", "event.timezone": "-02:00", "fileset.name": "log", @@ -290,6 +311,7 @@ { "@timestamp": "2019-04-12T10:11:15.101-02:00", "event.dataset": "rabbitmq.log", + "event.kind": "event", "event.module": "rabbitmq", "event.timezone": "-02:00", "fileset.name": "log", @@ -303,6 +325,7 @@ { "@timestamp": "2019-04-12T10:19:14.450-02:00", "event.dataset": "rabbitmq.log", + "event.kind": "event", "event.module": "rabbitmq", "event.timezone": "-02:00", "fileset.name": "log", @@ -319,6 +342,7 @@ { "@timestamp": "2019-04-12T10:19:14.450-02:00", "event.dataset": "rabbitmq.log", + "event.kind": "event", "event.module": "rabbitmq", "event.timezone": "-02:00", "fileset.name": "log", @@ -332,6 +356,7 @@ { "@timestamp": "2019-04-12T10:19:14.451-02:00", "event.dataset": "rabbitmq.log", + "event.kind": "event", "event.module": "rabbitmq", "event.timezone": "-02:00", "fileset.name": "log", From 8786d05e3e55a46921dc2be195b7e7254ff0465c Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Thu, 23 Apr 2020 14:08:42 -0400 Subject: [PATCH 013/116] Update metricbeat to use mage for all scenarios along with Makefile shim (#17799) * Update metricbeat to use mage and the mage make targets. * Run update and fmt. * Remove assets.go as its no longer used. * Split apart the metricbeat tests in travis into multiple jobs to take less time and not run over the timeline. * Add back in create-metricset with mage. * Re-add test_xpack_base.py. * Add kafka to requirements.txt. * Fix kafka to work with python 3.7. * Remove the extra kafka-python from rebase on master. * Fix issues from code review. * Add back in comment from merge. * Fix comment spacing. * Fix update target in generate metricbeat. * Update magefile in metricbeat generator. * Run fmt and update. --- .travis.yml | 28 ++++-- Jenkinsfile | 15 +++- Makefile | 2 +- dev-tools/make/mage-install.mk | 13 +++ dev-tools/make/mage.mk | 82 +++++++++++++++--- dev-tools/make/xpack.mk | 53 ------------ filebeat/docs/fields.asciidoc | 9 ++ .../_templates/metricbeat/{beat}/magefile.go | 14 +-- generator/common/Makefile | 2 +- libbeat/publisher/pipeline/testing.go | 13 +-- libbeat/scripts/Makefile | 2 +- metricbeat/Makefile | 85 ++----------------- metricbeat/magefile.go | 65 +++++++++++--- metricbeat/module/aerospike/fields.go | 2 +- metricbeat/module/apache/fields.go | 2 +- metricbeat/module/beat/fields.go | 2 +- metricbeat/module/ceph/fields.go | 2 +- metricbeat/module/consul/fields.go | 2 +- metricbeat/module/couchbase/fields.go | 2 +- metricbeat/module/couchdb/fields.go | 2 +- metricbeat/module/docker/fields.go | 2 +- metricbeat/module/dropwizard/fields.go | 2 +- metricbeat/module/elasticsearch/fields.go | 2 +- metricbeat/module/envoyproxy/fields.go | 2 +- metricbeat/module/etcd/fields.go | 2 +- metricbeat/module/golang/fields.go | 2 +- metricbeat/module/graphite/fields.go | 2 +- metricbeat/module/haproxy/fields.go | 2 +- metricbeat/module/http/fields.go | 2 +- metricbeat/module/jolokia/fields.go | 2 +- metricbeat/module/kafka/fields.go | 2 +- metricbeat/module/kibana/fields.go | 2 +- metricbeat/module/kubernetes/fields.go | 2 +- metricbeat/module/kvm/fields.go | 2 +- metricbeat/module/logstash/fields.go | 2 +- metricbeat/module/memcached/fields.go | 2 +- metricbeat/module/mongodb/fields.go | 2 +- metricbeat/module/munin/fields.go | 2 +- metricbeat/module/mysql/fields.go | 2 +- metricbeat/module/nats/fields.go | 2 +- metricbeat/module/nginx/fields.go | 2 +- metricbeat/module/php_fpm/fields.go | 2 +- metricbeat/module/postgresql/fields.go | 2 +- metricbeat/module/prometheus/fields.go | 2 +- metricbeat/module/rabbitmq/fields.go | 2 +- metricbeat/module/redis/fields.go | 2 +- metricbeat/module/system/fields.go | 2 +- metricbeat/module/traefik/fields.go | 2 +- metricbeat/module/uwsgi/fields.go | 2 +- metricbeat/module/vsphere/fields.go | 2 +- metricbeat/module/windows/fields.go | 2 +- metricbeat/module/zookeeper/fields.go | 2 +- metricbeat/scripts/assets/assets.go | 75 ---------------- x-pack/auditbeat/Makefile | 2 +- x-pack/dockerlogbeat/Makefile | 2 +- x-pack/elastic-agent/Makefile | 2 +- x-pack/filebeat/Makefile | 2 +- x-pack/functionbeat/Makefile | 2 +- x-pack/metricbeat/Makefile | 2 +- x-pack/metricbeat/magefile.go | 3 + .../tests/system/test_xpack_base.py | 3 +- x-pack/winlogbeat/Makefile | 2 +- 62 files changed, 252 insertions(+), 304 deletions(-) create mode 100644 dev-tools/make/mage-install.mk delete mode 100644 dev-tools/make/xpack.mk delete mode 100644 metricbeat/scripts/assets/assets.go diff --git a/.travis.yml b/.travis.yml index 7717c0366f8..463f57f27e3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -111,23 +111,22 @@ jobs: # Metricbeat - os: linux before_install: .ci/scripts/travis_has_changes.sh metricbeat libbeat || travis_terminate 0 - env: TARGETS="-C metricbeat unit-tests coverage-report" + env: TARGETS="-C metricbeat unit-tests" go: $TRAVIS_GO_VERSION stage: test - os: linux - before_install: .ci/scripts/travis_has_changes.sh metricbeat libbeat vendor || travis_terminate 0 - env: TARGETS="-C metricbeat integration-tests-environment coverage-report" + before_install: .ci/scripts/travis_has_changes.sh metricbeat libbeat || travis_terminate 0 + env: TARGETS="-C metricbeat integration-tests" go: $TRAVIS_GO_VERSION stage: test - os: linux - before_install: .ci/scripts/travis_has_changes.sh metricbeat libbeat vendor || travis_terminate 0 - env: TARGETS="-C metricbeat update system-tests-environment coverage-report" + before_install: .ci/scripts/travis_has_changes.sh metricbeat libbeat || travis_terminate 0 + env: TARGETS="-C metricbeat system-tests" go: $TRAVIS_GO_VERSION stage: test - - os: osx before_install: .ci/scripts/travis_has_changes.sh metricbeat libbeat || travis_terminate 0 - env: TARGETS="TEST_ENVIRONMENT=0 -C metricbeat testsuite" + env: TARGETS="-C metricbeat testsuite" go: $TRAVIS_GO_VERSION stage: test - os: linux @@ -137,6 +136,21 @@ jobs: stage: test - os: linux before_install: .ci/scripts/travis_has_changes.sh x-pack/metricbeat metricbeat libbeat || travis_terminate 0 + env: TARGETS="-C x-pack/metricbeat unit-tests" + go: $TRAVIS_GO_VERSION + stage: test + - os: linux + before_install: .ci/scripts/travis_has_changes.sh x-pack/metricbeat metricbeat libbeat || travis_terminate 0 + env: TARGETS="-C x-pack/metricbeat integration-tests" + go: $TRAVIS_GO_VERSION + stage: test + - os: linux + before_install: .ci/scripts/travis_has_changes.sh x-pack/metricbeat metricbeat libbeat || travis_terminate 0 + env: TARGETS="-C x-pack/metricbeat system-tests" + go: $TRAVIS_GO_VERSION + stage: test + - os: osx + before_install: .ci/scripts/travis_has_changes.sh metricbeat libbeat || travis_terminate 0 env: TARGETS="-C x-pack/metricbeat testsuite" go: $TRAVIS_GO_VERSION stage: test diff --git a/Jenkinsfile b/Jenkinsfile index aa2efab218c..5761f41b0f8 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -319,7 +319,20 @@ pipeline { } } steps { - mageTarget("Metricbeat OSS linux/amd64 (integTest)", "metricbeat", "integTest") + mageTarget("Metricbeat OSS linux/amd64 (goIntegTest)", "metricbeat", "goIntegTest") + } + } + stage('Metricbeat Python integration tests'){ + agent { label 'ubuntu && immutable' } + options { skipDefaultCheckout() } + when { + beforeAgent true + expression { + return env.BUILD_METRICBEAT != "false" + } + } + steps { + mageTarget("Metricbeat OSS linux/amd64 (pythonIntegTest)", "metricbeat", "pythonIntegTest") } } stage('Metricbeat x-pack'){ diff --git a/Makefile b/Makefile index d99caea10a5..b03fd92ba72 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ PROJECTS_XPACK_MAGE=$(PROJECTS_XPACK_PKG) x-pack/libbeat # # Includes # -include dev-tools/make/mage.mk +include dev-tools/make/mage-install.mk ## help : Show this help. help: Makefile diff --git a/dev-tools/make/mage-install.mk b/dev-tools/make/mage-install.mk new file mode 100644 index 00000000000..8966ed6f474 --- /dev/null +++ b/dev-tools/make/mage-install.mk @@ -0,0 +1,13 @@ +MAGE_VERSION ?= v1.9.0 +MAGE_PRESENT := $(shell mage --version 2> /dev/null | grep $(MAGE_VERSION)) +MAGE_IMPORT_PATH ?= github.com/magefile/mage +export MAGE_IMPORT_PATH + +.PHONY: mage +mage: +ifndef MAGE_PRESENT + @echo Installing mage $(MAGE_VERSION) from vendor dir. + @go install -mod=vendor -ldflags="-X $(MAGE_IMPORT_PATH)/mage.gitTag=$(MAGE_VERSION)" ${MAGE_IMPORT_PATH}/... + @-mage -clean +endif + @true diff --git a/dev-tools/make/mage.mk b/dev-tools/make/mage.mk index 8966ed6f474..e1e634c27bd 100644 --- a/dev-tools/make/mage.mk +++ b/dev-tools/make/mage.mk @@ -1,13 +1,69 @@ -MAGE_VERSION ?= v1.9.0 -MAGE_PRESENT := $(shell mage --version 2> /dev/null | grep $(MAGE_VERSION)) -MAGE_IMPORT_PATH ?= github.com/magefile/mage -export MAGE_IMPORT_PATH - -.PHONY: mage -mage: -ifndef MAGE_PRESENT - @echo Installing mage $(MAGE_VERSION) from vendor dir. - @go install -mod=vendor -ldflags="-X $(MAGE_IMPORT_PATH)/mage.gitTag=$(MAGE_VERSION)" ${MAGE_IMPORT_PATH}/... - @-mage -clean -endif - @true +# This is a minimal Makefile for Beats that are built with Mage. Its only +# responsibility is to provide compatibility with existing Jenkins and Travis +# setups. + +# +# Variables +# +.DEFAULT_GOAL := help +PWD := $(CURDIR) + +# +# Includes +# +include $(ES_BEATS)/dev-tools/make/mage-install.mk + +# +# Targets (alphabetically sorted). +# +.PHONY: check +check: mage + mage check + +.PHONY: clean +clean: mage + mage clean + +fix-permissions: + +.PHONY: fmt +fmt: mage + mage fmt + +# Default target. +.PHONY: help +help: + @echo Use mage rather than make. Here are the available mage targets: + @mage -l + +.PHONY: release +release: mage + mage package + +stop-environment: + +.PHONY: unit-tests +unit-tests: mage + mage unitTest + +.PHONY: integration-tests +integration-tests: mage + rm -f build/TEST-go-integration.out + mage goIntegTest || ( cat build/TEST-go-integration.out && false ) + +.PHONY: system-tests +system-tests: mage + mage pythonIntegTest + +.PHONY: testsuite +testsuite: mage + rm -f build/TEST-go-integration.out + mage update build unitTest integTest || ( cat build/TEST-go-integration.out && false ) + +.PHONY: update +update: mage + mage update + +.PHONY: crosscompile +crosscompile: mage + mage crossBuild diff --git a/dev-tools/make/xpack.mk b/dev-tools/make/xpack.mk deleted file mode 100644 index 54f60831108..00000000000 --- a/dev-tools/make/xpack.mk +++ /dev/null @@ -1,53 +0,0 @@ -# This is a minimal Makefile for Beats that are built with Mage. Its only -# responsibility is to provide compatibility with existing Jenkins and Travis -# setups. - -# -# Variables -# -.DEFAULT_GOAL := help -PWD := $(CURDIR) - -# -# Includes -# -include $(ES_BEATS)/dev-tools/make/mage.mk - -# -# Targets (alphabetically sorted). -# -.PHONY: check -check: mage - mage check - -.PHONY: clean -clean: mage - mage clean - -fix-permissions: - -.PHONY: fmt -fmt: mage - mage fmt - -# Default target. -.PHONY: help -help: - @echo Use mage rather than make. Here are the available mage targets: - @mage -l - -.PHONY: release -release: mage - mage package - -stop-environment: - -.PHONY: testsuite -testsuite: mage - rm -f build/TEST-go-integration.out - mage update build unitTest integTest || ( cat build/TEST-go-integration.out && false ) - -.PHONY: update -update: mage - mage update - diff --git a/filebeat/docs/fields.asciidoc b/filebeat/docs/fields.asciidoc index 5e163ab76c5..b1412d99429 100644 --- a/filebeat/docs/fields.asciidoc +++ b/filebeat/docs/fields.asciidoc @@ -28240,6 +28240,15 @@ type: keyword Palo Alto Networks name for the threat. +type: keyword + +-- + +*`panw.panos.action`*:: ++ +-- +Action taken for the session. + type: keyword -- diff --git a/generator/_templates/metricbeat/{beat}/magefile.go b/generator/_templates/metricbeat/{beat}/magefile.go index 22b3dcfcc76..b1b78829ee1 100644 --- a/generator/_templates/metricbeat/{beat}/magefile.go +++ b/generator/_templates/metricbeat/{beat}/magefile.go @@ -14,7 +14,6 @@ import ( "github.com/elastic/beats/v7/dev-tools/mage/target/common" "github.com/elastic/beats/v7/dev-tools/mage/target/pkg" "github.com/elastic/beats/v7/dev-tools/mage/target/unittest" - "github.com/elastic/beats/v7/dev-tools/mage/target/update" "github.com/elastic/beats/v7/generator/common/beatgen" metricbeat "github.com/elastic/beats/v7/metricbeat/scripts/mage" ) @@ -45,7 +44,7 @@ func Package() { devtools.UseCommunityBeatPackaging() - mg.Deps(update.Update) + mg.Deps(Update) mg.Deps(build.CrossBuild, build.CrossBuildGoDaemon) mg.SerialDeps(devtools.Package, pkg.PackageTest) } @@ -99,9 +98,14 @@ func Fmt() { common.Fmt() } -// Update updates the generated files (aka make update). -func Update() error { - return update.Update() +// Update is an alias for running fields, dashboards, config. +func Update() { + mg.SerialDeps(Fields, Dashboards, Config, Imports) +} + +// Dashboards collects all the dashboards and generates index patterns. +func Dashboards() error { + return devtools.KibanaDashboards("module") } // Imports generates an include/list.go file containing diff --git a/generator/common/Makefile b/generator/common/Makefile index 0a7b3608dae..927da092a22 100644 --- a/generator/common/Makefile +++ b/generator/common/Makefile @@ -5,7 +5,7 @@ BEAT_PATH=${GOPATH}/src/${BEAT_NAME} ES_BEATS=${GOPATH}/src/github.com/elastic/beats PREPARE_COMMAND?= --include ${ES_BEATS}/dev-tools/make/mage.mk +-include ${ES_BEATS}/dev-tools/make/mage-install.mk # Runs test build for mock beat .PHONY: test diff --git a/libbeat/publisher/pipeline/testing.go b/libbeat/publisher/pipeline/testing.go index 1d5c2b908ff..0db2780ba56 100644 --- a/libbeat/publisher/pipeline/testing.go +++ b/libbeat/publisher/pipeline/testing.go @@ -96,11 +96,14 @@ func (b *mockBatch) Events() []publisher.Event { return b.events } -func (b *mockBatch) ACK() { signalFn(b.onACK) } -func (b *mockBatch) Drop() { signalFn(b.onDrop) } -func (b *mockBatch) Retry() { signalFn(b.onRetry) } -func (b *mockBatch) Cancelled() { signalFn(b.onCancelled) } -func (b *mockBatch) RetryEvents(events []publisher.Event) { b.updateEvents(events); signalFn(b.onRetry) } +func (b *mockBatch) ACK() { signalFn(b.onACK) } +func (b *mockBatch) Drop() { signalFn(b.onDrop) } +func (b *mockBatch) Retry() { signalFn(b.onRetry) } +func (b *mockBatch) Cancelled() { signalFn(b.onCancelled) } +func (b *mockBatch) RetryEvents(events []publisher.Event) { + b.updateEvents(events) + signalFn(b.onRetry) +} func (b *mockBatch) reduceTTL() bool { if b.onReduceTTL != nil { diff --git a/libbeat/scripts/Makefile b/libbeat/scripts/Makefile index 97df237f194..f4420581f52 100755 --- a/libbeat/scripts/Makefile +++ b/libbeat/scripts/Makefile @@ -128,7 +128,7 @@ endif # # Includes # -include $(ES_BEATS)/dev-tools/make/mage.mk +include $(ES_BEATS)/dev-tools/make/mage-install.mk ### BUILDING ### diff --git a/metricbeat/Makefile b/metricbeat/Makefile index c558416eb44..9cbf88c3a59 100644 --- a/metricbeat/Makefile +++ b/metricbeat/Makefile @@ -1,83 +1,8 @@ -# Name can be overwritten, as Metricbeat is also a library -BEAT_NAME?=metricbeat -BEAT_TITLE?=Metricbeat -SYSTEM_TESTS?=true -TEST_ENVIRONMENT?=true -BEATS_DOCKER_INTEGRATION_TEST_ENV?=true -ES_BEATS?=.. +ES_BEATS ?= .. -# Metricbeat can only be cross-compiled on platforms not requiring CGO. -GOX_OS=netbsd linux windows -GOX_FLAGS=-arch="amd64 386 arm ppc64 ppc64le" - -DOCS_BRANCH=$(shell grep doc-branch ../libbeat/docs/version.asciidoc | cut -c 14-) - -include ${ES_BEATS}/libbeat/scripts/Makefile - -# Collects all module dashboards -.PHONY: kibana -kibana: mage - @mage dashboards - -# Collects all module docs -.PHONY: collect-docs -collect-docs: - mage CollectAll - -# Collects all module configs -.PHONY: configs -configs: python-env - @mkdir -p _meta - @cp ${ES_BEATS}/metricbeat/_meta/common.yml _meta/beat.yml - @cat ${ES_BEATS}/metricbeat/_meta/setup.yml >> _meta/beat.yml - @cat ${ES_BEATS}/metricbeat/_meta/common.reference.yml > _meta/beat.reference.yml - @${PYTHON_ENV_EXE} ${ES_BEATS}/script/config_collector.py --beat ${BEAT_NAME} --full $(PWD) >> _meta/beat.reference.yml - @rm -rf modules.d - mage config - @chmod go-w modules.d/* - @# Enable system by default: - @if [ -f modules.d/system.yml.disabled ]; then mv modules.d/system.yml.disabled modules.d/system.yml; fi - -# Generates imports for all modules and metricsets -.PHONY: imports -imports: - @mkdir -p include - mage imports - -# Runs all collection steps and updates afterwards -.PHONY: collect -collect: assets collect-docs configs kibana imports +include $(ES_BEATS)/dev-tools/make/mage.mk # Creates a new metricset. Requires the params MODULE and METRICSET -.PHONY: create-metricset -create-metricset: python-env - @${PYTHON_ENV_EXE} ${ES_BEATS}/metricbeat/scripts/create_metricset.py --path=$(PWD) --es_beats=$(ES_BEATS) --module=$(MODULE) --metricset=$(METRICSET) - -# Generates the data.json example documents -.PHONY: generate-json -generate-json: build-image - ${DOCKER_COMPOSE} run beat go test -tags=integration github.com/elastic/beats/metricbeat/module/... -data - -.PHONY: run-module -run-module: ## @testing Runs the given module with exposing the port. Needs $MODULE and $PORT as param -run-module: - ${DOCKER_COMPOSE} build ${MODULE} - ${DOCKER_COMPOSE} run -p ${PORT}:${PORT} ${MODULE} - -.PHONY: test-module -test-module: ## @testing Tests the given module. Needs $MODULE as param an run-module must be started first. -test-module: python-env update metricbeat.test - go test -tags=integration ${BEAT_PATH}/module/${MODULE}/... -v - . ${PYTHON_ENV}/bin/activate && INTEGRATION_TESTS=1 nosetests module/${MODULE} - -.PHONY: assets -assets: - go run ${INSTALL_FLAG} ${ES_BEATS}/metricbeat/scripts/assets/assets.go ${ES_BEATS}/metricbeat/module - mkdir -p include/fields - go run ${INSTALL_FLAG} ${ES_BEATS}/libbeat/scripts/cmd/global_fields/main.go -es_beats_path ${ES_BEATS} -beat_path ${PWD} | go run ${ES_BEATS}/dev-tools/cmd/asset/asset.go -license ${LICENSE} -out ./include/fields/fields.go -pkg include -priority asset.LibbeatFieldsPri ${ES_BEATS}/libbeat/fields.yml $(BEAT_NAME) - -.PHONY: integration-tests -integration-tests: ## @testing Run golang integration tests. -integration-tests: prepare-tests mage - rm -f docker-compose.yml.lock - mage goIntegTest +.PHONY: create-metricset +create-metricset: + mage createMetricset diff --git a/metricbeat/magefile.go b/metricbeat/magefile.go index 753e5e5a518..0281c6892ba 100644 --- a/metricbeat/magefile.go +++ b/metricbeat/magefile.go @@ -27,6 +27,7 @@ import ( "time" "github.com/magefile/mage/mg" + "github.com/magefile/mage/sh" devtools "github.com/elastic/beats/v7/dev-tools/mage" metricbeat "github.com/elastic/beats/v7/metricbeat/scripts/mage" @@ -46,14 +47,12 @@ import ( // mage:import "github.com/elastic/beats/v7/dev-tools/mage/target/unittest" // mage:import - "github.com/elastic/beats/v7/dev-tools/mage/target/update" - // mage:import _ "github.com/elastic/beats/v7/dev-tools/mage/target/compose" ) func init() { - common.RegisterCheckDeps(update.Update) - test.RegisterDeps(GoIntegTest, PythonIntegTest) + common.RegisterCheckDeps(Update) + test.RegisterDeps(IntegTest) unittest.RegisterGoTestDeps(Fields) unittest.RegisterPythonTestDeps(Fields) @@ -75,8 +74,9 @@ func Package() { devtools.UseElasticBeatOSSPackaging() metricbeat.CustomizePackaging() + devtools.PackageKibanaDashboardsFromBuildDir() - mg.Deps(update.Update, metricbeat.PrepareModulePackagingOSS) + mg.Deps(Update) mg.Deps(build.CrossBuild, build.CrossBuildGoDaemon) mg.SerialDeps(devtools.Package, TestPackages) } @@ -102,12 +102,6 @@ func Config() { mg.Deps(configYML, metricbeat.GenerateDirModulesD) } -// Imports generates an include/list_{suffix}.go file containing -// a import statement for each module and dataset. -func Imports() error { - return metricbeat.GenerateOSSMetricbeatModuleIncludeListGo() -} - func configYML() error { return devtools.Config(devtools.AllConfigTypes, metricbeat.OSSConfigFileParams(), ".") } @@ -133,11 +127,26 @@ func MockedTests(ctx context.Context) error { return devtools.GoTest(ctx, params) } -// Fields generates a fields.yml for the Beat. -func Fields() error { +// Fields generates a fields.yml and fields.go for each module. +func Fields() { + mg.Deps(fieldsYML, moduleFieldsGo) +} + +func fieldsYML() error { return devtools.GenerateFieldsYAML("module") } +func moduleFieldsGo() error { + return devtools.GenerateModuleFieldsGo("module") +} + +// Update is an alias for running fields, dashboards, config. +func Update() { + mg.SerialDeps(Fields, Dashboards, Config, + metricbeat.PrepareModulePackagingOSS, + metricbeat.GenerateOSSMetricbeatModuleIncludeListGo) +} + // FieldsDocs generates docs/fields.asciidoc containing all fields // (including x-pack). func FieldsDocs() error { @@ -168,7 +177,9 @@ func IntegTest() { // Use TEST_TAGS=tag1,tag2 to add additional build tags. // Use MODULE=module to run only tests for `module`. func GoIntegTest(ctx context.Context) error { - mg.Deps(Fields) + if !devtools.IsInIntegTestEnv() { + mg.SerialDeps(Fields, Dashboards) + } return devtools.GoTestIntegrationForModule(ctx) } @@ -186,3 +197,29 @@ func PythonIntegTest(ctx context.Context) error { return devtools.PythonNoseTest(devtools.DefaultPythonTestIntegrationArgs()) }, devtools.ListMatchingEnvVars("NOSE_")...) } + +// CreateMetricset creates a new metricset. +// +// Required ENV variables: +// * MODULE: Name of the module +// * METRICSET: Name of the metricset +func CreateMetricset(ctx context.Context) error { + ve, err := devtools.PythonVirtualenv() + if err != nil { + return err + } + python, err := devtools.LookVirtualenvPath(ve, "python") + if err != nil { + return err + } + path, err := os.Getwd() + if err != nil { + return err + } + + _, err = sh.Exec( + map[string]string{}, os.Stdout, os.Stderr, python, "scripts/create_metricset.py", + "--path", path, "--module", os.Getenv("MODULE"), "--metricset", os.Getenv("METRICSET"), + ) + return err +} diff --git a/metricbeat/module/aerospike/fields.go b/metricbeat/module/aerospike/fields.go index 4e03f23d432..0a063636e18 100644 --- a/metricbeat/module/aerospike/fields.go +++ b/metricbeat/module/aerospike/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetAerospike returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/aerospike. +// This is the base64 encoded gzipped contents of module/aerospike. func AssetAerospike() string { return "eJzUmMGO4zYMhu95CmIve2nyADkUWLSXHnZRFL0VxYCR6FgdWTQkalIDffhCdjzj2HKSycxuPDrkYEvk/8mkRGYNj9RsAclzqM0jrQDEiKUtfPrSP/u0AtAUlDe1GHZb+HkFAPD8HirW0aalnixhoC3scQVQGLI6bNvJa3BY0amjNKSp03TPsT4+yXg6NTU0l35DjYqe3+RMzprtRs7IKUk/xjKGUpQ15OTk1ZyWC3rS+KW1BkFQwmb0NqdiqESTJaHJ63NqrlA0UNU5APHoAqq0IOSVnlM7VEzes8/O6EVbdvuZCVfoTuNbrHbkgYvjl8pSSIkCBRpLGg5GSkDXicuRDcKQ5aHg6PRiGDyFaIU0GAeY9EGr7zxGiEpRCD8M4uiviPYMz3nJYiriOE677yf50r4nPRo4ylR2L9kT5uLkvZIzmf/oqTlm6FRcCIW7JeFE7SQDP0z+TYJnkdmX3/ArUu/gzXe9GFv7Hz35MhA3Xov3j+cpyyIDem7LZ0L6pdB7MmoczzeXnL+a8AhB2OOeurB9Zd2JT2gs7ixtapXbwk5ZUGhJPxSWMTepYF+hbKEmr6b19BUYaXwlDNFT2kWCyjhTxQoUOzH7yDGAblFTyQ+oPIcAaG37NBwLpr4lmD9LCk/3B/29W5y+GBcdlsIalZGmFZi8gJQmXEMkLGg3u0Yol7Jng7+HmVt8BcqfyXtn4Jnl+ImsZYXpKhUewUB7+KcnrM+QxUB6gWBJFuya65l6nvJQPew8oSppXO10NDtmS+hedwD8VoD4SD8NOvsSA/SO4HNp9uX6gEJ+/VfC+K+iin3z97pW8vliqPXiu0XvdWp9ba2dnluvbZgXmMrdJl1M5tfEv0bBuyXBl4qjkwEZKxVr08V/UvZGOOM0/btMunQHtfLeiBgWzBhIsdPom46UwhtZF3UVHXlvPq3Tb/a4e6TmwH58gl8Q+O3Z88TuSyesaVNyyP8beZtX1gQTkycO3xkzOZwl5N0/pCYF6s2XyB+k2OuZZu3S7VFhEMp1aWfD9IowfGkT/FHfMNbgUBpVAvpUw4p5oqOQTIdzUuD9EKHGjdPk5TyYzZMgXD+0rVD+w76lqBnLMQFU9J6c2Kb9eyYVmQfj9l0vFjar/wMAAP//lvJazw==" } diff --git a/metricbeat/module/apache/fields.go b/metricbeat/module/apache/fields.go index 619e56f1a83..332b5f2c1a0 100644 --- a/metricbeat/module/apache/fields.go +++ b/metricbeat/module/apache/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetApache returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/apache. +// This is the base64 encoded gzipped contents of module/apache. func AssetApache() string { return "eJzMl8GO2zYQhu9+ioHOjZE95OJDgTQB2qKLNtjdoIeiUGhqJBOmOSyHWsNvX5CUVa1XUmzXDKLTwlr9/zfD4Qz5BrZ4WIGwQm5wAeCV17iC4n38oVgAVMjSKesVmRX8uAAASC/hl6enTx+B0T2jgx16pySjZ5CkNUqPFdSOduA3ePxij+vu/5cLAN6Q86UkU6tmBbXQHAgcahSMK2hE+B/0XpmGV/BXwayLH6DYeG+LvxcAtUJd8SoivQEjdjgIJDz+YIOOo9Z2v4wEE54v6bMvIMl4oQxH6C4k8BvhYY8OgaUT9hhXimnZiQxhhkDshW+5/3kMKjwvoz4+E7gROQlfhjxYivQ5WNH0IYyFMQxlQ+zDXy9eHgPa4mFPrjp5NxPAoJCOwstRW09e6FJIiczIo+aaTHOZ81MQBdPu1uiAakjy4PCfFtnzHMl2ffDZOLZKU9RP+6QaBzlilhZdyShHYVgKjVVZaxL+MqiHTh4sOmCUZCY4ImkmiJ9iFs4n6HKSjaLTH8fYk9ui4+W65cONKuP3viaCaO8wa68qPb47/499EJ23b61XE23htM+d4fw5qsUGdeI33p6GJGm4lKNAs9k4gys8j2nYJX1QpqvNV5xfyc3NUcbXRdr2Vovy4dPn61ZEkzgdCnDOvjwzC4HrnkQ1swCMLjNAsIiBTlPwgT3uMnI8RoOw5NMQcqN05dCU3yAnVPd2MT9nUGXP0SlXMpzYO2QMyqA2Puqv2UO95HVbKR4/MnSSdAYZBDy9VoIPRi73ToVDeQaU90F/gAKd1deItoi2FFo95+i0CSpYYAXR5JJkSU2cNVmaGKtpoplGfHUlh44L4hmdaPDiMi7uimv3eHitTFPWQnpyK7h7+/a61A0DgJpcvBlpwR52yrQepxe1ePc907/r+Geqsrj7riO4mwihH6SSHK5JvLpqXl3Mj73i8fp88anTCxe6VPnKGm5x0OvUobXTq+pQVIFg/BZ0C4yH5DBxRX6RDjQdi9Wnt6GbJCTpw0PQn8YIDTvXSPgN0aZRMO1fGS410TZLUXw0DPdRfGYhutFT/jcbMpB8SCbnjURNTZNnGN5PKB+dGyck1q3Wh7JWRvEmD8bPvQ30NtPpCFfrUmoUJkuR/Bpu7p38zKKQDYdvTTm6xh82nLM1zfWLvYhnvLIml7dU/0xGceCcVa5ZT9zLxb8BAAD//2GFvEU=" } diff --git a/metricbeat/module/beat/fields.go b/metricbeat/module/beat/fields.go index 33bbffd7dc0..58ff1927451 100644 --- a/metricbeat/module/beat/fields.go +++ b/metricbeat/module/beat/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetBeat returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/beat. +// This is the base64 encoded gzipped contents of module/beat. func AssetBeat() string { return "eJzcl0+L2zwQxu/5FIPP7+YD+PAWSlvYSwtloYdSimxPvGJljSuNdvG3L5LXiWPJdtJmE6hOiRU/z0/zR1Lu4Am7HAoUvAFgyQpzyN6j4GwDUKEtjWxZks7h/w0AgJ+ChiqncANgUKGwmEMtNgAWmaWubQ7fM2tV9h9kj8xt9sPPPZLhnyXpnaxz2All/fs7iaqyeVC+Ay0a3LP4wV3rtQ259vVJguhYZawkq/2jQesJuxcy4+dJxX6Etd5/2EbCXusC0v69WNyy4Fh9HIRTtKcqx5kaxjRyY45GaFFjg5q3qEWhsDr62QBWECkUejK3gOfHvYUSNRuhRjbwavMujRNqbluS05wEkZqxRnMeyGfXFGiAdq/6FlJrHRjIcet4678kEeISOAVBNOgBenFwFisoupDFJMQvhw7fiCFoQ4FS12mQcZHaixSp/asidS3LBreNTcZCka7PC0TA6kWTfsbpYFiTIcdS46WMD4V4kPZu2qdC6vlyULIopnNz+TiB41MINpTUNKSBCYRSwXy60FRWIOqVaHoJ7QQ8P770jTItnjUwmN/AY7x0F50B6cdD1446exEIn1EnVwOrETsD6KN3gbCFoplzW4rfGFmUT9GREFMnOuEPwOGoQ/pgeQBNLwqreoHjQMvyeS7nb4vbWy+neHT9EVw+RtvKdUgD4irBQFoZatvblsAawh7VtUqWgm8ZV3ugWAfeCRlfuK4Ku0Kw30mJGqG7m5Aykb8+dqe2FhMLdUXSB+8HehLZxXPAoJiL+cVOga8oqtnTE844AYruVg0VnJeCNTpZjaHZs+5tKT3fkv+A+GIkL19HLpD1b97kn0i7Dxfj9B9nzHnDzIeMTgGWPvwOAAD//2j1+Zk=" } diff --git a/metricbeat/module/ceph/fields.go b/metricbeat/module/ceph/fields.go index f401f784976..a2623b56a06 100644 --- a/metricbeat/module/ceph/fields.go +++ b/metricbeat/module/ceph/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetCeph returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/ceph. +// This is the base64 encoded gzipped contents of module/ceph. func AssetCeph() string { return "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" } diff --git a/metricbeat/module/consul/fields.go b/metricbeat/module/consul/fields.go index 3e396c8a0b3..9890d32f00e 100644 --- a/metricbeat/module/consul/fields.go +++ b/metricbeat/module/consul/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetConsul returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/consul. +// This is the base64 encoded gzipped contents of module/consul. func AssetConsul() string { return "eJzcVsFu2zAMvecriN7TD8hhwLbDdlkLdL0XtMzY2mTRIKlu/vtBdtzYiZ2m2IAN0yWBJD6+90gT2sJ36nbgOGoKGwDzFmgHN8PGzQagJHXiW/Mcd/BuAwDwsT+EhssUaAMgFAiVdlCQ4QZg7ymUuuvvbiFiQ5MMeVnX0g4q4dQedqYh0zCsKNrL7lJkXqcExrXAfVzvMzB8IRPvlAz2ZK4mhWbYAR/3LA3mUNgLN4CjbB/VMDoCSTH6WAHqgDbBP5Uzk5SMWx/YZqdr0tbQpog1YbC6OzsfMQvmQBgXzmcG3T+TYAgHOOA9WE0Q2GEAJXkmAReSGsmiLknRfENXq5rlfhiCcyXRqBzL8EYjtDuNuEzhEuYUt+iMlpCP6IFjtXJhpvMuNQVJ9rbHzH8aalg64MLQRyqHbsvO33+9XVXaYAjsnhyneNpHr5KaEfpM2AIX38iZQg+a/b/Uau3T4fpv5b0/TQkce9U5A2AswSsgVBRzU44mtUKqSQh8LL1DY7mFx9orNNhBkUTtYF/uJePht0gGWnMKJQhZkphPENQIyw7U0AieMSRat7ti4WQ+LnbB9aIfDhPjCHeuNDCWf01nX41//BP60H84x74pur5vDtO5FXakeqGUKAVW9OQ4BHLG8ma5MzafBjh4gVsZXdf6JCn+GZvOiRkbBqCf5FK+spxm5NFi0tNZfp09Z0we+yat0fo6VWe8atQhXdlfwHYN9ZJ/U+4uidDiXLxewTX5pjlXqzZPeaF643qlir1TwwfvI0SMrOQ4luv5R4p9/f8HU+6Oood3ZTOMATVut1bT9gdLKE87Lb/kevMU1Of322FiqKEYlbebXwEAAP//P1vjFg==" } diff --git a/metricbeat/module/couchbase/fields.go b/metricbeat/module/couchbase/fields.go index 5168ad70677..8ddf5d937fd 100644 --- a/metricbeat/module/couchbase/fields.go +++ b/metricbeat/module/couchbase/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetCouchbase returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/couchbase. +// This is the base64 encoded gzipped contents of module/couchbase. func AssetCouchbase() string { return "eJzMmVFv28YPwN/zKYgAfyAF/tXe/TCg6DB0DymKLnsaBpW+o61bJJ12pOK6n37gnWTLruXYiWpPD3mwFPLHI4/k8d7CI61nYHxrijky3QCIk5JmcPu+/+32BsASm+Aacb6ewc83AAD3JMEZBuPLkoyQhUXwFWz+DZjCEwXObgC48EFy4+uFW85ggWXUFKgkZJrBEvUbEnH1kmfw5y1zeft/uC1Emtu/bgAWjkrLs6j3LdRY0S6zPrJuVFTwbdP9cgBany+b//wCxteCrmaQgqDqLJICBVYUCNgEbL6zLOtEDamGZPPWPJJsfj6EdgRPn+0iJlk9Wjb4anf1+mefacilf3de9GSPtF75YPfeHeHT5yNWBH4RVy5RZge1qorptD6sm5O0WhTMWiabzddCvCdn4UOFMoND7xJc6evleWS/u2+RrGUKUT2snBSu7jC5p+aGjFs4ssCCQinaMBAEYmepFnA1fH53P2KX48dsQWKKycA/ttWcguKpcOiEH1F/0WV9V/m2lg2d6oa7qODNYcSKKh/W14JM2hPmfD0I0+PQ/7ReMAtYXQH587v7F/K2TFljZIS2oWCo3n+beNlgSTZflB73P3iG+1OSikvaYb9b+ABoxD0R+PnfZITfAC41t0u0KhWfNqidyUDWDXv3vxELfcN5QyFnMpPvM99QQP2MdZGAyfjaHsZwQlVu1FWTU6hoBmT2xqHWb81XBxPrpuSWLQuFaSpbJ2ya0lZYmy0C0cW2z6+BCAoMFmzQmOMGDWnmjrHWmXZ0Cyly2kbiBcuLkX/YQkf1ENWrqvPYL0v9ECG/W3B8QlfivCQQfx5+rA9PWLaXi5kP+/TDrHse+Hydxw7nP4Eem51zQr/Cr3nKMZOmtnv82uf2KLY/UhwrYa629DXveoZqPhVJ6gLS/uooNjvsN1UZT0fOENzd60odLbMXpds48BiXNirD3HXZfaRF/8Wpax9da3zt7TXod7Z/G4L2/opy1l7a2nOFjDZiywu4L+8GrF7mhlFrrlEQ1QE7NfDktb9StLwoTq5S8vZpT6pym1GLtzRNmxwjcZIe2VQ2X9L054hlrLdVhbXlw4rVmMx6w5me4XM9eF1rfrDbvcRlBiU7HoRDC1Dwshb8ooHH3XRpQLx/csMUKycYwg2Kw/IZW147CEvH8LRvtER3WuHJ0eqk9d5gHg2aHxIYZ7PGD69BmgLiXM6LOv50xKbNW3Gl+xanI3lAOTRgeu0I6aEgeP/pDxioAlU1Pgzq2oI8jkxS1Zw8jfatR9JxAkbsNybtJuoTZkOxSTtIR00+X+ZpfLw/1Z92Ng0NBc20ZIFdbSjN1eN1E6yQgQWD0MhEbUmSF06mn55rHVTBI0d3zzLtBYyGcS+1v1wY905lbH/MxbL0Rr16nfm4QQ2QflK+gXlmZLDFDxRdfZj+BxD2+k66dhgdQk52taAaNkfe5yt+xzV+OHldyui9uD+HOxls9J7mldONYev+PIxvps8Hu1P+Ll/5+vt75F0UXmHzw9ylwlMveeK+izST+2gb1Alo29EchWkbcRVl013JPLiKwLbB1UtYFc5sS1ysI67eOhHumMwI19M8D9SUzmCuJTrV58njKUr9yXrTVlQLDy+Oo27Obv4NAAD//w/9DiA=" } diff --git a/metricbeat/module/couchdb/fields.go b/metricbeat/module/couchdb/fields.go index a6fc61d4264..5c0e6f2d20b 100644 --- a/metricbeat/module/couchdb/fields.go +++ b/metricbeat/module/couchdb/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetCouchdb returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/couchdb. +// This is the base64 encoded gzipped contents of module/couchdb. func AssetCouchdb() string { return "eJy8mEtv6jgUx/d8iiP2lcJrURaVWkrb0fSBplSjWUXGPhCriZ2xnSLup79yIJAUaK5b2Vkm9vn/ch5+nAt4x80YqCxowhYdAMNNimPoTuyb25tuB4ChpornhksxhqsOAFTjIZOsSLEDoDBFonEMK9IBWHJMmR6XQy9AkAzrEvYxm9wOVrLId28aKpPKPhrFqd4NqZutm9aoPlDtX5+yfqRwVfsAMJHCEC407H57ZxK0IUbXRjZ/s3o+c9XZEmNy1vhyDq8F0T4P8/msZOLaHNzyFUYd5YPjOlZI2OeJB6ZUitWJjy1Y5fNcZAtUIJelDjR1zjItivQ9Vvh/gdr4wDpQWSU4UjoLRlOOwuiKjYtVTBMiVuiXcicLS6mASmG4KGSh4Uj7LLfBLJeKqE3sNdwH4r2gU9yDhLwsl69D3ijTKthxhiaRR377WdnubMNp2221O3mZ/effV1bFoUYepte3/qGsigPU7OV17h/KqjhA3U4fp/Opf6ytjgPY/TSAs+6nLr6avYWI31sbUnNlsJtuoWMq2dEG8PPdvNBQGv7+1t7tR1HXv9f6UQQvf4NCnUuh/2Q76vajXhCwHkwUEoPMka4fhK4P15Ri7oo3COK8QdSDJ/mBDGaoMiJQmHTjyDkMwjmEZ2ngSTK+5I6uHAYpkGEUwQ1h8M9us3cjDBHsYdSDN0EKk0jFfzk7cRAEcQB3Ui04Yygc+bzk4THgNhHvZCFcHTgK4sARPJXHzBLzOk3l2jnSl0FAL+3Ve5ly6lgrvRDr9rDXh5lCKgXjdhrcEZ46OnIUZN0ZRRH8JQwqQVJ43fYupkpJ1cZ6ujvTpPzG0abqD337QMOIIQuiMV4rbjzfuQ3PUAPZa8KaaNheuFl7hGWOIq6m+gW1UnAsdRbNrvIxJTTBOOPaN51VQ2E4JWWtlLrwSfcsanXztrHwQPmIYmWSknJ/EedCc4b7jt+am0QW9nRBE/4vLtqZ9zkaoMlSpaikRYbClClqZWGpZFbLXaekSLjnBszJlGiofl1WUsdLnnrOW6uwnyHVoQecEF1idH4HAAD//3gQco4=" } diff --git a/metricbeat/module/docker/fields.go b/metricbeat/module/docker/fields.go index 89cd5fc99a9..eafb7a969e9 100644 --- a/metricbeat/module/docker/fields.go +++ b/metricbeat/module/docker/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetDocker returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/docker. +// This is the base64 encoded gzipped contents of module/docker. func AssetDocker() string { return "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" } diff --git a/metricbeat/module/dropwizard/fields.go b/metricbeat/module/dropwizard/fields.go index 3143c53ee41..5325527e89e 100644 --- a/metricbeat/module/dropwizard/fields.go +++ b/metricbeat/module/dropwizard/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetDropwizard returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/dropwizard. +// This is the base64 encoded gzipped contents of module/dropwizard. func AssetDropwizard() string { return "eJxsjk2KAjEQhfc5xSPr6TlAFrOaG7gUkZBUuoPpJFSVSHt6aRX/sJaveO/7BhxocYjc+imfPUcDaNZCDvb/EVoDRJLAuWtu1eHPAMBGvQpCK4WCUkTiNuPZ+jUAUyEv5DB6Awip5jqKw9aKFPsDO6l2u1t/U2Pdh1ZTHh2SL0IGSJlKFHfFDah+pg/Z9XTpK4Hbsd+TL7LvW7e9V7tLAAAA///zhlJc" } diff --git a/metricbeat/module/elasticsearch/fields.go b/metricbeat/module/elasticsearch/fields.go index b08f0c8122c..aadc37a05c3 100644 --- a/metricbeat/module/elasticsearch/fields.go +++ b/metricbeat/module/elasticsearch/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetElasticsearch returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/elasticsearch. +// This is the base64 encoded gzipped contents of module/elasticsearch. func AssetElasticsearch() string { return "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" } diff --git a/metricbeat/module/envoyproxy/fields.go b/metricbeat/module/envoyproxy/fields.go index 06afeacdf12..ab451a5ef5e 100644 --- a/metricbeat/module/envoyproxy/fields.go +++ b/metricbeat/module/envoyproxy/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetEnvoyproxy returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/envoyproxy. +// This is the base64 encoded gzipped contents of module/envoyproxy. func AssetEnvoyproxy() string { return "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" } diff --git a/metricbeat/module/etcd/fields.go b/metricbeat/module/etcd/fields.go index b2d735b9908..2873b9f1681 100644 --- a/metricbeat/module/etcd/fields.go +++ b/metricbeat/module/etcd/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetEtcd returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/etcd. +// This is the base64 encoded gzipped contents of module/etcd. func AssetEtcd() string { return "eJzMmU9v28gOwO/+FEQur+/h1e/wbjkssJsWiwKbomiL9rBYqPQMZc1mNKMOKbvup1+M/jiy9SdO5LjVoQ0sDfkbkuKQ1Eu4o901kCi9ABAjlq7h6rUofbUA0MQqmEKMd9fwywIAqifh1uvS0gIgkCVkuoY1LgCYRIxb8zX8ecVsr/4LV5lIcfXXAiA1ZDVfVzJegsOc9lrjJbsiSgm+LJpfBnTH60tc9AWUd4LGMbCgGBajGCRDgS0FgkCoIQ0+h9f3KroEXQosTLKhwMa7/b0W6I52Wx905/cRrHhFXfDruzfQSIPUB8hJQoQL8X/aoF0segSWUFPoKe9a4wHVN605KvfU8jqmWXYePnRZex0bp4uXemv9lgIvlS+d8MFDY7APAMfrY0bgynxFAXwKKRpLGtBp4FIpYk5LC+8xFXj/7gYCfS2J5WArY+BT8MtGeG9BuxHjhNYH3hjczhTj6TBx03NJGsONU/T/GGayKOTU7lzuhUYeiAdClUFBFMA4kIxA2ZKlt7fTndnIXuKGAq5p1Ias0JJOUutRHiFWlSGQ66+YKTbHbyYv87OLNW5S7FggHUvcC2RBpzHoV7QxKIeJ8UTi0dwGE8kVjqPqzauYGlBJifZYVC+NMoXNvDT6oZKwz9nVCRKjtcrsn/4P/2vvkNOFNwcBss+rK5JTM2uGnEwYaLWT48h+4J37nJFkFADbM4C+GRYee+smPJaoDN2amkw1iGe9Wz8O7+0+2zd4jRJgIgcop0MWwRee0XKifJ4bEdLPRKq8Y3Jc8r1OqHWSPpWxIKeNW1+QsNF4KmB9hlyQr1Y4jbcOhUpYMDyfc5mcwHr61OwhZej081krkCKzIf0QVoukDd/NyXmvDN9dLuPlG6USvUrEC9qEzXdaxiw3XFMOmDH1IUepU+PxogdM/MF8p8rn4gNp0CgYU87tp5ubaadv0SYp75xKdBmq43DpeLkq1R3J8j+D5H71N6nj2Kh/TJ4aIn805VTsKrbBxE4LMIudjvVrjlXWQSg8YivnDOTPwQh1ufrCH0HGvarmbFxtddpVMUi2QnVHTjcHzc8XAw3f/iw9KRAmNnXOaPitZavUnBwME3Tni4hjtsmA2Ocvyn3Yzcm3t5WEy2XctU9yyllQOEFrvbpYwm12WilFIV3LAOSYhW8p/xCZ4Hc/bm1HsvVh1vH2thZxOXsra8hJUhcv5ORi5q7qhaqgqe1cHbHTWaDL2tYdl+XdVztDzPeNnU3nxMDhdCxKO9tszBy3rye3tj2DxKAsnflaEhhNTkxqKFSZPt7JKRaIp3RuxqV+Oa/t7jvL6PjSVu1ZPRppO7gjrhGcqooXkx+3szOIIkuUCNuMYndrGJzXBFushsKh256NUJXFeZEwj8dbZagIFgkbK2XIsIo97tgMo0sY/z2nnQw3XvoXH8oe1B5IbZZYxAayaT4mKoLhydIDRPcz31rNvsnpeDEarAheEfOIHyvOFTq9NVqygL1RyYMjtZMx69xUUAAm5Z3ucBpuUlisxF60gzTwzu7+PY5d3K2fFXhv0KczD8IzOf28sTG0ieq7zmFo8PFw9oDwZ4gKrgcw8KLJAJV1l/AxPrNBW1YPlU5Tahxp8A7YuLVtE307BTv63nGwzR8eReff4/Bmf+zhMcY0Yvkn8ZCpxratHcN+JN+vhcQHmlMMfcyoFtL9dGqcsmV0qqsLOuMd4MqX9UTWF1Q3YIPvYjsOm1VHrWn009x4NjlYPPApbXjlyAs1Qz/P16/JktCTCZrl8xjKQuMMhmb5PAYVaA5Ds3wmg88LDIRO8xaLp7McijkX08xI6Ql6PNegXPpWmECPKAcGpWxRVEZhelv/BAAA//+7EUCM" } diff --git a/metricbeat/module/golang/fields.go b/metricbeat/module/golang/fields.go index 3a81eac5023..0a3fab7f077 100644 --- a/metricbeat/module/golang/fields.go +++ b/metricbeat/module/golang/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetGolang returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/golang. +// This is the base64 encoded gzipped contents of module/golang. func AssetGolang() string { return "eJzkV9tu4zYQffdXHPgpiyb+AANdYOui7kObFkhbFCgKYyyNJDa8CCRlW39fkJIcx5G0cjaBe9GTTQkz55y5cOYOj1wvkRtJOp8BXnjJS8zX8WA+A1J2iRWlF0Yv8XEGAM1LKJNWkmeAK4z1m8ToTORLZCRdOLUsmRwvkVP4hr0XOndL/DF3Ts5vMS+8L+d/zoBMsEzdMtq+gybFJ4jC4+syHFlTle1JD6rndk5t8aHckT0e99kbtNk8Lyw8J9c95wBOQSQqlULzs3cdkkeu98amZ+9G8ITnl4I7ozAZfCEc1galNbklBefJeuyFLxYvBCmYyi+RI7g+cRXMQejMWEXh8yCXcZxiW7fKLf710vUCy5NeTOdKTkC0JrulnJEYKTnxxsJVSpGtF2ef9ul0CskbT3JTUuXO9RrHNwFjI12wj/UK0QXSyjYhNzu2kCJjL1QUtbQmYefO8Y9xOOWhXb//loI0x+bwGhYAvu2QC437HpjPBU1Mpf2goANopuupK7VlG2Rbr7Anh4LKkjWnw7g0H/wmTzZSKHExsqZUl9jWnvuEnoL8ng++y9eg415I2eLGvmCN75nKT1KaBB+bEiMVVBymlJTVJrMUzQ0yyqShPr5TIH/XGg9Cr37+FTFXq7ZTrVfDyN6vnn4g51+Wkwu/hM7hC4YyWngT/5ZshelJimk1NZTEeMOywio4aTP5rEccOQnXJc4IoyfcrlKjqIf0vwT2UF+7FPN4JDClw2FKOC7hNqHbnQJTdHh3uX+kw39X7IvUpt2Q17dT+9OObZgu/n+K945trnaez1vKq0e3cM1109rtvhBJ0Vyr8R43W09Cc4rMGtU6ftVId4UbvmmJDZc+HrhxlQqd/vfD4aF22LI0+w/Dl2hn4wpUfhMU4/RQu9sRRou4ymxc7fB181OkkvFV+1tXjofpOU/J4xW4fRPpdFNMRAEKYxd5Y29BOg1ThOMLs/KJWLuwXSNuDbcOALyJA9FPD58p8JZ+GKTetMpP7L52P1PRRp8cX7pK3B93iNbHcEQzy70ReTsI0cNYK/iLE/8eEM73qTZknHY+P7PpXS3Jn4De8I41RCNi+gG+sKbKC1P5mP3dkj1M5GjqH0AmtB9tPGr2DR/cOFIMcmh2Q9qaHY/cGqEDX42H0NE/XEl6JHPCVrm7Kkpt9N0R6WL2dwAAAP//qwVEDQ==" } diff --git a/metricbeat/module/graphite/fields.go b/metricbeat/module/graphite/fields.go index a4845943868..a8deeafd0d3 100644 --- a/metricbeat/module/graphite/fields.go +++ b/metricbeat/module/graphite/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetGraphite returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/graphite. +// This is the base64 encoded gzipped contents of module/graphite. func AssetGraphite() string { return "eJx8j80OwiAcw+88RbP7XoCDN+PJhyBSJxkbBJi6tzf7zJjTHvunv5YSNXuJKij/MIkCSCZZShSX2SoEoBlvwfhkXCtxEgDWBK5Od3YIBlqqSIlKCeBuaHWU49sSrWqYtQxKvR9N1/nZOejJSVtaZHgyrPYR7ydz0hch/8Ki/YDtCL5V4y2z27KkZv9yQe9uf/YMOk/AqVR8AgAA///94G6N" } diff --git a/metricbeat/module/haproxy/fields.go b/metricbeat/module/haproxy/fields.go index 531515409dc..f5cd629e773 100644 --- a/metricbeat/module/haproxy/fields.go +++ b/metricbeat/module/haproxy/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetHaproxy returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/haproxy. +// This is the base64 encoded gzipped contents of module/haproxy. func AssetHaproxy() string { return "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" } diff --git a/metricbeat/module/http/fields.go b/metricbeat/module/http/fields.go index d8c94f37f07..ce8ab7e8223 100644 --- a/metricbeat/module/http/fields.go +++ b/metricbeat/module/http/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetHttp returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/http. +// This is the base64 encoded gzipped contents of module/http. func AssetHttp() string { return "eJzMlDFv6jAUhff8iqPMD+kNTBne/KanN7BVHdz4AIbETn1vaPn3lYEgkhokSofe0Tbf+Y6MM8OW+wpr1a4A1GnDCuXfxeJ/WQCWUkfXqQu+wp8CANIW2mD7hgUQ2dAIK6xMAQhVnV9JhadSpCl/oUzg8rkAlo6NlerAmMGblufUNLrvEiWGfljJZI8pl6TI156i5/Uc8Cp0mEO1EwnOL0NsTTp5cWyaP2pDYxllAj16hJcNa51s3ZQBFmselU5gCL1mmksXvPBbqh9RP7F7ZE23o82m18EyG73l/i1E+8VsUaO95Oh8N22XXsr89zxr1K2jkXuczsR/QbEMvX9QeiIweG1kdKV3/1PS79FSo6uFl3c6/hAMc+25CuOO8RGRT4TbAh8BAAD//zeEPwk=" } diff --git a/metricbeat/module/jolokia/fields.go b/metricbeat/module/jolokia/fields.go index 241a0320e41..574411f4107 100644 --- a/metricbeat/module/jolokia/fields.go +++ b/metricbeat/module/jolokia/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetJolokia returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/jolokia. +// This is the base64 encoded gzipped contents of module/jolokia. func AssetJolokia() string { return "eJx8kFFOwzAQRP99ipG/6QX8wQG4AkLISjbptrbX8m5RensUkoARqPM5uzN6mhOudA+4SJIrRwcYW6IA/7I53gEj6dC4GksJeHYAsF+RZbwlcoCepdn7IGXiOWCKSVe3UaKoFDCv1UpmXGYNePWqyT/Bn82qf3PAxJRGDV/lJ5SYqYdaZfe6FjW51d35h2vTHsQgxSIXRSZrPChoqaI04oPj91Ocqdge7ik2km2dvHTtf0EewvyMdcnLQULWffxe6dDB8hkAAP//TGJ6CQ==" } diff --git a/metricbeat/module/kafka/fields.go b/metricbeat/module/kafka/fields.go index 7920163da93..72ee2cdc60c 100644 --- a/metricbeat/module/kafka/fields.go +++ b/metricbeat/module/kafka/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetKafka returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/kafka. +// This is the base64 encoded gzipped contents of module/kafka. func AssetKafka() string { return "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" } diff --git a/metricbeat/module/kibana/fields.go b/metricbeat/module/kibana/fields.go index eeddf8d856a..fed14792f3b 100644 --- a/metricbeat/module/kibana/fields.go +++ b/metricbeat/module/kibana/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetKibana returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/kibana. +// This is the base64 encoded gzipped contents of module/kibana. func AssetKibana() string { return "eJzMmM1u4zYQx+9+ioEve0n0AD4UKNoCLYoExbZBD0VhjMWxxYYiVc7Iifv0BSnJkWXKllsLuzoEiT7+85vhfJB5hFc6rOBVb9DiAkC0GFrB8ud4Y7kAUMS515VoZ1fwzQIAoHkIpVO1oQUAF87LOnd2q3cr2KLhcNeTIWRawS4IM4lou+MV/LFkNssHWBYi1fLPBcBWk1G8itqPYLGkHlG45FAFHe/qqr2ToDrV6WuxoPDxbkpuVLK5WoejDqBV4Gv7KLokKEm8zjnrvX7qd3cN2fp8da3VyYMOEY1GHjypUIoVMPm9zik7+7DUO4+ND+JrGjy94GPPT21Z0OYELy8/fZ8kDj+TxK90eHN+CHWj2TP1zqy2it7vZ/cZSwK3be1/YtBWyFs0CUMdQuFYsnndDyZGQyAeLVeh3lApTzxMj6mJk/76HtkTLJD/xEc/YsUE5KRDe/KsnZ3sxjSItOppCNLvXArBsaFYrLhwkoTeOGcIh7JXsH8vSAryIAV1LmxqbRRoBjzaa+6lkQSlTifDPZIyLCehkSJlqGPInc1r78nGYWApD+JpJuPs7sZircsN+VCuudFkBXomoERFIC6Gr8m/DJ6dEEiBAhvv3pg8Q44WmKyCsjaiK0PAOvyKllzNJ4riwNPfNbH0Xo7KgMwU5oCAszk9xPSWgg5R3tNjzQTEghujuSDVl82Scau8y8cqeTilJkTql0aum02D56lB9IFC+7B6xrlqrcjgISuH33dgnKMhtd4ah8M6uEoI8EOwA8EORDugLZTaGM2UO6uGRju8kkrnD1lBOIzJpXhN4OliFpRHAjcWul5vdoIm2xyEzr+9kPhR2vkSZQVjH191AOC3YL1xAI1xOQqpkMZtdoUIp9WP2xAm9cXofwzcgQA2h+nIrP+htdGlli8G/oTvGTijgCvMKRKdxv/ZKcr+4s6phwkLUYW9Zar0rrgzAfclaoc22gvyaOV1RG0rvFeHAvjc9tbc1VZu6lQfUEpz21rnCNTHvGmd52aYvJGnnmkaztVBO5iBrCl0e8Y3snJcOcu0Dst+r/X73IpCEL1t0HyQ4X43T4p/uyePOzq6HimvDJjeiMH3ebCe8F2XdTkRa3Rfd/Ph9deo0C7H/zqnzn/qS++PvtrzcQBLI7dni6yp0nkON20LyOY65MS8yVyoJmOy8Of9V79Vj8ZGVj/dXf5z73pq5M7rASb0rEknHLhDs/iusdIzOH6MgPNBPY6U3h9PYupmdlgrzTL4t1d3jYUQJk9uuBa/ibyQmJX9wX05ojBhks9JegL3bwAAAP//xlKE7A==" } diff --git a/metricbeat/module/kubernetes/fields.go b/metricbeat/module/kubernetes/fields.go index 06292b72a57..dad0ab4252e 100644 --- a/metricbeat/module/kubernetes/fields.go +++ b/metricbeat/module/kubernetes/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetKubernetes returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/kubernetes. +// This is the base64 encoded gzipped contents of module/kubernetes. func AssetKubernetes() string { return "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" } diff --git a/metricbeat/module/kvm/fields.go b/metricbeat/module/kvm/fields.go index 7dd21356f95..456017804b4 100644 --- a/metricbeat/module/kvm/fields.go +++ b/metricbeat/module/kvm/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetKvm returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/kvm. +// This is the base64 encoded gzipped contents of module/kvm. func AssetKvm() string { return "eJyskDFuwzAMRXed4iN7LqChU9ceQq3YQLBoGrLsQrcvnEZFotByh3Lw4G+//8gzBioWw8oGyCFHsjgNK58MkCiSm8ninbIzgKf5I4UpBxktXgyA7T+w+CWSAT4DRT/ba3DG6JgqeJtcJrK4JFmm2xuF9wi5B3lhJp6zy7+Rxtzl3iKN0u5Zp1V5FGogPaFDrZ95I5ZUdLDucu+zPZW4Og1UviR59YtDs8Zur6uqrC4uPZco4+V/RLSmahGel+3UH1a/CrswPlO79+/f/q+dV/R3AAAA///hct15" } diff --git a/metricbeat/module/logstash/fields.go b/metricbeat/module/logstash/fields.go index a1f6963e9c0..bf8f9746c58 100644 --- a/metricbeat/module/logstash/fields.go +++ b/metricbeat/module/logstash/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetLogstash returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/logstash. +// This is the base64 encoded gzipped contents of module/logstash. func AssetLogstash() string { return "eJyslM2OmzAQgO88xYhzwwNw6KmtmqpV95TLarWyYALeGA/yDKzy9isCZMEx+d055OCJP3/jGbOCHe5TMFSwKC4jANFiMIX477AURwA5cuZ0LZpsCt8jAIAxDRXljcEIwKFBxZhCoSIARhFtC07hOWY28TeIS5E6fokAthpNzumBswKrKpwZdCH7uiM5auphJeAwJ01plnI8LoZoi8Q+vP3z0sbwD58KlMQyS4wSymjFXqZWUvZbku6nI3j/qHThVC8qrvGzZwrp4jexwAl0NG3RsSZ7oyyja3WGSXj3Q7rHydoE2KP1W1sFjf0eX3Hen80/WNsteYlQdy/f26fJDvfv5PJA/oJPF6HSp4fXOgRebhocG1c7ypA5CRPON+5K+af+CFj/CL7LhEUJP/o6Xw8UqFCczjh56LFii1b8K7t7nn4eaOBXuSQxFdHL42TIFve1Y20zqrQthjIho8YKumTRghr/0/UVGv8bKegWja02gg6XB/1+l18D+sTlIwAA//9sYbU7" } diff --git a/metricbeat/module/memcached/fields.go b/metricbeat/module/memcached/fields.go index d9fa12ddab8..9e213961d65 100644 --- a/metricbeat/module/memcached/fields.go +++ b/metricbeat/module/memcached/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetMemcached returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/memcached. +// This is the base64 encoded gzipped contents of module/memcached. func AssetMemcached() string { return "eJzEVj2TGzcM7fUrMGpsz9j6ASrSJE2KpHJ/Q5FYiTmS2BCgZP37DMhdfcerS9a+be7EXT68B+CB/AKveFxDxGiN3aFbAIiXgGtY/jGuLRcADtlm34untIZfFgAAp/cQyZWACwDeUZYXS6nz2zV0JrCuZgxoGNewNQuAzmNwvK4QXyCZiNfh9ZFjr59nKv2w8iD+NdQlHIsRPq0+gvtXyPbcAlwrGJ/b8JcUeu+u1kcSgdL25sV3eOjza8kZk0CfySIz/P4bUAeyw4sCiOHX1WnnQ0KlFx9xxWhn4nWOzpj3mMcI36chu4zG8Uwc/ixxg7nlo+JCYXSwOdb82CFzd0yHVE5QtZQSWg3NqwFqdtrUY7oMBEIgO893nD+rt0pwsMGqjU3EO9y9CQWBEpgQhn0MrmSftpAoRxM0YDYa6g3ihcSE+aQ35Vys1qArYYwFRgRjL+ckDOVinyyCF9gZhg1iUn9mQTehYYuy2nmZv9kuuC+3KEuwFKNJjuFjLRpo1E8D78q19J/B+b13tXrxDnvo2KWN7qUitlIKqYjWzCNwNjJlMhUePTPOL70zPqAbZGf8uyCLFsWawgiJZKfNdjAMQ/t2lFsxX/F4h1vf4fhp06x7hQgoTNXXRrfa4vymvKlpRot+r05s9RyaspVVJYOlkkR1+zqY73UeMONl11DWfU/I4x8hj6/kqR5vz/KargluOmxXm6M832Gdzh/BNTzaNcH9qw4gSCcFFeJcly5TrH2UUA6UX5ubTvNjQsohexFM762GMbk2+P6jDi8Yf9xBVdHHEzUcwadLah8Guz9F8U3Hyf9J9C17FsroAJt96/90JWMFX/WHZ0gEy2i++Vhi3XyHXS0/julNETBtCR34ZLPeFus9RIMdIeGhwoxha/bwqZzh3rdj+OdmjDZ/oRU1WaQrj7VzSAi6jAil17s75WOd86PM26hwOiDO15qM7a+XDwxjqgcsRqnj9GPw0ctLNN+qlk8TqaofvdUB86Srefhsj3oN1ZSo2noy1NZ/hn/V/B7sL69cnvUOSQesM0krp2K0e80WV4t/AgAA//8M190C" } diff --git a/metricbeat/module/mongodb/fields.go b/metricbeat/module/mongodb/fields.go index ae38c9b8724..8ee97315e84 100644 --- a/metricbeat/module/mongodb/fields.go +++ b/metricbeat/module/mongodb/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetMongodb returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/mongodb. +// This is the base64 encoded gzipped contents of module/mongodb. func AssetMongodb() string { return "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" } diff --git a/metricbeat/module/munin/fields.go b/metricbeat/module/munin/fields.go index 4201760b398..0d105e45fc3 100644 --- a/metricbeat/module/munin/fields.go +++ b/metricbeat/module/munin/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetMunin returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/munin. +// This is the base64 encoded gzipped contents of module/munin. func AssetMunin() string { return "eJx8kUFuwyAURPc+xSibSJHiA7DoDdorRNhMKA0GBN9qffvKhlaOanX5Z/5oHp8rHlwUpjm40AHixFPh9LrOpw4wLGN2SVwMCi8dAGweQjTERMluLOBXilmYOyDTUxcqWN0Bd0dvitpyVwQ9sXX1LdpfNg+QJVEhDh8cpUl1uFXHxHnw/OvcJp2SC7atnS/ntnNAvtHvkAsNhgUayc/WBcQ7dMWrz9OWQfoD+Lrfr9IT/oPLZ8zmf4I3PXGtknf+FI/Re47igl3V8nvYg+6nPpvjnJqyP3VN7b/iOwAA//84wJVl" } diff --git a/metricbeat/module/mysql/fields.go b/metricbeat/module/mysql/fields.go index 166d37fe757..283f3e4df4b 100644 --- a/metricbeat/module/mysql/fields.go +++ b/metricbeat/module/mysql/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetMysql returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/mysql. +// This is the base64 encoded gzipped contents of module/mysql. func AssetMysql() string { return "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" } diff --git a/metricbeat/module/nats/fields.go b/metricbeat/module/nats/fields.go index 215ffc0c5d2..39228af4d46 100644 --- a/metricbeat/module/nats/fields.go +++ b/metricbeat/module/nats/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetNats returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/nats. +// This is the base64 encoded gzipped contents of module/nats. func AssetNats() string { return "eJzUmM+S2zYMxu9+CkxO7SH7AD50ptNecugeuul5Q1OwzIlIKADojfP0HUqWLdGSLTdKtqujKeP76QP4B3wPn/GwhmBUVgDqtMI1vHs0Ku9WAAWKZVero7CG31YA0LwJf1ERK1wBMFZoBNdQmhXA1mFVyLp57z0E4/EUOT16qNObTLE+/jISPz2f0p8+gaWgxgUBUaNO1FkB3RmFF2QERlPAlsnD41miT9CnEOQ98oMrTiMdzmc8vBD3f5+ASs/HHR5DwYc/p0TUebyQKYziPI2nNn6KArQFj8rOgmU06e0LUUshoE1DciHat/qG6h+d101+z0FTho1iceToawxz3z15BvqsSmqqwUhH6oJiiZyNXeHtshGi3yAno2xkxqDVAYxVt0ewlcOgcuEYU1Rc0Kwm3tvxibF0oshY5E6c6liNLujP3vCi7sQ6m2FnxopCmQ1sib3RNRSRhxNotnU1sqMC9Dz3nUCs4RdB++sooUf/sDkMi2wW5NifZhAeSz/pEh8giimbtePx949PUDNZFBkFtcQTkAsUWkWls6ZqRRr7+jzAMQhk+Thx1XGUSqypsHjeVmR0wsIa2WLIR+8w0dZx4KBcdbCZqs/jizBcS/ldVjYq1aHdBbC4WNr6RIyepkpv4eVjRKmjcPlEG19EZsobTzFoknfBknehTBuqyUtgZNGAwbSUlNfcGriWphmAVyAnFTuksQl/k+faavF9vJcRO1CK+ZRaKKEUtaT/e0JPkG8koSfe6YRKRS9p3ZLokX/MqpUk4CTRO6BRaLaCUa6dal5P31VpKV537Djtie0+fme1MX55zo9GtxFnYHaojF8iivb7nbFimcDso0Z2o+O3QGfCZsBQOFF2m9g0DBTAU3BKnArwn78/PE3EuPYhkJ/Xv02+dmNu3flZcFHFO6fpnHLE6H/btMvD9uz12RuKe9H3hl+fPEHcCy5xI69P3lDci85E+T7788kTxBR43+NOZcFecRD27fTUxxuHKVP6x2JBvthBltlu29hANbadroALmaGVE51sGvbz+9U7O4YU+79xeaN294O41HkUMK1G6um3FEORDmNgBnzjLaqxO3wQ9238JmKRTktipSCojWOpgW5Er+DsnD7z8KYRfkrznES7ZIFhhA2mlYPTxMU9Hi9qb+FvTaCoD958XdZTb746Hz208dvTXwGbw1wgs88L7XipS3FT5WbP6X72yKbEKZ5/AwAA//9SJQAz" } diff --git a/metricbeat/module/nginx/fields.go b/metricbeat/module/nginx/fields.go index 3cd6e0856ee..e848db4b940 100644 --- a/metricbeat/module/nginx/fields.go +++ b/metricbeat/module/nginx/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetNginx returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/nginx. +// This is the base64 encoded gzipped contents of module/nginx. func AssetNginx() string { return "eJzElM9u2zAMxu9+ig8+r3kAH/YIOw3YYRhSVaJtIYrkkXTSvP0gR26cwC1SoMF0skWJ349/xCfs6NQgdj6+VoB6DdSg/pH/6wpwJJb9oD7FBt8rAJhsEOIDMUSNjoI9KXsrsCkEskoOLac9DoZ9yubkxkCyqQDpE+vWptj6rkFrglAFMAUyQg06k8+Qqo+dNPhdi4T6G+pedaj/VEDrKThpJpInRLOnC31eehqyG07jUHZWQsjrebr1DJuiGh8F2tNbHNobxZGYIJbNMMczXdkUF0uSJY3o+HJOy5tpDesDtAnv4uZzjPlI7F63OWPb7GR79rI9F2Gu2GA62iwUryswr9sYl3H2STR/XRnnSHd0OiZ2N7YP4r101ux3s6pqrPrDumZIsfuc4M+eYEdmioo47l+IkdoiARt83rcpRrLZg8BHG0bnY4dfxucWXVrf47U0qHwhsCY14Qo3K5BbAV5H6k10gW5r85VIReFuIsdpGB5KVBTuJmL6O5I8tG4FZVZa5yjd+dB+vwuEyeTGfyzI4qkd+zzZzhPByyw/TbcCip6MI17HPfL0Pv8XbpEvuDKkKIQXY3fQNG2ec/4Ou3k8u3dhdcIVbbSJYeZMb6p/AQAA//9pr1BQ" } diff --git a/metricbeat/module/php_fpm/fields.go b/metricbeat/module/php_fpm/fields.go index 7293bc9e2e2..1150b149093 100644 --- a/metricbeat/module/php_fpm/fields.go +++ b/metricbeat/module/php_fpm/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetPhpFpm returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/php_fpm. +// This is the base64 encoded gzipped contents of module/php_fpm. func AssetPhpFpm() string { return "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" } diff --git a/metricbeat/module/postgresql/fields.go b/metricbeat/module/postgresql/fields.go index 065acbda723..75826021f79 100644 --- a/metricbeat/module/postgresql/fields.go +++ b/metricbeat/module/postgresql/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetPostgresql returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/postgresql. +// This is the base64 encoded gzipped contents of module/postgresql. func AssetPostgresql() string { return "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" } diff --git a/metricbeat/module/prometheus/fields.go b/metricbeat/module/prometheus/fields.go index d8b0eb230f4..e93b578c7b7 100644 --- a/metricbeat/module/prometheus/fields.go +++ b/metricbeat/module/prometheus/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetPrometheus returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/prometheus. +// This is the base64 encoded gzipped contents of module/prometheus. func AssetPrometheus() string { return "eJzMkkGO2zAMRfc+xYe7G2TmAF70BAXaosuiCBT7O1ZHllSSniC3LxzHGU0yQJF2Uy75RfLxi4945rFBljTSBk5aAeYtsEH95ZKsK6CjtuKz+RQbfKwA4Js5U2grLrNDL2mEw2sVGLucfLSnCtAhiW3bFHu/b9C7oKwAYaBTNti7+Q3NfNxrg++1aqg3qAezXP+ogN4zdNqc5j4iupFX1HPYMc+9JE35nCnL5viAz9JR4BV+zEnMRcNA4QbB7RgUBx8CRmftgN6L2gY2EEI1OCG6NO0CL/1WlKX46eEirDBp95OtFeklsV3UZx4PSbpCfsfmNQpnR5r49jz1BmZR76e52u2Nuh1dzj7uz0/rh/ovoW9of02U4//G+uLCdPr1Kdh627P89VPB//Z639nqZqfyNP8Ac2qwfiVLHy5jdzRX5K9vfUURjsm4PYg3/gvR0genPivYqzNn45TyQrmD9ncAAAD//1baTA8=" } diff --git a/metricbeat/module/rabbitmq/fields.go b/metricbeat/module/rabbitmq/fields.go index e737f5f5808..07ea6fba3fe 100644 --- a/metricbeat/module/rabbitmq/fields.go +++ b/metricbeat/module/rabbitmq/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetRabbitmq returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/rabbitmq. +// This is the base64 encoded gzipped contents of module/rabbitmq. func AssetRabbitmq() string { return "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" } diff --git a/metricbeat/module/redis/fields.go b/metricbeat/module/redis/fields.go index 5ec77a24318..ccdb515cba3 100644 --- a/metricbeat/module/redis/fields.go +++ b/metricbeat/module/redis/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetRedis returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/redis. +// This is the base64 encoded gzipped contents of module/redis. func AssetRedis() string { return "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" } diff --git a/metricbeat/module/system/fields.go b/metricbeat/module/system/fields.go index 472019559a2..28135f5fa95 100644 --- a/metricbeat/module/system/fields.go +++ b/metricbeat/module/system/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetSystem returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/system. +// This is the base64 encoded gzipped contents of module/system. func AssetSystem() string { return "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" } diff --git a/metricbeat/module/traefik/fields.go b/metricbeat/module/traefik/fields.go index d18ad7398e8..19bd3b052b9 100644 --- a/metricbeat/module/traefik/fields.go +++ b/metricbeat/module/traefik/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetTraefik returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/traefik. +// This is the base64 encoded gzipped contents of module/traefik. func AssetTraefik() string { return "eJy00bFu2zAQBuBdT/HDS4ECdncNBTx2aFEUzWxQ1ElmLPGIO8qI3z6gLDmyojhGgtzIo3if/lvjQKccUQxV7pAB0cWGcqyGk1UGlKRWXIiOfY6fGQD8P3chdCRRQhB+OuEHGjYlCtMYb0nQUhRnNQOEGjJKOWqTAZWjptS8f2kNb1qaClLFU0iXhbswnCwgPgJJNR0/JezJNHF/OV5S3JCk+n2eAy6icZ5KVMLtaPymwwRs//4C+TKw83Hy/XVIY821U3EXomtpo2Sv2qO8YV/PGjfwqR76B8HVJVjnNaYQ4TyULPtSFylCGtgrLULmEd4h+Te8N9vdrVSmHMvdVbbvBnMHKdWfri1IUkDjD89lLwZzrHf9frrXdz4v2R5JTE0XB/rNOY/WWeGlVU1tGk3sdGe5JN18f5PHxSPZpRzPjd0XxIlAMvCQeNlzAAAA//9iMTnZ" } diff --git a/metricbeat/module/uwsgi/fields.go b/metricbeat/module/uwsgi/fields.go index 120cb4a8a24..78247b12e18 100644 --- a/metricbeat/module/uwsgi/fields.go +++ b/metricbeat/module/uwsgi/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetUwsgi returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/uwsgi. +// This is the base64 encoded gzipped contents of module/uwsgi. func AssetUwsgi() string { return "eJzEl8tu6zYQhvd+ikFWLWAb7daLAgGSRRdtUzvnslNocWQTpkhlOLQiP/0BdfFVVowDGoc7yfT/f8PLzGgCG6xm4Eu3UiMAVqxxBg/188MIQKJLSRWsrJnBXyMAaOZCbqXXOAIg1CgczmAlRgCZQi3drJ44ASNyPIiHwVURppL1Rfumx+FU5ljKsWDv9q/79MI4ZepGr1MzasRpow45MqnUIbcYRzPPuY7Z2LLQU8J3j47dyZSOU1uzOvthACqM1yAKnSishZEa5YA/fqRYq8UluCJ76l2SYkyQyNKd4q8doMfhfBOEvA+HkJ+7F0pGcn0hm6JzoPp3vLS0QZpGs/tW633iFi+61q64KUiR1gfwwuNnzRcsGMFmrf4Y/gSVgWOlNey9wGB5OHyW10ilcgh/DJFGzgD/+nyJFEj3IA5pixKWFfBaudZ3CEmiZpH8IjAQGSN1D8qF7GyFRAnlGk24U+kaJfzz+D2ZP///5XnxuhiKJXqCe94LAgnlrmTX1n0tSGwUqSS13nAkgieyRYHysI5hAVWO1vMQi1MrI3S0ZcgVM0poVeEywF775N2jx+hHqqMohaovIltY4mD566jOO4QDzwar0tJ59rotSbWtwW/pGkUxhkJ4h+NAOYald9UYlNT4+2BacBGp5uiURMOwQIaF2uEUcswtVZB6IjSsK/CuuYqiy7DTkOGELkXlYIdkgamCt8mk+eeEsLDEb2Br17AJx41bb0xbt4u0818VsRf6Mh6ndgjChfOAMhyDO4ZD3hhlVkm4e5EreCsNF9IXxcMVojRR08u80fz8RvNHrG6JhHH7fLIbjFhsVwnFCvVxiyRWCGEZrXF4fb1TSxivc2qL299P172aKUnE5lCEi95Zvwx5d4VlWjeo0ZM1nzbpTRdwA01IqiqNXztqWciUxoalv1k/ZSHrGWNtzbwWg96Gq9/eZlnTFEUi+K/TuwHiDp9th83IhNKh1QsegwsR+4vtAiFYuNGPAAAA//9dpnuJ" } diff --git a/metricbeat/module/vsphere/fields.go b/metricbeat/module/vsphere/fields.go index 1490fb00f37..b0588e42a72 100644 --- a/metricbeat/module/vsphere/fields.go +++ b/metricbeat/module/vsphere/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetVsphere returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/vsphere. +// This is the base64 encoded gzipped contents of module/vsphere. func AssetVsphere() string { return "eJzMls1u2zoQhfd+ikH28QNocTe5SLtxWyBNtwYjjSzWooYgRwnUpy9ISoZ+6CapqLZcGLBInfNJ1JzhLZyxy+DZ6goN7gBYco0Z3Dw/+Cs3O4ACbW6kZklNBv/tAAD6WVBUtLW7zWCNwmIGJ7EDKCXWhc380ltohMKxhRvcabfYUKv7KxGXqdBYrBAsLNNFLi55VbafiohMn2MYc4wxivudTAwkZ+xeyBSzuV/wuPH/wLTUHQxL6/TTWd7LGm1nGRUshAfPXGiRS+72TCzq/VPHaKMENTWn99l/dYrgFYFK4AqjG+NGSUYJzmBpv+AsDWJSzHuDmJyytVgkpXy0WGxDqXNOyajR5NjwWyn75ZfZRSBUZHlNFszu/9sx8JEsX0+AXLdhU1T1I+Wm3H15BNnAYaY6tg31n843VP8bjH1Bp/P15fyKrUJFZqsiPXhxZx+Tfr06e7it8jgR3jYxvBauQX4hcz66f3Gw3yraT0EWprKX84803IpaibySzapzy1Wl96eWi729nD/n2uCaCU7M3M9GWTk0k1mWb5XR38I2wCHsw/W4poTf2Nz0s0YjWDYneAhHuH+taYxD9NSi5c2ilEr44AxWh5Zn9R/rhqihUJKkf/rXOukByd6rbwbpYcctYT1r3lomdQyhGSWkp++4OAyHi8cVlX3njSFi/Efb1s8AAAD//5A1D00=" } diff --git a/metricbeat/module/windows/fields.go b/metricbeat/module/windows/fields.go index 8b21143cdb4..2cf2c008578 100644 --- a/metricbeat/module/windows/fields.go +++ b/metricbeat/module/windows/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetWindows returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/windows. +// This is the base64 encoded gzipped contents of module/windows. func AssetWindows() string { return "eJysVl1v2zYUffevuMhLgSIx1vZpfiiQxcvmYUmLJoUxIIDFkFfWXShS5b20Y2A/fqAkO5Y/YscoHwKFpM49555D0RfwhIsBzMkZP+cegJBYHMDZuJk56wEYZB2oEvJuAJ97AAA33kSLkPsA49WrXPggE+1dTtMB5Moy9gACWlSMA5iqHkBOaA0PapALcKrE9eJpyKJKm4OPVTuzo34XaB2swpCX3q3mdwGm0aW1HDtLNWMbeJPCCwlyLMpp7AA0PJ5wMffBdFY6Vf/rLAGMWiyYKRuxv6NaiRJIc/99//2Ogv7xX9TSWWimJs16br3auzwpVVWRm7Z7z96f7Sf+eYP4TU2roc0QUGJwaF4EbFnHGGbUadpu616pmrUYGWjvRJFjkAKBRUnk9bgui3H/YCa2bV4z2myI3m8yAD6rskqHqxh/uv3rWn+4rDZ2vNpPgEuIjn5EhNGw1lJLa3T0YSRADAoKxQX4vF4slS7I4TuGP76PhqCcSdNbuC1GrWmHP+uC099TJI9R80y/Te79i7xjqBniyqrF5GSKbTJ+n6ETuPLWohYf3s65JVLTWjqxtOl1CSwqNAfvDQKO6WLCjVUNs8mo3lB5Znq0uDytKiBkl1F8qYR0dr6Fmv3mvWTnkA2J1aNFk55vlIvKZud10LK7BQuW2VGST/Xs9h4uv9//+eXb6P6fh7+9VvZu6yNyRI8utfbRSeNYdAYDzAvSBahVAEN0fEBKpaQ4WcnV4GE8uh1+Gd89cN24Tx8feKYLz9LHZ4SLJ1jXBxdv/HZcR2sX8CMqSzmhqcmC+DoKOVkEKZQAJTIlOuH1jGzbT07baMhNQYVprF847LP85FQrLVHZBvn4VF95J+QiuemuWH9Vkeul5rHJ9bfoXDt5l+K6evb15bgKfPofzaHE4zOlX0nmJ3bj2oeX8qurDaQgTpdCagyG4AOkso3Tq1xj5YPwFuS8QNcczuSyeOBWbWotMczJWnjEGnuKDtNVv3G3bmGucYjOIndCBlXwMzLJpuXUBVeoKSe99uahM7jnRrbeTfcdvQ+//PrxhH4vU7G734rZa1KSjlrwOon9OhoeYB8roRL75aYdezXkPpRKBmBiUInsxjK5Kspkuakka4lRe2c2Cxx/E7/jliW05qABch3sfu//AAAA//8t/Vii" } diff --git a/metricbeat/module/zookeeper/fields.go b/metricbeat/module/zookeeper/fields.go index 9ab4ca9a74b..0087aaab85b 100644 --- a/metricbeat/module/zookeeper/fields.go +++ b/metricbeat/module/zookeeper/fields.go @@ -30,7 +30,7 @@ func init() { } // AssetZookeeper returns asset data. -// This is the base64 encoded gzipped contents of ../metricbeat/module/zookeeper. +// This is the base64 encoded gzipped contents of module/zookeeper. func AssetZookeeper() string { return "eJy0WM2O2zgMvucpiF56afMAc1ig2O6hWLSLLbqXXlxGZmJhZNGV6PzM0y8kW/5J7CQz4/EpiCx+Hyn+fPJHeKTTAzwxPxJV5FYAosXQA7z7yfx3/O/dCiAnr5yuRLN9gD9WAADdOpQkTisPio0hJZTD5gRSEGy5dh8NiYSX2Gphp+0OFJcl2tyvVwC+YCeZYrvVuwfYovG0AnBkCD09wA5XAFtNJvcPEfUjWCxpzDg8cqrC647rqv1ngnJ4fnU7f4FiK6itj2STF44qdq0TnY/d9iH38Ay5DfkptpZUAO+WpkiGZ+xsemboNxySbT/4/5zJkI22Qo68ZFz50QuJk2G7O1u4QiA8X1qTcG4yYf6uqaZ8IbR/o7EZz3tMR4r0fjHU7625m7ierCyE+WcPNTabsEor7mZOXQH5FQzckfvnBdzsO0vEWAzrm8l8LTcL9hJ+TcbvkU4HdufneSOEfWdKtteTyFhVjo+6RKEsR8HM66dpGs8/xk+9bQhmgbcDXgFtmpNBIatOa9yfA76YyZ4c7ihZhg3JgcgCWU/lxlAMkgdtodTGaE+KB/1tzI6qgkpyaHymuF4s57/V5YZcCFEHAE+Wc5qhsWVj+EBuqV7W43eWwYcYtWWgaufISozUNKMSj9lWG8oSErtFI/QVj7qsS7A9U20IejQPGJnnsGUXSffZVjlW5GdimRKuxOPCXFPC3ZVZHQ1tl6Kh7fNp2LrM0Og9ZXMtf4ksG9gG4cFJSYEC6LqMMyeIbKbJckX2TbPu23y2BexUHndmGtfiBW2u7S5z9LsmL8uHdgACCaQJqyXKQ7Q3lGj2c06Z2gu5ad4VqkcSv15YXPSc+/hZkgO7xwTZ6ZnrxBZUH/eQCnAzhKg5XX+yavmjba1DtB5OUqFzp3Di4yLqBlvXzKfZenJ7cpkXlAXFx3c2FHrNuDASpxkmJ6soz95urDUAg+nWCb1DQRYQwrAdBQS0B0OYzxXFnpwfX256pmg0nvtQoRRNyLWi9fTuu2Vdux3Q5rCptcnBi2sqvnFrmvMBRRVv1CCjbfKDxu1JgJs8MKzQ3Nsmo/B5I5aNqLqQ+Tf5jUvmebeP982mF1w4vNvHi0pzyXj/qlvGa8f5YHgbTVaGJ93absZL8OIiTBc6Z5LDeRjnfBrau7wnXPXpwq90O8AyJFzwT3RJIPhItpOS7QkKgyNfsY2OYhuJNGZnKV4qy+dRTHryLSleqM5nUmy15sIUO3qcv3hCfQ2dPRDqGKzhS2id3Uz6AFJoDyWegLQU5IJEano/sOuGxhr+CYsH7ekDaAkDIiotNGxpWky/rpH9YEEzuPHE3nVLWb6yuDvJ2Hy9ApRR4H6EQNFREeUenshxMz8Hp9sqNg8lOxopUNsGDYM0EU5NdvnvWBPeJHupzV5pUC8WkxOwwdYdkO04z/I5ITaxMIL+HARLm+M/0+fdNCQGjVpbqP10rj4d9XS07yix/6z+XRPs0dQTNMSh9RhHD3z5HHKIIloYG16HMPE2lmPFqoiSBiEWTazUmDLkBTdG+2Iwspv6DK9rH7yK3SQnIVdqS/EdobJihwbY5RT1URhfBdrdTB29tlYHrvoUiJaobh2c+ax0vnIv7l8xaHdG/lMKstc7q7eaGi1SkdOcB4aHQocjSLUcirXlv/o/AAD//7suFxk=" } diff --git a/metricbeat/scripts/assets/assets.go b/metricbeat/scripts/assets/assets.go deleted file mode 100644 index 51c24afe452..00000000000 --- a/metricbeat/scripts/assets/assets.go +++ /dev/null @@ -1,75 +0,0 @@ -// Licensed to Elasticsearch B.V. under one or more contributor -// license agreements. See the NOTICE file distributed with -// this work for additional information regarding copyright -// ownership. Elasticsearch B.V. licenses this file to you under -// the Apache License, Version 2.0 (the "License"); you may -// not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -package main - -import ( - "flag" - "fmt" - "io/ioutil" - "os" - "path" - - "github.com/elastic/beats/v7/libbeat/asset" - "github.com/elastic/beats/v7/libbeat/generator/fields" - "github.com/elastic/beats/v7/licenses" -) - -func main() { - - flag.Parse() - args := flag.Args() - - if len(args) != 1 { - fmt.Fprintln(os.Stderr, "Module path must be set") - os.Exit(1) - } - - dir := args[0] - - modules, err := fields.GetModules(dir) - if err != nil { - fmt.Fprintf(os.Stderr, "Error fetching modules: %s\n", err) - os.Exit(1) - } - - for _, module := range modules { - files, err := fields.CollectFiles(module, dir) - if err != nil { - fmt.Fprintf(os.Stderr, "Error fetching files for module %s: %s\n", module, err) - os.Exit(1) - } - - data, err := fields.GenerateFieldsYml(files) - if err != nil { - fmt.Fprintf(os.Stderr, "Error fetching files for module %s: %s\n", module, err) - os.Exit(1) - } - - bs, err := asset.CreateAsset(licenses.ASL2, "metricbeat", module, module, data, "asset.ModuleFieldsPri", dir+"/"+module) - if err != nil { - fmt.Fprintf(os.Stderr, "Error creating golang file from template: %s\n", err) - os.Exit(1) - } - - err = ioutil.WriteFile(path.Join(dir, module, "fields.go"), bs, 0644) - if err != nil { - fmt.Fprintf(os.Stderr, "Error writing fields.go: %s\n", err) - os.Exit(1) - } - } -} diff --git a/x-pack/auditbeat/Makefile b/x-pack/auditbeat/Makefile index 56633e2b3e5..019d3b9309a 100644 --- a/x-pack/auditbeat/Makefile +++ b/x-pack/auditbeat/Makefile @@ -1,3 +1,3 @@ ES_BEATS ?= ../.. -include $(ES_BEATS)/dev-tools/make/xpack.mk +include $(ES_BEATS)/dev-tools/make/mage.mk diff --git a/x-pack/dockerlogbeat/Makefile b/x-pack/dockerlogbeat/Makefile index 950d9029145..2367bfdeaa4 100644 --- a/x-pack/dockerlogbeat/Makefile +++ b/x-pack/dockerlogbeat/Makefile @@ -9,7 +9,7 @@ ES_BEATS?=../../ GOX_OS=linux GOX_FLAGS=-arch="amd64 386 arm ppc64 ppc64le" -include $(ES_BEATS)/dev-tools/make/xpack.mk +include $(ES_BEATS)/dev-tools/make/mage.mk .PHONY: unit-tests diff --git a/x-pack/elastic-agent/Makefile b/x-pack/elastic-agent/Makefile index 71dd1dea852..50e8845e1b2 100644 --- a/x-pack/elastic-agent/Makefile +++ b/x-pack/elastic-agent/Makefile @@ -7,7 +7,7 @@ ES_BEATS ?= ../.. # # Includes # -include $(ES_BEATS)/dev-tools/make/xpack.mk +include $(ES_BEATS)/dev-tools/make/mage.mk .PHONY: docs docs: ## @build Builds the documentation for the beat diff --git a/x-pack/filebeat/Makefile b/x-pack/filebeat/Makefile index 56633e2b3e5..019d3b9309a 100644 --- a/x-pack/filebeat/Makefile +++ b/x-pack/filebeat/Makefile @@ -1,3 +1,3 @@ ES_BEATS ?= ../.. -include $(ES_BEATS)/dev-tools/make/xpack.mk +include $(ES_BEATS)/dev-tools/make/mage.mk diff --git a/x-pack/functionbeat/Makefile b/x-pack/functionbeat/Makefile index fe15d9fe5c4..60d069da395 100644 --- a/x-pack/functionbeat/Makefile +++ b/x-pack/functionbeat/Makefile @@ -7,7 +7,7 @@ ES_BEATS?=../../ # # Includes # -include $(ES_BEATS)/dev-tools/make/xpack.mk +include $(ES_BEATS)/dev-tools/make/mage.mk .PHONY: test-gcp-functions test-gcp-functions: mage diff --git a/x-pack/metricbeat/Makefile b/x-pack/metricbeat/Makefile index 56633e2b3e5..019d3b9309a 100644 --- a/x-pack/metricbeat/Makefile +++ b/x-pack/metricbeat/Makefile @@ -1,3 +1,3 @@ ES_BEATS ?= ../.. -include $(ES_BEATS)/dev-tools/make/xpack.mk +include $(ES_BEATS)/dev-tools/make/mage.mk diff --git a/x-pack/metricbeat/magefile.go b/x-pack/metricbeat/magefile.go index 1821f21a435..ffd40949d70 100644 --- a/x-pack/metricbeat/magefile.go +++ b/x-pack/metricbeat/magefile.go @@ -143,6 +143,9 @@ func IntegTest() { // Use TEST_TAGS=tag1,tag2 to add additional build tags. // Use MODULE=module to run only tests for `module`. func GoIntegTest(ctx context.Context) error { + if !devtools.IsInIntegTestEnv() { + mg.SerialDeps(Fields, Dashboards) + } return devtools.GoTestIntegrationForModule(ctx) } diff --git a/x-pack/metricbeat/tests/system/test_xpack_base.py b/x-pack/metricbeat/tests/system/test_xpack_base.py index 69d07b61354..225ad779f0d 100644 --- a/x-pack/metricbeat/tests/system/test_xpack_base.py +++ b/x-pack/metricbeat/tests/system/test_xpack_base.py @@ -5,5 +5,4 @@ class Test(xpack_metricbeat.XPackTest, test_base.Test): - def kibana_dir(self): - return os.path.join(self.beat_path, 'build', 'kibana') + pass diff --git a/x-pack/winlogbeat/Makefile b/x-pack/winlogbeat/Makefile index 5da45563104..022bb515c23 100644 --- a/x-pack/winlogbeat/Makefile +++ b/x-pack/winlogbeat/Makefile @@ -7,4 +7,4 @@ GOX_OS := windows # # Includes # -include ../../dev-tools/make/xpack.mk +include ../../dev-tools/make/mage.mk From 86c59c09bc8fc6f6f36ff111624356b1cfec3102 Mon Sep 17 00:00:00 2001 From: kaiyan-sheng Date: Thu, 23 Apr 2020 12:59:10 -0600 Subject: [PATCH 014/116] run mage fmt update on filebeat (#17947) From ba46955a8f5864938a0934df1b6d76d5239b5866 Mon Sep 17 00:00:00 2001 From: Marius Iversen Date: Fri, 24 Apr 2020 01:20:06 +0200 Subject: [PATCH 015/116] Adding geoip and apply fixes for event.type appends (#17904) * MVP initial commit for fortigate module --- .../checkpoint/firewall/ingest/pipeline.json | 76 ++++- .../test/checkpoint.log-expected.json | 279 ++++++++++++++++++ 2 files changed, 348 insertions(+), 7 deletions(-) diff --git a/x-pack/filebeat/module/checkpoint/firewall/ingest/pipeline.json b/x-pack/filebeat/module/checkpoint/firewall/ingest/pipeline.json index cff272b7db1..e478d54e73d 100644 --- a/x-pack/filebeat/module/checkpoint/firewall/ingest/pipeline.json +++ b/x-pack/filebeat/module/checkpoint/firewall/ingest/pipeline.json @@ -61,9 +61,9 @@ } }, { - "set": { + "append": { "field": "event.category", - "value": ["network"], + "value": "network", "if": "ctx.checkpoint?.operation != 'Log In'" } }, @@ -242,9 +242,9 @@ } }, { - "set": { + "append": { "field": "event.category", - "value": ["authentication"], + "value": "authentication", "if": "ctx.checkpoint?.operation == 'Log In'" } }, @@ -270,7 +270,7 @@ } }, { - "set" : { + "append" : { "field": "event.type", "value": ["allowed", "connection"], "if": "['Accept', 'Allow'].contains(ctx.checkpoint?.rule_action)" @@ -298,7 +298,7 @@ } }, { - "set" : { + "append" : { "field": "event.type", "value": ["connection", "denied"], "if": "['Drop', 'Reject', 'Block', 'Prevent'].contains(ctx.checkpoint?.rule_action)" @@ -1028,6 +1028,68 @@ "if": "ctx.checkpoint?.sys_message != null" } }, + { + "geoip" : { + "field": "source.ip", + "target_field": "source.geo", + "ignore_missing": true, + "if": "ctx.source?.geo == null" + } + }, + { + "geoip" : { + "field": "destination.ip", + "target_field": "destination.geo", + "ignore_missing": true, + "if": "ctx.destination?.geo == null" + } + }, + { + "geoip" : { + "database_file": "GeoLite2-ASN.mmdb", + "field": "source.ip", + "target_field": "source.as", + "properties": ["asn", "organization_name"], + "ignore_missing": true + } + }, + { + "geoip" : { + "database_file": "GeoLite2-ASN.mmdb", + "field": "destination.ip", + "target_field": "destination.as", + "properties": ["asn", "organization_name"], + "ignore_missing": true + } + }, + { + "rename" : { + "field": "source.as.asn", + "target_field": "source.as.number", + "ignore_missing": true + } + }, + { + "rename" : { + "field": "source.as.organization_name", + "target_field": "source.as.organization.name", + "ignore_missing": true + } + }, + { + "rename" : { + "field": "destination.as.asn", + "target_field": "destination.as.number", + "ignore_missing": true + } + }, + { + "rename" : { + "field": "destination.as.organization_name", + "target_field": "destination.as.organization.name", + "ignore_missing": true + } + }, { "remove" : { "field": [ @@ -1050,4 +1112,4 @@ } } ] -} \ No newline at end of file +} diff --git a/x-pack/filebeat/module/checkpoint/firewall/test/checkpoint.log-expected.json b/x-pack/filebeat/module/checkpoint/firewall/test/checkpoint.log-expected.json index f966163307d..4e8517f4794 100644 --- a/x-pack/filebeat/module/checkpoint/firewall/test/checkpoint.log-expected.json +++ b/x-pack/filebeat/module/checkpoint/firewall/test/checkpoint.log-expected.json @@ -140,6 +140,15 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "61794", + "destination.as.number": 25046, + "destination.as.organization.name": "Check Point Software Technologies LTD", + "destination.geo.city_name": "Tel Aviv", + "destination.geo.continent_name": "Asia", + "destination.geo.country_iso_code": "IL", + "destination.geo.location.lat": 32.0678, + "destination.geo.location.lon": 34.7647, + "destination.geo.region_iso_code": "IL-TA", + "destination.geo.region_name": "Tel Aviv", "destination.ip": "194.29.39.10", "destination.port": "443", "event.action": "Accept", @@ -249,6 +258,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "41566", + "destination.as.number": 16625, + "destination.as.organization.name": "Akamai Technologies, Inc.", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "104.99.234.45", "destination.port": "443", "event.action": "Accept", @@ -358,6 +373,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "48698", + "destination.as.number": 30148, + "destination.as.organization.name": "Sucuri", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "192.124.249.31", "destination.port": "80", "event.action": "Accept", @@ -467,6 +488,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "61150", + "destination.as.number": 30148, + "destination.as.organization.name": "Sucuri", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "192.124.249.36", "destination.port": "80", "event.action": "Accept", @@ -576,6 +603,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "55110", + "destination.as.number": 16625, + "destination.as.organization.name": "Akamai Technologies, Inc.", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "104.81.142.43", "destination.port": "443", "event.action": "Accept", @@ -685,6 +718,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "48718", + "destination.as.number": 30148, + "destination.as.organization.name": "Sucuri", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "192.124.249.31", "destination.port": "80", "event.action": "Accept", @@ -794,6 +833,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "62206", + "destination.as.number": 30148, + "destination.as.organization.name": "Sucuri", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "192.124.249.41", "destination.port": "80", "event.action": "Accept", @@ -903,6 +948,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "41596", + "destination.as.number": 16625, + "destination.as.organization.name": "Akamai Technologies, Inc.", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "104.99.234.45", "destination.port": "443", "event.action": "Accept", @@ -1012,6 +1063,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "61180", + "destination.as.number": 30148, + "destination.as.organization.name": "Sucuri", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "192.124.249.36", "destination.port": "80", "event.action": "Accept", @@ -1121,6 +1178,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "48732", + "destination.as.number": 30148, + "destination.as.organization.name": "Sucuri", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "192.124.249.31", "destination.port": "80", "event.action": "Accept", @@ -1230,6 +1293,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "62222", + "destination.as.number": 30148, + "destination.as.organization.name": "Sucuri", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "192.124.249.41", "destination.port": "80", "event.action": "Accept", @@ -1339,6 +1408,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "61188", + "destination.as.number": 30148, + "destination.as.organization.name": "Sucuri", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "192.124.249.36", "destination.port": "80", "event.action": "Accept", @@ -1448,6 +1523,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "41624", + "destination.as.number": 16625, + "destination.as.organization.name": "Akamai Technologies, Inc.", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "104.99.234.45", "destination.port": "443", "event.action": "Accept", @@ -1557,6 +1638,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "48758", + "destination.as.number": 30148, + "destination.as.organization.name": "Sucuri", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "192.124.249.31", "destination.port": "80", "event.action": "Accept", @@ -1666,6 +1753,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "62246", + "destination.as.number": 30148, + "destination.as.organization.name": "Sucuri", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "192.124.249.41", "destination.port": "80", "event.action": "Accept", @@ -1775,6 +1868,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "41638", + "destination.as.number": 16625, + "destination.as.organization.name": "Akamai Technologies, Inc.", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "104.99.234.45", "destination.port": "443", "event.action": "Accept", @@ -1884,6 +1983,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "61224", + "destination.as.number": 30148, + "destination.as.organization.name": "Sucuri", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "192.124.249.36", "destination.port": "80", "event.action": "Accept", @@ -2037,6 +2142,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "48776", + "destination.as.number": 30148, + "destination.as.organization.name": "Sucuri", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "192.124.249.31", "destination.port": "80", "event.action": "Accept", @@ -2119,6 +2230,15 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "51436", + "destination.as.number": 25046, + "destination.as.organization.name": "Check Point Software Technologies LTD", + "destination.geo.city_name": "Tel Aviv", + "destination.geo.continent_name": "Asia", + "destination.geo.country_iso_code": "IL", + "destination.geo.location.lat": 32.0678, + "destination.geo.location.lon": 34.7647, + "destination.geo.region_iso_code": "IL-TA", + "destination.geo.region_name": "Tel Aviv", "destination.ip": "194.29.39.47", "destination.port": "443", "event.action": "Accept", @@ -2334,6 +2454,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "62396", + "destination.as.number": 30148, + "destination.as.organization.name": "Sucuri", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "192.124.249.41", "destination.port": "80", "event.action": "Accept", @@ -2443,6 +2569,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "48914", + "destination.as.number": 30148, + "destination.as.organization.name": "Sucuri", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "192.124.249.31", "destination.port": "80", "event.action": "Accept", @@ -2552,6 +2684,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "41844", + "destination.as.number": 16625, + "destination.as.organization.name": "Akamai Technologies, Inc.", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "104.99.234.45", "destination.port": "443", "event.action": "Accept", @@ -2661,6 +2799,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "62468", + "destination.as.number": 30148, + "destination.as.organization.name": "Sucuri", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "192.124.249.41", "destination.port": "80", "event.action": "Accept", @@ -2770,6 +2914,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "61434", + "destination.as.number": 30148, + "destination.as.organization.name": "Sucuri", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "192.124.249.36", "destination.port": "80", "event.action": "Accept", @@ -2879,6 +3029,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "41856", + "destination.as.number": 16625, + "destination.as.organization.name": "Akamai Technologies, Inc.", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "104.99.234.45", "destination.port": "443", "event.action": "Accept", @@ -3032,6 +3188,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "48990", + "destination.as.number": 30148, + "destination.as.organization.name": "Sucuri", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "192.124.249.31", "destination.port": "80", "event.action": "Accept", @@ -3141,6 +3303,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "62478", + "destination.as.number": 30148, + "destination.as.organization.name": "Sucuri", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "192.124.249.41", "destination.port": "80", "event.action": "Accept", @@ -3250,6 +3418,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "41864", + "destination.as.number": 16625, + "destination.as.organization.name": "Akamai Technologies, Inc.", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "104.99.234.45", "destination.port": "443", "event.action": "Accept", @@ -3359,6 +3533,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "61446", + "destination.as.number": 30148, + "destination.as.organization.name": "Sucuri", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "192.124.249.36", "destination.port": "80", "event.action": "Accept", @@ -3468,6 +3648,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "48998", + "destination.as.number": 30148, + "destination.as.organization.name": "Sucuri", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "192.124.249.31", "destination.port": "80", "event.action": "Accept", @@ -3524,6 +3710,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "41870", + "destination.as.number": 16625, + "destination.as.organization.name": "Akamai Technologies, Inc.", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "104.99.234.45", "destination.port": "443", "event.action": "Accept", @@ -3686,6 +3878,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "62488", + "destination.as.number": 30148, + "destination.as.organization.name": "Sucuri", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "192.124.249.41", "destination.port": "80", "event.action": "Accept", @@ -3795,6 +3993,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "61454", + "destination.as.number": 30148, + "destination.as.organization.name": "Sucuri", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "192.124.249.36", "destination.port": "80", "event.action": "Accept", @@ -3929,6 +4133,15 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "62122", + "destination.as.number": 25046, + "destination.as.organization.name": "Check Point Software Technologies LTD", + "destination.geo.city_name": "Tel Aviv", + "destination.geo.continent_name": "Asia", + "destination.geo.country_iso_code": "IL", + "destination.geo.location.lat": 32.0678, + "destination.geo.location.lon": 34.7647, + "destination.geo.region_iso_code": "IL-TA", + "destination.geo.region_name": "Tel Aviv", "destination.ip": "194.29.39.10", "destination.port": "443", "event.action": "Accept", @@ -4091,6 +4304,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "55424", + "destination.as.number": 16625, + "destination.as.organization.name": "Akamai Technologies, Inc.", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "104.81.142.43", "destination.port": "443", "event.action": "Accept", @@ -4200,6 +4419,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "49026", + "destination.as.number": 30148, + "destination.as.organization.name": "Sucuri", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "192.124.249.31", "destination.port": "80", "event.action": "Accept", @@ -4309,6 +4534,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "62514", + "destination.as.number": 30148, + "destination.as.organization.name": "Sucuri", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "192.124.249.41", "destination.port": "80", "event.action": "Accept", @@ -4418,6 +4649,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "41902", + "destination.as.number": 16625, + "destination.as.organization.name": "Akamai Technologies, Inc.", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "104.99.234.45", "destination.port": "443", "event.action": "Accept", @@ -4527,6 +4764,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "61490", + "destination.as.number": 30148, + "destination.as.organization.name": "Sucuri", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "192.124.249.36", "destination.port": "80", "event.action": "Accept", @@ -4636,6 +4879,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "49042", + "destination.as.number": 30148, + "destination.as.organization.name": "Sucuri", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "192.124.249.31", "destination.port": "80", "event.action": "Accept", @@ -4745,6 +4994,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "41914", + "destination.as.number": 16625, + "destination.as.organization.name": "Akamai Technologies, Inc.", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "104.99.234.45", "destination.port": "443", "event.action": "Accept", @@ -4854,6 +5109,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "62534", + "destination.as.number": 30148, + "destination.as.organization.name": "Sucuri", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "192.124.249.41", "destination.port": "80", "event.action": "Accept", @@ -4963,6 +5224,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "61500", + "destination.as.number": 30148, + "destination.as.organization.name": "Sucuri", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "192.124.249.36", "destination.port": "80", "event.action": "Accept", @@ -5072,6 +5339,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "41938", + "destination.as.number": 16625, + "destination.as.organization.name": "Akamai Technologies, Inc.", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "104.99.234.45", "destination.port": "443", "event.action": "Accept", @@ -5181,6 +5454,12 @@ "checkpoint.rule_action": "Accept", "client.ip": "192.168.1.100", "client.port": "49102", + "destination.as.number": 30148, + "destination.as.organization.name": "Sucuri", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "192.124.249.31", "destination.port": "80", "event.action": "Accept", From 684106eeaf562ffa56906e6883e52451944fe504 Mon Sep 17 00:00:00 2001 From: Ivan Fernandez Calvo Date: Fri, 24 Apr 2020 13:55:55 +0200 Subject: [PATCH 016/116] feat: trigger packaging after a succefull build on master (#17961) --- .ci/packaging.groovy | 1 + 1 file changed, 1 insertion(+) diff --git a/.ci/packaging.groovy b/.ci/packaging.groovy index 36734003aba..32d2d428c7f 100644 --- a/.ci/packaging.groovy +++ b/.ci/packaging.groovy @@ -23,6 +23,7 @@ pipeline { } triggers { issueCommentTrigger('(?i)^\\/packaging$') + upstream('Beats/beats-beats-mbp/master') } parameters { booleanParam(name: 'macos', defaultValue: false, description: 'Allow macOS stages.') From 41d9ca6bb5059cc4c665bb82b312a050cd5b6f91 Mon Sep 17 00:00:00 2001 From: Pier-Hugues Pellerin Date: Fri, 24 Apr 2020 08:28:09 -0400 Subject: [PATCH 017/116] [Elastic Agent] Fleet issues with configuring the Gateway. (#17949) * [Elastic Agent] Fleet issues with configuring the Gateway. This pull request ensure that the values configuration locally in the elastic-agent.yml are correctly applied to the fleet gateway. This also ensure that we have appropriate default sets on the Gateway. The following options are now supported on the Gateway. ```yaml # Check in frequency configure the time between calls to fleet to retrieve the new configuration. # # Default is 30s #checkin_frequency: 30s # Add variance between API calls to better distribute the calls. #jitter: 5s # The Elastic Agent does Exponential backoff when an error happen. # #backoff: # # Initial time to wait before retrying the call. #init: 1s # # Maximum time to wait before retrying the call. #max: 10s Fixes: #17900 * oh yaml * Oh yaml again * Changelog --- x-pack/elastic-agent/CHANGELOG.asciidoc | 1 + .../_meta/elastic-agent.fleet.yml | 18 ++++++++ .../pkg/agent/application/application.go | 14 +++---- .../agent/application/configuration_embed.go | 2 +- .../pkg/agent/application/fleet_gateway.go | 41 +++++++++++++------ .../pkg/agent/application/managed_mode.go | 33 ++++++++++----- 6 files changed, 78 insertions(+), 31 deletions(-) diff --git a/x-pack/elastic-agent/CHANGELOG.asciidoc b/x-pack/elastic-agent/CHANGELOG.asciidoc index 1271df44646..9cdaa81432c 100644 --- a/x-pack/elastic-agent/CHANGELOG.asciidoc +++ b/x-pack/elastic-agent/CHANGELOG.asciidoc @@ -25,6 +25,7 @@ - Fixed injected log path to monitoring beat {pull}17833[17833] - Make sure that the Elastic Agent connect over TLS in cloud. {pull}17843[17843] - Moved stream.* fields to top of event {pull}17858[17858] +- Fix an issue where the checkin_frequency, jitter, and backoff options where not configurable. {pull}17843[17843] ==== New features diff --git a/x-pack/elastic-agent/_meta/elastic-agent.fleet.yml b/x-pack/elastic-agent/_meta/elastic-agent.fleet.yml index 08304cbaa92..20143509c1b 100644 --- a/x-pack/elastic-agent/_meta/elastic-agent.fleet.yml +++ b/x-pack/elastic-agent/_meta/elastic-agent.fleet.yml @@ -5,6 +5,24 @@ management: mode: "fleet" + # Check in frequency configure the time between calls to fleet to retrieve the new configuration. + # + # Default is 30s + #checkin_frequency: 30s + + # Add variance between API calls to better distribute the calls. + #jitter: 5s + + # The Elastic Agent does Exponential backoff when an error happen. + # + #backoff: + # + # Initial time to wait before retrying the call. + # init: 1s + # + # Maximum time to wait before retrying the call. + # max: 10s + download: # source of the artifacts, requires elastic like structure and naming of the binaries # e.g /windows-x86.zip diff --git a/x-pack/elastic-agent/pkg/agent/application/application.go b/x-pack/elastic-agent/pkg/agent/application/application.go index 78634055989..878809dbd08 100644 --- a/x-pack/elastic-agent/pkg/agent/application/application.go +++ b/x-pack/elastic-agent/pkg/agent/application/application.go @@ -26,28 +26,28 @@ func New(log *logger.Logger, pathConfigFile string) (Application, error) { // Load configuration from disk to understand in which mode of operation // we must start the elastic-agent, the mode of operation cannot be changed without restarting the // elastic-agent. - config, err := config.LoadYAML(pathConfigFile) + rawConfig, err := config.LoadYAML(pathConfigFile) if err != nil { return nil, err } - if err := InjectAgentConfig(config); err != nil { + if err := InjectAgentConfig(rawConfig); err != nil { return nil, err } - return createApplication(log, pathConfigFile, config) + return createApplication(log, pathConfigFile, rawConfig) } func createApplication( log *logger.Logger, pathConfigFile string, - config *config.Config, + rawConfig *config.Config, ) (Application, error) { warn.LogNotGA(log) log.Info("Detecting execution mode") c := localDefaultConfig() - err := config.Unpack(c) + err := rawConfig.Unpack(c) if err != nil { return nil, errors.New(err, "initiating application") } @@ -63,10 +63,10 @@ func createApplication( switch mgmt.Mode { case localMode: log.Info("Agent is managed locally") - return newLocal(ctx, log, pathConfigFile, config) + return newLocal(ctx, log, pathConfigFile, rawConfig) case fleetMode: log.Info("Agent is managed by Fleet") - return newManaged(ctx, log, config) + return newManaged(ctx, log, rawConfig) default: return nil, ErrInvalidMgmtMode } diff --git a/x-pack/elastic-agent/pkg/agent/application/configuration_embed.go b/x-pack/elastic-agent/pkg/agent/application/configuration_embed.go index 8a064cf9268..a34b1b7a2dd 100644 --- a/x-pack/elastic-agent/pkg/agent/application/configuration_embed.go +++ b/x-pack/elastic-agent/pkg/agent/application/configuration_embed.go @@ -15,7 +15,7 @@ var DefaultAgentFleetConfig []byte func init() { // Packed File // _meta/elastic-agent.fleet.yml - unpacked := packer.MustUnpack("eJyMlk2Xo7oRhvf5GbPPDR/NnCE7CzdCjKFjfFtfmxwkeQS2ZHNigw05+e85wm53z6Rzz130xkh6q956qqr//eWfdnuu/7Y19encyr/Wens4//bDbLfn30Zrvvz9C5q8+B+//7k/9KyMsHhk5KL/7J3H3/q4Q8+5YbSaEDQTguUgLO94iEdO1lqRaM8p0pW9Gk7XJ5SZQW3AjpEnzSG2NYkMgv4gw7WWAfYUND0Pi+9oBFba+Iyy6sg3YFWTvBH2WePXfY/S9zcUBXtGK+PO1bTQ3JoT3wBPjGAngsjWRPnSvmoFG4Oy0qis6oRVkzvP6NrpNO67sLGHstKXGRjkoTI8Ae12A6CA2Kjk6bsgac+IMoLgXi2P31ECziKozEu7aPkttzbR3V6Eqhcwbvh8ZrFDWTkomu/4Bgy8BV4NXzWjuVcT3rCwmlYJGDlN/ZrmRo7ACJhOCpodgteuDl61DKtRkdJTNDcIpj1PQM+I3wkrtQjYLS+Yd8KmY03wlLQLjUZgVlbqVag6YatBhcV5u9TB6tB1cnHUj5gw3mx+P2pkG09lYHppvw0/xWbxidHSq0nRs+DbwGEcCnsdGKmmVXCr+WpE9zxBo6DWCn67x1R10uKdgvG43cy18BgxfU2iA4LVoILoJIJ07xiRMN3VQXrgtHC+eozmB06rHxyakZPSE2EeOZ/z7HxhtDqubNUomMar4P0d+Yij6gTBg6JrzW08ftSatTPQsODc8OB11qqJb0SIvZd2MRVZcXsD8k7AVxeXVyczSw3KgM/stWMj+Bene61o6RixIkRaWTzWhEef5fbRz3ttOt6CXoTrN1Y7OYKLtHjiJPLQ88NzvXa8HdZaBeYkEuDJw372d+YrARdF8pM7Vwc4QpCPIvA0J9dGhlXHxmgnRhBykk81rcxKu7qDg7zl9hUlaL8N55r1HKYeo97w4e6Fh4v2LV5xKD1Grid6O+/6dmJh3jGLp3t/nkRQNgqaQbQz5487v9R3kFk+8DBvBC169Gy828xITzL4dvPU8ZkUt9pluWGkmll/3NuASUK8qwnvWJB6NYl7OT65Wv4a56c51iSaFExPIkHfkwMYRVAaGRZf77meaxJ1gqQnNM+BtRYHfGYWjwjyQbZOOw2FxXuUvd0tjbwcNYJ+J0x8EWHuWLoWy8WluOciSPpUE98XGzB/RzDyBcmNbG/8SBdjEDmeLtLGO07L6d4LZ0abHxLGo1oedTEtLsVy8X84L0dOnB+Owcio8ae3epSpIydPs48swJMcZ+5mDmWId/NvAfcFvEZvb838zsw17uyjV+5eOR9HlYBBQjwyWnUieNJuBrMg7mVg9pzmLqcGQd7U5OrmsMvp5qGJ/6f3kkNuVJZHL/rOAIka5mZrAvacVkZaN/ccc+rIaXV0e0VaPPeF05UwnmRgWgFfe/RcGW5TX2RrfdsLjrHX3sX+eHd51CrLfb6+6c1vWWOkH+9EiHuVvLPGafGBhdg6NlWWd7O3v2gVM4+LHUpv+bidqFxvu3vLZy0IbsSh0DV99HbLN24PVG6G7xGMe7fDnD8IplbBWdP9ZhkxJ7cLbv38FkP5gwWxLw7rr2j5NsMqt0silJUXTsqOu7xGsHc9vt2ATrTgLEfQclqFnOD+D/OZwDy7OHSzae80LjPzd7Zp8KmWY7cTFJ8fsUDTI+hPCDotZWYe3zwfn3QFsWUUn1Ti2FxMxfKm+1HjpV344l6vijYXEURGHKrO/e/CIfY4zV0PO/2whtirZ+8e+m5evbPtuPj5jf7ONq1pdZzzfPesEXati/FRp2nmNrnocrc4oeXz+Ngfn7Pn2DfbDAzC4l5B04jl8fHty3/+8t8AAAD///ycVl0=") + unpacked := packer.MustUnpack("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") raw, ok := unpacked["_meta/elastic-agent.fleet.yml"] if !ok { // ensure we have something loaded. diff --git a/x-pack/elastic-agent/pkg/agent/application/fleet_gateway.go b/x-pack/elastic-agent/pkg/agent/application/fleet_gateway.go index e4f021fdaad..d21e2392f5b 100644 --- a/x-pack/elastic-agent/pkg/agent/application/fleet_gateway.go +++ b/x-pack/elastic-agent/pkg/agent/application/fleet_gateway.go @@ -11,11 +11,33 @@ import ( "github.com/elastic/beats/v7/libbeat/common/backoff" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/errors" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/config" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/core/logger" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/fleetapi" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/scheduler" ) +// Default Configuration for the Fleet Gateway. +var defaultGatewaySettings = &fleetGatewaySettings{ + Duration: 30 * time.Second, + Jitter: 5 * time.Second, + Backoff: backoffSettings{ + Init: 1 * time.Second, + Max: 10 * time.Second, + }, +} + +type fleetGatewaySettings struct { + Duration time.Duration `config:"checkin_frequency"` + Jitter time.Duration `config:"jitter"` + Backoff backoffSettings `config:"backoff"` +} + +type backoffSettings struct { + Init time.Duration `config:"init"` + Max time.Duration `config:"max"` +} + type dispatcher interface { Dispatch(acker fleetAcker, actions ...action) error } @@ -52,27 +74,22 @@ type fleetGateway struct { acker fleetAcker } -type fleetGatewaySettings struct { - Duration time.Duration - Jitter time.Duration - Backoff backoffSettings -} - -type backoffSettings struct { - Init time.Duration - Max time.Duration -} - func newFleetGateway( ctx context.Context, log *logger.Logger, - settings *fleetGatewaySettings, + rawConfig *config.Config, agentInfo agentInfo, client clienter, d dispatcher, r fleetReporter, acker fleetAcker, ) (*fleetGateway, error) { + + settings := defaultGatewaySettings + if err := rawConfig.Unpack(settings); err != nil { + return nil, errors.New(err, "fail to read gateway configuration") + } + scheduler := scheduler.NewPeriodicJitter(settings.Duration, settings.Jitter) return newFleetGatewayWithScheduler( ctx, diff --git a/x-pack/elastic-agent/pkg/agent/application/managed_mode.go b/x-pack/elastic-agent/pkg/agent/application/managed_mode.go index 6431ec03975..08ff4c7a479 100644 --- a/x-pack/elastic-agent/pkg/agent/application/managed_mode.go +++ b/x-pack/elastic-agent/pkg/agent/application/managed_mode.go @@ -11,8 +11,6 @@ import ( "net/http" "net/url" - "time" - "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application/filters" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application/info" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/errors" @@ -26,13 +24,8 @@ import ( logreporter "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/reporter/log" ) -var gatewaySettings = &fleetGatewaySettings{ - Duration: 2 * time.Second, - Jitter: 1 * time.Second, - Backoff: backoffSettings{ - Init: 1 * time.Second, - Max: 10 * time.Second, - }, +type managementCfg struct { + Management *config.Config `config:"management"` } type apiClient interface { @@ -87,6 +80,7 @@ func newManaged( errors.M(errors.MetaKeyPath, path)) } + // merge local configuration and configuration persisted from fleet. rawConfig.Merge(config) cfg := defaultFleetAgentConfig() @@ -97,6 +91,15 @@ func newManaged( errors.M(errors.MetaKeyPath, path)) } + // Extract only management related configuration. + managementCfg := &managementCfg{} + if err := rawConfig.Unpack(managementCfg); err != nil { + return nil, errors.New(err, + fmt.Sprintf("fail to unpack configuration from %s", path), + errors.TypeFilesystem, + errors.M(errors.MetaKeyPath, path)) + } + client, err := fleetapi.NewAuthWithConfig(log, cfg.API.AccessAPIKey, cfg.API.Kibana) if err != nil { return nil, errors.New(err, @@ -129,7 +132,15 @@ func newManaged( return nil, errors.New(err, "fail to initialize pipeline router") } - emit := emitter(log, router, &configModifiers{Decorators: []decoratorFunc{injectMonitoring}, Filters: []filterFunc{filters.ConstraintFilter}}, monitor) + emit := emitter( + log, + router, + &configModifiers{ + Decorators: []decoratorFunc{injectMonitoring}, + Filters: []filterFunc{filters.ConstraintFilter}, + }, + monitor, + ) acker, err := newActionAcker(log, agentInfo, client) if err != nil { return nil, err @@ -175,7 +186,7 @@ func newManaged( gateway, err := newFleetGateway( managedApplication.bgContext, log, - gatewaySettings, + managementCfg.Management, agentInfo, client, actionDispatcher, From d086c6374e25d50394d7be82279ca08b546b421c Mon Sep 17 00:00:00 2001 From: Chris Mark Date: Fri, 24 Apr 2020 17:06:36 +0300 Subject: [PATCH 018/116] Fix remote_write doc content (#17933) --- metricbeat/module/prometheus/remote_write/_meta/docs.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metricbeat/module/prometheus/remote_write/_meta/docs.asciidoc b/metricbeat/module/prometheus/remote_write/_meta/docs.asciidoc index 386f7e5729d..2f46dc6e47c 100644 --- a/metricbeat/module/prometheus/remote_write/_meta/docs.asciidoc +++ b/metricbeat/module/prometheus/remote_write/_meta/docs.asciidoc @@ -8,7 +8,7 @@ remote_write: ------------------------------------------------------------------------------ -TIP: In order to assure the health of the whole queue, the following two configuration +TIP: In order to assure the health of the whole queue, the following configuration https://prometheus.io/docs/practices/remote_write/#parameters[parameters] should be considered: - `max_shards`: Sets the maximum number of parallelism with which Prometheus will try to send samples to Metricbeat. From 98f02e151d4c8368a49a218b761f6aa8e348b877 Mon Sep 17 00:00:00 2001 From: kaiyan-sheng Date: Fri, 24 Apr 2020 08:11:36 -0600 Subject: [PATCH 019/116] [Metricbeat] Add aggregation aligner as a config param for stackdriver metricset in GCP (#17719) * Add metricDescriptor to get sample period and ingest delay time * add aggregation for ListTimeSeriesRequest * Add aligner into metric name suffix (eg: .avg, .sum) --- CHANGELOG.next.asciidoc | 1 + metricbeat/docs/fields.asciidoc | 1239 +++++++++++++++++ metricbeat/docs/modules/googlecloud.asciidoc | 26 +- .../modules/googlecloud/stackdriver.asciidoc | 23 + metricbeat/docs/modules_list.asciidoc | 3 +- x-pack/metricbeat/metricbeat.reference.yml | 10 +- .../module/googlecloud/_meta/config.yml | 10 +- .../module/googlecloud/_meta/docs.asciidoc | 12 +- .../module/googlecloud/_meta/fields.yml | 25 +- .../googlecloud/compute/_meta/data.json | 22 +- .../compute/_meta/data_network_01.json | 74 - .../compute/_meta/data_network_02.json | 72 - .../googlecloud/compute/_meta/fields.yml | 28 +- .../compute/compute_integration_test.go | 4 +- .../module/googlecloud/compute/manifest.yml | 27 +- .../module/googlecloud/constants.go | 60 +- .../metricbeat/module/googlecloud/fields.go | 2 +- .../googlecloud/loadbalancing/_meta/data.json | 126 +- .../loadbalancing/_meta/fields.yml | 120 +- .../googlecloud/loadbalancing/manifest.yml | 51 +- .../module/googlecloud/pubsub/_meta/data.json | 26 +- .../googlecloud/pubsub/_meta/fields.yml | 97 +- .../module/googlecloud/pubsub/manifest.yml | 95 +- .../stackdriver/_meta/docs.asciidoc | 82 ++ .../stackdriver/compute/identity.go | 2 +- .../{ => stackdriver}/integration.go | 8 +- .../stackdriver/metrics_requester.go | 117 +- .../stackdriver/metrics_requester_test.go | 61 + .../googlecloud/stackdriver/metricset.go | 123 +- .../stackdriver/response_parser.go | 16 +- .../stackdriver/response_parser_test.go | 32 + .../stackdriver_integration_test.go | 3 +- .../googlecloud/stackdriver/timeseries.go | 63 +- .../googlecloud/storage/_meta/data.json | 15 +- .../googlecloud/storage/_meta/fields.yml | 18 +- .../module/googlecloud/storage/manifest.yml | 19 +- .../storage/storage_integration_test.go | 4 +- .../modules.d/googlecloud.yml.disabled | 10 +- 38 files changed, 2068 insertions(+), 658 deletions(-) create mode 100644 metricbeat/docs/modules/googlecloud/stackdriver.asciidoc delete mode 100644 x-pack/metricbeat/module/googlecloud/compute/_meta/data_network_01.json delete mode 100644 x-pack/metricbeat/module/googlecloud/compute/_meta/data_network_02.json create mode 100644 x-pack/metricbeat/module/googlecloud/stackdriver/_meta/docs.asciidoc rename x-pack/metricbeat/module/googlecloud/{ => stackdriver}/integration.go (86%) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index b55ad88642f..2f1e4043572 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -345,6 +345,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Reference kubernetes manifests mount data directory from the host when running metricbeat as daemonset, so data persist between executions in the same node. {pull}17429[17429] - Add more detailed error messages, system tests and small refactoring to the service metricset in windows. {pull}17725[17725] - Stack Monitoring modules now auto-configure required metricsets when `xpack.enabled: true` is set. {issue}16471[[16471] {pull}17609[17609] +- Add aggregation aligner as a config parameter for googlecloud stackdriver metricset. {issue}17141[[17141] {pull}17719[17719] - Move the perfmon metricset to GA. {issue}16608[16608] {pull}17879[17879] *Packetbeat* diff --git a/metricbeat/docs/fields.asciidoc b/metricbeat/docs/fields.asciidoc index 81e2d066557..04c431efd59 100644 --- a/metricbeat/docs/fields.asciidoc +++ b/metricbeat/docs/fields.asciidoc @@ -17174,6 +17174,7 @@ GCP module + *`googlecloud.labels`*:: + -- @@ -17181,6 +17182,1244 @@ type: object -- +*`googlecloud.stackdriver.*.*.*.*`*:: ++ +-- +Metrics that returned from StackDriver API query. + + +type: object + +-- + +[float] +=== compute + +Google Cloud Compute metrics + + + + +*`googlecloud.compute.instance.firewall.dropped_bytes_count.value`*:: ++ +-- +Incoming bytes dropped by the firewall + +type: long + +-- + +*`googlecloud.compute.instance.firewall.dropped_packets_count.value`*:: ++ +-- +Incoming packets dropped by the firewall + +type: long + +-- + + +*`googlecloud.compute.instance.cpu.reserved_cores.value`*:: ++ +-- +Number of cores reserved on the host of the instance + +type: double + +-- + +*`googlecloud.compute.instance.cpu.utilization.value`*:: ++ +-- +The fraction of the allocated CPU that is currently in use on the instance + +type: double + +-- + +*`googlecloud.compute.instance.cpu.usage_time.value`*:: ++ +-- +Usage for all cores in seconds + +type: double + +-- + + +*`googlecloud.compute.instance.disk.read_bytes_count.value`*:: ++ +-- +Count of bytes read from disk + +type: long + +-- + +*`googlecloud.compute.instance.disk.read_ops_count.value`*:: ++ +-- +Count of disk read IO operations + +type: long + +-- + +*`googlecloud.compute.instance.disk.write_bytes_count.value`*:: ++ +-- +Count of bytes written to disk + +type: long + +-- + +*`googlecloud.compute.instance.disk.write_ops_count.value`*:: ++ +-- +Count of disk write IO operations + +type: long + +-- + +*`googlecloud.compute.instance.uptime.value`*:: ++ +-- +How long the VM has been running, in seconds + +type: long + +-- + + +*`googlecloud.compute.instance.network.received_bytes_count.value`*:: ++ +-- +Count of bytes received from the network + +type: long + +-- + +*`googlecloud.compute.instance.network.received_packets_count.value`*:: ++ +-- +Count of packets received from the network + +type: long + +-- + +*`googlecloud.compute.instance.network.sent_bytes_count.value`*:: ++ +-- +Count of bytes sent over the network + +type: long + +-- + +*`googlecloud.compute.instance.network.sent_packets_count.value`*:: ++ +-- +Count of packets sent over the network + +type: long + +-- + +[float] +=== loadbalancing + +Google Cloud Load Balancing metrics + + +[float] +=== https + +Google Cloud Load Balancing metrics + + +[float] +=== backend_latencies + +A distribution of the latency calculated from when the request was sent by the proxy to the backend until the proxy received from the backend the last byte of response. + + +*`googlecloud.loadbalancing.https.backend_latencies.count.value`*:: ++ +-- +type: long + +-- + +*`googlecloud.loadbalancing.https.backend_latencies.mean.value`*:: ++ +-- +type: long + +-- + +*`googlecloud.loadbalancing.https.backend_latencies.bucket_counts.value`*:: ++ +-- +type: long + +-- + + + + +*`googlecloud.loadbalancing.https.backend_latencies.bucket_options.Options.ExponentialBuckets.growth_factor.value`*:: ++ +-- +type: double + +-- + +*`googlecloud.loadbalancing.https.backend_latencies.bucket_options.Options.ExponentialBuckets.scale.value`*:: ++ +-- +type: long + +-- + +*`googlecloud.loadbalancing.https.backend_latencies.bucket_options.Options.ExponentialBuckets.num_finite_buckets.value`*:: ++ +-- +type: long + +-- + +*`googlecloud.loadbalancing.https.backend_request_bytes_count.value`*:: ++ +-- +The number of bytes sent as requests from HTTP/S load balancer to backends. + +type: long + +-- + +*`googlecloud.loadbalancing.https.backend_request_count.value`*:: ++ +-- +The number of requests served by backends of HTTP/S load balancer. + +type: long + +-- + +*`googlecloud.loadbalancing.https.backend_response_bytes_count.value`*:: ++ +-- +The number of bytes sent as responses from backends (or cache) to HTTP/S load balancer. + +type: long + +-- + +[float] +=== frontend_tcp_rtt + +A distribution of the RTT measured for each connection between client and proxy. + + +*`googlecloud.loadbalancing.https.frontend_tcp_rtt.count.value`*:: ++ +-- +type: long + +-- + +*`googlecloud.loadbalancing.https.frontend_tcp_rtt.mean.value`*:: ++ +-- +type: long + +-- + +*`googlecloud.loadbalancing.https.frontend_tcp_rtt.bucket_counts.value`*:: ++ +-- +type: long + +-- + + + + +*`googlecloud.loadbalancing.https.frontend_tcp_rtt.bucket_options.Options.ExponentialBuckets.growth_factor.value`*:: ++ +-- +type: double + +-- + +*`googlecloud.loadbalancing.https.frontend_tcp_rtt.bucket_options.Options.ExponentialBuckets.scale.value`*:: ++ +-- +type: long + +-- + +*`googlecloud.loadbalancing.https.frontend_tcp_rtt.bucket_options.Options.ExponentialBuckets.num_finite_buckets.value`*:: ++ +-- +type: long + +-- + + +[float] +=== backend_latencies + +A distribution of the latency calculated from when the request was sent by the proxy to the backend until the proxy received from the backend the last byte of response. + + +*`googlecloud.loadbalancing.https.internal.backend_latencies.count.value`*:: ++ +-- +type: long + +-- + +*`googlecloud.loadbalancing.https.internal.backend_latencies.mean.value`*:: ++ +-- +type: long + +-- + +*`googlecloud.loadbalancing.https.internal.backend_latencies.bucket_counts.value`*:: ++ +-- +type: long + +-- + + + + +*`googlecloud.loadbalancing.https.internal.backend_latencies.bucket_options.Options.ExponentialBuckets.growth_factor.value`*:: ++ +-- +type: double + +-- + +*`googlecloud.loadbalancing.https.internal.backend_latencies.bucket_options.Options.ExponentialBuckets.scale.value`*:: ++ +-- +type: long + +-- + +*`googlecloud.loadbalancing.https.internal.backend_latencies.bucket_options.Options.ExponentialBuckets.num_finite_buckets.value`*:: ++ +-- +type: long + +-- + +*`googlecloud.loadbalancing.https.internal.request_bytes_count.value`*:: ++ +-- +The number of bytes sent as requests from clients to HTTP/S load balancer. + +type: long + +-- + +*`googlecloud.loadbalancing.https.internal.request_count.value`*:: ++ +-- +The number of requests served by HTTP/S load balancer. + +type: long + +-- + +*`googlecloud.loadbalancing.https.internal.response_bytes_count.value`*:: ++ +-- +The number of bytes sent as responses from HTTP/S load balancer to clients. + +type: long + +-- + +[float] +=== total_latencies + +A distribution of the latency calculated from when the request was received by the proxy until the proxy got ACK from client on last response byte. + + +*`googlecloud.loadbalancing.https.internal.total_latencies.count.value`*:: ++ +-- +type: long + +-- + +*`googlecloud.loadbalancing.https.internal.total_latencies.mean.value`*:: ++ +-- +type: long + +-- + +*`googlecloud.loadbalancing.https.internal.total_latencies.bucket_counts.value`*:: ++ +-- +type: long + +-- + + + + +*`googlecloud.loadbalancing.https.internal.total_latencies.bucket_options.Options.ExponentialBuckets.growth_factor.value`*:: ++ +-- +type: double + +-- + +*`googlecloud.loadbalancing.https.internal.total_latencies.bucket_options.Options.ExponentialBuckets.scale.value`*:: ++ +-- +type: long + +-- + +*`googlecloud.loadbalancing.https.internal.total_latencies.bucket_options.Options.ExponentialBuckets.num_finite_buckets.value`*:: ++ +-- +type: long + +-- + +*`googlecloud.loadbalancing.https.request_bytes_count.value`*:: ++ +-- +The number of bytes sent as requests from clients to HTTP/S load balancer. + +type: long + +-- + +*`googlecloud.loadbalancing.https.request_count.value`*:: ++ +-- +The number of requests served by HTTP/S load balancer. + +type: long + +-- + +*`googlecloud.loadbalancing.https.response_bytes_count.value`*:: ++ +-- +The number of bytes sent as responses from HTTP/S load balancer to clients. + +type: long + +-- + +[float] +=== total_latencies + +A distribution of the latency calculated from when the request was received by the proxy until the proxy got ACK from client on last response byte. + + +*`googlecloud.loadbalancing.https.total_latencies.count.value`*:: ++ +-- +type: long + +-- + +*`googlecloud.loadbalancing.https.total_latencies.mean.value`*:: ++ +-- +type: long + +-- + +*`googlecloud.loadbalancing.https.total_latencies.bucket_counts.value`*:: ++ +-- +type: long + +-- + + + + +*`googlecloud.loadbalancing.https.total_latencies.bucket_options.Options.ExponentialBuckets.growth_factor.value`*:: ++ +-- +type: double + +-- + +*`googlecloud.loadbalancing.https.total_latencies.bucket_options.Options.ExponentialBuckets.scale.value`*:: ++ +-- +type: long + +-- + +*`googlecloud.loadbalancing.https.total_latencies.bucket_options.Options.ExponentialBuckets.num_finite_buckets.value`*:: ++ +-- +type: long + +-- + +[float] +=== l3.internal + +Google Cloud Load Balancing metrics + + +*`googlecloud.loadbalancing.l3.internal.egress_bytes_count.value`*:: ++ +-- +The number of bytes sent from ILB backend to client (for TCP flows it's counting bytes on application stream only). + +type: long + +-- + +*`googlecloud.loadbalancing.l3.internal.egress_packets_count.value`*:: ++ +-- +The number of packets sent from ILB backend to client of the flow. + +type: long + +-- + +*`googlecloud.loadbalancing.l3.internal.ingress_bytes_count.value`*:: ++ +-- +The number of bytes sent from client to ILB backend (for TCP flows it's counting bytes on application stream only). + +type: long + +-- + +*`googlecloud.loadbalancing.l3.internal.ingress_packets_count.value`*:: ++ +-- +The number of packets sent from client to ILB backend. + +type: long + +-- + +[float] +=== rtt_latencies + +A distribution of RTT measured over TCP connections for ILB flows. + + +*`googlecloud.loadbalancing.l3.internal.rtt_latencies.count.value`*:: ++ +-- +type: long + +-- + +*`googlecloud.loadbalancing.l3.internal.rtt_latencies.mean.value`*:: ++ +-- +type: long + +-- + +*`googlecloud.loadbalancing.l3.internal.rtt_latencies.bucket_counts.value`*:: ++ +-- +type: long + +-- + + + + +*`googlecloud.loadbalancing.l3.internal.rtt_latencies.bucket_options.Options.ExponentialBuckets.growth_factor.value`*:: ++ +-- +type: double + +-- + +*`googlecloud.loadbalancing.l3.internal.rtt_latencies.bucket_options.Options.ExponentialBuckets.scale.value`*:: ++ +-- +type: long + +-- + +*`googlecloud.loadbalancing.l3.internal.rtt_latencies.bucket_options.Options.ExponentialBuckets.num_finite_buckets.value`*:: ++ +-- +type: long + +-- + +[float] +=== tcp_ssl_proxy + +Google Cloud Load Balancing metrics + + +*`googlecloud.loadbalancing.tcp_ssl_proxy.closed_connections.value`*:: ++ +-- +Number of connections that were terminated over TCP/SSL proxy. + +type: long + +-- + +*`googlecloud.loadbalancing.tcp_ssl_proxy.egress_bytes_count.value`*:: ++ +-- +Number of bytes sent from VM to client using proxy. + +type: long + +-- + +[float] +=== frontend_tcp_rtt + +A distribution of the smoothed RTT (in ms) measured by the proxy's TCP stack, each minute application layer bytes pass from proxy to client. + + +*`googlecloud.loadbalancing.tcp_ssl_proxy.frontend_tcp_rtt.count.value`*:: ++ +-- +type: long + +-- + +*`googlecloud.loadbalancing.tcp_ssl_proxy.frontend_tcp_rtt.mean.value`*:: ++ +-- +type: long + +-- + +*`googlecloud.loadbalancing.tcp_ssl_proxy.frontend_tcp_rtt.bucket_counts.value`*:: ++ +-- +type: long + +-- + + + + +*`googlecloud.loadbalancing.tcp_ssl_proxy.frontend_tcp_rtt.bucket_options.Options.ExponentialBuckets.growth_factor.value`*:: ++ +-- +type: double + +-- + +*`googlecloud.loadbalancing.tcp_ssl_proxy.frontend_tcp_rtt.bucket_options.Options.ExponentialBuckets.scale.value`*:: ++ +-- +type: long + +-- + +*`googlecloud.loadbalancing.tcp_ssl_proxy.frontend_tcp_rtt.bucket_options.Options.ExponentialBuckets.num_finite_buckets.value`*:: ++ +-- +type: long + +-- + +*`googlecloud.loadbalancing.tcp_ssl_proxy.ingress_bytes_count.value`*:: ++ +-- +Number of bytes sent from client to VM using proxy. + +type: long + +-- + +*`googlecloud.loadbalancing.tcp_ssl_proxy.new_connections.value`*:: ++ +-- +Number of connections that were created over TCP/SSL proxy. + +type: long + +-- + +*`googlecloud.loadbalancing.tcp_ssl_proxy.open_connections.value`*:: ++ +-- +Current number of outstanding connections through the TCP/SSL proxy. + +type: long + +-- + +[float] +=== pubsub + +Google Cloud PubSub metrics + + +[float] +=== subscription + +Suscription related metrics + + +*`googlecloud.pubsub.subscription.ack_message_count.value`*:: ++ +-- +Cumulative count of messages acknowledged by Acknowledge requests, grouped by delivery type. + +type: long + +-- + +*`googlecloud.pubsub.subscription.backlog_bytes.value`*:: ++ +-- +Total byte size of the unacknowledged messages (a.k.a. backlog messages) in a subscription. + +type: long + +-- + +*`googlecloud.pubsub.subscription.num_outstanding_messages.value`*:: ++ +-- +Number of messages delivered to a subscription's push endpoint, but not yet acknowledged. + +type: long + +-- + +*`googlecloud.pubsub.subscription.num_undelivered_messages.value`*:: ++ +-- +Number of unacknowledged messages (a.k.a. backlog messages) in a subscription. + +type: long + +-- + +*`googlecloud.pubsub.subscription.oldest_unacked_message_age.value`*:: ++ +-- +Age (in seconds) of the oldest unacknowledged message (a.k.a. backlog message) in a subscription. + +type: long + +-- + +*`googlecloud.pubsub.subscription.pull_ack_message_operation_count.value`*:: ++ +-- +Cumulative count of acknowledge message operations, grouped by result. For a definition of message operations, see Cloud Pub/Sub metric subscription/mod_ack_deadline_message_operation_count. + +type: long + +-- + +*`googlecloud.pubsub.subscription.pull_ack_request_count.value`*:: ++ +-- +Cumulative count of acknowledge requests, grouped by result. + +type: long + +-- + +*`googlecloud.pubsub.subscription.pull_message_operation_count.value`*:: ++ +-- +Cumulative count of pull message operations, grouped by result. For a definition of message operations, see Cloud Pub/Sub metric subscription/mod_ack_deadline_message_operation_count. + +type: long + +-- + +*`googlecloud.pubsub.subscription.pull_request_count.value`*:: ++ +-- +Cumulative count of pull requests, grouped by result. + +type: long + +-- + +*`googlecloud.pubsub.subscription.push_request_count.value`*:: ++ +-- +Cumulative count of push attempts, grouped by result. Unlike pulls, the push server implementation does not batch user messages. So each request only contains one user message. The push server retries on errors, so a given user message can appear multiple times. + +type: long + +-- + +*`googlecloud.pubsub.subscription.push_request_latencies.value`*:: ++ +-- +Distribution of push request latencies (in microseconds), grouped by result. + +type: long + +-- + +*`googlecloud.pubsub.subscription.sent_message_count.value`*:: ++ +-- +Cumulative count of messages sent by Cloud Pub/Sub to subscriber clients, grouped by delivery type. + +type: long + +-- + +*`googlecloud.pubsub.subscription.streaming_pull_ack_message_operation_count.value`*:: ++ +-- +Cumulative count of StreamingPull acknowledge message operations, grouped by result. For a definition of message operations, see Cloud Pub/Sub metric subscription/mod_ack_deadline_message_operation_count. + +type: long + +-- + +*`googlecloud.pubsub.subscription.streaming_pull_ack_request_count.value`*:: ++ +-- +Cumulative count of streaming pull requests with non-empty acknowledge ids, grouped by result. + +type: long + +-- + +*`googlecloud.pubsub.subscription.streaming_pull_message_operation_count.value`*:: ++ +-- +Cumulative count of streaming pull message operations, grouped by result. For a definition of message operations, see Cloud Pub/Sub metric subscription/mod_ack_deadline_message_operation_count + +type: long + +-- + +*`googlecloud.pubsub.subscription.streaming_pull_response_count.value`*:: ++ +-- +Cumulative count of streaming pull responses, grouped by result. + +type: long + +-- + +*`googlecloud.pubsub.subscription.dead_letter_message_count.value`*:: ++ +-- +Cumulative count of messages published to dead letter topic, grouped by result. + +type: long + +-- + +*`googlecloud.pubsub.subscription.mod_ack_deadline_message_count.value`*:: ++ +-- +Cumulative count of messages whose deadline was updated by ModifyAckDeadline requests, grouped by delivery type. + +type: long + +-- + +*`googlecloud.pubsub.subscription.mod_ack_deadline_message_operation_count.value`*:: ++ +-- +Cumulative count of ModifyAckDeadline message operations, grouped by result. + +type: long + +-- + +*`googlecloud.pubsub.subscription.mod_ack_deadline_request_count.value`*:: ++ +-- +Cumulative count of ModifyAckDeadline requests, grouped by result. + +type: long + +-- + +*`googlecloud.pubsub.subscription.oldest_retained_acked_message_age.value`*:: ++ +-- +Age (in seconds) of the oldest acknowledged message retained in a subscription. + +type: long + +-- + +*`googlecloud.pubsub.subscription.oldest_retained_acked_message_age_by_region.value`*:: ++ +-- +Age (in seconds) of the oldest acknowledged message retained in a subscription, broken down by Cloud region. + +type: long + +-- + +*`googlecloud.pubsub.subscription.oldest_unacked_message_age_by_region.value`*:: ++ +-- +Age (in seconds) of the oldest unacknowledged message in a subscription, broken down by Cloud region. + +type: long + +-- + +*`googlecloud.pubsub.subscription.retained_acked_bytes.value`*:: ++ +-- +Total byte size of the acknowledged messages retained in a subscription. + +type: long + +-- + +*`googlecloud.pubsub.subscription.retained_acked_bytes_by_region.value`*:: ++ +-- +Total byte size of the acknowledged messages retained in a subscription, broken down by Cloud region. + +type: long + +-- + +*`googlecloud.pubsub.subscription.seek_request_count.value`*:: ++ +-- +Cumulative count of seek attempts, grouped by result. + +type: long + +-- + +*`googlecloud.pubsub.subscription.streaming_pull_mod_ack_deadline_message_operation_count.value`*:: ++ +-- +Cumulative count of StreamingPull ModifyAckDeadline operations, grouped by result. + +type: long + +-- + +*`googlecloud.pubsub.subscription.streaming_pull_mod_ack_deadline_request_count.value`*:: ++ +-- +Cumulative count of streaming pull requests with non-empty ModifyAckDeadline fields, grouped by result. + +type: long + +-- + +*`googlecloud.pubsub.subscription.byte_cost.value`*:: ++ +-- +Cumulative cost of operations, measured in bytes. This is used to measure quota utilization. + +type: long + +-- + +*`googlecloud.pubsub.subscription.config_updates_count.value`*:: ++ +-- +Cumulative count of configuration changes for each subscription, grouped by operation type and result. + +type: long + +-- + +*`googlecloud.pubsub.subscription.unacked_bytes_by_region.value`*:: ++ +-- +Total byte size of the unacknowledged messages in a subscription, broken down by Cloud region. + +type: long + +-- + +[float] +=== topic + +Topic related metrics + + +*`googlecloud.pubsub.topic.streaming_pull_response_count.value`*:: ++ +-- +Cumulative count of streaming pull responses, grouped by result. + +type: long + +-- + +*`googlecloud.pubsub.topic.send_message_operation_count.value`*:: ++ +-- +Cumulative count of publish message operations, grouped by result. For a definition of message operations, see Cloud Pub/Sub metric subscription/mod_ack_deadline_message_operation_count. + +type: long + +-- + +*`googlecloud.pubsub.topic.send_request_count.value`*:: ++ +-- +Cumulative count of publish requests, grouped by result. + +type: long + +-- + +*`googlecloud.pubsub.topic.oldest_retained_acked_message_age_by_region.value`*:: ++ +-- +Age (in seconds) of the oldest acknowledged message retained in a topic, broken down by Cloud region. + +type: long + +-- + +*`googlecloud.pubsub.topic.oldest_unacked_message_age_by_region.value`*:: ++ +-- +Age (in seconds) of the oldest unacknowledged message in a topic, broken down by Cloud region. + +type: long + +-- + +*`googlecloud.pubsub.topic.retained_acked_bytes_by_region.value`*:: ++ +-- +Total byte size of the acknowledged messages retained in a topic, broken down by Cloud region. + +type: long + +-- + +*`googlecloud.pubsub.topic.byte_cost.value`*:: ++ +-- +Cost of operations, measured in bytes. This is used to measure utilization for quotas. + +type: long + +-- + +*`googlecloud.pubsub.topic.config_updates_count.value`*:: ++ +-- +Cumulative count of configuration changes, grouped by operation type and result. + +type: long + +-- + +*`googlecloud.pubsub.topic.message_sizes.value`*:: ++ +-- +Distribution of publish message sizes (in bytes) + +type: long + +-- + +*`googlecloud.pubsub.topic.unacked_bytes_by_region.value`*:: ++ +-- +Total byte size of the unacknowledged messages in a topic, broken down by Cloud region. + +type: long + +-- + +[float] +=== snapshot + +Snapshot related metrics + + +*`googlecloud.pubsub.snapshot.oldest_message_age.value`*:: ++ +-- +Age (in seconds) of the oldest message retained in a snapshot. + +type: long + +-- + +*`googlecloud.pubsub.snapshot.oldest_message_age_by_region.value`*:: ++ +-- +Age (in seconds) of the oldest message retained in a snapshot, broken down by Cloud region. + +type: long + +-- + +*`googlecloud.pubsub.snapshot.backlog_bytes.value`*:: ++ +-- +Total byte size of the messages retained in a snapshot. + +type: long + +-- + +*`googlecloud.pubsub.snapshot.backlog_bytes_by_region.value`*:: ++ +-- +Total byte size of the messages retained in a snapshot, broken down by Cloud region. + +type: long + +-- + +*`googlecloud.pubsub.snapshot.num_messages.value`*:: ++ +-- +Number of messages retained in a snapshot. + +type: long + +-- + +*`googlecloud.pubsub.snapshot.num_messages_by_region.value`*:: ++ +-- +Number of messages retained in a snapshot, broken down by Cloud region. + +type: long + +-- + +*`googlecloud.pubsub.snapshot.config_updates_count.value`*:: ++ +-- +Cumulative count of configuration changes, grouped by operation type and result. + +type: long + +-- + +[float] +=== storage + +Google Cloud Storage metrics + + + +*`googlecloud.storage.api.request_count.value`*:: ++ +-- +Delta count of API calls, grouped by the API method name and response code. + +type: long + +-- + + +*`googlecloud.storage.authz.acl_based_object_access_count.value`*:: ++ +-- +Delta count of requests that result in an object being granted access solely due to object ACLs. + +type: long + +-- + +*`googlecloud.storage.authz.acl_operations_count.value`*:: ++ +-- +Usage of ACL operations broken down by type. + +type: long + +-- + +*`googlecloud.storage.authz.object_specific_acl_mutation_count.value`*:: ++ +-- +Delta count of changes made to object specific ACLs. + +type: long + +-- + + +*`googlecloud.storage.network.received_bytes_count.value`*:: ++ +-- +Delta count of bytes received over the network, grouped by the API method name and response code. + +type: long + +-- + +*`googlecloud.storage.network.sent_bytes_count.value`*:: ++ +-- +Delta count of bytes sent over the network, grouped by the API method name and response code. + +type: long + +-- + + +*`googlecloud.storage.storage.object_count.value`*:: ++ +-- +Total number of objects per bucket, grouped by storage class. This value is measured once per day, and the value is repeated at each sampling interval throughout the day. + +type: long + +-- + +*`googlecloud.storage.storage.total_byte_seconds.value`*:: ++ +-- +Delta count of bytes received over the network, grouped by the API method name and response code. + +type: long + +-- + +*`googlecloud.storage.storage.total_bytes.value`*:: ++ +-- +Total size of all objects in the bucket, grouped by storage class. This value is measured once per day, and the value is repeated at each sampling interval throughout the day. + +type: long + +-- + [[exported-fields-graphite]] == Graphite fields diff --git a/metricbeat/docs/modules/googlecloud.asciidoc b/metricbeat/docs/modules/googlecloud.asciidoc index 19a741eb5d0..6e3d7e881d5 100644 --- a/metricbeat/docs/modules/googlecloud.asciidoc +++ b/metricbeat/docs/modules/googlecloud.asciidoc @@ -17,17 +17,23 @@ Note: extra GCP charges on Stackdriver Monitoring API requests will be generated This is a list of the possible module parameters you can tune: * *zone*: A single string with the zone you want to monitor like "us-central1-a". If you need to fetch from multiple regions, you have to setup a different configuration for each (but you don't need a new instance of Metricbeat running) + * *region*: A single string with the region you want to monitor like "us-central1". This will enable monitoring for all zones under this region. + * *project_id*: A single string with your GCP Project ID + * *credentials_file_path*: A single string pointing to the JSON file path reachable by Metricbeat that you have created using IAM. + * *exclude_labels*: (`true`/`false` default `false`) Do not extract extra labels and metadata information from Metricsets and fetch metrics onlly. At the moment, *labels and metadata extraction is only supported* in Compute Metricset. +* *period*: A single time duration specified for this module collection frequency. + [float] == Authentication, authorization and permissions. Authentication and authorization in Google Cloud Platform can be achieved in many ways. For the current version of the Google Cloud Platform module for Metricbeat, the only supported method is using Service Account JSON files. A typical JSON with a private key looks like this: [float] -==== Example Credentials +=== Example Credentials [source,json] ---- { @@ -62,7 +68,9 @@ Google Cloud Platform offers the https://cloud.google.com/monitoring/api/metrics If you also want to *extract service labels* (by setting `exclude_labels` to false, which is the default state). You also make a new API check on the corresponding service. Service labels requires a new API call to extract those metrics. In the worst case the number of API calls will be doubled. In the best case, all metrics come from the same GCP entity and 100% of the required information is included in the first API call (which is cached for subsequent calls). -A recommended `period` value between fetches is between 5 and 10 minutes, depending on how granular you want your metrics. GCP restricts information for less than 5 minutes. +If `period` value is set to 5-minute and sample period of the metric type is 60-second, then this module will collect data from this metric type once every 5 minutes with aggregation. +GCP monitoring data has a up to 240 seconds latency, which means latest monitoring data will be up to 4 minutes old. Please see https://cloud.google.com/monitoring/api/v3/latency-n-retention[Latency of GCP Monitoring Metric Data] for more details. +In googlecloud module, metrics are collected based on this ingest delay, which is also obtained from ListMetricDescriptors API. [float] === Rough estimation of the number of API Calls @@ -101,13 +109,21 @@ metricbeat.modules: - module: googlecloud metricsets: - compute + region: "us-central1" + project_id: "your project id" + credentials_file_path: "your JSON credentials file path" + exclude_labels: false + period: 300s + +- module: googlecloud + metricsets: - pubsub - loadbalancing zone: "us-central1-a" project_id: "your project id" credentials_file_path: "your JSON credentials file path" exclude_labels: false - period: 300s + period: 60s - module: googlecloud metricsets: @@ -130,6 +146,8 @@ The following metricsets are available: * <> +* <> + * <> include::googlecloud/compute.asciidoc[] @@ -138,5 +156,7 @@ include::googlecloud/loadbalancing.asciidoc[] include::googlecloud/pubsub.asciidoc[] +include::googlecloud/stackdriver.asciidoc[] + include::googlecloud/storage.asciidoc[] diff --git a/metricbeat/docs/modules/googlecloud/stackdriver.asciidoc b/metricbeat/docs/modules/googlecloud/stackdriver.asciidoc new file mode 100644 index 00000000000..16609f7b01e --- /dev/null +++ b/metricbeat/docs/modules/googlecloud/stackdriver.asciidoc @@ -0,0 +1,23 @@ +//// +This file is generated! See scripts/mage/docs_collector.go +//// + +[[metricbeat-metricset-googlecloud-stackdriver]] +=== Google Cloud Platform stackdriver metricset + +beta[] + +include::../../../../x-pack/metricbeat/module/googlecloud/stackdriver/_meta/docs.asciidoc[] + + +==== Fields + +For a description of each field in the metricset, see the +<> section. + +Here is an example document generated by this metricset: + +[source,json] +---- +include::../../../../x-pack/metricbeat/module/googlecloud/stackdriver/_meta/data.json[] +---- diff --git a/metricbeat/docs/modules_list.asciidoc b/metricbeat/docs/modules_list.asciidoc index 6bdb22404f5..fe03abd62a3 100644 --- a/metricbeat/docs/modules_list.asciidoc +++ b/metricbeat/docs/modules_list.asciidoc @@ -109,9 +109,10 @@ This file is generated! See scripts/mage/docs_collector.go .2+| .2+| |<> |<> |<> beta[] |image:./images/icon-yes.png[Prebuilt dashboards are available] | -.4+| .4+| |<> beta[] +.5+| .5+| |<> beta[] |<> beta[] |<> beta[] +|<> beta[] |<> beta[] |<> |image:./images/icon-no.png[No prebuilt dashboards] | .1+| .1+| |<> diff --git a/x-pack/metricbeat/metricbeat.reference.yml b/x-pack/metricbeat/metricbeat.reference.yml index c843bc364d7..3e751957bd3 100644 --- a/x-pack/metricbeat/metricbeat.reference.yml +++ b/x-pack/metricbeat/metricbeat.reference.yml @@ -498,13 +498,21 @@ metricbeat.modules: - module: googlecloud metricsets: - compute + region: "us-central1" + project_id: "your project id" + credentials_file_path: "your JSON credentials file path" + exclude_labels: false + period: 300s + +- module: googlecloud + metricsets: - pubsub - loadbalancing zone: "us-central1-a" project_id: "your project id" credentials_file_path: "your JSON credentials file path" exclude_labels: false - period: 300s + period: 60s - module: googlecloud metricsets: diff --git a/x-pack/metricbeat/module/googlecloud/_meta/config.yml b/x-pack/metricbeat/module/googlecloud/_meta/config.yml index 5df057bba18..e717a98ee6d 100644 --- a/x-pack/metricbeat/module/googlecloud/_meta/config.yml +++ b/x-pack/metricbeat/module/googlecloud/_meta/config.yml @@ -1,13 +1,21 @@ - module: googlecloud metricsets: - compute + region: "us-central1" + project_id: "your project id" + credentials_file_path: "your JSON credentials file path" + exclude_labels: false + period: 300s + +- module: googlecloud + metricsets: - pubsub - loadbalancing zone: "us-central1-a" project_id: "your project id" credentials_file_path: "your JSON credentials file path" exclude_labels: false - period: 300s + period: 60s - module: googlecloud metricsets: diff --git a/x-pack/metricbeat/module/googlecloud/_meta/docs.asciidoc b/x-pack/metricbeat/module/googlecloud/_meta/docs.asciidoc index ff4b03cb023..a02a1b80979 100644 --- a/x-pack/metricbeat/module/googlecloud/_meta/docs.asciidoc +++ b/x-pack/metricbeat/module/googlecloud/_meta/docs.asciidoc @@ -7,17 +7,23 @@ Note: extra GCP charges on Stackdriver Monitoring API requests will be generated This is a list of the possible module parameters you can tune: * *zone*: A single string with the zone you want to monitor like "us-central1-a". If you need to fetch from multiple regions, you have to setup a different configuration for each (but you don't need a new instance of Metricbeat running) + * *region*: A single string with the region you want to monitor like "us-central1". This will enable monitoring for all zones under this region. + * *project_id*: A single string with your GCP Project ID + * *credentials_file_path*: A single string pointing to the JSON file path reachable by Metricbeat that you have created using IAM. + * *exclude_labels*: (`true`/`false` default `false`) Do not extract extra labels and metadata information from Metricsets and fetch metrics onlly. At the moment, *labels and metadata extraction is only supported* in Compute Metricset. +* *period*: A single time duration specified for this module collection frequency. + [float] == Authentication, authorization and permissions. Authentication and authorization in Google Cloud Platform can be achieved in many ways. For the current version of the Google Cloud Platform module for Metricbeat, the only supported method is using Service Account JSON files. A typical JSON with a private key looks like this: [float] -==== Example Credentials +=== Example Credentials [source,json] ---- { @@ -52,7 +58,9 @@ Google Cloud Platform offers the https://cloud.google.com/monitoring/api/metrics If you also want to *extract service labels* (by setting `exclude_labels` to false, which is the default state). You also make a new API check on the corresponding service. Service labels requires a new API call to extract those metrics. In the worst case the number of API calls will be doubled. In the best case, all metrics come from the same GCP entity and 100% of the required information is included in the first API call (which is cached for subsequent calls). -A recommended `period` value between fetches is between 5 and 10 minutes, depending on how granular you want your metrics. GCP restricts information for less than 5 minutes. +If `period` value is set to 5-minute and sample period of the metric type is 60-second, then this module will collect data from this metric type once every 5 minutes with aggregation. +GCP monitoring data has a up to 240 seconds latency, which means latest monitoring data will be up to 4 minutes old. Please see https://cloud.google.com/monitoring/api/v3/latency-n-retention[Latency of GCP Monitoring Metric Data] for more details. +In googlecloud module, metrics are collected based on this ingest delay, which is also obtained from ListMetricDescriptors API. [float] === Rough estimation of the number of API Calls diff --git a/x-pack/metricbeat/module/googlecloud/_meta/fields.yml b/x-pack/metricbeat/module/googlecloud/_meta/fields.yml index 8bbae3bf4fe..41c1c09fad9 100644 --- a/x-pack/metricbeat/module/googlecloud/_meta/fields.yml +++ b/x-pack/metricbeat/module/googlecloud/_meta/fields.yml @@ -4,14 +4,23 @@ description: > GCP module fields: - - name: googlecloud.labels - type: object + - name: googlecloud + type: group fields: - - name: user.* + - name: labels type: object - - name: metadata.* - type: object - - name: metrics.* - type: object - - name: system.* + fields: + - name: user.* + type: object + - name: metadata.* + type: object + - name: metrics.* + type: object + - name: system.* + type: object + - name: "stackdriver.*.*.*.*" type: object + object_type: double + object_type_mapping_type: "*" + description: > + Metrics that returned from StackDriver API query. diff --git a/x-pack/metricbeat/module/googlecloud/compute/_meta/data.json b/x-pack/metricbeat/module/googlecloud/compute/_meta/data.json index cd37490aa90..f1f9f8f386b 100644 --- a/x-pack/metricbeat/module/googlecloud/compute/_meta/data.json +++ b/x-pack/metricbeat/module/googlecloud/compute/_meta/data.json @@ -5,8 +5,8 @@ "id": "elastic-observability" }, "instance": { - "id": "9077240380975650630", - "name": "gke-observability-8--observability-8--bc1afd95-cwh3" + "id": "6889336735612324102", + "name": "gke-dev-oblt-dev-oblt-pool-83a8831b-kd53" }, "machine": { "type": "n1-standard-4" @@ -23,16 +23,24 @@ "compute": { "instance": { "disk": { - "read_bytes_count": 0, - "read_ops_count": 0, - "write_bytes_count": 0, - "write_ops_count": 0 + "read_bytes_count": { + "value": 0 + }, + "read_ops_count": { + "value": 0 + }, + "write_bytes_count": { + "value": 0 + }, + "write_ops_count": { + "value": 0 + } } } }, "labels": { "metrics": { - "device_name": "gke-observability-8-0--pvc-ad47fe58-7bcf-11e9-a839-42010a8401a4", + "device_name": "disk-4", "device_type": "permanent", "storage_type": "pd-standard" }, diff --git a/x-pack/metricbeat/module/googlecloud/compute/_meta/data_network_01.json b/x-pack/metricbeat/module/googlecloud/compute/_meta/data_network_01.json deleted file mode 100644 index 26c21d62295..00000000000 --- a/x-pack/metricbeat/module/googlecloud/compute/_meta/data_network_01.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "@timestamp": "2020-01-08T16:04:00.000Z", - "@metadata": { - "beat": "metricbeat", - "type": "_doc", - "version": "8.0.0" - }, - "cloud": { - "machine": { - "type": "f1-micro" - }, - "availability_zone": "us-central1-a", - "account": { - "id": "elastic-metricbeat" - }, - "provider": "googlecloud", - "instance": { - "id": "4503798379141677974", - "name": "instance-1" - } - }, - "ecs": { - "version": "1.2.0" - }, - "host": { - "name": "mcastro", - "hostname": "mcastro", - "architecture": "x86_64", - "os": { - "kernel": "5.4.3-arch1-1", - "platform": "antergos", - "version": "", - "family": "", - "name": "Antergos Linux" - }, - "id": "54f70115bae545cbac2b150f254472a0", - "containerized": false - }, - "agent": { - "hostname": "mcastro", - "id": "7e36a073-1a32-4a94-b65b-4c7f971fb228", - "version": "8.0.0", - "type": "metricbeat", - "ephemeral_id": "8b802033-b611-414b-bcaf-1aa19e5f5901" - }, - "event": { - "duration": 1397728251, - "dataset": "googlecloud.compute", - "module": "googlecloud" - }, - "metricset": { - "name": "compute", - "period": 300000 - }, - "googlecloud": { - "labels": { - "metrics": { - "loadbalanced": "false" - } - }, - "compute": { - "instance": { - "network": { - "received_bytes_count": 3846, - "sent_bytes_count": 1750, - "received_packets_count": 17 - } - } - } - }, - "service": { - "type": "googlecloud" - } -} diff --git a/x-pack/metricbeat/module/googlecloud/compute/_meta/data_network_02.json b/x-pack/metricbeat/module/googlecloud/compute/_meta/data_network_02.json deleted file mode 100644 index cc71eda5683..00000000000 --- a/x-pack/metricbeat/module/googlecloud/compute/_meta/data_network_02.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "@timestamp": "2020-01-08T16:05:00.000Z", - "@metadata": { - "beat": "metricbeat", - "type": "_doc", - "version": "8.0.0" - }, - "event": { - "dataset": "googlecloud.compute", - "module": "googlecloud", - "duration": 1398297740 - }, - "metricset": { - "name": "compute", - "period": 300000 - }, - "googlecloud": { - "labels": { - "metrics": { - "loadbalanced": "false" - } - }, - "compute": { - "instance": { - "network": { - "sent_bytes_count": 3977 - } - } - } - }, - "service": { - "type": "googlecloud" - }, - "ecs": { - "version": "1.2.0" - }, - "host": { - "containerized": false, - "name": "mcastro", - "hostname": "mcastro", - "architecture": "x86_64", - "os": { - "family": "", - "name": "Antergos Linux", - "kernel": "5.4.3-arch1-1", - "platform": "antergos", - "version": "" - }, - "id": "54f70115bae545cbac2b150f254472a0" - }, - "agent": { - "id": "7e36a073-1a32-4a94-b65b-4c7f971fb228", - "version": "8.0.0", - "type": "metricbeat", - "ephemeral_id": "8b802033-b611-414b-bcaf-1aa19e5f5901", - "hostname": "mcastro" - }, - "cloud": { - "account": { - "id": "elastic-metricbeat" - }, - "provider": "googlecloud", - "instance": { - "id": "4503798379141677974", - "name": "instance-1" - }, - "machine": { - "type": "f1-micro" - }, - "availability_zone": "us-central1-a" - } -} diff --git a/x-pack/metricbeat/module/googlecloud/compute/_meta/fields.yml b/x-pack/metricbeat/module/googlecloud/compute/_meta/fields.yml index 01be5ebf386..5cbdfb3ea56 100644 --- a/x-pack/metricbeat/module/googlecloud/compute/_meta/fields.yml +++ b/x-pack/metricbeat/module/googlecloud/compute/_meta/fields.yml @@ -9,54 +9,54 @@ - name: firewall type: group fields: - - name: dropped_bytes_count + - name: dropped_bytes_count.value type: long description: Incoming bytes dropped by the firewall - - name: dropped_packets_count + - name: dropped_packets_count.value type: long description: Incoming packets dropped by the firewall - name: cpu type: group fields: - - name: reserved_cores + - name: reserved_cores.value type: double description: Number of cores reserved on the host of the instance - - name: utilization + - name: utilization.value type: double description: The fraction of the allocated CPU that is currently in use on the instance - - name: usage_time + - name: usage_time.value type: double description: Usage for all cores in seconds - name: disk type: group fields: - - name: read_bytes_count + - name: read_bytes_count.value type: long description: Count of bytes read from disk - - name: read_ops_count + - name: read_ops_count.value type: long description: Count of disk read IO operations - - name: write_bytes_count + - name: write_bytes_count.value type: long description: Count of bytes written to disk - - name: write_ops_count + - name: write_ops_count.value type: long description: Count of disk write IO operations - - name: uptime + - name: uptime.value type: long description: How long the VM has been running, in seconds - name: network type: group fields: - - name: received_bytes_count + - name: received_bytes_count.value type: long description: Count of bytes received from the network - - name: received_packets_count + - name: received_packets_count.value type: long description: Count of packets received from the network - - name: sent_bytes_count + - name: sent_bytes_count.value type: long description: Count of bytes sent over the network - - name: sent_packets_count + - name: sent_packets_count.value type: long description: Count of packets sent over the network diff --git a/x-pack/metricbeat/module/googlecloud/compute/compute_integration_test.go b/x-pack/metricbeat/module/googlecloud/compute/compute_integration_test.go index ae9400fdeb3..a4d47d95dd6 100644 --- a/x-pack/metricbeat/module/googlecloud/compute/compute_integration_test.go +++ b/x-pack/metricbeat/module/googlecloud/compute/compute_integration_test.go @@ -11,11 +11,11 @@ import ( "testing" mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing" - "github.com/elastic/beats/v7/x-pack/metricbeat/module/googlecloud" + "github.com/elastic/beats/v7/x-pack/metricbeat/module/googlecloud/stackdriver" ) func TestData(t *testing.T) { - config := googlecloud.GetConfigForTest(t, "compute") + config := stackdriver.GetConfigForTest(t, "compute") metricSet := mbtest.NewFetcher(t, config) metricSet.WriteEvents(t, "/") } diff --git a/x-pack/metricbeat/module/googlecloud/compute/manifest.yml b/x-pack/metricbeat/module/googlecloud/compute/manifest.yml index 03b16dcd440..34210db8c0e 100644 --- a/x-pack/metricbeat/module/googlecloud/compute/manifest.yml +++ b/x-pack/metricbeat/module/googlecloud/compute/manifest.yml @@ -6,16 +6,17 @@ input: stackdriver: service: compute metrics: - - "compute.googleapis.com/firewall/dropped_bytes_count" - - "compute.googleapis.com/firewall/dropped_packets_count" - - "compute.googleapis.com/instance/cpu/reserved_cores" - - "compute.googleapis.com/instance/cpu/usage_time" - - "compute.googleapis.com/instance/cpu/utilization" - - "compute.googleapis.com/instance/disk/read_bytes_count" - - "compute.googleapis.com/instance/disk/read_ops_count" - - "compute.googleapis.com/instance/disk/write_bytes_count" - - "compute.googleapis.com/instance/disk/write_ops_count" - - "compute.googleapis.com/instance/network/received_bytes_count" - - "compute.googleapis.com/instance/network/received_packets_count" - - "compute.googleapis.com/instance/network/sent_bytes_count" - - "compute.googleapis.com/instance/uptime" + - metric_types: + - "compute.googleapis.com/firewall/dropped_bytes_count" + - "compute.googleapis.com/firewall/dropped_packets_count" + - "compute.googleapis.com/instance/cpu/reserved_cores" + - "compute.googleapis.com/instance/cpu/usage_time" + - "compute.googleapis.com/instance/cpu/utilization" + - "compute.googleapis.com/instance/disk/read_bytes_count" + - "compute.googleapis.com/instance/disk/read_ops_count" + - "compute.googleapis.com/instance/disk/write_bytes_count" + - "compute.googleapis.com/instance/disk/write_ops_count" + - "compute.googleapis.com/instance/network/received_bytes_count" + - "compute.googleapis.com/instance/network/received_packets_count" + - "compute.googleapis.com/instance/network/sent_bytes_count" + - "compute.googleapis.com/instance/uptime" diff --git a/x-pack/metricbeat/module/googlecloud/constants.go b/x-pack/metricbeat/module/googlecloud/constants.go index b5ded8bf9bc..19b7e27c53d 100644 --- a/x-pack/metricbeat/module/googlecloud/constants.go +++ b/x-pack/metricbeat/module/googlecloud/constants.go @@ -4,16 +4,14 @@ package googlecloud +import monitoringpb "google.golang.org/genproto/googleapis/monitoring/v3" + const ( // ModuleName in Metricbeat ModuleName = "googlecloud" - // MinTimeIntervalDataWindowMinutes is the minimum time in minutes that we allow the user to specify when requesting past metrics. Less than 5 minutes - // usually return no results. - MinTimeIntervalDataWindowMinutes = 5 - - // MaxTimeIntervalDataWindowMinutes is the max time in minutes that we allow the user to specify when requesting past metrics. - MaxTimeIntervalDataWindowMinutes = 60 + // MonitoringMetricsSamplingRate (in second) refers to how frequent monitoring collects measurement in GCP. + MonitoringMetricsSamplingRate = 60 ) // Metricsets / GCP services names @@ -73,3 +71,53 @@ const ( LabelUser = "user" LabelMetadata = "metadata" ) + +// Available perSeriesAligner map +// https://cloud.google.com/monitoring/api/ref_v3/rest/v3/projects.alertPolicies#Aligner +var AlignersMapToGCP = map[string]monitoringpb.Aggregation_Aligner{ + "ALIGN_NONE": monitoringpb.Aggregation_ALIGN_NONE, + "ALIGN_DELTA": monitoringpb.Aggregation_ALIGN_DELTA, + "ALIGN_RATE": monitoringpb.Aggregation_ALIGN_RATE, + "ALIGN_INTERPOLATE": monitoringpb.Aggregation_ALIGN_INTERPOLATE, + "ALIGN_NEXT_OLDER": monitoringpb.Aggregation_ALIGN_NEXT_OLDER, + "ALIGN_MIN": monitoringpb.Aggregation_ALIGN_MIN, + "ALIGN_MAX": monitoringpb.Aggregation_ALIGN_MAX, + "ALIGN_MEAN": monitoringpb.Aggregation_ALIGN_MEAN, + "ALIGN_COUNT": monitoringpb.Aggregation_ALIGN_COUNT, + "ALIGN_SUM": monitoringpb.Aggregation_ALIGN_SUM, + "ALIGN_STDDEV": monitoringpb.Aggregation_ALIGN_STDDEV, + "ALIGN_COUNT_TRUE": monitoringpb.Aggregation_ALIGN_COUNT_TRUE, + "ALIGN_COUNT_FALSE": monitoringpb.Aggregation_ALIGN_COUNT_FALSE, + "ALIGN_FRACTION_TRUE": monitoringpb.Aggregation_ALIGN_FRACTION_TRUE, + "ALIGN_PERCENTILE_99": monitoringpb.Aggregation_ALIGN_PERCENTILE_99, + "ALIGN_PERCENTILE_95": monitoringpb.Aggregation_ALIGN_PERCENTILE_95, + "ALIGN_PERCENTILE_50": monitoringpb.Aggregation_ALIGN_PERCENTILE_50, + "ALIGN_PERCENTILE_05": monitoringpb.Aggregation_ALIGN_PERCENTILE_05, + "ALIGN_PERCENT_CHANGE": monitoringpb.Aggregation_ALIGN_PERCENT_CHANGE, +} + +const ( + DefaultAligner = "ALIGN_NONE" +) + +var AlignersMapToSuffix = map[string]string{ + "ALIGN_NONE": ".value", + "ALIGN_DELTA": ".delta", + "ALIGN_RATE": ".rate", + "ALIGN_INTERPOLATE": ".interpolate", + "ALIGN_NEXT_OLDER": ".next_older", + "ALIGN_MIN": ".min", + "ALIGN_MAX": ".max", + "ALIGN_MEAN": ".avg", + "ALIGN_COUNT": ".count", + "ALIGN_SUM": ".sum", + "ALIGN_STDDEV": ".stddev", + "ALIGN_COUNT_TRUE": ".count_true", + "ALIGN_COUNT_FALSE": ".count_false", + "ALIGN_FRACTION_TRUE": ".fraction_true", + "ALIGN_PERCENTILE_99": ".percentile_99", + "ALIGN_PERCENTILE_95": ".percentile_95", + "ALIGN_PERCENTILE_50": ".percentile_50", + "ALIGN_PERCENTILE_05": ".percentile_05", + "ALIGN_PERCENT_CHANGE": ".percent_change", +} diff --git a/x-pack/metricbeat/module/googlecloud/fields.go b/x-pack/metricbeat/module/googlecloud/fields.go index 8fac4947787..6d629ce05c2 100644 --- a/x-pack/metricbeat/module/googlecloud/fields.go +++ b/x-pack/metricbeat/module/googlecloud/fields.go @@ -19,5 +19,5 @@ func init() { // AssetGooglecloud returns asset data. // This is the base64 encoded gzipped contents of module/googlecloud. func AssetGooglecloud() string { - return "eJzsXUuT2zYSvvtX9M321nh82JtrK1WTyW7iWnsztTPJVQWCLQk7IMDgMYry67cAkBQp8QFSpPwo3WYkEv11A/i+bgCk3sEz7j/ARsoNR8qlTV8BGGY4foDXP/tP4d59DA+cmLVU2etXAAo5Eo0fIEFDXgGkqKliuWFSfIAfXgEA/Hz/AJlMLcdXAGuGPNUf/BfvQJAMGyZvOUmQa/81gNnn+AFk8j+kpviofn+9DatR3f6t+rj13sPVGRqSEkNG3aEY1fE36L02mMVfT2WWW4O1y49D22xmo6TNa582At/orvvQculC7Z7jYNbxMKENERQbX3YZ72qs3uCaKdwRzk8u6Gu0r+F646mSeY7pKtkb1CsqrTCt15e2uBSbjgsagfwoqMyY2IBvuDQDyR7MFvtcOoWWE/qMZhFwRdPR8Koxl9tFekOhRvWC6YpKhbrX11TahB8PslZv/2OzBBXINfhWKyMghfd2K7Vx37q/OwZvE6U1jLO/iGt9JohPLuiKUPdfCYVwLikxmML9w29gtsQA00CtUigM3wMTjrtKJ+KAa7LBlWFZF6ixuH9zDcJaKoe2CC8ToJFKkZ72XzWymX5eaPyQ+afyvWvJ9UqYys4GrJXMutw4giPzRcA44wHLx19B5qj8eGyfMyWenWIGl46PM2JQgJHDAQqAloyQtzAQomp65B1ToxdAw/gvcuev83Py98+wJRoSRAHKCsHE5iZmegg0O6mWmiEU2csCgncyS4KdMFNcNLq9akE3v+ZV+ErNm4ZQozBLx87ZAPmCahyqC8SsH1mJhkuSJoQTQVnDzlx56SdJUvixNDAyPd0akx9Pve5pdQaILiB1MImLq0hXnBgUlLWmPA0Id47VjGKJracK4fY9UMKp5T5n8IN6t8WQHSj8w6I2sCNFHxaJXq7kn3vH1O6fAgxYYRivfX06Ucorg21t/Lh1YBTqXAqNt4tw1xkD+1CNkf6cbbCFxLqpECZZf4Ya25TMu5V7KGwwELq6uV977cTZirFXt/nPP3MpUBhG+I/e2z7z8RBiYdShbJTcme1qTaiRavCuqFz41IimJOLqCB4+bVrYbLVmwmdwUaEcsHPMQQVHDIhbfCrkahpRlV41ZSO65CMdKOWXp6eH949eNiDohhMYWSLTp2zShX1u1BXOomJM9hUo93Ub8BiwgSQvFOlgrAh1hf6NVEAJ3eJbF+lxjqyVFMZ5Ymi+UqYNfIRo/ffpydGxtspJi1SAhG6BSiEw1MEJmp1LnSln3hmRBkG6istVXK7iMtj4VyEuTBhUgsy5djsmd/5+sufhwds/ZGNoLmLkRFDdiFZi6G58c/2UF8cCw/M/nvjieSeOdSbT3xgCjKfA6SQ4kgbHE+EoKjyTDAdsHVa4YrLsQeATM+2QSun4lO8U+BKQW9Ls8fCikuo5A9tIrLtqmCLifdCNNIRfWMYqVWpI2bF2baSBu/t/18cOSBFEq/Tfh+WqWGc1d1Wss4HEg4GrYkXYGqdXS6wKjdaqWKWacTFoLLAvt/AzWp/i1ekb1KbrclFHU9flotEQYmHAdbmo1U61a/z3247Vogtvz5aAcKNQ616y7gldHFF79vr46cfDolBJyfBmLRU83T/AmsudBmZe68A6h1OFUgDJc86oP9gC2igkGUjB92+PWe/Iqf6zAtPcahwS6HGs0ATnVhdKJi4V+wKTkQ2wy8S+9OoywW/1rAuaMqZH4wcUvrGT40+HuNAddnK0395xKHw4TwW5j2iHF4In1vqDMhx197AEj2mmT37nqaaHhHfeUnOk5C5TdY4T2xFSO05oo2X2DJEdkFhD85XWfOUz6C8rsrXpy6X2J74rujirJqqf9j4QkD8+vUOFYFBlTPgSpKSq94+Pn7q2lkckA9NQHgvS759rUmm1P6XfD22e7XidSWm2mHo2f8MEZPrtgdXrpdhr7fldG0Kfb8KufcaENdgQRE72qArvcqKLMrTaMgsOXmuza212rc0GG/9KtvIvRYGHzPX3z3EUKHB3MQGhCkerh8xRzAbwPjwCVEv+pTXaEJG6SDVBK2k3W0/dHUhLhLlNtE1qpuc6rP1gk0ebjDykrW1StTctT3m01T/OFd9j0/ITQp9XGWr/6NT5o/7eZpYTw14wyJTrvqJ17UwJueOYboLk3h3+r1afb4Lv4YIUOXtBtfcA+k8ecrkJU/e8FWdpCA8HVjT7C8vkwYoG9MqhN+T2+ZbclgCqL94CE0Aa3dwzuW22qo3wsjPmmuUV2CKY6NcqmuBea8it3gKKNJdMmBtIrAEhDezRNLqt3w0rKiNzu7FoF0ieojYrb+MAfUU25z0tdbdBn2oWD0S9LYdTMNfhUpdHoxzKLeer+syuHg9baI7XPKkcOTyS1pjUCrXl5hb+JRUQSNGLeZGpt92qsca17w9k2wjF+0ym3uEUScqZwC7PI2I23wbbUKRaSa+ITz/OS/WrM/ZNd+iynenDM7EX9XZxcHoLxBjM8nZw8Jvg7Bm9F/omVMDuHr8BrIBlOccMhQkVbypRe01IiKFb/2KJim1v4VGGUrnc6ZSC712yZggTGqTAxg23fnm3bky5IRDWnVEpqdw4cTK1YS8oGvcCJX5tGomCzHLDco5gWIY9u7yNaPdt9sZH/KejRQbvTOl9ZSIsNTCqZCkCo0aJf/DxQtlZefS3OTWNLKelE+JiO31KkhZ2EFyCc3FxeixNP7jp+h1IVUssl6WSymCT8WDHzBaEFO8cx+wboWXpOEI88ulSY+PIs0sNiH9QmeIPk4ZFbASr4zgXGhLFeZxRne58XXE0BtWlaC63CWd6G8ogZx+CfTAyZ3QU+s4eW9iF3VZqhNKqP1Rk89SvASR7+CxTtt7f0eefygvOqa0nj8pzvT11I25uxnuyLGVGdsMQ6qI0VegSKfQeXK5Cba1PSygTCuxuL1bJfqVw0/7KoS/lzw0kSj6jS3134pAaBZxTVhMu42XHusJc7h114mKLbu3rPVNGXxvimbpiJuwTu0IjLp75IT73FpHRCd2XEpJm9n9KyxPlZMi9ryIhP/U2bAeM8tSN7hWVekYvwhvp6pGvtuKZCBtmt/C0ZRqYdsW/T9aKS+APKw2pv6WuGzqVYs02q5AdzbGr19YXwYgNrgDdEuEme/XIf3Oe1+Jeue/t+xcADHVFqSkXoLCuBfdzuOtw6D5ndNrG15O79cwtr2+wUNIo0gsu+voS6ZtdHZn3lSl9EVompf9qkuGiJP7usuBz/Po6k8lzPJpJ388T9Zqce+n0It+zov/llP170fCxQ6aiV0FyvZXH0Y49uFLcfaaEF+xyoXWQjqWCwpVBDrw49/XjnUoTS5+y6aqTB8PcQLbsjBrAODG0wmaLHfsZG8w6lpliGY1qYvi+HS1413nu0P/GhD9znir2gvWTuWOPJMb/rsGhIJKqSaFzHY98DC2PPB9JchatLv0POfaXAbHPov2E3JDDULh7+AiU+DMTte539OC+ydBsZertl2MgPK1NZYrtkkqs2f51psf1E518lRCN6Sr8pMaKUIp6jnlxFIdq1cmfHg5j3U9oUfyYByTo6t6NIsJJfcABWnLke0gtugSwuPLu/lNPtudcOiSWM3gS3uzv+vL+Uy1lPeaf/r2xIrw6R8rWjK4cysyauUrzo2iXC0sZSeuBK623RPBwfLztFdvTB1jkS9Yne3r0ivXjF3KfO+vg+IDNom60vlN8LuI4Je7zerYY0TO8usOnT7Uj/L5hDTmq4nmfRggKP4Byossi8YVwi65UPDx7Kyj6FlKyv/EhcqGrrlOYh0cXiClWXkmWc0dA/sH7F8LLJwakNf7WlPQ81BDeDOKL465fNPg2xvjBkTnS9jIbJpxXvcrCW06+so79fwAAAP//qGqEJg==" + return "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" } diff --git a/x-pack/metricbeat/module/googlecloud/loadbalancing/_meta/data.json b/x-pack/metricbeat/module/googlecloud/loadbalancing/_meta/data.json index 2106cb1e277..f9335292924 100644 --- a/x-pack/metricbeat/module/googlecloud/loadbalancing/_meta/data.json +++ b/x-pack/metricbeat/module/googlecloud/loadbalancing/_meta/data.json @@ -14,120 +14,30 @@ "googlecloud": { "labels": { "metrics": { - "cache_result": "DISABLED", - "proxy_continent": "Europe", - "response_code": "502", - "response_code_class": "500" + "client_network": "ocp-be-c5kjr-network", + "client_subnetwork": "ocp-be-c5kjr-worker-subnet", + "client_zone": "us-central1-a" }, "resource": { - "backend_name": "INVALID_BACKEND", - "backend_scope": "INVALID_BACKEND", - "backend_scope_type": "INVALID_BACKEND", - "backend_target_name": "test1-backend-ks", + "backend_name": "ocp-be-c5kjr-master-us-central1-a", + "backend_scope": "us-central1-a", + "backend_scope_type": "ZONE", + "backend_subnetwork_name": "ocp-be-c5kjr-master-subnet", + "backend_target_name": "ocp-be-c5kjr-api-internal", "backend_target_type": "BACKEND_SERVICE", - "backend_type": "INVALID_BACKEND", - "forwarding_rule_name": "test-lb-ks-forwarding-rule", - "matched_url_path_rule": "UNMATCHED", - "region": "global", - "target_proxy_name": "test-lb-ks-target-proxy", - "url_map_name": "test-lb-ks" + "backend_type": "INSTANCE_GROUP", + "forwarding_rule_name": "ocp-be-c5kjr-api-internal", + "load_balancer_name": "ocp-be-c5kjr-api-internal", + "network_name": "ocp-be-c5kjr-network", + "region": "us-central1" } }, "loadbalancing": { - "https": { - "backend_latencies": { - "count": 4, - "mean": 97.927, - "bucket_options": { - "Options": { - "ExponentialBuckets": { - "num_finite_buckets": 66, - "growth_factor": 1.4, - "scale": 1 - } - } - }, - "bucket_counts": [ - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 4 - ] - }, - "backend_request_bytes_count": 736, - "backend_request_count": 4, - "backend_response_bytes_count": 1952, - "frontend_tcp_rtt": { - "count": 4, - "mean": 50, - "bucket_options": { - "Options": { - "ExponentialBuckets": { - "num_finite_buckets": 66, - "growth_factor": 1.4, - "scale": 1 - } - } - }, - "bucket_counts": [ - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 4 - ] - }, - "request_bytes_count": 736, - "request_count": 4, - "response_bytes_count": 1952, - "total_latencies": { - "count": 4, - "mean": 98.423, - "bucket_options": { - "Options": { - "ExponentialBuckets": { - "num_finite_buckets": 66, - "growth_factor": 1.4, - "scale": 1 - } - } - }, - "bucket_counts": [ - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 4 - ] + "l3": { + "internal": { + "egress_packets_count": { + "value": 0 + } } } } diff --git a/x-pack/metricbeat/module/googlecloud/loadbalancing/_meta/fields.yml b/x-pack/metricbeat/module/googlecloud/loadbalancing/_meta/fields.yml index 57761c8ea1b..93855f7ee9c 100644 --- a/x-pack/metricbeat/module/googlecloud/loadbalancing/_meta/fields.yml +++ b/x-pack/metricbeat/module/googlecloud/loadbalancing/_meta/fields.yml @@ -11,11 +11,11 @@ description: A distribution of the latency calculated from when the request was sent by the proxy to the backend until the proxy received from the backend the last byte of response. type: group fields: - - name: count + - name: count.value type: long - - name: mean + - name: mean.value type: long - - name: bucket_counts + - name: bucket_counts.value type: long - name: bucket_options type: group @@ -26,30 +26,30 @@ - name: ExponentialBuckets type: group fields: - - name: growth_factor + - name: growth_factor.value type: double - - name: scale + - name: scale.value type: long - - name: num_finite_buckets + - name: num_finite_buckets.value type: long - - name: backend_request_bytes_count + - name: backend_request_bytes_count.value type: long description: The number of bytes sent as requests from HTTP/S load balancer to backends. - - name: backend_request_count + - name: backend_request_count.value type: long description: The number of requests served by backends of HTTP/S load balancer. - - name: backend_response_bytes_count + - name: backend_response_bytes_count.value type: long description: The number of bytes sent as responses from backends (or cache) to HTTP/S load balancer. - name: frontend_tcp_rtt description: A distribution of the RTT measured for each connection between client and proxy. type: group fields: - - name: count + - name: count.value type: long - - name: mean + - name: mean.value type: long - - name: bucket_counts + - name: bucket_counts.value type: long - name: bucket_options type: group @@ -60,11 +60,11 @@ - name: ExponentialBuckets type: group fields: - - name: growth_factor + - name: growth_factor.value type: double - - name: scale + - name: scale.value type: long - - name: num_finite_buckets + - name: num_finite_buckets.value type: long - name: internal type: group @@ -73,11 +73,11 @@ description: A distribution of the latency calculated from when the request was sent by the proxy to the backend until the proxy received from the backend the last byte of response. type: group fields: - - name: count + - name: count.value type: long - - name: mean + - name: mean.value type: long - - name: bucket_counts + - name: bucket_counts.value type: long - name: bucket_options type: group @@ -88,30 +88,30 @@ - name: ExponentialBuckets type: group fields: - - name: growth_factor + - name: growth_factor.value type: double - - name: scale + - name: scale.value type: long - - name: num_finite_buckets + - name: num_finite_buckets.value type: long - - name: request_bytes_count + - name: request_bytes_count.value type: long description: The number of bytes sent as requests from clients to HTTP/S load balancer. - - name: request_count + - name: request_count.value type: long description: The number of requests served by HTTP/S load balancer. - - name: response_bytes_count + - name: response_bytes_count.value type: long description: The number of bytes sent as responses from HTTP/S load balancer to clients. - name: total_latencies description: A distribution of the latency calculated from when the request was received by the proxy until the proxy got ACK from client on last response byte. type: group fields: - - name: count + - name: count.value type: long - - name: mean + - name: mean.value type: long - - name: bucket_counts + - name: bucket_counts.value type: long - name: bucket_options type: group @@ -122,30 +122,30 @@ - name: ExponentialBuckets type: group fields: - - name: growth_factor + - name: growth_factor.value type: double - - name: scale + - name: scale.value type: long - - name: num_finite_buckets + - name: num_finite_buckets.value type: long - - name: request_bytes_count + - name: request_bytes_count.value type: long description: The number of bytes sent as requests from clients to HTTP/S load balancer. - - name: request_count + - name: request_count.value type: long description: The number of requests served by HTTP/S load balancer. - - name: response_bytes_count + - name: response_bytes_count.value type: long description: The number of bytes sent as responses from HTTP/S load balancer to clients. - name: total_latencies description: A distribution of the latency calculated from when the request was received by the proxy until the proxy got ACK from client on last response byte. type: group fields: - - name: count + - name: count.value type: long - - name: mean + - name: mean.value type: long - - name: bucket_counts + - name: bucket_counts.value type: long - name: bucket_options type: group @@ -156,37 +156,37 @@ - name: ExponentialBuckets type: group fields: - - name: growth_factor + - name: growth_factor.value type: double - - name: scale + - name: scale.value type: long - - name: num_finite_buckets + - name: num_finite_buckets.value type: long - name: l3.internal type: group description: Google Cloud Load Balancing metrics fields: - - name: egress_bytes_count + - name: egress_bytes_count.value type: long description: The number of bytes sent from ILB backend to client (for TCP flows it's counting bytes on application stream only). - - name: egress_packets_count + - name: egress_packets_count.value type: long description: The number of packets sent from ILB backend to client of the flow. - - name: ingress_bytes_count + - name: ingress_bytes_count.value type: long description: The number of bytes sent from client to ILB backend (for TCP flows it's counting bytes on application stream only). - - name: ingress_packets_count + - name: ingress_packets_count.value type: long description: The number of packets sent from client to ILB backend. - name: rtt_latencies description: A distribution of RTT measured over TCP connections for ILB flows. type: group fields: - - name: count + - name: count.value type: long - - name: mean + - name: mean.value type: long - - name: bucket_counts + - name: bucket_counts.value type: long - name: bucket_options type: group @@ -197,31 +197,31 @@ - name: ExponentialBuckets type: group fields: - - name: growth_factor + - name: growth_factor.value type: double - - name: scale + - name: scale.value type: long - - name: num_finite_buckets + - name: num_finite_buckets.value type: long - name: tcp_ssl_proxy type: group description: Google Cloud Load Balancing metrics fields: - - name: closed_connections + - name: closed_connections.value type: long description: Number of connections that were terminated over TCP/SSL proxy. - - name: egress_bytes_count + - name: egress_bytes_count.value type: long description: Number of bytes sent from VM to client using proxy. - name: frontend_tcp_rtt description: A distribution of the smoothed RTT (in ms) measured by the proxy's TCP stack, each minute application layer bytes pass from proxy to client. type: group fields: - - name: count + - name: count.value type: long - - name: mean + - name: mean.value type: long - - name: bucket_counts + - name: bucket_counts.value type: long - name: bucket_options type: group @@ -232,18 +232,18 @@ - name: ExponentialBuckets type: group fields: - - name: growth_factor + - name: growth_factor.value type: double - - name: scale + - name: scale.value type: long - - name: num_finite_buckets + - name: num_finite_buckets.value type: long - - name: ingress_bytes_count + - name: ingress_bytes_count.value type: long description: Number of bytes sent from client to VM using proxy. - - name: new_connections + - name: new_connections.value type: long description: Number of connections that were created over TCP/SSL proxy. - - name: open_connections + - name: open_connections.value type: long description: Current number of outstanding connections through the TCP/SSL proxy. diff --git a/x-pack/metricbeat/module/googlecloud/loadbalancing/manifest.yml b/x-pack/metricbeat/module/googlecloud/loadbalancing/manifest.yml index 479f92c94d4..173b8fbd265 100644 --- a/x-pack/metricbeat/module/googlecloud/loadbalancing/manifest.yml +++ b/x-pack/metricbeat/module/googlecloud/loadbalancing/manifest.yml @@ -6,28 +6,29 @@ input: stackdriver: service: loadbalancing metrics: - - "loadbalancing.googleapis.com/https/backend_latencies" - - "loadbalancing.googleapis.com/https/backend_latencies" - - "loadbalancing.googleapis.com/https/backend_request_bytes_count" - - "loadbalancing.googleapis.com/https/backend_request_count" - - "loadbalancing.googleapis.com/https/backend_response_bytes_count" - - "loadbalancing.googleapis.com/https/frontend_tcp_rtt" - - "loadbalancing.googleapis.com/https/request_bytes_count" - - "loadbalancing.googleapis.com/https/request_bytes_count" - - "loadbalancing.googleapis.com/https/request_count" - - "loadbalancing.googleapis.com/https/request_count" - - "loadbalancing.googleapis.com/https/response_bytes_count" - - "loadbalancing.googleapis.com/https/response_bytes_count" - - "loadbalancing.googleapis.com/https/total_latencies" - - "loadbalancing.googleapis.com/https/total_latencies" - - "loadbalancing.googleapis.com/l3/internal/egress_bytes_count" - - "loadbalancing.googleapis.com/l3/internal/egress_packets_count" - - "loadbalancing.googleapis.com/l3/internal/ingress_bytes_count" - - "loadbalancing.googleapis.com/l3/internal/ingress_packets_count" - - "loadbalancing.googleapis.com/l3/internal/rtt_latencies" - - "loadbalancing.googleapis.com/tcp_ssl_proxy/closed_connections" - - "loadbalancing.googleapis.com/tcp_ssl_proxy/egress_bytes_count" - - "loadbalancing.googleapis.com/tcp_ssl_proxy/frontend_tcp_rtt" - - "loadbalancing.googleapis.com/tcp_ssl_proxy/ingress_bytes_count" - - "loadbalancing.googleapis.com/tcp_ssl_proxy/new_connections" - - "loadbalancing.googleapis.com/tcp_ssl_proxy/open_connections" + - metric_types: + - "loadbalancing.googleapis.com/https/backend_latencies" + - "loadbalancing.googleapis.com/https/backend_latencies" + - "loadbalancing.googleapis.com/https/backend_request_bytes_count" + - "loadbalancing.googleapis.com/https/backend_request_count" + - "loadbalancing.googleapis.com/https/backend_response_bytes_count" + - "loadbalancing.googleapis.com/https/frontend_tcp_rtt" + - "loadbalancing.googleapis.com/https/request_bytes_count" + - "loadbalancing.googleapis.com/https/request_bytes_count" + - "loadbalancing.googleapis.com/https/request_count" + - "loadbalancing.googleapis.com/https/request_count" + - "loadbalancing.googleapis.com/https/response_bytes_count" + - "loadbalancing.googleapis.com/https/response_bytes_count" + - "loadbalancing.googleapis.com/https/total_latencies" + - "loadbalancing.googleapis.com/https/total_latencies" + - "loadbalancing.googleapis.com/l3/internal/egress_bytes_count" + - "loadbalancing.googleapis.com/l3/internal/egress_packets_count" + - "loadbalancing.googleapis.com/l3/internal/ingress_bytes_count" + - "loadbalancing.googleapis.com/l3/internal/ingress_packets_count" + - "loadbalancing.googleapis.com/l3/internal/rtt_latencies" + - "loadbalancing.googleapis.com/tcp_ssl_proxy/closed_connections" + - "loadbalancing.googleapis.com/tcp_ssl_proxy/egress_bytes_count" + - "loadbalancing.googleapis.com/tcp_ssl_proxy/frontend_tcp_rtt" + - "loadbalancing.googleapis.com/tcp_ssl_proxy/ingress_bytes_count" + - "loadbalancing.googleapis.com/tcp_ssl_proxy/new_connections" + - "loadbalancing.googleapis.com/tcp_ssl_proxy/open_connections" diff --git a/x-pack/metricbeat/module/googlecloud/pubsub/_meta/data.json b/x-pack/metricbeat/module/googlecloud/pubsub/_meta/data.json index 86da8d924e5..fd0dd71838a 100644 --- a/x-pack/metricbeat/module/googlecloud/pubsub/_meta/data.json +++ b/x-pack/metricbeat/module/googlecloud/pubsub/_meta/data.json @@ -18,30 +18,10 @@ } }, "pubsub": { - "snapshot": { - "backlog_bytes": 19, - "backlog_bytes_by_region": 19, - "num_messages": 4, - "num_messages_by_region": 4, - "oldest_message_age": 69319, - "oldest_message_age_by_region": 69319 - }, "subscription": { - "backlog_bytes": 0, - "num_undelivered_messages": 0, - "oldest_retained_acked_message_age": 0, - "oldest_retained_acked_message_age_by_region": 0, - "oldest_unacked_message_age": 0, - "oldest_unacked_message_age_by_region": 69277, - "retained_acked_bytes": 0, - "retained_acked_bytes_by_region": 0, - "unacked_bytes_by_region": 19 - }, - "topic": { - "oldest_retained_acked_message_age_by_region": 0, - "oldest_unacked_message_age_by_region": 69319, - "retained_acked_bytes_by_region": 0, - "unacked_bytes_by_region": 76 + "oldest_unacked_message_age": { + "value": 0 + } } } }, diff --git a/x-pack/metricbeat/module/googlecloud/pubsub/_meta/fields.yml b/x-pack/metricbeat/module/googlecloud/pubsub/_meta/fields.yml index 32e4acbb521..ae6443e219f 100644 --- a/x-pack/metricbeat/module/googlecloud/pubsub/_meta/fields.yml +++ b/x-pack/metricbeat/module/googlecloud/pubsub/_meta/fields.yml @@ -7,152 +7,155 @@ type: group description: Suscription related metrics fields: - - name: ack_message_count + - name: ack_message_count.value type: long description: Cumulative count of messages acknowledged by Acknowledge requests, grouped by delivery type. - - name: backlog_bytes + - name: backlog_bytes.value type: long description: Total byte size of the unacknowledged messages (a.k.a. backlog messages) in a subscription. - - name: num_outstanding_messages + - name: num_outstanding_messages.value type: long description: Number of messages delivered to a subscription's push endpoint, but not yet acknowledged. - - name: num_undelivered_messages + - name: num_undelivered_messages.value type: long description: Number of unacknowledged messages (a.k.a. backlog messages) in a subscription. - - name: oldest_unacked_message_age + - name: oldest_unacked_message_age.value type: long description: Age (in seconds) of the oldest unacknowledged message (a.k.a. backlog message) in a subscription. - - name: pull_ack_message_operation_count + - name: pull_ack_message_operation_count.value type: long description: Cumulative count of acknowledge message operations, grouped by result. For a definition of message operations, see Cloud Pub/Sub metric subscription/mod_ack_deadline_message_operation_count. - - name: pull_ack_request_count + - name: pull_ack_request_count.value type: long description: Cumulative count of acknowledge requests, grouped by result. - - name: pull_message_operation_count + - name: pull_message_operation_count.value type: long description: Cumulative count of pull message operations, grouped by result. For a definition of message operations, see Cloud Pub/Sub metric subscription/mod_ack_deadline_message_operation_count. - - name: pull_request_count + - name: pull_request_count.value type: long description: Cumulative count of pull requests, grouped by result. - - name: push_request_count + - name: push_request_count.value type: long description: Cumulative count of push attempts, grouped by result. Unlike pulls, the push server implementation does not batch user messages. So each request only contains one user message. The push server retries on errors, so a given user message can appear multiple times. - - name: push_request_latencies + - name: push_request_latencies.value type: long description: Distribution of push request latencies (in microseconds), grouped by result. - - name: sent_message_count + - name: sent_message_count.value type: long description: Cumulative count of messages sent by Cloud Pub/Sub to subscriber clients, grouped by delivery type. - - name: streaming_pull_ack_message_operation_count + - name: streaming_pull_ack_message_operation_count.value type: long description: Cumulative count of StreamingPull acknowledge message operations, grouped by result. For a definition of message operations, see Cloud Pub/Sub metric subscription/mod_ack_deadline_message_operation_count. - - name: streaming_pull_ack_request_count + - name: streaming_pull_ack_request_count.value type: long description: Cumulative count of streaming pull requests with non-empty acknowledge ids, grouped by result. - - name: streaming_pull_message_operation_count + - name: streaming_pull_message_operation_count.value type: long description: Cumulative count of streaming pull message operations, grouped by result. For a definition of message operations, see Cloud Pub/Sub metric subscription/mod_ack_deadline_message_operation_count - - name: streaming_pull_response_count + - name: streaming_pull_response_count.value type: long description: Cumulative count of streaming pull responses, grouped by result. - - name: dead_letter_message_count + - name: dead_letter_message_count.value type: long description: Cumulative count of messages published to dead letter topic, grouped by result. - - name: mod_ack_deadline_message_count + - name: mod_ack_deadline_message_count.value type: long description: Cumulative count of messages whose deadline was updated by ModifyAckDeadline requests, grouped by delivery type. - - name: mod_ack_deadline_message_operation_count + - name: mod_ack_deadline_message_operation_count.value type: long description: Cumulative count of ModifyAckDeadline message operations, grouped by result. - - name: mod_ack_deadline_request_count + - name: mod_ack_deadline_request_count.value type: long description: Cumulative count of ModifyAckDeadline requests, grouped by result. - - name: oldest_retained_acked_message_age + - name: oldest_retained_acked_message_age.value type: long description: Age (in seconds) of the oldest acknowledged message retained in a subscription. - - name: oldest_retained_acked_message_age_by_region + - name: oldest_retained_acked_message_age_by_region.value type: long description: Age (in seconds) of the oldest acknowledged message retained in a subscription, broken down by Cloud region. - - name: oldest_unacked_message_age_by_region + - name: oldest_unacked_message_age_by_region.value type: long description: Age (in seconds) of the oldest unacknowledged message in a subscription, broken down by Cloud region. - - name: retained_acked_bytes + - name: retained_acked_bytes.value type: long description: Total byte size of the acknowledged messages retained in a subscription. - - name: retained_acked_bytes_by_region + - name: retained_acked_bytes_by_region.value type: long description: Total byte size of the acknowledged messages retained in a subscription, broken down by Cloud region. - - name: seek_request_count + - name: seek_request_count.value type: long description: Cumulative count of seek attempts, grouped by result. - - name: streaming_pull_mod_ack_deadline_message_operation_count + - name: streaming_pull_mod_ack_deadline_message_operation_count.value type: long description: Cumulative count of StreamingPull ModifyAckDeadline operations, grouped by result. - - name: streaming_pull_mod_ack_deadline_request_count + - name: streaming_pull_mod_ack_deadline_request_count.value type: long description: Cumulative count of streaming pull requests with non-empty ModifyAckDeadline fields, grouped by result. - - name: byte_cost + - name: byte_cost.value type: long description: Cumulative cost of operations, measured in bytes. This is used to measure quota utilization. - - name: config_updates_count + - name: config_updates_count.value type: long description: Cumulative count of configuration changes for each subscription, grouped by operation type and result. - - name: unacked_bytes_by_region + - name: unacked_bytes_by_region.value type: long description: Total byte size of the unacknowledged messages in a subscription, broken down by Cloud region. - name: topic type: group description: Topic related metrics fields: - - name: streaming_pull_response_count + - name: streaming_pull_response_count.value type: long description: Cumulative count of streaming pull responses, grouped by result. - - name: send_message_operation_count + - name: send_message_operation_count.value type: long description: Cumulative count of publish message operations, grouped by result. For a definition of message operations, see Cloud Pub/Sub metric subscription/mod_ack_deadline_message_operation_count. - - name: send_request_count + - name: send_request_count.value type: long description: Cumulative count of publish requests, grouped by result. - - name: oldest_retained_acked_message_age_by_region + - name: oldest_retained_acked_message_age_by_region.value type: long description: Age (in seconds) of the oldest acknowledged message retained in a topic, broken down by Cloud region. - - name: oldest_unacked_message_age_by_region + - name: oldest_unacked_message_age_by_region.value type: long description: Age (in seconds) of the oldest unacknowledged message in a topic, broken down by Cloud region. - - name: retained_acked_bytes_by_region + - name: retained_acked_bytes_by_region.value type: long description: Total byte size of the acknowledged messages retained in a topic, broken down by Cloud region. - - name: byte_cost + - name: byte_cost.value type: long description: Cost of operations, measured in bytes. This is used to measure utilization for quotas. - - name: config_updates_count + - name: config_updates_count.value type: long description: Cumulative count of configuration changes, grouped by operation type and result. - - name: unacked_bytes_by_region + - name: message_sizes.value + type: long + description: Distribution of publish message sizes (in bytes) + - name: unacked_bytes_by_region.value type: long description: Total byte size of the unacknowledged messages in a topic, broken down by Cloud region. - name: snapshot type: group description: Snapshot related metrics fields: - - name: oldest_message_age + - name: oldest_message_age.value type: long description: Age (in seconds) of the oldest message retained in a snapshot. - - name: oldest_message_age_by_region + - name: oldest_message_age_by_region.value type: long description: Age (in seconds) of the oldest message retained in a snapshot, broken down by Cloud region. - - name: backlog_bytes + - name: backlog_bytes.value type: long description: Total byte size of the messages retained in a snapshot. - - name: backlog_bytes_by_region + - name: backlog_bytes_by_region.value type: long description: Total byte size of the messages retained in a snapshot, broken down by Cloud region. - - name: num_messages + - name: num_messages.value type: long description: Number of messages retained in a snapshot. - - name: num_messages_by_region + - name: num_messages_by_region.value type: long description: Number of messages retained in a snapshot, broken down by Cloud region. - - name: config_updates_count + - name: config_updates_count.value type: long description: Cumulative count of configuration changes, grouped by operation type and result. diff --git a/x-pack/metricbeat/module/googlecloud/pubsub/manifest.yml b/x-pack/metricbeat/module/googlecloud/pubsub/manifest.yml index 3d8cdb0949c..285136f3cf8 100644 --- a/x-pack/metricbeat/module/googlecloud/pubsub/manifest.yml +++ b/x-pack/metricbeat/module/googlecloud/pubsub/manifest.yml @@ -6,50 +6,51 @@ input: stackdriver: service: pubsub metrics: - - "pubsub.googleapis.com/snapshot/backlog_bytes" - - "pubsub.googleapis.com/snapshot/backlog_bytes_by_region" - - "pubsub.googleapis.com/snapshot/config_updates_count" - - "pubsub.googleapis.com/snapshot/num_messages" - - "pubsub.googleapis.com/snapshot/num_messages_by_region" - - "pubsub.googleapis.com/snapshot/oldest_message_age" - - "pubsub.googleapis.com/snapshot/oldest_message_age_by_region" - - "pubsub.googleapis.com/subscription/ack_message_count" - - "pubsub.googleapis.com/subscription/backlog_bytes" - - "pubsub.googleapis.com/subscription/byte_cost" - - "pubsub.googleapis.com/subscription/config_updates_count" - - "pubsub.googleapis.com/subscription/dead_letter_message_count" - - "pubsub.googleapis.com/subscription/mod_ack_deadline_message_count" - - "pubsub.googleapis.com/subscription/mod_ack_deadline_message_operation_count" - - "pubsub.googleapis.com/subscription/mod_ack_deadline_request_count" - - "pubsub.googleapis.com/subscription/num_outstanding_messages" - - "pubsub.googleapis.com/subscription/num_undelivered_messages" - - "pubsub.googleapis.com/subscription/oldest_retained_acked_message_age" - - "pubsub.googleapis.com/subscription/oldest_retained_acked_message_age_by_region" - - "pubsub.googleapis.com/subscription/oldest_unacked_message_age" - - "pubsub.googleapis.com/subscription/oldest_unacked_message_age_by_region" - - "pubsub.googleapis.com/subscription/pull_ack_message_operation_count" - - "pubsub.googleapis.com/subscription/pull_ack_request_count" - - "pubsub.googleapis.com/subscription/pull_message_operation_count" - - "pubsub.googleapis.com/subscription/pull_request_count" - - "pubsub.googleapis.com/subscription/push_request_count" - - "pubsub.googleapis.com/subscription/push_request_latencies" - - "pubsub.googleapis.com/subscription/retained_acked_bytes" - - "pubsub.googleapis.com/subscription/retained_acked_bytes_by_region" - - "pubsub.googleapis.com/subscription/seek_request_count" - - "pubsub.googleapis.com/subscription/sent_message_count" - - "pubsub.googleapis.com/subscription/streaming_pull_ack_message_operation_count" - - "pubsub.googleapis.com/subscription/streaming_pull_ack_request_count" - - "pubsub.googleapis.com/subscription/streaming_pull_message_operation_count" - - "pubsub.googleapis.com/subscription/streaming_pull_mod_ack_deadline_message_operation_count" - - "pubsub.googleapis.com/subscription/streaming_pull_mod_ack_deadline_request_count" - - "pubsub.googleapis.com/subscription/streaming_pull_response_count" - - "pubsub.googleapis.com/subscription/unacked_bytes_by_region" - - "pubsub.googleapis.com/topic/byte_cost" - - "pubsub.googleapis.com/topic/config_updates_count" - - "pubsub.googleapis.com/topic/message_sizes" - - "pubsub.googleapis.com/topic/oldest_retained_acked_message_age_by_region" - - "pubsub.googleapis.com/topic/oldest_unacked_message_age_by_region" - - "pubsub.googleapis.com/topic/retained_acked_bytes_by_region" - - "pubsub.googleapis.com/topic/send_message_operation_count" - - "pubsub.googleapis.com/topic/send_request_count" - - "pubsub.googleapis.com/topic/unacked_bytes_by_region" + - metric_types: + - "pubsub.googleapis.com/snapshot/backlog_bytes" + - "pubsub.googleapis.com/snapshot/backlog_bytes_by_region" + - "pubsub.googleapis.com/snapshot/config_updates_count" + - "pubsub.googleapis.com/snapshot/num_messages" + - "pubsub.googleapis.com/snapshot/num_messages_by_region" + - "pubsub.googleapis.com/snapshot/oldest_message_age" + - "pubsub.googleapis.com/snapshot/oldest_message_age_by_region" + - "pubsub.googleapis.com/subscription/ack_message_count" + - "pubsub.googleapis.com/subscription/backlog_bytes" + - "pubsub.googleapis.com/subscription/byte_cost" + - "pubsub.googleapis.com/subscription/config_updates_count" + - "pubsub.googleapis.com/subscription/dead_letter_message_count" + - "pubsub.googleapis.com/subscription/mod_ack_deadline_message_count" + - "pubsub.googleapis.com/subscription/mod_ack_deadline_message_operation_count" + - "pubsub.googleapis.com/subscription/mod_ack_deadline_request_count" + - "pubsub.googleapis.com/subscription/num_outstanding_messages" + - "pubsub.googleapis.com/subscription/num_undelivered_messages" + - "pubsub.googleapis.com/subscription/oldest_retained_acked_message_age" + - "pubsub.googleapis.com/subscription/oldest_retained_acked_message_age_by_region" + - "pubsub.googleapis.com/subscription/oldest_unacked_message_age" + - "pubsub.googleapis.com/subscription/oldest_unacked_message_age_by_region" + - "pubsub.googleapis.com/subscription/pull_ack_message_operation_count" + - "pubsub.googleapis.com/subscription/pull_ack_request_count" + - "pubsub.googleapis.com/subscription/pull_message_operation_count" + - "pubsub.googleapis.com/subscription/pull_request_count" + - "pubsub.googleapis.com/subscription/push_request_count" + - "pubsub.googleapis.com/subscription/push_request_latencies" + - "pubsub.googleapis.com/subscription/retained_acked_bytes" + - "pubsub.googleapis.com/subscription/retained_acked_bytes_by_region" + - "pubsub.googleapis.com/subscription/seek_request_count" + - "pubsub.googleapis.com/subscription/sent_message_count" + - "pubsub.googleapis.com/subscription/streaming_pull_ack_message_operation_count" + - "pubsub.googleapis.com/subscription/streaming_pull_ack_request_count" + - "pubsub.googleapis.com/subscription/streaming_pull_message_operation_count" + - "pubsub.googleapis.com/subscription/streaming_pull_mod_ack_deadline_message_operation_count" + - "pubsub.googleapis.com/subscription/streaming_pull_mod_ack_deadline_request_count" + - "pubsub.googleapis.com/subscription/streaming_pull_response_count" + - "pubsub.googleapis.com/subscription/unacked_bytes_by_region" + - "pubsub.googleapis.com/topic/byte_cost" + - "pubsub.googleapis.com/topic/config_updates_count" + - "pubsub.googleapis.com/topic/message_sizes" + - "pubsub.googleapis.com/topic/oldest_retained_acked_message_age_by_region" + - "pubsub.googleapis.com/topic/oldest_unacked_message_age_by_region" + - "pubsub.googleapis.com/topic/retained_acked_bytes_by_region" + - "pubsub.googleapis.com/topic/send_message_operation_count" + - "pubsub.googleapis.com/topic/send_request_count" + - "pubsub.googleapis.com/topic/unacked_bytes_by_region" diff --git a/x-pack/metricbeat/module/googlecloud/stackdriver/_meta/docs.asciidoc b/x-pack/metricbeat/module/googlecloud/stackdriver/_meta/docs.asciidoc new file mode 100644 index 00000000000..00cce58cac8 --- /dev/null +++ b/x-pack/metricbeat/module/googlecloud/stackdriver/_meta/docs.asciidoc @@ -0,0 +1,82 @@ +Stackdriver provides visibility into the performance, uptime, and overall health +of cloud-powered applications. It collects metrics, events, and metadata from +different services from Google Cloud. This metricset is to collect monitoring +metrics from Google Cloud using `ListTimeSeries` API. + +[float] +== Metricset config and parameters + +* *metric_types*: Required, a list of metric type strings. Each call of the +`ListTimeSeries` API can return any number of time series from a single metric +type. Metric type is to used for identifying a specific time series. + +* *aligner*: A single string with which aggregation operation need to be applied +onto time series data for ListTimeSeries API. If it's not given, default aligner +is set to be `ALIGN_NONE`. Sample period of each metric type is obtained from +making https://cloud.google.com/monitoring/api/ref_v3/rest/v3/projects.metricDescriptors/list [ListMetricDescriptors API] call. + +[float] +=== Example Configuration +* `stackdriver` metricset is enabled to collect metrics from all zones under +`europe-west1-c` region in `elastic-observability` project. Two sets of metrics +are specified: first one is to collect CPU usage time and utilization with +aggregation aligner ALIGN_MEAN; second one is to collect uptime with aggregation +aligner ALIGN_SUM. These metric types all have 240 seconds ingest delay time and +60 seconds sample period. With `period` specified as `300s` in the config below, +Metricbeat will collect compute metrics from googlecloud every 5-minute with +given aggregation aligner applied for each metric type. ++ +[source,yaml] +---- +- module: googlecloud + metricsets: + - stackdriver + zone: "europe-west1-c" + project_id: elastic-observability + credentials_file_path: "your JSON credentials file path" + exclude_labels: false + period: 300s + stackdriver: + service: compute + metrics: + - aligner: ALIGN_MEAN + metric_types: + - "compute.googleapis.com/instance/cpu/usage_time" + - "compute.googleapis.com/instance/cpu/utilization" + - aligner: ALIGN_SUM + metric_types: + - "compute.googleapis.com/instance/uptime" + +---- + +* `stackdriver` metricset is enabled to collect metrics from all zones under +`europe-west1-c` region in `elastic-observability` project. Two sets of metrics +are specified: first one is to collect CPU usage time and utilization with +aggregation aligner ALIGN_MEAN; second one is to collect uptime with aggregation +aligner ALIGN_SUM. These metric types all have 240 seconds ingest delay time and +60 seconds sample period. With `period` specified as `60s` in the config below, +Metricbeat will collect compute metrics from googlecloud every minute with no +aggregation. This case, the aligners specified in the configuration will be +ignored. ++ +[source,yaml] +---- +- module: googlecloud + metricsets: + - stackdriver + zone: "europe-west1-c" + project_id: elastic-observability + credentials_file_path: "your JSON credentials file path" + exclude_labels: false + period: 60s + stackdriver: + service: compute + metrics: + - aligner: ALIGN_MEAN + metric_types: + - "compute.googleapis.com/instance/cpu/usage_time" + - "compute.googleapis.com/instance/cpu/utilization" + - aligner: ALIGN_SUM + metric_types: + - "compute.googleapis.com/instance/uptime" +---- diff --git a/x-pack/metricbeat/module/googlecloud/stackdriver/compute/identity.go b/x-pack/metricbeat/module/googlecloud/stackdriver/compute/identity.go index 9367862e6c7..19e434e8df7 100644 --- a/x-pack/metricbeat/module/googlecloud/stackdriver/compute/identity.go +++ b/x-pack/metricbeat/module/googlecloud/stackdriver/compute/identity.go @@ -23,7 +23,7 @@ func (s *metadataCollector) ID(ctx context.Context, in *googlecloud.MetadataColl if in.Timestamp != nil { metadata.ECS.Put("timestamp", in.Timestamp) } else if in.Point != nil { - metadata.ECS.Put("timestamp", in.Point.Interval.StartTime) + metadata.ECS.Put("timestamp", in.Point.Interval.EndTime) } else { return "", errors.New("no timestamp information found") } diff --git a/x-pack/metricbeat/module/googlecloud/integration.go b/x-pack/metricbeat/module/googlecloud/stackdriver/integration.go similarity index 86% rename from x-pack/metricbeat/module/googlecloud/integration.go rename to x-pack/metricbeat/module/googlecloud/stackdriver/integration.go index 0e7ac055bc7..68e3750d5a5 100644 --- a/x-pack/metricbeat/module/googlecloud/integration.go +++ b/x-pack/metricbeat/module/googlecloud/stackdriver/integration.go @@ -2,7 +2,7 @@ // or more contributor license agreements. Licensed under the Elastic License; // you may not use this file except in compliance with the Elastic License. -package googlecloud +package stackdriver import ( "os" @@ -36,7 +36,11 @@ func GetConfigForTest(t *testing.T, metricSetName string) map[string]interface{} if metricSetName == "stackdriver" { config["stackdriver.service"] = "compute" - config["stackdriver.metrics"] = []string{"compute.googleapis.com/instance/uptime"} + stackDriverConfig := stackDriverConfig{ + Aligner: "ALIGN_NONE", + MetricTypes: []string{"compute.googleapis.com/instance/uptime"}, + } + config["stackdriver.metrics"] = stackDriverConfig } } return config diff --git a/x-pack/metricbeat/module/googlecloud/stackdriver/metrics_requester.go b/x-pack/metricbeat/module/googlecloud/stackdriver/metrics_requester.go index 8d2147d285c..edfd52a64e3 100644 --- a/x-pack/metricbeat/module/googlecloud/stackdriver/metrics_requester.go +++ b/x-pack/metricbeat/module/googlecloud/stackdriver/metrics_requester.go @@ -11,9 +11,10 @@ import ( "sync" "time" + "github.com/golang/protobuf/ptypes/duration" + monitoring "cloud.google.com/go/monitoring/apiv3" "github.com/golang/protobuf/ptypes/timestamp" - "github.com/pkg/errors" "google.golang.org/api/iterator" monitoringpb "google.golang.org/genproto/googleapis/monitoring/v3" @@ -21,42 +22,31 @@ import ( "github.com/elastic/beats/v7/x-pack/metricbeat/module/googlecloud" ) -func newStackdriverMetricsRequester(ctx context.Context, c config, window time.Duration, logger *logp.Logger) (*stackdriverMetricsRequester, error) { - interval, err := getTimeInterval(window) - if err != nil { - return nil, errors.Wrap(err, "error trying to get time window") - } - - client, err := monitoring.NewMetricClient(ctx, c.opt...) - if err != nil { - return nil, errors.Wrap(err, "error creating Stackdriver client") - } - - return &stackdriverMetricsRequester{ - config: c, - client: client, - logger: logger, - interval: interval, - }, nil -} - type stackdriverMetricsRequester struct { config config - client *monitoring.MetricClient - interval *monitoringpb.TimeInterval + client *monitoring.MetricClient logger *logp.Logger } -func (r *stackdriverMetricsRequester) Metric(ctx context.Context, m string) (out []*monitoringpb.TimeSeries) { - out = make([]*monitoringpb.TimeSeries, 0) +type timeSeriesWithAligner struct { + timeSeries []*monitoringpb.TimeSeries + aligner string +} + +func (r *stackdriverMetricsRequester) Metric(ctx context.Context, metricType string, timeInterval *monitoringpb.TimeInterval, aligner string) (out timeSeriesWithAligner) { + timeSeries := make([]*monitoringpb.TimeSeries, 0) req := &monitoringpb.ListTimeSeriesRequest{ Name: "projects/" + r.config.ProjectID, - Interval: r.interval, + Interval: timeInterval, View: monitoringpb.ListTimeSeriesRequest_FULL, - Filter: r.getFilterForMetric(m), + Filter: r.getFilterForMetric(metricType), + Aggregation: &monitoringpb.Aggregation{ + PerSeriesAligner: googlecloud.AlignersMapToGCP[aligner], + AlignmentPeriod: &r.config.period, + }, } it := r.client.ListTimeSeries(ctx, req) @@ -67,13 +57,15 @@ func (r *stackdriverMetricsRequester) Metric(ctx context.Context, m string) (out } if err != nil { - r.logger.Errorf("Could not read time series value: %s: %v", m, err) + r.logger.Errorf("Could not read time series value: %s: %v", metricType, err) break } - out = append(out, resp) + timeSeries = append(timeSeries, resp) } + out.aligner = aligner + out.timeSeries = timeSeries return } @@ -89,23 +81,29 @@ func constructFilter(m string, region string, zone string) string { return filter } -func (r *stackdriverMetricsRequester) Metrics(ctx context.Context, ms []string) ([]*monitoringpb.TimeSeries, error) { +func (r *stackdriverMetricsRequester) Metrics(ctx context.Context, stackDriverConfigs []stackDriverConfig, metricsMeta map[string]metricMeta) ([]timeSeriesWithAligner, error) { var lock sync.Mutex var wg sync.WaitGroup - results := make([]*monitoringpb.TimeSeries, 0) + results := make([]timeSeriesWithAligner, 0) - for _, metric := range ms { - wg.Add(1) + for _, sdc := range stackDriverConfigs { + aligner := sdc.Aligner + for _, mt := range sdc.MetricTypes { + metricType := mt + wg.Add(1) - go func(m string) { - defer wg.Done() + go func(metricType string) { + defer wg.Done() - ts := r.Metric(ctx, m) + metricMeta := metricsMeta[metricType] + interval, aligner := getTimeIntervalAligner(metricMeta.ingestDelay, metricMeta.samplePeriod, r.config.period, aligner) + ts := r.Metric(ctx, metricType, interval, aligner) - lock.Lock() - defer lock.Unlock() - results = append(results, ts...) - }(metric) + lock.Lock() + defer lock.Unlock() + results = append(results, ts) + }(metricType) + } } wg.Wait() @@ -144,21 +142,28 @@ func (r *stackdriverMetricsRequester) getFilterForMetric(m string) (f string) { return } -// Returns a GCP TimeInterval based on the provided config -func getTimeInterval(windowTime time.Duration) (*monitoringpb.TimeInterval, error) { - var startTime, endTime time.Time - - if windowTime > 0 { - endTime = time.Now().UTC() - startTime = time.Now().UTC().Add(-windowTime) +// Returns a GCP TimeInterval based on the ingestDelay and samplePeriod from ListMetricDescriptor +func getTimeIntervalAligner(ingestDelay time.Duration, samplePeriod time.Duration, collectionPeriod duration.Duration, inputAligner string) (*monitoringpb.TimeInterval, string) { + var startTime, endTime, currentTime time.Time + var needsAggregation bool + currentTime = time.Now().UTC() + + // When samplePeriod < collectionPeriod, aggregation will be done in ListTimeSeriesRequest. + // For example, samplePeriod = 60s, collectionPeriod = 300s, if perSeriesAligner is not given, + // ALIGN_MEAN will be used by default. + if int64(samplePeriod.Seconds()) < collectionPeriod.Seconds { + endTime = currentTime.Add(-ingestDelay) + startTime = endTime.Add(-time.Duration(collectionPeriod.Seconds) * time.Second) + needsAggregation = true } - if windowTime.Minutes() < googlecloud.MinTimeIntervalDataWindowMinutes { - return nil, errors.Errorf("the provided window time is too small. No less than %d minutes can be fetched", googlecloud.MinTimeIntervalDataWindowMinutes) - } - - if windowTime.Minutes() >= googlecloud.MaxTimeIntervalDataWindowMinutes { - return nil, errors.Errorf("the provided window time is too big. No more than %d minutes can be fetched", googlecloud.MaxTimeIntervalDataWindowMinutes) + // When samplePeriod == collectionPeriod, aggregation is not needed + // When samplePeriod > collectionPeriod, aggregation is not needed, use sample period + // to determine startTime and endTime to make sure there will be data point in this time range. + if int64(samplePeriod.Seconds()) >= collectionPeriod.Seconds { + endTime = time.Now().UTC().Add(-ingestDelay) + startTime = endTime.Add(-samplePeriod) + needsAggregation = false } interval := &monitoringpb.TimeInterval{ @@ -170,5 +175,11 @@ func getTimeInterval(windowTime time.Duration) (*monitoringpb.TimeInterval, erro }, } - return interval, nil + // Default aligner for aggregation is ALIGN_NONE if it's not given + updatedAligner := googlecloud.DefaultAligner + if needsAggregation && inputAligner != "" { + updatedAligner = inputAligner + } + + return interval, updatedAligner } diff --git a/x-pack/metricbeat/module/googlecloud/stackdriver/metrics_requester_test.go b/x-pack/metricbeat/module/googlecloud/stackdriver/metrics_requester_test.go index be7b824d224..1ed10814b76 100644 --- a/x-pack/metricbeat/module/googlecloud/stackdriver/metrics_requester_test.go +++ b/x-pack/metricbeat/module/googlecloud/stackdriver/metrics_requester_test.go @@ -6,7 +6,9 @@ package stackdriver import ( "testing" + "time" + "github.com/golang/protobuf/ptypes/duration" "github.com/stretchr/testify/assert" "github.com/elastic/beats/v7/libbeat/logp" @@ -103,3 +105,62 @@ func TestGetFilterForMetric(t *testing.T) { }) } } + +func TestGetTimeIntervalAligner(t *testing.T) { + cases := []struct { + title string + ingestDelay time.Duration + samplePeriod time.Duration + collectionPeriod duration.Duration + inputAligner string + expectedAligner string + }{ + { + "test collectionPeriod equals to samplePeriod", + time.Duration(240) * time.Second, + time.Duration(60) * time.Second, + duration.Duration{ + Seconds: int64(60), + }, + "", + "ALIGN_NONE", + }, + { + "test collectionPeriod larger than samplePeriod", + time.Duration(240) * time.Second, + time.Duration(60) * time.Second, + duration.Duration{ + Seconds: int64(300), + }, + "ALIGN_MEAN", + "ALIGN_MEAN", + }, + { + "test collectionPeriod smaller than samplePeriod", + time.Duration(240) * time.Second, + time.Duration(60) * time.Second, + duration.Duration{ + Seconds: int64(30), + }, + "ALIGN_MAX", + "ALIGN_NONE", + }, + { + "test collectionPeriod equals to samplePeriod with given aligner", + time.Duration(240) * time.Second, + time.Duration(60) * time.Second, + duration.Duration{ + Seconds: int64(60), + }, + "ALIGN_MEAN", + "ALIGN_NONE", + }, + } + + for _, c := range cases { + t.Run(c.title, func(t *testing.T) { + _, aligner := getTimeIntervalAligner(c.ingestDelay, c.samplePeriod, c.collectionPeriod, c.inputAligner) + assert.Equal(t, c.expectedAligner, aligner) + }) + } +} diff --git a/x-pack/metricbeat/module/googlecloud/stackdriver/metricset.go b/x-pack/metricbeat/module/googlecloud/stackdriver/metricset.go index 69ac38ca101..81fa98751aa 100644 --- a/x-pack/metricbeat/module/googlecloud/stackdriver/metricset.go +++ b/x-pack/metricbeat/module/googlecloud/stackdriver/metricset.go @@ -6,8 +6,13 @@ package stackdriver import ( "context" + "fmt" "time" + "github.com/golang/protobuf/ptypes/duration" + + monitoring "cloud.google.com/go/monitoring/apiv3" + "github.com/pkg/errors" "google.golang.org/api/option" @@ -38,19 +43,33 @@ func init() { // interface methods except for Fetch. type MetricSet struct { mb.BaseMetricSet - config config + config config + metricsMeta map[string]metricMeta + requester *stackdriverMetricsRequester + stackDriverConfig []stackDriverConfig `config:"metrics" validate:"nonzero,required"` +} + +//stackDriverConfig holds a configuration specific for stackdriver metricset. +type stackDriverConfig struct { + MetricTypes []string `config:"metric_types" validate:"required"` + Aligner string `config:"aligner"` +} + +type metricMeta struct { + samplePeriod time.Duration + ingestDelay time.Duration } type config struct { - Metrics []string `config:"stackdriver.metrics" validate:"required"` - Zone string `config:"zone"` - Region string `config:"region"` - ProjectID string `config:"project_id" validate:"required"` - ExcludeLabels bool `config:"exclude_labels"` - ServiceName string `config:"stackdriver.service" validate:"required"` - CredentialsFilePath string `config:"credentials_file_path"` - - opt []option.ClientOption + Zone string `config:"zone"` + Region string `config:"region"` + ProjectID string `config:"project_id" validate:"required"` + ExcludeLabels bool `config:"exclude_labels"` + ServiceName string `config:"stackdriver.service" validate:"required"` + CredentialsFilePath string `config:"credentials_file_path"` + + opt []option.ClientOption + period duration.Duration } // New creates a new instance of the MetricSet. New is responsible for unpacking @@ -64,12 +83,39 @@ func New(base mb.BaseMetricSet) (mb.MetricSet, error) { return nil, err } + stackDriverConfigs := struct { + StackDriverMetrics []stackDriverConfig `config:"stackdriver.metrics" validate:"nonzero,required"` + }{} + + if err := base.Module().UnpackConfig(&stackDriverConfigs); err != nil { + return nil, err + } + + m.stackDriverConfig = stackDriverConfigs.StackDriverMetrics m.config.opt = []option.ClientOption{option.WithCredentialsFile(m.config.CredentialsFilePath)} + m.config.period.Seconds = int64(m.Module().Config().Period.Seconds()) if err := validatePeriodForGCP(m.Module().Config().Period); err != nil { return nil, err } + // Get ingest delay and sample period for each metric type + ctx := context.Background() + client, err := monitoring.NewMetricClient(ctx, m.config.opt...) + if err != nil { + return nil, errors.Wrap(err, "error creating Stackdriver client") + } + + m.metricsMeta, err = metricDescriptor(ctx, client, m.config.ProjectID, m.stackDriverConfig) + if err != nil { + return nil, errors.Wrap(err, "error calling metricDescriptor function") + } + + m.requester = &stackdriverMetricsRequester{ + config: m.config, + client: client, + logger: m.Logger(), + } return m, nil } @@ -77,12 +123,7 @@ func New(base mb.BaseMetricSet) (mb.MetricSet, error) { // format. It publishes the event which is then forwarded to the output. In case // of an error set the Error field of mb.Event or simply call report.Error(). func (m *MetricSet) Fetch(ctx context.Context, reporter mb.ReporterV2) (err error) { - reqs, err := newStackdriverMetricsRequester(ctx, m.config, m.Module().Config().Period, m.Logger()) - if err != nil { - return errors.Wrapf(err, "error trying to do create a request client to GCP project '%s' in zone '%s' or region '%s'", m.config.ProjectID, m.config.Zone, m.config.Region) - } - - responses, err := reqs.Metrics(ctx, m.config.Metrics) + responses, err := m.requester.Metrics(ctx, m.stackDriverConfig, m.metricsMeta) if err != nil { return errors.Wrapf(err, "error trying to get metrics for project '%s' and zone '%s' or region '%s'", m.config.ProjectID, m.config.Zone, m.config.Region) } @@ -99,7 +140,7 @@ func (m *MetricSet) Fetch(ctx context.Context, reporter mb.ReporterV2) (err erro return nil } -func (m *MetricSet) eventMapping(ctx context.Context, tss []*monitoringpb.TimeSeries) ([]mb.Event, error) { +func (m *MetricSet) eventMapping(ctx context.Context, tss []timeSeriesWithAligner) ([]mb.Event, error) { e := newIncomingFieldExtractor(m.Logger()) var gcpService = googlecloud.NewStackdriverMetadataServiceForTimeSeries(nil) @@ -140,13 +181,14 @@ func (m *MetricSet) eventMapping(ctx context.Context, tss []*monitoringpb.TimeSe // validatePeriodForGCP returns nil if the Period in the module config is in the accepted threshold func validatePeriodForGCP(d time.Duration) (err error) { - if d.Seconds() < 300 { - return errors.New("period in Google Cloud config file cannot be set to less than 300 seconds") + if d.Seconds() < googlecloud.MonitoringMetricsSamplingRate { + return errors.Errorf("period in Google Cloud config file cannot be set to less than %d seconds", googlecloud.MonitoringMetricsSamplingRate) } return nil } +// Validate googlecloud module config func (c *config) Validate() error { // storage metricset does not require region or zone config parameter. if c.ServiceName == "storage" { @@ -158,3 +200,46 @@ func (c *config) Validate() error { } return nil } + +// Validate stackdriver related config +func (mc *stackDriverConfig) Validate() error { + gcpAlignerNames := make([]string, 0) + for k := range googlecloud.AlignersMapToGCP { + gcpAlignerNames = append(gcpAlignerNames, k) + } + + if mc.Aligner != "" { + if _, ok := googlecloud.AlignersMapToGCP[mc.Aligner]; !ok { + return errors.Errorf("the given aligner is not supported, please specify one of %s as aligner", gcpAlignerNames) + } + } + return nil +} + +// metricDescriptor calls ListMetricDescriptorsRequest API to get metric metadata +// (sample period and ingest delay) of each given metric type +func metricDescriptor(ctx context.Context, client *monitoring.MetricClient, projectID string, stackDriverConfigs []stackDriverConfig) (map[string]metricMeta, error) { + metricsWithMeta := make(map[string]metricMeta, 0) + + for _, sdc := range stackDriverConfigs { + for _, mt := range sdc.MetricTypes { + req := &monitoringpb.ListMetricDescriptorsRequest{ + Name: "projects/" + projectID, + Filter: fmt.Sprintf(`metric.type = "%s"`, mt), + } + + it := client.ListMetricDescriptors(ctx, req) + out, err := it.Next() + if err != nil { + return metricsWithMeta, errors.Errorf("Could not make ListMetricDescriptors request: %s: %v", mt, err) + } + + metricsWithMeta[mt] = metricMeta{ + samplePeriod: time.Duration(out.Metadata.SamplePeriod.Seconds) * time.Second, + ingestDelay: time.Duration(out.Metadata.IngestDelay.Seconds) * time.Second, + } + } + } + + return metricsWithMeta, nil +} diff --git a/x-pack/metricbeat/module/googlecloud/stackdriver/response_parser.go b/x-pack/metricbeat/module/googlecloud/stackdriver/response_parser.go index 474f04a244b..b6e38f4d333 100644 --- a/x-pack/metricbeat/module/googlecloud/stackdriver/response_parser.go +++ b/x-pack/metricbeat/module/googlecloud/stackdriver/response_parser.go @@ -9,9 +9,10 @@ import ( "strings" "time" - "github.com/pkg/errors" + "github.com/elastic/beats/v7/x-pack/metricbeat/module/googlecloud" "github.com/golang/protobuf/ptypes" + "github.com/pkg/errors" "google.golang.org/genproto/googleapis/monitoring/v3" "github.com/elastic/beats/v7/libbeat/common" @@ -36,7 +37,7 @@ type KeyValuePoint struct { } // extractTimeSeriesMetricValues valuable to send to Elasticsearch. This includes, for example, metric values, labels and timestamps -func (e *incomingFieldExtractor) extractTimeSeriesMetricValues(resp *monitoring.TimeSeries) (points []KeyValuePoint, err error) { +func (e *incomingFieldExtractor) extractTimeSeriesMetricValues(resp *monitoring.TimeSeries, aligner string) (points []KeyValuePoint, err error) { points = make([]KeyValuePoint, 0) for _, point := range resp.Points { @@ -48,7 +49,7 @@ func (e *incomingFieldExtractor) extractTimeSeriesMetricValues(resp *monitoring. } p := KeyValuePoint{ - Key: cleanMetricNameString(resp.Metric.Type), + Key: cleanMetricNameString(resp.Metric.Type, aligner), Value: getValueFromPoint(point), Timestamp: ts, } @@ -62,8 +63,8 @@ func (e *incomingFieldExtractor) extractTimeSeriesMetricValues(resp *monitoring. func (e *incomingFieldExtractor) getTimestamp(p *monitoring.Point) (ts time.Time, err error) { // Don't add point intervals that can't be "stated" at some timestamp. if p.Interval != nil { - if ts, err = ptypes.Timestamp(p.Interval.StartTime); err != nil { - return time.Time{}, errors.Errorf("error trying to parse timestamp '%#v' from metric\n", p.Interval.StartTime) + if ts, err = ptypes.Timestamp(p.Interval.EndTime); err != nil { + return time.Time{}, errors.Errorf("error trying to parse timestamp '%#v' from metric\n", p.Interval.EndTime) } return ts, nil } @@ -73,7 +74,7 @@ func (e *incomingFieldExtractor) getTimestamp(p *monitoring.Point) (ts time.Time var rx = regexp.MustCompile(`^[a-z_-]+\.googleapis.com\/`) -func cleanMetricNameString(s string) string { +func cleanMetricNameString(s string, aligner string) string { if s == "" { return "unknown" } @@ -83,7 +84,8 @@ func cleanMetricNameString(s string) string { removedPrefix := strings.TrimPrefix(s, prefix) replacedChars := strings.Replace(removedPrefix, "/", ".", -1) - return replacedChars + metricName := replacedChars + googlecloud.AlignersMapToSuffix[aligner] + return metricName } func getValueFromPoint(p *monitoring.Point) (out interface{}) { diff --git a/x-pack/metricbeat/module/googlecloud/stackdriver/response_parser_test.go b/x-pack/metricbeat/module/googlecloud/stackdriver/response_parser_test.go index 4f055ffc0cc..84f689d042b 100644 --- a/x-pack/metricbeat/module/googlecloud/stackdriver/response_parser_test.go +++ b/x-pack/metricbeat/module/googlecloud/stackdriver/response_parser_test.go @@ -5,7 +5,10 @@ package stackdriver import ( + "testing" + "github.com/golang/protobuf/ptypes/timestamp" + "github.com/stretchr/testify/assert" "google.golang.org/genproto/googleapis/api/metric" "google.golang.org/genproto/googleapis/api/monitoredres" "google.golang.org/genproto/googleapis/monitoring/v3" @@ -65,3 +68,32 @@ var metrics = []string{ "compute.googleapis.com/instance/disk/read_bytes_count", "compute.googleapis.com/http/server/response_latencies", } + +func TestCleanMetricNameString(t *testing.T) { + cases := []struct { + title string + metricType string + aligner string + expectedMetricName string + }{ + { + "test construct metric name with ALIGN_MEAN aligner", + "compute.googleapis.com/instance/cpu/usage_time", + "ALIGN_MEAN", + "instance.cpu.usage_time.avg", + }, + { + "test construct metric name with ALIGN_NONE aligner", + "compute.googleapis.com/instance/cpu/utilization", + "ALIGN_NONE", + "instance.cpu.utilization.value", + }, + } + + for _, c := range cases { + t.Run(c.title, func(t *testing.T) { + metricName := cleanMetricNameString(c.metricType, c.aligner) + assert.Equal(t, c.expectedMetricName, metricName) + }) + } +} diff --git a/x-pack/metricbeat/module/googlecloud/stackdriver/stackdriver_integration_test.go b/x-pack/metricbeat/module/googlecloud/stackdriver/stackdriver_integration_test.go index 8fe568ebe95..fd11a50c6e9 100644 --- a/x-pack/metricbeat/module/googlecloud/stackdriver/stackdriver_integration_test.go +++ b/x-pack/metricbeat/module/googlecloud/stackdriver/stackdriver_integration_test.go @@ -11,11 +11,10 @@ import ( "testing" mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing" - "github.com/elastic/beats/v7/x-pack/metricbeat/module/googlecloud" ) func TestData(t *testing.T) { - config := googlecloud.GetConfigForTest(t, "stackdriver") + config := GetConfigForTest(t, "stackdriver") metricSet := mbtest.NewFetcher(t, config) metricSet.WriteEvents(t, "/") } diff --git a/x-pack/metricbeat/module/googlecloud/stackdriver/timeseries.go b/x-pack/metricbeat/module/googlecloud/stackdriver/timeseries.go index fcf3184717c..c0b456f9954 100644 --- a/x-pack/metricbeat/module/googlecloud/stackdriver/timeseries.go +++ b/x-pack/metricbeat/module/googlecloud/stackdriver/timeseries.go @@ -7,53 +7,54 @@ package stackdriver import ( "context" - monitoringpb "google.golang.org/genproto/googleapis/monitoring/v3" - "github.com/elastic/beats/v7/x-pack/metricbeat/module/googlecloud" ) //timeSeriesGrouped groups TimeSeries responses into common Elasticsearch friendly events. This is to avoid sending // events with a single metric that shares info (like timestamp) with another event with a single metric too -func (m *MetricSet) timeSeriesGrouped(ctx context.Context, gcpService googlecloud.MetadataService, tss []*monitoringpb.TimeSeries, e *incomingFieldExtractor) (map[string][]KeyValuePoint, error) { +func (m *MetricSet) timeSeriesGrouped(ctx context.Context, gcpService googlecloud.MetadataService, tsas []timeSeriesWithAligner, e *incomingFieldExtractor) (map[string][]KeyValuePoint, error) { eventGroups := make(map[string][]KeyValuePoint) metadataService := gcpService - for _, ts := range tss { - keyValues, err := e.extractTimeSeriesMetricValues(ts) - if err != nil { - return nil, err - } - - sdCollectorInputData := googlecloud.NewStackdriverCollectorInputData(ts, m.config.ProjectID, m.config.Zone, m.config.Region) - if gcpService == nil { - metadataService = googlecloud.NewStackdriverMetadataServiceForTimeSeries(ts) - } - - for i := range keyValues { - sdCollectorInputData.Timestamp = &keyValues[i].Timestamp - - id, err := metadataService.ID(ctx, sdCollectorInputData) + for _, tsa := range tsas { + aligner := tsa.aligner + for _, ts := range tsa.timeSeries { + keyValues, err := e.extractTimeSeriesMetricValues(ts, aligner) if err != nil { - m.Logger().Errorf("error trying to retrieve ID from metric event '%v'", err) - continue + return nil, err } - metadataCollectorData, err := metadataService.Metadata(ctx, sdCollectorInputData.TimeSeries) - if err != nil { - m.Logger().Error("error trying to retrieve labels from metric event") - continue + sdCollectorInputData := googlecloud.NewStackdriverCollectorInputData(ts, m.config.ProjectID, m.config.Zone, m.config.Region) + if gcpService == nil { + metadataService = googlecloud.NewStackdriverMetadataServiceForTimeSeries(ts) } - if _, ok := eventGroups[id]; !ok { - eventGroups[id] = make([]KeyValuePoint, 0) - } + for i := range keyValues { + sdCollectorInputData.Timestamp = &keyValues[i].Timestamp + + id, err := metadataService.ID(ctx, sdCollectorInputData) + if err != nil { + m.Logger().Errorf("error trying to retrieve ID from metric event '%v'", err) + continue + } - keyValues[i].ECS = metadataCollectorData.ECS - keyValues[i].Labels = metadataCollectorData.Labels + metadataCollectorData, err := metadataService.Metadata(ctx, sdCollectorInputData.TimeSeries) + if err != nil { + m.Logger().Error("error trying to retrieve labels from metric event") + continue + } - // Group the data into common events - eventGroups[id] = append(eventGroups[id], keyValues[i]) + if _, ok := eventGroups[id]; !ok { + eventGroups[id] = make([]KeyValuePoint, 0) + } + + keyValues[i].ECS = metadataCollectorData.ECS + keyValues[i].Labels = metadataCollectorData.Labels + + // Group the data into common events + eventGroups[id] = append(eventGroups[id], keyValues[i]) + } } } diff --git a/x-pack/metricbeat/module/googlecloud/storage/_meta/data.json b/x-pack/metricbeat/module/googlecloud/storage/_meta/data.json index f94a1375997..02541f8a1e7 100644 --- a/x-pack/metricbeat/module/googlecloud/storage/_meta/data.json +++ b/x-pack/metricbeat/module/googlecloud/storage/_meta/data.json @@ -14,18 +14,19 @@ "googlecloud": { "labels": { "metrics": { - "storage_class": "REGIONAL" + "method": "GetBucketMetadata", + "response_code": "OK" }, "resource": { - "bucket_name": "elastic-vsphere-images", - "location": "us-east1" + "bucket_name": "ocp-be-c5kjr-image-registry-us-central1-dsoafnbgctvfimpavswkgn", + "location": "us-central1" } }, "storage": { - "storage": { - "object_count": 3, - "total_byte_seconds": 58816542441472, - "total_bytes": 680747019 + "network": { + "sent_bytes_count": { + "value": 2637 + } } } }, diff --git a/x-pack/metricbeat/module/googlecloud/storage/_meta/fields.yml b/x-pack/metricbeat/module/googlecloud/storage/_meta/fields.yml index 71fcb2bdbeb..cdf2fde2fff 100644 --- a/x-pack/metricbeat/module/googlecloud/storage/_meta/fields.yml +++ b/x-pack/metricbeat/module/googlecloud/storage/_meta/fields.yml @@ -6,39 +6,39 @@ - name: api type: group fields: - - name: request_count + - name: request_count.value type: long description: Delta count of API calls, grouped by the API method name and response code. - name: authz type: group fields: - - name: acl_based_object_access_count + - name: acl_based_object_access_count.value type: long description: Delta count of requests that result in an object being granted access solely due to object ACLs. - - name: acl_operations_count + - name: acl_operations_count.value type: long description: Usage of ACL operations broken down by type. - - name: object_specific_acl_mutation_count + - name: object_specific_acl_mutation_count.value type: long description: Delta count of changes made to object specific ACLs. - name: network type: group fields: - - name: received_bytes_count + - name: received_bytes_count.value type: long description: Delta count of bytes received over the network, grouped by the API method name and response code. - - name: sent_bytes_count + - name: sent_bytes_count.value type: long description: Delta count of bytes sent over the network, grouped by the API method name and response code. - name: storage type: group fields: - - name: object_count + - name: object_count.value type: long description: Total number of objects per bucket, grouped by storage class. This value is measured once per day, and the value is repeated at each sampling interval throughout the day. - - name: total_byte_seconds + - name: total_byte_seconds.value type: long description: Delta count of bytes received over the network, grouped by the API method name and response code. - - name: total_bytes + - name: total_bytes.value type: long description: Total size of all objects in the bucket, grouped by storage class. This value is measured once per day, and the value is repeated at each sampling interval throughout the day. diff --git a/x-pack/metricbeat/module/googlecloud/storage/manifest.yml b/x-pack/metricbeat/module/googlecloud/storage/manifest.yml index f462867dcb5..2a9363cf78d 100644 --- a/x-pack/metricbeat/module/googlecloud/storage/manifest.yml +++ b/x-pack/metricbeat/module/googlecloud/storage/manifest.yml @@ -6,12 +6,13 @@ input: stackdriver: service: storage metrics: - - "storage.googleapis.com/api/request_count" - - "storage.googleapis.com/authz/acl_based_object_access_count" - - "storage.googleapis.com/authz/acl_operations_count" - - "storage.googleapis.com/authz/object_specific_acl_mutation_count" - - "storage.googleapis.com/network/received_bytes_count" - - "storage.googleapis.com/network/sent_bytes_count" - - "storage.googleapis.com/storage/object_count" - - "storage.googleapis.com/storage/total_byte_seconds" - - "storage.googleapis.com/storage/total_bytes" + - metric_types: + - "storage.googleapis.com/api/request_count" + - "storage.googleapis.com/authz/acl_based_object_access_count" + - "storage.googleapis.com/authz/acl_operations_count" + - "storage.googleapis.com/authz/object_specific_acl_mutation_count" + - "storage.googleapis.com/network/received_bytes_count" + - "storage.googleapis.com/network/sent_bytes_count" + - "storage.googleapis.com/storage/object_count" + - "storage.googleapis.com/storage/total_byte_seconds" + - "storage.googleapis.com/storage/total_bytes" diff --git a/x-pack/metricbeat/module/googlecloud/storage/storage_integration_test.go b/x-pack/metricbeat/module/googlecloud/storage/storage_integration_test.go index f669a7c46e9..0f2b010f5f2 100644 --- a/x-pack/metricbeat/module/googlecloud/storage/storage_integration_test.go +++ b/x-pack/metricbeat/module/googlecloud/storage/storage_integration_test.go @@ -11,11 +11,11 @@ import ( "testing" mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing" - "github.com/elastic/beats/v7/x-pack/metricbeat/module/googlecloud" + "github.com/elastic/beats/v7/x-pack/metricbeat/module/googlecloud/stackdriver" ) func TestData(t *testing.T) { - config := googlecloud.GetConfigForTest(t, "storage") + config := stackdriver.GetConfigForTest(t, "storage") metricSet := mbtest.NewFetcher(t, config) metricSet.WriteEvents(t, "/") } diff --git a/x-pack/metricbeat/modules.d/googlecloud.yml.disabled b/x-pack/metricbeat/modules.d/googlecloud.yml.disabled index fc7d792dadf..392b64718d8 100644 --- a/x-pack/metricbeat/modules.d/googlecloud.yml.disabled +++ b/x-pack/metricbeat/modules.d/googlecloud.yml.disabled @@ -4,13 +4,21 @@ - module: googlecloud metricsets: - compute + region: "us-central1" + project_id: "your project id" + credentials_file_path: "your JSON credentials file path" + exclude_labels: false + period: 300s + +- module: googlecloud + metricsets: - pubsub - loadbalancing zone: "us-central1-a" project_id: "your project id" credentials_file_path: "your JSON credentials file path" exclude_labels: false - period: 300s + period: 60s - module: googlecloud metricsets: From 5e1885047cfe1fcfda3ca2a507cd6794687d7451 Mon Sep 17 00:00:00 2001 From: kaiyan-sheng Date: Fri, 24 Apr 2020 11:46:29 -0600 Subject: [PATCH 020/116] [Metricbeat] remove distribution type metrics from googlecloud (#17946) * remove distribution type metrics from googlecloud --- .../googlecloud/compute/_meta/docs.asciidoc | 23 ++++++++++++++++--- .../loadbalancing/_meta/docs.asciidoc | 19 ++++++++++----- .../googlecloud/loadbalancing/manifest.yml | 10 -------- .../googlecloud/pubsub/_meta/docs.asciidoc | 10 ++++---- .../module/googlecloud/pubsub/manifest.yml | 2 -- .../googlecloud/storage/_meta/docs.asciidoc | 7 +++--- 6 files changed, 43 insertions(+), 28 deletions(-) diff --git a/x-pack/metricbeat/module/googlecloud/compute/_meta/docs.asciidoc b/x-pack/metricbeat/module/googlecloud/compute/_meta/docs.asciidoc index 8cc6b3f85a2..f72103099ea 100644 --- a/x-pack/metricbeat/module/googlecloud/compute/_meta/docs.asciidoc +++ b/x-pack/metricbeat/module/googlecloud/compute/_meta/docs.asciidoc @@ -1,11 +1,15 @@ -Compute Metricset to fetch metrics from https://cloud.google.com/compute/[Compute Engine] Virtual Machines in Google Cloud Platform. No Monitoring or Logging agent is required in your instances to use this Metricset. +Compute metricset to fetch metrics from https://cloud.google.com/compute/[Compute Engine] Virtual Machines in Google Cloud Platform. No Monitoring or Logging agent is required in your instances to use this metricset. -The `compute` Metricset contains all metrics exported from the https://cloud.google.com/monitoring/api/metrics_gcp#gcp-compute[Stackdriver API]. The field names have been left untouched for people already familiar with them. +The `compute` metricset contains all metrics exported from the https://cloud.google.com/monitoring/api/metrics_gcp#gcp-compute[Stackdriver API]. The field names have been left untouched for people already familiar with them. Extra labels and metadata are also extracted using the https://cloud.google.com/compute/docs/reference/rest/v1/instances/get[Compute API]. This is enough to get most of the info associated with a metric like Compute labels and metadata and metric specific Labels. [float] -=== Fields and labels +=== Metrics and labels +Here is a list of metrics collected by `compute` metricset: + +[float] +==== firewall * `instance.firewall.dropped_bytes_count`: Incoming bytes dropped by the firewall. - `instance_name`: The name of the VM instance. @@ -13,14 +17,21 @@ Extra labels and metadata are also extracted using the https://cloud.google.com/ * `instance.firewall.dropped_packets_count`: Incoming packets dropped by the firewall. - `instance_name`: The name of the VM instance. +[float] +==== cpu + * `instance.cpu.reserved_cores`: Number of cores reserved on the host of the `instance`. - `instance_name`: The name of the VM instance. * `instance.cpu.utilization`: The fraction of the allocated CPU that is currently in use on the `instance`. - `instance_name`: The name of the VM instance. + * `instance.cpu.usage_time`: Usage for all cores in seconds. - `instance_name`: The name of the VM instance. +[float] +==== disk + * `instance.disk.read_bytes_count`: Count of bytes read from disk. - `instance_name`: The name of the VM instance. - `device_name`: The name of the disk device. @@ -45,9 +56,15 @@ Extra labels and metadata are also extracted using the https://cloud.google.com/ - `storage_type`: The storage type: `pd-standard`, `pd-ssd`, or `local-ssd`. - `device_type`: The disk type: `ephemeral` or `permanent`. +[float] +==== uptime + * `instance.uptime`: How long the VM has been running, in seconds - `instance_name`: The name of the VM instance. +[float] +==== network + * `instance.network.received_bytes_count`: Count of bytes received from the network - `instance_name`: The name of the VM instance. - `loadBalanced`: Whether traffic was sent from an L3 loadbalanced IP address assigned to the VM. Traffic that is externally routed from the VM's standard internal or external IP address, such as L7 loadbalanced traffic, is not considered to be loadbalanced in this metric. diff --git a/x-pack/metricbeat/module/googlecloud/loadbalancing/_meta/docs.asciidoc b/x-pack/metricbeat/module/googlecloud/loadbalancing/_meta/docs.asciidoc index 256e744ba6b..2022b44d1c7 100644 --- a/x-pack/metricbeat/module/googlecloud/loadbalancing/_meta/docs.asciidoc +++ b/x-pack/metricbeat/module/googlecloud/loadbalancing/_meta/docs.asciidoc @@ -3,25 +3,32 @@ Load Balancing metricset to fetch metrics from https://cloud.google.com/load-bal The `loadbalancing` metricset contains all metrics exported from the https://cloud.google.com/monitoring/api/metrics_gcp#gcp-loadbalancing[Stackdriver API]. The field names have been left untouched for people already familiar with them. [float] -=== Fields +=== Metrics +Here is a list of metrics collected by `loadbalancing` metricset: + +[float] +==== https -- `loadbalancing.https.backend_latencies`: A distribution of the latency calculated from when the request was sent by the proxy to the backend until the proxy received from the backend the last byte of response. - `loadbalancing.https.backend_request_bytes_count`: The number of bytes sent as requests from HTTP/S load balancer to backends. - `loadbalancing.https.backend_request_count`: The number of requests served by backends of HTTP/S load balancer. - `loadbalancing.https.backend_response_bytes_count`: The number of bytes sent as responses from backends (or cache) to HTTP/S load balancer. -- `loadbalancing.https.frontend_tcp_rtt`: A distribution of the RTT measured for each connection between client and proxy. - `loadbalancing.https.request_bytes_count`: The number of bytes sent as requests from clients to HTTP/S load balancer. - `loadbalancing.https.request_count`: The number of requests served by HTTP/S load balancer. - `loadbalancing.https.response_bytes_count`: The number of bytes sent as responses from HTTP/S load balancer to clients. -- `loadbalancing.https.total_latencies`: A distribution of the latency calculated from when the request was received by the proxy until the proxy got ACK from client on last response byte. + +[float] +==== l3 + - `loadbalancing.l3.internal.egress_bytes_count`: The number of bytes sent from ILB backend to client (for TCP flows it's counting bytes on application stream only). - `loadbalancing.l3.internal.egress_packets_count`: The number of packets sent from ILB backend to client of the flow. - `loadbalancing.l3.internal.ingress_bytes_count`: The number of bytes sent from client to ILB backend (for TCP flows it's counting bytes on application stream only). - `loadbalancing.l3.internal.ingress_packets_count`: The number of packets sent from client to ILB backend. -- `loadbalancing.l3.internal.rtt_latencies`: A distribution of RTT measured over TCP connections for ILB flows. + +[float] +==== tcp_ssl_proxy + - `loadbalancing.tcp_ssl_proxy.closed_connections`: Number of connections that were terminated over TCP/SSL proxy. - `loadbalancing.tcp_ssl_proxy.egress_bytes_count`: Number of bytes sent from VM to client using proxy. -- `loadbalancing.tcp_ssl_proxy.frontend_tcp_rtt`: A distribution of the smoothed RTT (in ms) measured by the proxy's TCP stack, each minute application layer bytes pass from proxy to client. - `loadbalancing.tcp_ssl_proxy.ingress_bytes_count`: Number of bytes sent from client to VM using proxy. - `loadbalancing.tcp_ssl_proxy.new_connections`: Number of connections that were created over TCP/SSL proxy. - `loadbalancing.tcp_ssl_proxy.open_connections`: Current number of outstanding connections through the TCP/SSL proxy. diff --git a/x-pack/metricbeat/module/googlecloud/loadbalancing/manifest.yml b/x-pack/metricbeat/module/googlecloud/loadbalancing/manifest.yml index 173b8fbd265..5ec1dc8d417 100644 --- a/x-pack/metricbeat/module/googlecloud/loadbalancing/manifest.yml +++ b/x-pack/metricbeat/module/googlecloud/loadbalancing/manifest.yml @@ -7,28 +7,18 @@ input: service: loadbalancing metrics: - metric_types: - - "loadbalancing.googleapis.com/https/backend_latencies" - - "loadbalancing.googleapis.com/https/backend_latencies" - "loadbalancing.googleapis.com/https/backend_request_bytes_count" - "loadbalancing.googleapis.com/https/backend_request_count" - "loadbalancing.googleapis.com/https/backend_response_bytes_count" - - "loadbalancing.googleapis.com/https/frontend_tcp_rtt" - "loadbalancing.googleapis.com/https/request_bytes_count" - - "loadbalancing.googleapis.com/https/request_bytes_count" - - "loadbalancing.googleapis.com/https/request_count" - "loadbalancing.googleapis.com/https/request_count" - "loadbalancing.googleapis.com/https/response_bytes_count" - - "loadbalancing.googleapis.com/https/response_bytes_count" - - "loadbalancing.googleapis.com/https/total_latencies" - - "loadbalancing.googleapis.com/https/total_latencies" - "loadbalancing.googleapis.com/l3/internal/egress_bytes_count" - "loadbalancing.googleapis.com/l3/internal/egress_packets_count" - "loadbalancing.googleapis.com/l3/internal/ingress_bytes_count" - "loadbalancing.googleapis.com/l3/internal/ingress_packets_count" - - "loadbalancing.googleapis.com/l3/internal/rtt_latencies" - "loadbalancing.googleapis.com/tcp_ssl_proxy/closed_connections" - "loadbalancing.googleapis.com/tcp_ssl_proxy/egress_bytes_count" - - "loadbalancing.googleapis.com/tcp_ssl_proxy/frontend_tcp_rtt" - "loadbalancing.googleapis.com/tcp_ssl_proxy/ingress_bytes_count" - "loadbalancing.googleapis.com/tcp_ssl_proxy/new_connections" - "loadbalancing.googleapis.com/tcp_ssl_proxy/open_connections" diff --git a/x-pack/metricbeat/module/googlecloud/pubsub/_meta/docs.asciidoc b/x-pack/metricbeat/module/googlecloud/pubsub/_meta/docs.asciidoc index cd6e94083ef..f8faf2bada0 100644 --- a/x-pack/metricbeat/module/googlecloud/pubsub/_meta/docs.asciidoc +++ b/x-pack/metricbeat/module/googlecloud/pubsub/_meta/docs.asciidoc @@ -1,9 +1,13 @@ -PubSub Metricset to fetch metrics from https://cloud.google.com/pubsub/[Pub/Sub] topics and subscriptions in Google Cloud Platform. +PubSub metricsetf to fetch metrics from https://cloud.google.com/pubsub/[Pub/Sub] topics and subscriptions in Google Cloud Platform. -The `pubsub` Metricset contains all GA stage metrics exported from the https://cloud.google.com/monitoring/api/metrics_gcp#gcp-pubsub[Stackdriver API]. The field names have been left untouched for people already familiar with them. +The `pubsub` metricset contains all GA stage metrics exported from the https://cloud.google.com/monitoring/api/metrics_gcp#gcp-pubsub[Stackdriver API]. The field names have been left untouched for people already familiar with them. No special permissions are needed apart from the ones detailed in the module section of the docs. +[float] +=== Metrics +Here is a list of metrics collected by `pubsub` metricset: + [float] ==== Snapshot Metrics - `pubsub.snapshot.backlog_bytes`: Total byte size of the messages retained in a snapshot. @@ -35,7 +39,6 @@ No special permissions are needed apart from the ones detailed in the module sec - `pubsub.subscription.pull_message_operation_count`: Cumulative count of pull message operations, grouped by result. For a definition of message operations, see Cloud Pub/Sub metric subscription/mod_ack_deadline_message_operation_count. - `pubsub.subscription.pull_request_count`: Cumulative count of pull requests, grouped by result. - `pubsub.subscription.push_request_count`: Cumulative count of push attempts, grouped by result. Unlike pulls, the push server implementation does not batch user messages. So each request only contains one user message. The push server retries on errors, so a given user message can appear multiple times. -- `pubsub.subscription.push_request_latencies`: Distribution of push request latencies (in microseconds), grouped by result. - `pubsub.subscription.retained_acked_bytes`: otal byte size of the acknowledged messages retained in a subscription. - `pubsub.subscription.retained_acked_bytes_by_region`: Total byte size of the acknowledged messages retained in a subscription, broken down by Cloud region. - `pubsub.subscription.seek_request_count`: Cumulative count of seek attempts, grouped by result. @@ -52,7 +55,6 @@ No special permissions are needed apart from the ones detailed in the module sec ==== Topic Metrics - `pubsub.topic.byte_cost`: Cost of operations, measured in bytes. This is used to measure utilization for quotas. - `pubsub.topic.config_updates_count`: Cumulative count of configuration changes, grouped by operation type and result. -- `pubsub.topic.message_sizes`: Distribution of publish message sizes (in bytes). - `pubsub.topic.oldest_retained_acked_message_age_by_region`: Age (in seconds) of the oldest acknowledged message retained in a topic, broken down by Cloud region. - `pubsub.topic.oldest_unacked_message_age_by_region`: Age (in seconds) of the oldest unacknowledged message in a topic, broken down by Cloud region. - `pubsub.topic.retained_acked_bytes_by_region`: Total byte size of the acknowledged messages retained in a topic, broken down by Cloud region. diff --git a/x-pack/metricbeat/module/googlecloud/pubsub/manifest.yml b/x-pack/metricbeat/module/googlecloud/pubsub/manifest.yml index 285136f3cf8..a002820ebd6 100644 --- a/x-pack/metricbeat/module/googlecloud/pubsub/manifest.yml +++ b/x-pack/metricbeat/module/googlecloud/pubsub/manifest.yml @@ -33,7 +33,6 @@ input: - "pubsub.googleapis.com/subscription/pull_message_operation_count" - "pubsub.googleapis.com/subscription/pull_request_count" - "pubsub.googleapis.com/subscription/push_request_count" - - "pubsub.googleapis.com/subscription/push_request_latencies" - "pubsub.googleapis.com/subscription/retained_acked_bytes" - "pubsub.googleapis.com/subscription/retained_acked_bytes_by_region" - "pubsub.googleapis.com/subscription/seek_request_count" @@ -47,7 +46,6 @@ input: - "pubsub.googleapis.com/subscription/unacked_bytes_by_region" - "pubsub.googleapis.com/topic/byte_cost" - "pubsub.googleapis.com/topic/config_updates_count" - - "pubsub.googleapis.com/topic/message_sizes" - "pubsub.googleapis.com/topic/oldest_retained_acked_message_age_by_region" - "pubsub.googleapis.com/topic/oldest_unacked_message_age_by_region" - "pubsub.googleapis.com/topic/retained_acked_bytes_by_region" diff --git a/x-pack/metricbeat/module/googlecloud/storage/_meta/docs.asciidoc b/x-pack/metricbeat/module/googlecloud/storage/_meta/docs.asciidoc index 4c9ff62e4ae..d58ceda230f 100644 --- a/x-pack/metricbeat/module/googlecloud/storage/_meta/docs.asciidoc +++ b/x-pack/metricbeat/module/googlecloud/storage/_meta/docs.asciidoc @@ -1,11 +1,12 @@ -Storage Metricset to fetch metrics from https://cloud.google.com/storage/[Storage] in Google Cloud Platform. +Storage metricset to fetch metrics from https://cloud.google.com/storage/[Storage] in Google Cloud Platform. -The `storage` Metricset contains all metrics exported from the https://cloud.google.com/monitoring/api/metrics_gcp#gcp-storage[Stackdriver API]. The field names have been left untouched for people already familiar with them. +The `storage` metricset contains all metrics exported from the https://cloud.google.com/monitoring/api/metrics_gcp#gcp-storage[Stackdriver API]. The field names have been left untouched for people already familiar with them. You can specify a single region to fetch metrics like `us-central1`. Be aware that GCP Storage does not use zones so `us-central1-a` will return nothing. If no region is specified, it will return metrics from all buckets. [float] -=== Fields +=== Metrics +Here is a list of metrics collected by `storage` metricset: - `storage.api.request_count`: Delta count of API calls, grouped by the API method name and response code. - `storage.authz.acl_based_object_access_count`: Delta count of requests that result in an object being granted access solely due to object ACLs. From 7c1b39eea9bc14f2f63e1aa451f6346a55fd1c19 Mon Sep 17 00:00:00 2001 From: DeDe Morton Date: Fri, 24 Apr 2020 12:25:52 -0700 Subject: [PATCH 021/116] [docs] Fix attribute resolution in example (#17955) --- filebeat/docs/running-on-cloudfoundry.asciidoc | 11 ++++++----- filebeat/docs/running-on-kubernetes.asciidoc | 2 +- metricbeat/docs/running-on-cloudfoundry.asciidoc | 11 ++++++----- metricbeat/docs/running-on-kubernetes.asciidoc | 2 +- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/filebeat/docs/running-on-cloudfoundry.asciidoc b/filebeat/docs/running-on-cloudfoundry.asciidoc index 34c225ed831..ae9603dc012 100644 --- a/filebeat/docs/running-on-cloudfoundry.asciidoc +++ b/filebeat/docs/running-on-cloudfoundry.asciidoc @@ -1,5 +1,5 @@ [[running-on-cloudfoundry]] -=== Running {beatname_uc} on Cloud Foundry +=== Run {beatname_uc} on Cloud Foundry You can use {beatname_uc} on Cloud Foundry to retrieve and ship logs. @@ -14,18 +14,19 @@ endif::[] [float] ==== Cloud Foundry credentials -{beatname_uc} needs credentials created with UAA so it can connect to loggregator to receive the logs. The uaac +{beatname_uc} needs credentials created with UAA so it can connect to loggregator to receive the logs. The `uaac` command will create the required credentials for connecting to loggregator. -["source", "sh"] +["source","sh",subs="attributes"] ------------------------------------------------ uaac client add {beatname_lc} --name {beatname_lc} --secret changeme --authorized_grant_types client_credentials,refresh_token --authorities doppler.firehose,cloud_controller.admin_read_only ------------------------------------------------ [WARNING] ======================================= -*Use a unique secret:* The uaac command above is just an example and the secret should be changed and the -`{beatname_lc}.yml` should be updated with your choosen secret. +*Use a unique secret:* The `uaac` command shown here is an example. Remember to +replace `changeme` with your secret, and update the +{beatname_lc}.yml+ file to +use your chosen secret. ======================================= diff --git a/filebeat/docs/running-on-kubernetes.asciidoc b/filebeat/docs/running-on-kubernetes.asciidoc index f104a06e245..0df3c811a95 100644 --- a/filebeat/docs/running-on-kubernetes.asciidoc +++ b/filebeat/docs/running-on-kubernetes.asciidoc @@ -1,5 +1,5 @@ [[running-on-kubernetes]] -=== Running {beatname_uc} on Kubernetes +=== Run {beatname_uc} on Kubernetes You can use {beatname_uc} <> on Kubernetes to retrieve and ship container logs. diff --git a/metricbeat/docs/running-on-cloudfoundry.asciidoc b/metricbeat/docs/running-on-cloudfoundry.asciidoc index e6c25d02587..2988e4d3a8b 100644 --- a/metricbeat/docs/running-on-cloudfoundry.asciidoc +++ b/metricbeat/docs/running-on-cloudfoundry.asciidoc @@ -1,5 +1,5 @@ [[running-on-cloudfoundry]] -=== Running {beatname_uc} on Cloud Foundry +=== Run {beatname_uc} on Cloud Foundry You can use {beatname_uc} on Cloud Foundry to retrieve and ship metrics. @@ -14,18 +14,19 @@ endif::[] [float] ==== Cloud Foundry credentials -{beatname_uc} needs credentials created with UAA so it can connect to loggregator to receive the logs. The uaac +{beatname_uc} needs credentials created with UAA so it can connect to loggregator to receive the logs. The `uaac` command will create the required credentials for connecting to loggregator. -["source", "sh"] +["source","sh",subs="attributes"] ------------------------------------------------ uaac client add {beatname_lc} --name {beatname_lc} --secret changeme --authorized_grant_types client_credentials,refresh_token --authorities doppler.firehose,cloud_controller.admin_read_only ------------------------------------------------ [WARNING] ======================================= -*Use a unique secret:* The uaac command above is just an example and the secret should be changed and the -`{beatname_lc}.yml` should be updated with your choosen secret. +*Use a unique secret:* The `uaac` command shown here is an example. Remember to +replace `changeme` with your secret, and update the +{beatname_lc}.yml+ file to +use your chosen secret. ======================================= diff --git a/metricbeat/docs/running-on-kubernetes.asciidoc b/metricbeat/docs/running-on-kubernetes.asciidoc index 2d757906110..7267c0f9872 100644 --- a/metricbeat/docs/running-on-kubernetes.asciidoc +++ b/metricbeat/docs/running-on-kubernetes.asciidoc @@ -1,5 +1,5 @@ [[running-on-kubernetes]] -=== Running Metricbeat on Kubernetes +=== Run Metricbeat on Kubernetes You can use {beatname_uc} <> on Kubernetes to retrieve cluster metrics. From 3bba66d9915affc6a5596db48ea016c36bbfbf8b Mon Sep 17 00:00:00 2001 From: kaiyan-sheng Date: Fri, 24 Apr 2020 14:48:07 -0600 Subject: [PATCH 022/116] [Metricbeat GCP] Improve integration test to generate more specific data-*.json (#17906) * Improve TestData integration test to generate more specific data-*.json * rerun TestData to generate data.json files --- .../googlecloud/compute/_meta/data.json | 6 +- .../googlecloud/compute/_meta/data_cpu.json | 83 ++++++++--------- .../googlecloud/compute/_meta/data_disk.json | 59 ++++++++++++ .../compute/_meta/data_disk_01.json | 74 --------------- .../compute/_meta/data_disk_02.json | 77 --------------- .../compute/_meta/data_firewall.json | 93 ++++++++----------- .../compute/_meta/data_instance.json | 73 --------------- .../compute/_meta/data_network.json | 54 +++++++++++ .../compute/compute_integration_test.go | 29 +++++- .../googlecloud/loadbalancing/_meta/data.json | 2 +- .../loadbalancing/_meta/data_l3.json | 50 ++++++++++ .../loadbalancing_integration_test.go | 32 ++++++- .../module/googlecloud/pubsub/_meta/data.json | 4 +- .../pubsub/_meta/data_subscription.json | 35 +++++++ .../googlecloud/pubsub/_meta/data_topic.json | 43 +++++++++ .../pubsub/pubsub_integration_test.go | 32 ++++++- .../googlecloud/storage/_meta/data.json | 15 ++- .../storage/_meta/data_network.json | 38 ++++++++ .../storage/_meta/data_storage.json | 39 ++++++++ .../storage/storage_integration_test.go | 28 +++++- 20 files changed, 518 insertions(+), 348 deletions(-) create mode 100644 x-pack/metricbeat/module/googlecloud/compute/_meta/data_disk.json delete mode 100644 x-pack/metricbeat/module/googlecloud/compute/_meta/data_disk_01.json delete mode 100644 x-pack/metricbeat/module/googlecloud/compute/_meta/data_disk_02.json delete mode 100644 x-pack/metricbeat/module/googlecloud/compute/_meta/data_instance.json create mode 100644 x-pack/metricbeat/module/googlecloud/compute/_meta/data_network.json create mode 100644 x-pack/metricbeat/module/googlecloud/loadbalancing/_meta/data_l3.json create mode 100644 x-pack/metricbeat/module/googlecloud/pubsub/_meta/data_subscription.json create mode 100644 x-pack/metricbeat/module/googlecloud/pubsub/_meta/data_topic.json create mode 100644 x-pack/metricbeat/module/googlecloud/storage/_meta/data_network.json create mode 100644 x-pack/metricbeat/module/googlecloud/storage/_meta/data_storage.json diff --git a/x-pack/metricbeat/module/googlecloud/compute/_meta/data.json b/x-pack/metricbeat/module/googlecloud/compute/_meta/data.json index f1f9f8f386b..4a3ab08217c 100644 --- a/x-pack/metricbeat/module/googlecloud/compute/_meta/data.json +++ b/x-pack/metricbeat/module/googlecloud/compute/_meta/data.json @@ -5,8 +5,8 @@ "id": "elastic-observability" }, "instance": { - "id": "6889336735612324102", - "name": "gke-dev-oblt-dev-oblt-pool-83a8831b-kd53" + "id": "1174463293187628268", + "name": "gke-observability-8--observability-8--bc1afd95-ngmh" }, "machine": { "type": "n1-standard-4" @@ -40,7 +40,7 @@ }, "labels": { "metrics": { - "device_name": "disk-4", + "device_name": "disk-2", "device_type": "permanent", "storage_type": "pd-standard" }, diff --git a/x-pack/metricbeat/module/googlecloud/compute/_meta/data_cpu.json b/x-pack/metricbeat/module/googlecloud/compute/_meta/data_cpu.json index 8496bfd79b1..1d3a120a218 100644 --- a/x-pack/metricbeat/module/googlecloud/compute/_meta/data_cpu.json +++ b/x-pack/metricbeat/module/googlecloud/compute/_meta/data_cpu.json @@ -1,69 +1,62 @@ { - "@timestamp": "2020-01-08T16:06:00.000Z", - "@metadata": { - "beat": "metricbeat", - "type": "_doc", - "version": "8.0.0" - }, - "host": { - "name": "mcastro", - "id": "54f70115bae545cbac2b150f254472a0", - "containerized": false, - "hostname": "mcastro", - "architecture": "x86_64", - "os": { - "version": "", - "family": "", - "name": "Antergos Linux", - "kernel": "5.4.3-arch1-1", - "platform": "antergos" - } - }, - "agent": { - "ephemeral_id": "8b802033-b611-414b-bcaf-1aa19e5f5901", - "hostname": "mcastro", - "id": "7e36a073-1a32-4a94-b65b-4c7f971fb228", - "version": "8.0.0", - "type": "metricbeat" - }, + "@timestamp": "2017-10-12T08:05:34.853Z", "cloud": { "account": { - "id": "elastic-metricbeat" + "id": "elastic-observability" }, - "provider": "googlecloud", "instance": { - "name": "instance-1", - "id": "4503798379141677974" + "id": "1174463293187628268", + "name": "gke-observability-8--observability-8--bc1afd95-ngmh" }, "machine": { - "type": "f1-micro" + "type": "n1-standard-4" }, - "availability_zone": "us-central1-a" + "provider": "googlecloud" }, + "cloud.availability_zone": "europe-west1-c", "event": { - "duration": 1398412653, "dataset": "googlecloud.compute", + "duration": 115000, "module": "googlecloud" }, - "metricset": { - "name": "compute", - "period": 300000 - }, "googlecloud": { "compute": { + "firewall": { + "dropped_bytes_count": { + "value": 181 + }, + "dropped_packets_count": { + "value": 3 + } + }, "instance": { "cpu": { - "reserved_cores": 0.2, - "utilization": 0.005524845140497596 + "reserved_cores": { + "value": 4 + }, + "usage_time": { + "value": 63.478293027728796 + }, + "utilization": { + "value": 0.26449288761553663 + } + }, + "uptime": { + "value": 60 } } }, - "labels": {} + "labels": { + "user": { + "goog-gke-node": "" + } + } + }, + "metricset": { + "name": "compute", + "period": 10000 }, "service": { "type": "googlecloud" - }, - "ecs": { - "version": "1.2.0" } -} +} \ No newline at end of file diff --git a/x-pack/metricbeat/module/googlecloud/compute/_meta/data_disk.json b/x-pack/metricbeat/module/googlecloud/compute/_meta/data_disk.json new file mode 100644 index 00000000000..8da39b6ab7d --- /dev/null +++ b/x-pack/metricbeat/module/googlecloud/compute/_meta/data_disk.json @@ -0,0 +1,59 @@ +{ + "@timestamp": "2017-10-12T08:05:34.853Z", + "cloud": { + "account": { + "id": "elastic-observability" + }, + "instance": { + "id": "8390997210852978465", + "name": "gke-observability-7--observability-7--3dd3e39b-0jm5" + }, + "machine": { + "type": "n1-standard-4" + }, + "provider": "googlecloud" + }, + "cloud.availability_zone": "europe-west1-c", + "event": { + "dataset": "googlecloud.compute", + "duration": 115000, + "module": "googlecloud" + }, + "googlecloud": { + "compute": { + "instance": { + "disk": { + "read_bytes_count": { + "value": 0 + }, + "read_ops_count": { + "value": 0 + }, + "write_bytes_count": { + "value": 0 + }, + "write_ops_count": { + "value": 0 + } + } + } + }, + "labels": { + "metrics": { + "device_name": "gke-observability-7-1--pvc-65581044-7d5d-11ea-8cd9-42010af0011c", + "device_type": "permanent", + "storage_type": "pd-standard" + }, + "user": { + "goog-gke-node": "" + } + } + }, + "metricset": { + "name": "compute", + "period": 10000 + }, + "service": { + "type": "googlecloud" + } +} \ No newline at end of file diff --git a/x-pack/metricbeat/module/googlecloud/compute/_meta/data_disk_01.json b/x-pack/metricbeat/module/googlecloud/compute/_meta/data_disk_01.json deleted file mode 100644 index 038c451d934..00000000000 --- a/x-pack/metricbeat/module/googlecloud/compute/_meta/data_disk_01.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "@timestamp": "2020-01-08T16:05:00.000Z", - "@metadata": { - "beat": "metricbeat", - "type": "_doc", - "version": "8.0.0" - }, - "agent": { - "hostname": "mcastro", - "id": "7e36a073-1a32-4a94-b65b-4c7f971fb228", - "version": "8.0.0", - "type": "metricbeat", - "ephemeral_id": "8b802033-b611-414b-bcaf-1aa19e5f5901" - }, - "ecs": { - "version": "1.2.0" - }, - "googlecloud": { - "labels": { - "metrics": { - "device_type": "permanent", - "storage_type": "pd-standard", - "device_name": "instance-1" - } - }, - "compute": { - "instance": { - "disk": { - "write_bytes_count": 945853 - } - } - } - }, - "service": { - "type": "googlecloud" - }, - "cloud": { - "account": { - "id": "elastic-metricbeat" - }, - "provider": "googlecloud", - "instance": { - "name": "instance-1", - "id": "4503798379141677974" - }, - "machine": { - "type": "f1-micro" - }, - "availability_zone": "us-central1-a" - }, - "metricset": { - "name": "compute", - "period": 300000 - }, - "event": { - "module": "googlecloud", - "duration": 1398637364, - "dataset": "googlecloud.compute" - }, - "host": { - "containerized": false, - "hostname": "mcastro", - "architecture": "x86_64", - "os": { - "platform": "antergos", - "version": "", - "family": "", - "name": "Antergos Linux", - "kernel": "5.4.3-arch1-1" - }, - "name": "mcastro", - "id": "54f70115bae545cbac2b150f254472a0" - } -} diff --git a/x-pack/metricbeat/module/googlecloud/compute/_meta/data_disk_02.json b/x-pack/metricbeat/module/googlecloud/compute/_meta/data_disk_02.json deleted file mode 100644 index d3d0bc9c5ba..00000000000 --- a/x-pack/metricbeat/module/googlecloud/compute/_meta/data_disk_02.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "@timestamp": "2020-01-08T16:04:00.000Z", - "@metadata": { - "beat": "metricbeat", - "type": "_doc", - "version": "8.0.0" - }, - "service": { - "type": "googlecloud" - }, - "ecs": { - "version": "1.2.0" - }, - "host": { - "os": { - "name": "Antergos Linux", - "kernel": "5.4.3-arch1-1", - "platform": "antergos", - "version": "", - "family": "" - }, - "id": "54f70115bae545cbac2b150f254472a0", - "containerized": false, - "name": "mcastro", - "hostname": "mcastro", - "architecture": "x86_64" - }, - "agent": { - "ephemeral_id": "8b802033-b611-414b-bcaf-1aa19e5f5901", - "hostname": "mcastro", - "id": "7e36a073-1a32-4a94-b65b-4c7f971fb228", - "version": "8.0.0", - "type": "metricbeat" - }, - "cloud": { - "availability_zone": "us-central1-a", - "account": { - "id": "elastic-metricbeat" - }, - "provider": "googlecloud", - "instance": { - "id": "4503798379141677974", - "name": "instance-1" - }, - "machine": { - "type": "f1-micro" - } - }, - "metricset": { - "name": "compute", - "period": 300000 - }, - "event": { - "dataset": "googlecloud.compute", - "module": "googlecloud", - "duration": 1398743696 - }, - "googlecloud": { - "labels": { - "metrics": { - "device_name": "instance-1", - "device_type": "permanent", - "storage_type": "pd-standard" - } - }, - "compute": { - "instance": { - "disk": { - "write_ops_count": 140, - "read_ops_count": 2897, - "read_bytes_count": 71574649, - "write_bytes_count": 2557677 - } - } - } - } -} diff --git a/x-pack/metricbeat/module/googlecloud/compute/_meta/data_firewall.json b/x-pack/metricbeat/module/googlecloud/compute/_meta/data_firewall.json index ee219ec74e0..04b750b8b7d 100644 --- a/x-pack/metricbeat/module/googlecloud/compute/_meta/data_firewall.json +++ b/x-pack/metricbeat/module/googlecloud/compute/_meta/data_firewall.json @@ -1,75 +1,62 @@ { - "@timestamp": "2020-01-08T16:05:00.000Z", - "@metadata": { - "beat": "metricbeat", - "type": "_doc", - "version": "8.0.0" - }, - "ecs": { - "version": "1.2.0" - }, - "host": { - "containerized": false, - "name": "mcastro", - "hostname": "mcastro", - "architecture": "x86_64", - "os": { - "version": "", - "family": "", - "name": "Antergos Linux", - "kernel": "5.4.3-arch1-1", - "platform": "antergos" - }, - "id": "54f70115bae545cbac2b150f254472a0" - }, - "agent": { - "type": "metricbeat", - "ephemeral_id": "8b802033-b611-414b-bcaf-1aa19e5f5901", - "hostname": "mcastro", - "id": "7e36a073-1a32-4a94-b65b-4c7f971fb228", - "version": "8.0.0" - }, + "@timestamp": "2017-10-12T08:05:34.853Z", "cloud": { - "availability_zone": "us-central1-a", "account": { - "id": "elastic-metricbeat" + "id": "elastic-observability" }, - "provider": "googlecloud", "instance": { - "id": "4503798379141677974", - "name": "instance-1" + "id": "2528596280375797115", + "name": "gke-dev-next-oblt-dev-next-oblt-pool-404d7f0c-cpj6" }, "machine": { - "type": "f1-micro" - } + "type": "n1-standard-4" + }, + "provider": "googlecloud" }, + "cloud.availability_zone": "europe-west1-c", "event": { "dataset": "googlecloud.compute", - "module": "googlecloud", - "duration": 1397755844 - }, - "metricset": { - "name": "compute", - "period": 300000 + "duration": 115000, + "module": "googlecloud" }, "googlecloud": { - "labels": {}, "compute": { + "firewall": { + "dropped_bytes_count": { + "value": 386 + }, + "dropped_packets_count": { + "value": 7 + } + }, "instance": { - "uptime": 60.00000000000001, "cpu": { - "reserved_cores": 0.2, - "utilization": 0.38202685489490784, - "usage_time": 0.06629814168597115 + "reserved_cores": { + "value": 4 + }, + "usage_time": { + "value": 106.88293868489563 + }, + "utilization": { + "value": 0.4453455778537318 + } + }, + "uptime": { + "value": 60 } - }, - "firewall": { - "dropped_packets_count": 3 + } + }, + "labels": { + "user": { + "goog-gke-node": "" } } }, + "metricset": { + "name": "compute", + "period": 10000 + }, "service": { "type": "googlecloud" } -} - +} \ No newline at end of file diff --git a/x-pack/metricbeat/module/googlecloud/compute/_meta/data_instance.json b/x-pack/metricbeat/module/googlecloud/compute/_meta/data_instance.json deleted file mode 100644 index 4306273f73c..00000000000 --- a/x-pack/metricbeat/module/googlecloud/compute/_meta/data_instance.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "@timestamp": "2020-01-08T16:04:00.000Z", - "@metadata": { - "beat": "metricbeat", - "type": "_doc", - "version": "8.0.0" - }, - "ecs": { - "version": "1.2.0" - }, - "host": { - "os": { - "platform": "antergos", - "version": "", - "family": "", - "name": "Antergos Linux", - "kernel": "5.4.3-arch1-1" - }, - "id": "54f70115bae545cbac2b150f254472a0", - "containerized": false, - "hostname": "mcastro", - "name": "mcastro", - "architecture": "x86_64" - }, - "agent": { - "ephemeral_id": "8b802033-b611-414b-bcaf-1aa19e5f5901", - "hostname": "mcastro", - "id": "7e36a073-1a32-4a94-b65b-4c7f971fb228", - "version": "8.0.0", - "type": "metricbeat" - }, - "cloud": { - "provider": "googlecloud", - "instance": { - "id": "4503798379141677974", - "name": "instance-1" - }, - "machine": { - "type": "f1-micro" - }, - "availability_zone": "us-central1-a", - "account": { - "id": "elastic-metricbeat" - } - }, - "event": { - "module": "googlecloud", - "duration": 1397750508, - "dataset": "googlecloud.compute" - }, - "metricset": { - "period": 300000, - "name": "compute" - }, - "googlecloud": { - "labels": {}, - "compute": { - "firewall": { - "dropped_bytes_count": 0, - "dropped_packets_count": 0 - }, - "instance": { - "uptime": 46.181442, - "cpu": { - "usage_time": 4.5843222587388945 - } - } - } - }, - "service": { - "type": "googlecloud" - } -} diff --git a/x-pack/metricbeat/module/googlecloud/compute/_meta/data_network.json b/x-pack/metricbeat/module/googlecloud/compute/_meta/data_network.json new file mode 100644 index 00000000000..d543fc2382f --- /dev/null +++ b/x-pack/metricbeat/module/googlecloud/compute/_meta/data_network.json @@ -0,0 +1,54 @@ +{ + "@timestamp": "2017-10-12T08:05:34.853Z", + "cloud": { + "account": { + "id": "elastic-observability" + }, + "instance": { + "id": "7208038667777737825", + "name": "gke-dev-next-oblt-dev-next-oblt-pool-404d7f0c-fgxk" + }, + "machine": { + "type": "n1-standard-4" + }, + "provider": "googlecloud" + }, + "cloud.availability_zone": "europe-west1-c", + "event": { + "dataset": "googlecloud.compute", + "duration": 115000, + "module": "googlecloud" + }, + "googlecloud": { + "compute": { + "instance": { + "network": { + "received_bytes_count": { + "value": 17913 + }, + "received_packets_count": { + "value": 128 + }, + "sent_bytes_count": { + "value": 841 + } + } + } + }, + "labels": { + "metrics": { + "loadbalanced": "true" + }, + "user": { + "goog-gke-node": "" + } + } + }, + "metricset": { + "name": "compute", + "period": 10000 + }, + "service": { + "type": "googlecloud" + } +} \ No newline at end of file diff --git a/x-pack/metricbeat/module/googlecloud/compute/compute_integration_test.go b/x-pack/metricbeat/module/googlecloud/compute/compute_integration_test.go index a4d47d95dd6..be2dd08cdec 100644 --- a/x-pack/metricbeat/module/googlecloud/compute/compute_integration_test.go +++ b/x-pack/metricbeat/module/googlecloud/compute/compute_integration_test.go @@ -8,14 +8,39 @@ package compute import ( + "fmt" "testing" + "github.com/elastic/beats/v7/libbeat/common" mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing" "github.com/elastic/beats/v7/x-pack/metricbeat/module/googlecloud/stackdriver" ) func TestData(t *testing.T) { + metricPrefixIs := func(metricPrefix string) func(e common.MapStr) bool { + return func(e common.MapStr) bool { + v, err := e.GetValue(metricPrefix) + return err == nil && v != nil + } + } + + dataFiles := []struct { + metricPrefix string + path string + }{ + {"googlecloud.compute.instance", "./_meta/data.json"}, + {"googlecloud.compute.instance.disk", "./_meta/data_disk.json"}, + {"googlecloud.compute.instance.network", "./_meta/data_network.json"}, + {"googlecloud.compute.instance.cpu", "./_meta/data_cpu.json"}, + {"googlecloud.compute.firewall", "./_meta/data_firewall.json"}, + } + config := stackdriver.GetConfigForTest(t, "compute") - metricSet := mbtest.NewFetcher(t, config) - metricSet.WriteEvents(t, "/") + + for _, df := range dataFiles { + metricSet := mbtest.NewFetcher(t, config) + t.Run(fmt.Sprintf("metric prefix: %s", df.metricPrefix), func(t *testing.T) { + metricSet.WriteEventsCond(t, df.path, metricPrefixIs(df.metricPrefix)) + }) + } } diff --git a/x-pack/metricbeat/module/googlecloud/loadbalancing/_meta/data.json b/x-pack/metricbeat/module/googlecloud/loadbalancing/_meta/data.json index f9335292924..97a9192732a 100644 --- a/x-pack/metricbeat/module/googlecloud/loadbalancing/_meta/data.json +++ b/x-pack/metricbeat/module/googlecloud/loadbalancing/_meta/data.json @@ -49,4 +49,4 @@ "service": { "type": "googlecloud" } -} \ No newline at end of file +} diff --git a/x-pack/metricbeat/module/googlecloud/loadbalancing/_meta/data_l3.json b/x-pack/metricbeat/module/googlecloud/loadbalancing/_meta/data_l3.json new file mode 100644 index 00000000000..9a58a5ebc5b --- /dev/null +++ b/x-pack/metricbeat/module/googlecloud/loadbalancing/_meta/data_l3.json @@ -0,0 +1,50 @@ +{ + "@timestamp": "2017-10-12T08:05:34.853Z", + "cloud": { + "account": { + "id": "elastic-observability" + }, + "provider": "googlecloud" + }, + "event": { + "dataset": "googlecloud.loadbalancing", + "duration": 115000, + "module": "googlecloud" + }, + "googlecloud": { + "labels": { + "metrics": { + "client_network": "UNKNOWN", + "client_subnetwork": "REMOTE_IS_EXTERNAL", + "client_zone": "UNKNOWN" + }, + "resource": { + "backend_name": "ocp-be-c5kjr-master-us-central1-c", + "backend_scope": "us-central1-c", + "backend_scope_type": "ZONE", + "backend_subnetwork_name": "ocp-be-c5kjr-master-subnet", + "backend_target_name": "ocp-be-c5kjr-api-internal", + "backend_target_type": "BACKEND_SERVICE", + "backend_type": "INSTANCE_GROUP", + "forwarding_rule_name": "ocp-be-c5kjr-api-internal", + "load_balancer_name": "ocp-be-c5kjr-api-internal", + "network_name": "ocp-be-c5kjr-network", + "region": "us-central1" + } + }, + "loadbalancing": { + "l3": { + "internal": { + "egress_packets_count": 394 + } + } + } + }, + "metricset": { + "name": "loadbalancing", + "period": 10000 + }, + "service": { + "type": "googlecloud" + } +} \ No newline at end of file diff --git a/x-pack/metricbeat/module/googlecloud/loadbalancing/loadbalancing_integration_test.go b/x-pack/metricbeat/module/googlecloud/loadbalancing/loadbalancing_integration_test.go index 80a5e99c57e..c070d96a736 100644 --- a/x-pack/metricbeat/module/googlecloud/loadbalancing/loadbalancing_integration_test.go +++ b/x-pack/metricbeat/module/googlecloud/loadbalancing/loadbalancing_integration_test.go @@ -8,14 +8,38 @@ package loadbalancing import ( + "fmt" "testing" + "github.com/elastic/beats/v7/libbeat/common" mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing" - "github.com/elastic/beats/v7/x-pack/metricbeat/module/googlecloud" + "github.com/elastic/beats/v7/x-pack/metricbeat/module/googlecloud/stackdriver" ) func TestData(t *testing.T) { - config := googlecloud.GetConfigForTest(t, "loadbalancing") - metricSet := mbtest.NewFetcher(t, config) - metricSet.WriteEvents(t, "/") + metricPrefixIs := func(metricPrefix string) func(e common.MapStr) bool { + return func(e common.MapStr) bool { + v, err := e.GetValue(metricPrefix) + return err == nil && v != nil + } + } + + dataFiles := []struct { + metricPrefix string + path string + }{ + {"googlecloud.loadbalancing", "./_meta/data.json"}, + {"googlecloud.loadbalancing.https", "./_meta/data_https.json"}, + {"googlecloud.loadbalancing.l3", "./_meta/data_l3.json"}, + {"googlecloud.loadbalancing.tcp_ssl_proxy", "./_meta/data_tcp_ssl_proxy.json"}, + } + + config := stackdriver.GetConfigForTest(t, "loadbalancing") + + for _, df := range dataFiles { + metricSet := mbtest.NewFetcher(t, config) + t.Run(fmt.Sprintf("metric prefix: %s", df.metricPrefix), func(t *testing.T) { + metricSet.WriteEventsCond(t, df.path, metricPrefixIs(df.metricPrefix)) + }) + } } diff --git a/x-pack/metricbeat/module/googlecloud/pubsub/_meta/data.json b/x-pack/metricbeat/module/googlecloud/pubsub/_meta/data.json index fd0dd71838a..c1a0365401d 100644 --- a/x-pack/metricbeat/module/googlecloud/pubsub/_meta/data.json +++ b/x-pack/metricbeat/module/googlecloud/pubsub/_meta/data.json @@ -14,12 +14,12 @@ "googlecloud": { "labels": { "resource": { - "subscription_id": "test-ks" + "subscription_id": "test-subscription-1" } }, "pubsub": { "subscription": { - "oldest_unacked_message_age": { + "backlog_bytes": { "value": 0 } } diff --git a/x-pack/metricbeat/module/googlecloud/pubsub/_meta/data_subscription.json b/x-pack/metricbeat/module/googlecloud/pubsub/_meta/data_subscription.json new file mode 100644 index 00000000000..13c2724143f --- /dev/null +++ b/x-pack/metricbeat/module/googlecloud/pubsub/_meta/data_subscription.json @@ -0,0 +1,35 @@ +{ + "@timestamp": "2017-10-12T08:05:34.853Z", + "cloud": { + "account": { + "id": "elastic-observability" + }, + "provider": "googlecloud" + }, + "event": { + "dataset": "googlecloud.pubsub", + "duration": 115000, + "module": "googlecloud" + }, + "googlecloud": { + "labels": { + "resource": { + "subscription_id": "test-ks" + } + }, + "pubsub": { + "subscription": { + "backlog_bytes": { + "value": 0 + } + } + } + }, + "metricset": { + "name": "pubsub", + "period": 10000 + }, + "service": { + "type": "googlecloud" + } +} \ No newline at end of file diff --git a/x-pack/metricbeat/module/googlecloud/pubsub/_meta/data_topic.json b/x-pack/metricbeat/module/googlecloud/pubsub/_meta/data_topic.json new file mode 100644 index 00000000000..7f296406136 --- /dev/null +++ b/x-pack/metricbeat/module/googlecloud/pubsub/_meta/data_topic.json @@ -0,0 +1,43 @@ +{ + "@timestamp": "2017-10-12T08:05:34.853Z", + "cloud": { + "account": { + "id": "elastic-observability" + }, + "provider": "googlecloud" + }, + "event": { + "dataset": "googlecloud.pubsub", + "duration": 115000, + "module": "googlecloud" + }, + "googlecloud": { + "labels": { + "resource": { + "topic_id": "test-ks" + } + }, + "pubsub": { + "topic": { + "message_sizes": { + "bucket_options": { + "Options": { + "ExponentialBuckets": { + "num_finite_buckets": 16, + "growth_factor": 4, + "scale": 1 + } + } + } + } + } + } + }, + "metricset": { + "name": "pubsub", + "period": 10000 + }, + "service": { + "type": "googlecloud" + } +} \ No newline at end of file diff --git a/x-pack/metricbeat/module/googlecloud/pubsub/pubsub_integration_test.go b/x-pack/metricbeat/module/googlecloud/pubsub/pubsub_integration_test.go index 90032d50310..6d739326dea 100644 --- a/x-pack/metricbeat/module/googlecloud/pubsub/pubsub_integration_test.go +++ b/x-pack/metricbeat/module/googlecloud/pubsub/pubsub_integration_test.go @@ -8,14 +8,38 @@ package pubsub import ( + "fmt" "testing" + "github.com/elastic/beats/v7/libbeat/common" mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing" - "github.com/elastic/beats/v7/x-pack/metricbeat/module/googlecloud" + "github.com/elastic/beats/v7/x-pack/metricbeat/module/googlecloud/stackdriver" ) func TestData(t *testing.T) { - config := googlecloud.GetConfigForTest(t, "pubsub") - metricSet := mbtest.NewFetcher(t, config) - metricSet.WriteEvents(t, "/") + metricPrefixIs := func(metricPrefix string) func(e common.MapStr) bool { + return func(e common.MapStr) bool { + v, err := e.GetValue(metricPrefix) + return err == nil && v != nil + } + } + + dataFiles := []struct { + metricPrefix string + path string + }{ + {"googlecloud.pubsub", "./_meta/data.json"}, + {"googlecloud.pubsub.snapshot", "./_meta/data_snapshot.json"}, + {"googlecloud.pubsub.subscription", "./_meta/data_subscription.json"}, + {"googlecloud.pubsub.topic", "./_meta/data_topic.json"}, + } + + config := stackdriver.GetConfigForTest(t, "pubsub") + + for _, df := range dataFiles { + metricSet := mbtest.NewFetcher(t, config) + t.Run(fmt.Sprintf("metric prefix: %s", df.metricPrefix), func(t *testing.T) { + metricSet.WriteEventsCond(t, df.path, metricPrefixIs(df.metricPrefix)) + }) + } } diff --git a/x-pack/metricbeat/module/googlecloud/storage/_meta/data.json b/x-pack/metricbeat/module/googlecloud/storage/_meta/data.json index 02541f8a1e7..679102209b2 100644 --- a/x-pack/metricbeat/module/googlecloud/storage/_meta/data.json +++ b/x-pack/metricbeat/module/googlecloud/storage/_meta/data.json @@ -2,7 +2,7 @@ "@timestamp": "2017-10-12T08:05:34.853Z", "cloud": { "account": { - "id": "elastic-observability" + "id": "elastic-apm" }, "provider": "googlecloud" }, @@ -14,18 +14,17 @@ "googlecloud": { "labels": { "metrics": { - "method": "GetBucketMetadata", - "response_code": "OK" + "storage_class": "MULTI_REGIONAL" }, "resource": { - "bucket_name": "ocp-be-c5kjr-image-registry-us-central1-dsoafnbgctvfimpavswkgn", - "location": "us-central1" + "bucket_name": "artifacts.elastic-apm.appspot.com", + "location": "us" } }, "storage": { - "network": { - "sent_bytes_count": { - "value": 2637 + "storage": { + "object_count": { + "value": 15 } } } diff --git a/x-pack/metricbeat/module/googlecloud/storage/_meta/data_network.json b/x-pack/metricbeat/module/googlecloud/storage/_meta/data_network.json new file mode 100644 index 00000000000..7bb1c6a4c86 --- /dev/null +++ b/x-pack/metricbeat/module/googlecloud/storage/_meta/data_network.json @@ -0,0 +1,38 @@ +{ + "@timestamp": "2017-10-12T08:05:34.853Z", + "cloud": { + "account": { + "id": "elastic-observability" + }, + "provider": "googlecloud" + }, + "event": { + "dataset": "googlecloud.storage", + "duration": 115000, + "module": "googlecloud" + }, + "googlecloud": { + "labels": { + "metrics": { + "method": "GetBucketMetadata", + "response_code": "OK" + }, + "resource": { + "bucket_name": "ocp-be-c5kjr-image-registry-us-central1-dsoafnbgctvfimpavswkgn", + "location": "us-central1" + } + }, + "storage": { + "network": { + "received_bytes_count": 0 + } + } + }, + "metricset": { + "name": "storage", + "period": 10000 + }, + "service": { + "type": "googlecloud" + } +} \ No newline at end of file diff --git a/x-pack/metricbeat/module/googlecloud/storage/_meta/data_storage.json b/x-pack/metricbeat/module/googlecloud/storage/_meta/data_storage.json new file mode 100644 index 00000000000..f98a6a6f744 --- /dev/null +++ b/x-pack/metricbeat/module/googlecloud/storage/_meta/data_storage.json @@ -0,0 +1,39 @@ +{ + "@timestamp": "2017-10-12T08:05:34.853Z", + "cloud": { + "account": { + "id": "elastic-observability" + }, + "provider": "googlecloud" + }, + "event": { + "dataset": "googlecloud.storage", + "duration": 115000, + "module": "googlecloud" + }, + "googlecloud": { + "labels": { + "metrics": { + "storage_class": "MULTI_REGIONAL" + }, + "resource": { + "bucket_name": "fstuermer-log-data-categorization-7-6-0", + "location": "us" + } + }, + "storage": { + "storage": { + "total_bytes": { + "value": 4472520191 + } + } + } + }, + "metricset": { + "name": "storage", + "period": 10000 + }, + "service": { + "type": "googlecloud" + } +} \ No newline at end of file diff --git a/x-pack/metricbeat/module/googlecloud/storage/storage_integration_test.go b/x-pack/metricbeat/module/googlecloud/storage/storage_integration_test.go index 0f2b010f5f2..7d40e7b2bf9 100644 --- a/x-pack/metricbeat/module/googlecloud/storage/storage_integration_test.go +++ b/x-pack/metricbeat/module/googlecloud/storage/storage_integration_test.go @@ -8,14 +8,38 @@ package storage import ( + "fmt" "testing" + "github.com/elastic/beats/v7/libbeat/common" mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing" "github.com/elastic/beats/v7/x-pack/metricbeat/module/googlecloud/stackdriver" ) func TestData(t *testing.T) { + metricPrefixIs := func(metricPrefix string) func(e common.MapStr) bool { + return func(e common.MapStr) bool { + v, err := e.GetValue(metricPrefix) + return err == nil && v != nil + } + } + + dataFiles := []struct { + metricPrefix string + path string + }{ + {"googlecloud.storage", "./_meta/data.json"}, + {"googlecloud.storage.authz", "./_meta/data_authz.json"}, + {"googlecloud.storage.network", "./_meta/data_network.json"}, + {"googlecloud.storage.storage", "./_meta/data_storage.json"}, + } + config := stackdriver.GetConfigForTest(t, "storage") - metricSet := mbtest.NewFetcher(t, config) - metricSet.WriteEvents(t, "/") + + for _, df := range dataFiles { + metricSet := mbtest.NewFetcher(t, config) + t.Run(fmt.Sprintf("metric prefix: %s", df.metricPrefix), func(t *testing.T) { + metricSet.WriteEventsCond(t, df.path, metricPrefixIs(df.metricPrefix)) + }) + } } From c44482d6524de02b90d6ff13678b05d0be4c20aa Mon Sep 17 00:00:00 2001 From: Andrew Kroh Date: Fri, 24 Apr 2020 18:47:39 -0400 Subject: [PATCH 023/116] Don't bind to wildcard address in unit test (#17973) This changes the TCP input test to only bind to the loopback address for testing. It prevents firewall security popups when testing on macOS. --- filebeat/inputsource/tcp/server_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/filebeat/inputsource/tcp/server_test.go b/filebeat/inputsource/tcp/server_test.go index 15831666206..032f7d33e29 100644 --- a/filebeat/inputsource/tcp/server_test.go +++ b/filebeat/inputsource/tcp/server_test.go @@ -213,7 +213,7 @@ func TestReceiveNewEventsConcurrently(t *testing.T) { to := func(message []byte, mt inputsource.NetworkMetadata) { ch <- &info{message: string(message), mt: mt} } - cfg, err := common.NewConfigFrom(map[string]interface{}{"host": ":0"}) + cfg, err := common.NewConfigFrom(map[string]interface{}{"host": "127.0.0.1:0"}) if !assert.NoError(t, err) { return } From 76d534984e8a288e1ae1ba0c0bc0cb851208224c Mon Sep 17 00:00:00 2001 From: DeDe Morton Date: Fri, 24 Apr 2020 16:42:13 -0700 Subject: [PATCH 024/116] [docs] Add release highlights for 7.7 (#17887) * [docs] Add release highlights for 7.7 * Fix link to loadbalancing metricset * Add Cloudfoundry links * Commenting out link before merging --- .../highlights/highlights-7.7.0.asciidoc | 136 +++++++++++++++++- 1 file changed, 131 insertions(+), 5 deletions(-) diff --git a/libbeat/docs/release-notes/highlights/highlights-7.7.0.asciidoc b/libbeat/docs/release-notes/highlights/highlights-7.7.0.asciidoc index 51fe50c30a9..6bba7ee896e 100644 --- a/libbeat/docs/release-notes/highlights/highlights-7.7.0.asciidoc +++ b/libbeat/docs/release-notes/highlights/highlights-7.7.0.asciidoc @@ -7,8 +7,8 @@ Each release of {beats} brings new features and product improvements. Following are the most notable features and enhancements in 7.7. -For a complete list of related highlights, see the -https://www.elastic.co/blog/elastic-observability-7-6-0-released[Observability 7.7 release blog]. +//For a complete list of related highlights, see the +//https://www.elastic.co/blog/elastic-observability-7-7-0-released[Observability 7.7 release blog]. For a list of bug fixes and other changes, see the {beats} <> and <>. @@ -18,9 +18,135 @@ For a list of bug fixes and other changes, see the {beats} // tag::notable-highlights[] -//[float] -//==== highlight +[float] +[role="xpack"] +==== Azure Kubernetes and container monitoring -//Description +We've enhanced the {metricbeat} Azure module with three new metricsets +for monitoring Microsoft Azure container services: +{metricbeat-ref}/metricbeat-metricset-azure-container_instance.html[`container_instance`], +{metricbeat-ref}/metricbeat-metricset-azure-container_registry.html[`container_registry`], and +{metricbeat-ref}/metricbeat-metricset-azure-container_service.html[`container_service`]. +These metricsets collect metrics from the following services: + +* Azure Kubernetes Service +* Azure Container Instances +* Azure Container Registry + +Each metricset comes with a dashboard that makes it easy to get started +monitoring Azure containers. + +[float] +[role="xpack"] +==== AWS VPCs, Lambdas, and DynamoDB monitoring + +In the {metricbeat} AWS module, we've added support for monitoring +mission-critical services in the Amazon VPC ecosystem: + +* The {metricbeat-ref}/metricbeat-metricset-aws-natgateway.html[`natgateway`] +metricset enables you to monitor NAT gateway services to gain a +better perspective on how web applications or services are performing. +* The {metricbeat-ref}/metricbeat-metricset-aws-natgateway.html[`transitgateway`] +metricset collects metrics sent to CloudWatch by VPC when requests are flowing +through the gateway.  +* The {metricbeat-ref}/metricbeat-metricset-aws-vpn.html[`vpn`] metricset +enables you to monitor VPN tunnels. VPN metric data is automatically sent to +CloudWatch as it becomes available. + +Also new in this release, the +{metricbeat-ref}/metricbeat-metricset-aws-lambda.html[`lambda`] metricset monitors +Lambda functions across multiple accounts and regions. The metricset collects +metrics such as total invocations, errors, duration, throttles, dead-letter queue +errors, and iterator age for stream-based invocations. You can use these metrics +to configure alerts to respond to events such as changes in performance and +error rates. + +We’ve also added the +{metricbeat-ref}/metricbeat-metricset-aws-dynamodb.html[`dynamodb`] metricset to +monitor AWS DynamoDB instances. This metricset collects metrics, such as request +latency, transaction conflicts, provisioned and consumed capacity, and many +others.   
 + +For Amazon Aurora users, we've enhanced the +{metricbeat-ref}/metricbeat-metricset-aws-rds.html[`rds`] metricset to collect +metrics about your Aurora instances. + +[float] +[role="xpack"] +==== Google Cloud Platform (GCP) Pub/Sub and Load Balancer monitoring + +We've enhanced the {metricbeat} Google Cloud Platform module with support +for monitoring additional services: + +* The {metricbeat-ref}/metricbeat-metricset-googlecloud-pubsub.html[`pubsub`] +metricset connects to the Stackdriver API and collects metrics for topics, +subscriptions, and snapshots used by a specified account.  +* The {metricbeat-ref}/metricbeat-metricset-googlecloud-loadbalancing.html[`loadbalancing`] +metricset captures load balancing performance metrics for HTTP(S), TCP, and UDP +applications. + +[float] +[role="xpack"] +==== Pivotal Cloud Foundry (PCF) monitoring + +We continue to expand coverage of container platforms by adding support for +Pivotal Cloud Foundry. + +The new {metricbeat} +{metricbeat-ref}/metricbeat-module-cloudfoundry.html[Cloudfoundry module] +connects to the Cloud Foundry API and pulls container, counter, and value +metrics from it. These metrics are stored in `cloudfoundry.container`, +`cloudfoundry.counter` and `cloudfoundry.value` metricsets. + +In {filebeat}, the new +{filebeat-ref}/filebeat-input-cloudfoundry.html[`cloudfoundry`] input collects +http access logs, container logs, and error logs from Cloud Foundry. + +To learn how to run {beats} on Cloud Foundry, see: + +* {metricbeat-ref}/running-on-cloudfoundry.html[Run {metricbeat} on Cloud Foundry] +* {filebeat-ref}/running-on-cloudfoundry.html[Run {filebeat} on Cloud Foundry] + +[float] +[role="xpack"] +==== IBM MQ monitoring + +Prior to this release, we offered support in {filebeat} for collecting and +parsing queue manager error logs from IBM MQ. + +In this release, we’ve added the missing piece: metrics. The new {metricbeat} +{metricbeat-ref}/metricbeat-module-ibmmq.html[IBM MQ module] pulls status +information for the Queue Manager, which is responsible for maintaining queues +and ensuring that messages in the queues reach their destination. + +[float] +[role="xpack"] +==== Redis Enterprise monitoring + +In addition to our existing Redis module, which focuses on the open source +version of the database, we’ve added the new {metricbeat} +{metricbeat-ref}/metricbeat-module-redisenterprise.html[Redis Enterprise] module +to monitor features such as nodes and proxies in a Redis cluster. + +[float] +[role="xpack"] +==== Istio monitoring + +For Istio users, we've introduced the {metricbeat} +{metricbeat-ref}/metricbeat-module-istio.html[Istio module] to +collect metrics about service traffic (in, out, and within a service mesh), +control-plane metrics for Istio Pilot, Galley, Mixer components, and much +more. + +[float] +==== ECS field improvements in {filebeat} + +The {ecs-ref}/index.html[Elastic Common Schema] (ECS) defines a common set of +fields to be used when storing event data in {es}. + +In 7.7, we've improved ECS field mappings in numerous {filebeat} modules, +making it easier for you to analyze, visualize, and correlate data across +events. For a list of affected modules, see the +<> for 7.7.0. // end::notable-highlights[] From cbf05e0a3443dac9c2dea89796349b950f33d208 Mon Sep 17 00:00:00 2001 From: DeDe Morton Date: Fri, 24 Apr 2020 19:19:13 -0700 Subject: [PATCH 025/116] [docs] Change release notes link to use global style (#17987) --- libbeat/docs/release-notes/highlights/highlights-7.7.0.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libbeat/docs/release-notes/highlights/highlights-7.7.0.asciidoc b/libbeat/docs/release-notes/highlights/highlights-7.7.0.asciidoc index 6bba7ee896e..959bbea91f5 100644 --- a/libbeat/docs/release-notes/highlights/highlights-7.7.0.asciidoc +++ b/libbeat/docs/release-notes/highlights/highlights-7.7.0.asciidoc @@ -147,6 +147,6 @@ fields to be used when storing event data in {es}. In 7.7, we've improved ECS field mappings in numerous {filebeat} modules, making it easier for you to analyze, visualize, and correlate data across events. For a list of affected modules, see the -<> for 7.7.0. +{beats-ref}/release-notes.html[Release Notes] for 7.7.0. // end::notable-highlights[] From 96f66a4471a9994ddd5f2f0bdd51339b0b813b0a Mon Sep 17 00:00:00 2001 From: Andrew Kroh Date: Sat, 25 Apr 2020 05:17:20 -0400 Subject: [PATCH 026/116] Fix filebeat script tester tests (#17972) The tester assumes pipelines have a JSON extension. So it started failing when the one of the pipelines was changed to YAML. I made changes to fix the test, but added an explicit error if you try to use this tool with a YAML pipeline. Additionally I noticed this tool does not handle templated pipelines. --- filebeat/fileset/fileset_test.go | 8 +++--- filebeat/fileset/modules_integration_test.go | 2 +- filebeat/magefile.go | 4 ++- filebeat/scripts/tester/main.go | 30 ++++++++++++++++---- filebeat/scripts/tester/main_test.go | 2 +- 5 files changed, 33 insertions(+), 13 deletions(-) diff --git a/filebeat/fileset/fileset_test.go b/filebeat/fileset/fileset_test.go index 59e5862eb2f..e7865074d8d 100644 --- a/filebeat/fileset/fileset_test.go +++ b/filebeat/fileset/fileset_test.go @@ -56,7 +56,7 @@ func TestLoadManifestNginx(t *testing.T) { manifest, err := fs.readManifest() assert.NoError(t, err) assert.Equal(t, manifest.ModuleVersion, "1.0") - assert.Equal(t, manifest.IngestPipeline, []string{"ingest/default.json"}) + assert.Equal(t, manifest.IngestPipeline, []string{"ingest/pipeline.yml"}) assert.Equal(t, manifest.Input, "config/nginx-access.yml") vars := manifest.Vars @@ -189,7 +189,7 @@ func TestGetInputConfigNginx(t *testing.T) { assert.True(t, cfg.HasField("pipeline")) pipelineID, err := cfg.String("pipeline", -1) assert.NoError(t, err) - assert.Equal(t, "filebeat-5.2.0-nginx-access-default", pipelineID) + assert.Equal(t, "filebeat-5.2.0-nginx-access-pipeline", pipelineID) } func TestGetInputConfigNginxOverrides(t *testing.T) { @@ -217,7 +217,7 @@ func TestGetInputConfigNginxOverrides(t *testing.T) { pipelineID, err := c.String("pipeline", -1) assert.NoError(t, err) - assert.Equal(t, "filebeat-5.2.0-nginx-access-default", pipelineID) + assert.Equal(t, "filebeat-5.2.0-nginx-access-pipeline", pipelineID) }, }, "pipeline": { @@ -276,7 +276,7 @@ func TestGetPipelineNginx(t *testing.T) { assert.Len(t, pipelines, 1) pipeline := pipelines[0] - assert.Equal(t, "filebeat-5.2.0-nginx-access-default", pipeline.id) + assert.Equal(t, "filebeat-5.2.0-nginx-access-pipeline", pipeline.id) assert.Contains(t, pipeline.contents, "description") assert.Contains(t, pipeline.contents, "processors") } diff --git a/filebeat/fileset/modules_integration_test.go b/filebeat/fileset/modules_integration_test.go index 8c5bc91bf70..5428fb1f549 100644 --- a/filebeat/fileset/modules_integration_test.go +++ b/filebeat/fileset/modules_integration_test.go @@ -115,7 +115,7 @@ func TestSetupNginx(t *testing.T) { t.Fatal(err) } - status, _, _ := client.Request("GET", "/_ingest/pipeline/filebeat-5.2.0-nginx-access-default", "", nil, nil) + status, _, _ := client.Request("GET", "/_ingest/pipeline/filebeat-5.2.0-nginx-access-pipeline", "", nil, nil) assert.Equal(t, 200, status) status, _, _ = client.Request("GET", "/_ingest/pipeline/filebeat-5.2.0-nginx-error-pipeline", "", nil, nil) assert.Equal(t, 200, status) diff --git a/filebeat/magefile.go b/filebeat/magefile.go index 0c3d8881892..c65d99a910d 100644 --- a/filebeat/magefile.go +++ b/filebeat/magefile.go @@ -185,7 +185,9 @@ func IntegTest() { // Use TEST_COVERAGE=true to enable code coverage profiling. // Use RACE_DETECTOR=true to enable the race detector. func GoIntegTest(ctx context.Context) error { - return devtools.GoTest(ctx, devtools.DefaultGoTestIntegrationArgs()) + return devtools.RunIntegTest("goIntegTest", func() error { + return devtools.GoTest(ctx, devtools.DefaultGoTestIntegrationArgs()) + }) } // PythonIntegTest executes the python system tests in the integration environment (Docker). diff --git a/filebeat/scripts/tester/main.go b/filebeat/scripts/tester/main.go index 2ae9de44388..6da063e6204 100644 --- a/filebeat/scripts/tester/main.go +++ b/filebeat/scripts/tester/main.go @@ -108,6 +108,11 @@ func main() { } for _, path := range paths { + // TODO: Add support for testing YAML pipelines. + if filepath.Ext(path) == ".yml" { + fmt.Fprintf(os.Stderr, "YAML pipelines are not supported by this tool. Cannot process %q.", path) + os.Exit(3) + } err = testPipeline(*esURL, path, logs, *verbose, *simulateVerbose) if err != nil { os.Stderr.WriteString(err.Error()) @@ -185,8 +190,14 @@ func getPipelinePath(path, modulesPath string) ([]string, error) { module := parts[0] fileset := parts[1] - pathToPipeline := filepath.Join(modulesPath, module, fileset, "ingest", "pipeline.json") - _, err := os.Stat(pathToPipeline) + var pathToPipeline string + for _, ext := range []string{".json", ".yml"} { + pathToPipeline = filepath.Join(modulesPath, module, fileset, "ingest", "pipeline"+ext) + _, err = os.Stat(pathToPipeline) + if err == nil { + break + } + } if err != nil { return nil, fmt.Errorf("Cannot find pipeline in %s: %v %v\n", path, err, pathToPipeline) } @@ -199,8 +210,7 @@ func getPipelinePath(path, modulesPath string) ([]string, error) { return nil, err } for _, f := range files { - isPipelineFile := strings.HasSuffix(f.Name(), ".json") - if isPipelineFile { + if isPipelineFileExtension(f.Name()) { fullPath := filepath.Join(path, f.Name()) paths = append(paths, fullPath) } @@ -211,8 +221,7 @@ func getPipelinePath(path, modulesPath string) ([]string, error) { return paths, nil } - isPipelineFile := strings.HasSuffix(path, ".json") - if isPipelineFile { + if isPipelineFileExtension(path) { return []string{path}, nil } @@ -220,6 +229,15 @@ func getPipelinePath(path, modulesPath string) ([]string, error) { } +func isPipelineFileExtension(path string) bool { + ext := filepath.Ext(path) + switch strings.ToLower(ext) { + case ".yml", ".json": + return true + } + return false +} + func testPipeline(esURL, path string, logs []string, verbose, simulateVerbose bool) error { pipeline, err := readPipeline(path) if err != nil { diff --git a/filebeat/scripts/tester/main_test.go b/filebeat/scripts/tester/main_test.go index 79f53186a1b..6284f6f2e5e 100644 --- a/filebeat/scripts/tester/main_test.go +++ b/filebeat/scripts/tester/main_test.go @@ -29,7 +29,7 @@ func TestGetPipelinePath(t *testing.T) { count int }{ { - pipelinePath: "../../module/postgresql/log/ingest/pipeline.json", + pipelinePath: "../../module/postgresql/log/ingest/pipeline.yml", count: 1, }, { From ad21d71a86c1a771eebf9dca816841fc362d8e33 Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Sat, 25 Apr 2020 12:23:17 -0700 Subject: [PATCH 027/116] Fixes typo in log message (#17897) * Fixes typo in log message * Adding CHANGELOG entry --- CHANGELOG.next.asciidoc | 1 + filebeat/input/log/input.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 2f1e4043572..cfd844884d3 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -114,6 +114,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Remove migrationVersion map 7.7.0 reference from Kibana dashboard file to fix backward compatibility issues. {pull}17425[17425] - Fix issue 17734 to retry on rate-limit error in the Filebeat httpjson input. {issue}17734[17734] {pull}17735[17735] - Fixed `cloudfoundry.access` to have the correct `cloudfoundry.app.id` contents. {pull}17847[17847] +- Fixed typo in log message. {pull}17897[17897] *Heartbeat* diff --git a/filebeat/input/log/input.go b/filebeat/input/log/input.go index ac0d71cf53d..b3cf4049551 100644 --- a/filebeat/input/log/input.go +++ b/filebeat/input/log/input.go @@ -161,7 +161,7 @@ func NewInput( // It goes through all states coming from the registry. Only the states which match the glob patterns of // the input will be loaded and updated. All other states will not be touched. func (p *Input) loadStates(states []file.State) error { - logp.Debug("input", "exclude_files: %s. Number of stats: %d", p.config.ExcludeFiles, len(states)) + logp.Debug("input", "exclude_files: %s. Number of states: %d", p.config.ExcludeFiles, len(states)) for _, state := range states { // Check if state source belongs to this input. If yes, update the state. From ae1125d0cd8bc7b0ffe09953dfb3a83105ea3235 Mon Sep 17 00:00:00 2001 From: Mario Castro Date: Mon, 27 Apr 2020 13:19:23 +0200 Subject: [PATCH 028/116] [Filebeat] Fix Cisco ASA 3020** and 106023 messages (#17964) --- CHANGELOG.next.asciidoc | 1 + .../module/cisco/asa/test/asa-fix.log | 5 + .../cisco/asa/test/asa-fix.log-expected.json | 152 +++++++++++++++++ .../module/cisco/ftd/test/asa-fix.log | 5 + .../cisco/ftd/test/asa-fix.log-expected.json | 157 ++++++++++++++++++ .../cisco/shared/ingest/asa-ftd-pipeline.yml | 10 +- 6 files changed, 326 insertions(+), 4 deletions(-) create mode 100644 x-pack/filebeat/module/cisco/asa/test/asa-fix.log create mode 100644 x-pack/filebeat/module/cisco/asa/test/asa-fix.log-expected.json create mode 100644 x-pack/filebeat/module/cisco/ftd/test/asa-fix.log create mode 100644 x-pack/filebeat/module/cisco/ftd/test/asa-fix.log-expected.json diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index cfd844884d3..146c9534cac 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -115,6 +115,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Fix issue 17734 to retry on rate-limit error in the Filebeat httpjson input. {issue}17734[17734] {pull}17735[17735] - Fixed `cloudfoundry.access` to have the correct `cloudfoundry.app.id` contents. {pull}17847[17847] - Fixed typo in log message. {pull}17897[17897] +- Fix Cisco ASA ASA 3020** and 106023 messages {pull}17964[17964] *Heartbeat* diff --git a/x-pack/filebeat/module/cisco/asa/test/asa-fix.log b/x-pack/filebeat/module/cisco/asa/test/asa-fix.log new file mode 100644 index 00000000000..00819e8eec1 --- /dev/null +++ b/x-pack/filebeat/module/cisco/asa/test/asa-fix.log @@ -0,0 +1,5 @@ +Apr 17 2020 14:08:08 SNL-ASA-VPN-A01 : %ASA-6-302016: Teardown UDP connection 110577675 for Outside:10.123.123.123/53723(LOCAL\Elastic) to Inside:10.233.123.123/53 duration 0:00:00 bytes 148 (zzzzzz) +Apr 17 2020 14:00:31 SNL-ASA-VPN-A01 : %ASA-4-106023: Deny icmp src Inside:10.123.123.123 dst Outside:10.123.123.123 (type 11, code 0) by access-group "Inside_access_in" [0x0, 0x0] +Apr 15 2013 09:36:50: %ASA-4-106023: Deny tcp src dmz:10.123.123.123/6316 dst outside:10.123.123.123/53 type 3, code 0, by access-group "acl_dmz" [0xe3afb522, 0x0] +Apr 17 2020 14:16:20 SNL-ASA-VPN-A01 : %ASA-4-106023: Deny udp src Inside:10.123.123.123/57621(LOCAL\Elastic) dst Outside:10.123.123.123/57621 by access-group "Inside_access_in" [0x0, 0x0] +Apr 17 2020 14:15:07 SNL-ASA-VPN-A01 : %ASA-2-106017: Deny IP due to Land Attack from 10.123.123.123 to 10.123.123.123 diff --git a/x-pack/filebeat/module/cisco/asa/test/asa-fix.log-expected.json b/x-pack/filebeat/module/cisco/asa/test/asa-fix.log-expected.json new file mode 100644 index 00000000000..de470786f66 --- /dev/null +++ b/x-pack/filebeat/module/cisco/asa/test/asa-fix.log-expected.json @@ -0,0 +1,152 @@ +[ + { + "cisco.asa.connection_id": "110577675", + "cisco.asa.destination_interface": "Inside", + "cisco.asa.message_id": "302016", + "cisco.asa.source_interface": "Outside", + "cisco.asa.source_username": "(LOCAL\\Elastic)", + "destination.address": "10.233.123.123", + "destination.ip": "10.233.123.123", + "destination.port": 53, + "event.action": "flow-expiration", + "event.code": 302016, + "event.dataset": "cisco.asa", + "event.duration": 0, + "event.end": "2020-04-17T14:08:08.000-02:00", + "event.module": "cisco", + "event.original": "%ASA-6-302016: Teardown UDP connection 110577675 for Outside:10.123.123.123/53723(LOCAL\\Elastic) to Inside:10.233.123.123/53 duration 0:00:00 bytes 148 (zzzzzz)", + "event.severity": 6, + "event.start": "2020-04-17T16:08:08.000Z", + "event.timezone": "-02:00", + "fileset.name": "asa", + "host.hostname": "SNL-ASA-VPN-A01", + "input.type": "log", + "log.level": "informational", + "log.offset": 0, + "network.bytes": 148, + "network.iana_number": 17, + "network.transport": "udp", + "service.type": "cisco", + "source.address": "10.123.123.123", + "source.ip": "10.123.123.123", + "source.port": 53723, + "tags": [ + "cisco-asa" + ] + }, + { + "cisco.asa.destination_interface": "Outside", + "cisco.asa.message_id": "106023", + "cisco.asa.rule_name": "Inside_access_in", + "cisco.asa.source_interface": "Inside", + "destination.address": "10.123.123.123", + "destination.ip": "10.123.123.123", + "event.action": "firewall-rule", + "event.code": 106023, + "event.dataset": "cisco.asa", + "event.module": "cisco", + "event.original": "%ASA-4-106023: Deny icmp src Inside:10.123.123.123 dst Outside:10.123.123.123 (type 11, code 0) by access-group \"Inside_access_in\" [0x0, 0x0]", + "event.outcome": "deny", + "event.severity": 4, + "event.timezone": "-02:00", + "fileset.name": "asa", + "host.hostname": "SNL-ASA-VPN-A01", + "input.type": "log", + "log.level": "warning", + "log.offset": 200, + "network.iana_number": 1, + "network.transport": "icmp", + "service.type": "cisco", + "source.address": "10.123.123.123", + "source.ip": "10.123.123.123", + "tags": [ + "cisco-asa" + ] + }, + { + "cisco.asa.destination_interface": "outside", + "cisco.asa.message_id": "106023", + "cisco.asa.rule_name": "acl_dmz", + "cisco.asa.source_interface": "dmz", + "destination.address": "10.123.123.123", + "destination.ip": "10.123.123.123", + "destination.port": 53, + "event.action": "firewall-rule", + "event.code": 106023, + "event.dataset": "cisco.asa", + "event.module": "cisco", + "event.original": "%ASA-4-106023: Deny tcp src dmz:10.123.123.123/6316 dst outside:10.123.123.123/53 type 3, code 0, by access-group \"acl_dmz\" [0xe3afb522, 0x0]", + "event.outcome": "deny", + "event.severity": 4, + "event.timezone": "-02:00", + "fileset.name": "asa", + "input.type": "log", + "log.level": "warning", + "log.offset": 381, + "network.iana_number": 6, + "network.transport": "tcp", + "service.type": "cisco", + "source.address": "10.123.123.123", + "source.ip": "10.123.123.123", + "source.port": 6316, + "tags": [ + "cisco-asa" + ] + }, + { + "cisco.asa.destination_interface": "Outside", + "cisco.asa.message_id": "106023", + "cisco.asa.rule_name": "Inside_access_in", + "cisco.asa.source_interface": "Inside", + "cisco.asa.source_username": "(LOCAL\\Elastic)", + "destination.address": "10.123.123.123", + "destination.ip": "10.123.123.123", + "destination.port": 57621, + "event.action": "firewall-rule", + "event.code": 106023, + "event.dataset": "cisco.asa", + "event.module": "cisco", + "event.original": "%ASA-4-106023: Deny udp src Inside:10.123.123.123/57621(LOCAL\\Elastic) dst Outside:10.123.123.123/57621 by access-group \"Inside_access_in\" [0x0, 0x0]", + "event.outcome": "deny", + "event.severity": 4, + "event.timezone": "-02:00", + "fileset.name": "asa", + "host.hostname": "SNL-ASA-VPN-A01", + "input.type": "log", + "log.level": "warning", + "log.offset": 545, + "network.iana_number": 17, + "network.transport": "udp", + "service.type": "cisco", + "source.address": "10.123.123.123", + "source.ip": "10.123.123.123", + "source.port": 57621, + "tags": [ + "cisco-asa" + ] + }, + { + "cisco.asa.message_id": "106017", + "destination.address": "10.123.123.123", + "destination.ip": "10.123.123.123", + "event.action": "firewall-rule", + "event.code": 106017, + "event.dataset": "cisco.asa", + "event.module": "cisco", + "event.original": "%ASA-2-106017: Deny IP due to Land Attack from 10.123.123.123 to 10.123.123.123", + "event.outcome": "deny", + "event.severity": 2, + "event.timezone": "-02:00", + "fileset.name": "asa", + "host.hostname": "SNL-ASA-VPN-A01", + "input.type": "log", + "log.level": "critical", + "log.offset": 734, + "service.type": "cisco", + "source.address": "10.123.123.123", + "source.ip": "10.123.123.123", + "tags": [ + "cisco-asa" + ] + } +] \ No newline at end of file diff --git a/x-pack/filebeat/module/cisco/ftd/test/asa-fix.log b/x-pack/filebeat/module/cisco/ftd/test/asa-fix.log new file mode 100644 index 00000000000..00819e8eec1 --- /dev/null +++ b/x-pack/filebeat/module/cisco/ftd/test/asa-fix.log @@ -0,0 +1,5 @@ +Apr 17 2020 14:08:08 SNL-ASA-VPN-A01 : %ASA-6-302016: Teardown UDP connection 110577675 for Outside:10.123.123.123/53723(LOCAL\Elastic) to Inside:10.233.123.123/53 duration 0:00:00 bytes 148 (zzzzzz) +Apr 17 2020 14:00:31 SNL-ASA-VPN-A01 : %ASA-4-106023: Deny icmp src Inside:10.123.123.123 dst Outside:10.123.123.123 (type 11, code 0) by access-group "Inside_access_in" [0x0, 0x0] +Apr 15 2013 09:36:50: %ASA-4-106023: Deny tcp src dmz:10.123.123.123/6316 dst outside:10.123.123.123/53 type 3, code 0, by access-group "acl_dmz" [0xe3afb522, 0x0] +Apr 17 2020 14:16:20 SNL-ASA-VPN-A01 : %ASA-4-106023: Deny udp src Inside:10.123.123.123/57621(LOCAL\Elastic) dst Outside:10.123.123.123/57621 by access-group "Inside_access_in" [0x0, 0x0] +Apr 17 2020 14:15:07 SNL-ASA-VPN-A01 : %ASA-2-106017: Deny IP due to Land Attack from 10.123.123.123 to 10.123.123.123 diff --git a/x-pack/filebeat/module/cisco/ftd/test/asa-fix.log-expected.json b/x-pack/filebeat/module/cisco/ftd/test/asa-fix.log-expected.json new file mode 100644 index 00000000000..bf6c6b521da --- /dev/null +++ b/x-pack/filebeat/module/cisco/ftd/test/asa-fix.log-expected.json @@ -0,0 +1,157 @@ +[ + { + "@timestamp": "2020-04-17T14:08:08.000-02:00", + "cisco.ftd.connection_id": "110577675", + "cisco.ftd.destination_interface": "Inside", + "cisco.ftd.message_id": "302016", + "cisco.ftd.source_interface": "Outside", + "cisco.ftd.source_username": "(LOCAL\\Elastic)", + "destination.address": "10.233.123.123", + "destination.ip": "10.233.123.123", + "destination.port": 53, + "event.action": "flow-expiration", + "event.code": 302016, + "event.dataset": "cisco.ftd", + "event.duration": 0, + "event.end": "2020-04-17T14:08:08.000-02:00", + "event.module": "cisco", + "event.original": "%ASA-6-302016: Teardown UDP connection 110577675 for Outside:10.123.123.123/53723(LOCAL\\Elastic) to Inside:10.233.123.123/53 duration 0:00:00 bytes 148 (zzzzzz)", + "event.severity": 6, + "event.start": "2020-04-17T16:08:08.000Z", + "event.timezone": "-02:00", + "fileset.name": "ftd", + "host.hostname": "SNL-ASA-VPN-A01", + "input.type": "log", + "log.level": "informational", + "log.offset": 0, + "network.bytes": 148, + "network.iana_number": 17, + "network.transport": "udp", + "service.type": "cisco", + "source.address": "10.123.123.123", + "source.ip": "10.123.123.123", + "source.port": 53723, + "tags": [ + "cisco-ftd" + ] + }, + { + "@timestamp": "2020-04-17T14:00:31.000-02:00", + "cisco.ftd.destination_interface": "Outside", + "cisco.ftd.message_id": "106023", + "cisco.ftd.rule_name": "Inside_access_in", + "cisco.ftd.source_interface": "Inside", + "destination.address": "10.123.123.123", + "destination.ip": "10.123.123.123", + "event.action": "firewall-rule", + "event.code": 106023, + "event.dataset": "cisco.ftd", + "event.module": "cisco", + "event.original": "%ASA-4-106023: Deny icmp src Inside:10.123.123.123 dst Outside:10.123.123.123 (type 11, code 0) by access-group \"Inside_access_in\" [0x0, 0x0]", + "event.outcome": "deny", + "event.severity": 4, + "event.timezone": "-02:00", + "fileset.name": "ftd", + "host.hostname": "SNL-ASA-VPN-A01", + "input.type": "log", + "log.level": "warning", + "log.offset": 200, + "network.iana_number": 1, + "network.transport": "icmp", + "service.type": "cisco", + "source.address": "10.123.123.123", + "source.ip": "10.123.123.123", + "tags": [ + "cisco-ftd" + ] + }, + { + "@timestamp": "2013-04-15T09:36:50.000-02:00", + "cisco.ftd.destination_interface": "outside", + "cisco.ftd.message_id": "106023", + "cisco.ftd.rule_name": "acl_dmz", + "cisco.ftd.source_interface": "dmz", + "destination.address": "10.123.123.123", + "destination.ip": "10.123.123.123", + "destination.port": 53, + "event.action": "firewall-rule", + "event.code": 106023, + "event.dataset": "cisco.ftd", + "event.module": "cisco", + "event.original": "%ASA-4-106023: Deny tcp src dmz:10.123.123.123/6316 dst outside:10.123.123.123/53 type 3, code 0, by access-group \"acl_dmz\" [0xe3afb522, 0x0]", + "event.outcome": "deny", + "event.severity": 4, + "event.timezone": "-02:00", + "fileset.name": "ftd", + "input.type": "log", + "log.level": "warning", + "log.offset": 381, + "network.iana_number": 6, + "network.transport": "tcp", + "service.type": "cisco", + "source.address": "10.123.123.123", + "source.ip": "10.123.123.123", + "source.port": 6316, + "tags": [ + "cisco-ftd" + ] + }, + { + "@timestamp": "2020-04-17T14:16:20.000-02:00", + "cisco.ftd.destination_interface": "Outside", + "cisco.ftd.message_id": "106023", + "cisco.ftd.rule_name": "Inside_access_in", + "cisco.ftd.source_interface": "Inside", + "cisco.ftd.source_username": "(LOCAL\\Elastic)", + "destination.address": "10.123.123.123", + "destination.ip": "10.123.123.123", + "destination.port": 57621, + "event.action": "firewall-rule", + "event.code": 106023, + "event.dataset": "cisco.ftd", + "event.module": "cisco", + "event.original": "%ASA-4-106023: Deny udp src Inside:10.123.123.123/57621(LOCAL\\Elastic) dst Outside:10.123.123.123/57621 by access-group \"Inside_access_in\" [0x0, 0x0]", + "event.outcome": "deny", + "event.severity": 4, + "event.timezone": "-02:00", + "fileset.name": "ftd", + "host.hostname": "SNL-ASA-VPN-A01", + "input.type": "log", + "log.level": "warning", + "log.offset": 545, + "network.iana_number": 17, + "network.transport": "udp", + "service.type": "cisco", + "source.address": "10.123.123.123", + "source.ip": "10.123.123.123", + "source.port": 57621, + "tags": [ + "cisco-ftd" + ] + }, + { + "@timestamp": "2020-04-17T14:15:07.000-02:00", + "cisco.ftd.message_id": "106017", + "destination.address": "10.123.123.123", + "destination.ip": "10.123.123.123", + "event.action": "firewall-rule", + "event.code": 106017, + "event.dataset": "cisco.ftd", + "event.module": "cisco", + "event.original": "%ASA-2-106017: Deny IP due to Land Attack from 10.123.123.123 to 10.123.123.123", + "event.outcome": "deny", + "event.severity": 2, + "event.timezone": "-02:00", + "fileset.name": "ftd", + "host.hostname": "SNL-ASA-VPN-A01", + "input.type": "log", + "log.level": "critical", + "log.offset": 734, + "service.type": "cisco", + "source.address": "10.123.123.123", + "source.ip": "10.123.123.123", + "tags": [ + "cisco-ftd" + ] + } +] \ No newline at end of file diff --git a/x-pack/filebeat/module/cisco/shared/ingest/asa-ftd-pipeline.yml b/x-pack/filebeat/module/cisco/shared/ingest/asa-ftd-pipeline.yml index 9dfc96d77e8..babf697616b 100644 --- a/x-pack/filebeat/module/cisco/shared/ingest/asa-ftd-pipeline.yml +++ b/x-pack/filebeat/module/cisco/shared/ingest/asa-ftd-pipeline.yml @@ -1,3 +1,4 @@ +--- description: "Pipeline for Cisco {< .internal_PREFIX >} logs" processors: # @@ -240,10 +241,11 @@ processors: if: "ctx._temp_.cisco.message_id == '106022'" field: "message" pattern: "%{event.outcome} %{network.transport} connection spoof from %{source.address} to %{destination.address} on interface %{_temp_.cisco.source_interface}" - - dissect: + - grok: if: "ctx._temp_.cisco.message_id == '106023'" field: "message" - pattern: '%{event.outcome} %{network.transport} src %{_temp_.cisco.source_interface}:%{source.address}/%{source.port} dst %{_temp_.cisco.destination_interface}:%{destination.address}/%{destination.port} %{} access%{}group "%{_temp_.cisco.list_id}"%{}' + patterns: + - ^%{NOTSPACE:event.outcome} %{NOTSPACE:network.transport} src %{NOTSPACE:_temp_.cisco.source_interface}:%{IPORHOST:source.address}(/%{POSINT:source.port})?\s*(%{GREEDYDATA:_temp_.cisco.source_username} )?dst %{NOTSPACE:_temp_.cisco.destination_interface}:%{IPORHOST:destination.address}(/%{POSINT:destination.port})?%{DATA}by access.group "%{NOTSPACE:_temp_.cisco.list_id}" - dissect: if: "ctx._temp_.cisco.message_id == '106027'" field: "message" @@ -440,8 +442,8 @@ processors: field: "message" if: '["302014", "302016", "302018", "302021", "302036", "302304", "302306"].contains(ctx._temp_.cisco.message_id)' patterns: - - "Teardown %{NOTSPACE:network.transport} (?:state-bypass )?connection %{NOTSPACE:_temp_.cisco.connection_id} (?:for|from) %{NOTCOLON:_temp_.cisco.source_interface}:%{DATA:source.address}/%{NUMBER:source.port:int} (?:%{NOTSPACE:_temp_.cisco.source_username} )?to %{NOTCOLON:_temp_.cisco.destination_interface}:%{DATA:destination.address}/%{NUMBER:destination.port:int} (?:%{NOTSPACE:_temp_.cisco.destination_username} )?(?:duration %{TIME:_temp_.duration_hms} bytes %{NUMBER:network.bytes:int})%{GREEDYDATA}" - - "Teardown %{NOTSPACE:network.transport} connection for faddr (?:%{NOTCOLON:_temp_.cisco.source_interface}:)?%{ECSDESTIPORHOST}/%{NUMBER} (?:%{NOTSPACE:_temp_.cisco.destination_username} )?gaddr (?:%{NOTCOLON}:)?%{MAPPEDSRC}/%{NUMBER} laddr (?:%{NOTCOLON:_temp_.cisco.source_interface}:)?%{ECSSOURCEIPORHOST}/%{NUMBER}(?: %{NOTSPACE:_temp_.cisco.source_username})?%{GREEDYDATA}" + - Teardown %{NOTSPACE:network.transport} (?:state-bypass )?connection %{NOTSPACE:_temp_.cisco.connection_id} (?:for|from) %{NOTCOLON:_temp_.cisco.source_interface}:%{DATA:source.address}/%{NUMBER:source.port:int}\s*(?:%{NOTSPACE:_temp_.cisco.source_username} )?to %{NOTCOLON:_temp_.cisco.destination_interface}:%{DATA:destination.address}/%{NUMBER:destination.port:int}\s*(?:%{NOTSPACE:_temp_.cisco.destination_username} )?(?:duration %{TIME:_temp_.duration_hms} bytes %{NUMBER:network.bytes:int})%{GREEDYDATA} + - Teardown %{NOTSPACE:network.transport} connection for faddr (?:%{NOTCOLON:_temp_.cisco.source_interface}:)?%{ECSDESTIPORHOST}/%{NUMBER}\s*(?:%{NOTSPACE:_temp_.cisco.destination_username} )?gaddr (?:%{NOTCOLON}:)?%{MAPPEDSRC}/%{NUMBER} laddr (?:%{NOTCOLON:_temp_.cisco.source_interface}:)?%{ECSSOURCEIPORHOST}/%{NUMBER}\s*(?:%{NOTSPACE:_temp_.cisco.source_username})?%{GREEDYDATA} pattern_definitions: NOTCOLON: "[^:]*" ECSSOURCEIPORHOST: "(?:%{IP:source.address}|%{HOSTNAME:source.domain})" From cc6c4e33db3fdcf2bd7d962532c14fc080d5371b Mon Sep 17 00:00:00 2001 From: Lee Hinman <57081003+leehinman@users.noreply.github.com> Date: Mon, 27 Apr 2020 08:54:45 -0500 Subject: [PATCH 029/116] Improve ECS categorization field mappings in redis module (#17918) - event.kind - event.category - event.type Closes #16179 --- CHANGELOG.next.asciidoc | 1 + .../module/redis/log/ingest/pipeline.json | 85 --- filebeat/module/redis/log/ingest/pipeline.yml | 84 +++ filebeat/module/redis/log/manifest.yml | 2 +- .../log/test/redis-5.0.3.log-expected.json | 7 + .../test/redis-darwin-3.0.2.log-expected.json | 126 ++++ .../test/redis-debian-1.2.6.log-expected.json | 700 ++++++++++++++++++ .../redis-windows-2.4.6.log-expected.json | 238 ++++++ .../redis/log/test/test.log-expected.json | 28 + 9 files changed, 1185 insertions(+), 86 deletions(-) delete mode 100644 filebeat/module/redis/log/ingest/pipeline.json create mode 100644 filebeat/module/redis/log/ingest/pipeline.yml diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 146c9534cac..ad59347f35e 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -274,6 +274,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Improve ECS categorization field mappings for nginx module. {issue}16174[16174] {pull}17844[17844] - Improve ECS categorization field mappings in postgresql module. {issue}16177[16177] {pull}17914[17914] - Improve ECS categorization field mappings in rabbitmq module. {issue}16178[16178] {pull}17916[17916] +- Improve ECS categorization field mappings in redis module. {issue}16179[16179] {pull}17918[17918] *Heartbeat* diff --git a/filebeat/module/redis/log/ingest/pipeline.json b/filebeat/module/redis/log/ingest/pipeline.json deleted file mode 100644 index c9ec2d3371b..00000000000 --- a/filebeat/module/redis/log/ingest/pipeline.json +++ /dev/null @@ -1,85 +0,0 @@ -{ - "description": "Pipeline for parsing redis logs", - "processors": [ - { - "grok": { - "field": "message", - "patterns": [ - "(%{POSINT:process.pid:long}:%{CHAR:redis.log.role} )?(%{REDISTIMESTAMP1:redis.log.timestamp}||%{REDISTIMESTAMP2:redis.log.timestamp}) %{REDISLEVEL:log.level} %{GREEDYDATA:message}", - "%{POSINT:process.pid:long}:signal-handler \\(%{POSINT:redis.log.timestamp}\\) %{GREEDYDATA:message}" - ], - "pattern_definitions": { - "CHAR": "[a-zA-Z]", - "REDISLEVEL": "[.\\-*#]", - "REDISTIMESTAMP1": "%{MONTHDAY} %{MONTH} %{TIME}", - "REDISTIMESTAMP2": "%{MONTHDAY} %{MONTH} %{YEAR} %{TIME}" - } - } - }, - { - "script": { - "lang": "painless", - "source": "if (ctx.log.level == params.dot) {\n ctx.log.level = params.debug;\n } else if (ctx.log.level == params.dash) {\n ctx.log.level = params.verbose;\n } else if (ctx.log.level == params.asterisk) {\n ctx.log.level = params.notice;\n } else if (ctx.log.level == params.hash) {\n ctx.log.level = params.warning;\n }", - "params": { - "dot": ".", - "debug": "debug", - "dash": "-", - "verbose": "verbose", - "asterisk": "*", - "notice": "notice", - "hash": "#", - "warning": "warning" - } - } - }, - { - "script": { - "lang": "painless", - "source": "if (ctx.redis.log.role == params.master_abbrev) {\n ctx.redis.log.role = params.master;\n } else if (ctx.redis.log.role == params.slave_abbrev) {\n ctx.redis.log.role = params.slave;\n } else if (ctx.redis.log.role == params.child_abbrev) {\n ctx.redis.log.role = params.child;\n } else if (ctx.redis.log.role == params.sentinel_abbrev) {\n ctx.redis.log.role = params.sentinel;\n }\n ", - "params": { - "master_abbrev": "M", - "master": "master", - "slave_abbrev": "S", - "slave": "slave", - "child_abbrev": "C", - "child": "child", - "sentinel_abbrev": "X", - "sentinel": "sentinel" - } - } - }, - { - "rename": { - "field": "@timestamp", - "target_field": "event.created" - } - }, - { - "date": { - "field": "redis.log.timestamp", - "target_field": "@timestamp", - "formats": [ - "dd MMM yyyy H:m:s.SSS", - "dd MMM H:m:s.SSS", - "dd MMM H:m:s", - "UNIX" - ], - "ignore_failure": true - } - }, - { - "remove": { - "field": "redis.log.timestamp", - "ignore_failure": true - } - } - ], - "on_failure": [ - { - "set": { - "field": "error.message", - "value": "{{ _ingest.on_failure_message }}" - } - } - ] -} diff --git a/filebeat/module/redis/log/ingest/pipeline.yml b/filebeat/module/redis/log/ingest/pipeline.yml new file mode 100644 index 00000000000..d1c08cab378 --- /dev/null +++ b/filebeat/module/redis/log/ingest/pipeline.yml @@ -0,0 +1,84 @@ +description: Pipeline for parsing redis logs +processors: +- grok: + field: message + patterns: + - (%{POSINT:process.pid:long}:%{CHAR:redis.log.role} )?(%{REDISTIMESTAMP1:redis.log.timestamp}||%{REDISTIMESTAMP2:redis.log.timestamp}) + %{REDISLEVEL:log.level} %{GREEDYDATA:message} + - '%{POSINT:process.pid:long}:signal-handler \(%{POSINT:redis.log.timestamp}\) + %{GREEDYDATA:message}' + pattern_definitions: + CHAR: '[a-zA-Z]' + REDISLEVEL: '[.\-*#]' + REDISTIMESTAMP1: '%{MONTHDAY} %{MONTH} %{TIME}' + REDISTIMESTAMP2: '%{MONTHDAY} %{MONTH} %{YEAR} %{TIME}' +- script: + lang: painless + source: >- + if (ctx.log.level == params.dot) { + ctx.log.level = params.debug; + } else if (ctx.log.level == params.dash) { + ctx.log.level = params.verbose; + } else if (ctx.log.level == params.asterisk) { + ctx.log.level = params.notice; + } else if (ctx.log.level == params.hash) { + ctx.log.level = params.warning; + } + params: + dot: . + debug: debug + dash: '-' + verbose: verbose + asterisk: '*' + notice: notice + hash: '#' + warning: warning +- script: + lang: painless + source: >- + if (ctx.redis.log.role == params.master_abbrev) { + ctx.redis.log.role = params.master; + } else if (ctx.redis.log.role == params.slave_abbrev) { + ctx.redis.log.role = params.slave; + } else if (ctx.redis.log.role == params.child_abbrev) { + ctx.redis.log.role = params.child; + } else if (ctx.redis.log.role == params.sentinel_abbrev) { + ctx.redis.log.role = params.sentinel; + } + params: + master_abbrev: M + master: master + slave_abbrev: S + slave: slave + child_abbrev: C + child: child + sentinel_abbrev: X + sentinel: sentinel +- rename: + field: '@timestamp' + target_field: event.created +- date: + field: redis.log.timestamp + target_field: '@timestamp' + formats: + - dd MMM yyyy H:m:s.SSS + - dd MMM H:m:s.SSS + - dd MMM H:m:s + - UNIX + ignore_failure: true +- remove: + field: redis.log.timestamp + ignore_failure: true +- set: + field: event.kind + value: event +- append: + field: event.category + value: database +- append: + field: event.type + value: info +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/filebeat/module/redis/log/manifest.yml b/filebeat/module/redis/log/manifest.yml index 3c63a894c28..728e098d4c2 100644 --- a/filebeat/module/redis/log/manifest.yml +++ b/filebeat/module/redis/log/manifest.yml @@ -10,5 +10,5 @@ var: os.windows: - "c:/program files/Redis/logs/redis.log*" -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/log.yml diff --git a/filebeat/module/redis/log/test/redis-5.0.3.log-expected.json b/filebeat/module/redis/log/test/redis-5.0.3.log-expected.json index 71d76c30a96..d3efc715fe3 100644 --- a/filebeat/module/redis/log/test/redis-5.0.3.log-expected.json +++ b/filebeat/module/redis/log/test/redis-5.0.3.log-expected.json @@ -1,7 +1,14 @@ [ { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "notice", diff --git a/filebeat/module/redis/log/test/redis-darwin-3.0.2.log-expected.json b/filebeat/module/redis/log/test/redis-darwin-3.0.2.log-expected.json index ff533b577ac..365ced2400b 100644 --- a/filebeat/module/redis/log/test/redis-darwin-3.0.2.log-expected.json +++ b/filebeat/module/redis/log/test/redis-darwin-3.0.2.log-expected.json @@ -1,7 +1,14 @@ [ { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "notice", @@ -12,8 +19,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "warning", @@ -24,8 +38,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "notice", @@ -36,8 +57,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "notice", @@ -48,8 +76,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.offset": 1478, @@ -58,8 +93,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "warning", @@ -70,8 +112,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "notice", @@ -82,8 +131,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "notice", @@ -94,8 +150,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "warning", @@ -106,8 +169,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "notice", @@ -118,8 +188,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "warning", @@ -130,8 +207,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "notice", @@ -142,8 +226,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "notice", @@ -154,8 +245,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.offset": 3273, @@ -164,8 +262,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "warning", @@ -176,8 +281,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "notice", @@ -188,8 +300,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "notice", @@ -200,8 +319,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "warning", diff --git a/filebeat/module/redis/log/test/redis-debian-1.2.6.log-expected.json b/filebeat/module/redis/log/test/redis-debian-1.2.6.log-expected.json index ff13e461ef4..a8f9d71736e 100644 --- a/filebeat/module/redis/log/test/redis-debian-1.2.6.log-expected.json +++ b/filebeat/module/redis/log/test/redis-debian-1.2.6.log-expected.json @@ -1,7 +1,14 @@ [ { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -10,8 +17,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "notice", @@ -20,8 +34,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -30,8 +51,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -40,8 +68,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "notice", @@ -50,8 +85,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -60,8 +102,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -70,8 +119,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "notice", @@ -80,8 +136,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -90,8 +153,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -100,8 +170,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -110,8 +187,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -120,8 +204,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -130,8 +221,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -140,8 +238,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -150,8 +255,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -160,8 +272,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -170,8 +289,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -180,8 +306,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -190,8 +323,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -200,8 +340,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -210,8 +357,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -220,8 +374,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -230,8 +391,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -240,8 +408,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -250,8 +425,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -260,8 +442,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -270,8 +459,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -280,8 +476,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -290,8 +493,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -300,8 +510,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -310,8 +527,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -320,8 +544,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -330,8 +561,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -340,8 +578,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -350,8 +595,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -360,8 +612,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -370,8 +629,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -380,8 +646,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -390,8 +663,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -400,8 +680,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -410,8 +697,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -420,8 +714,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -430,8 +731,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -440,8 +748,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -450,8 +765,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -460,8 +782,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -470,8 +799,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -480,8 +816,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -490,8 +833,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -500,8 +850,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -510,8 +867,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -520,8 +884,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -530,8 +901,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -540,8 +918,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -550,8 +935,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -560,8 +952,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -570,8 +969,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -580,8 +986,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -590,8 +1003,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -600,8 +1020,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -610,8 +1037,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -620,8 +1054,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -630,8 +1071,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -640,8 +1088,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -650,8 +1105,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -660,8 +1122,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -670,8 +1139,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -680,8 +1156,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -690,8 +1173,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -700,8 +1190,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -710,8 +1207,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -720,8 +1224,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -730,8 +1241,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -740,8 +1258,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -750,8 +1275,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -760,8 +1292,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -770,8 +1309,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -780,8 +1326,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -790,8 +1343,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -800,8 +1360,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -810,8 +1377,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -820,8 +1394,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -830,8 +1411,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -840,8 +1428,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -850,8 +1445,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -860,8 +1462,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -870,8 +1479,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -880,8 +1496,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -890,8 +1513,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -900,8 +1530,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -910,8 +1547,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -920,8 +1564,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -930,8 +1581,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -940,8 +1598,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -950,8 +1615,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -960,8 +1632,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -970,8 +1649,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -980,8 +1666,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -990,8 +1683,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", diff --git a/filebeat/module/redis/log/test/redis-windows-2.4.6.log-expected.json b/filebeat/module/redis/log/test/redis-windows-2.4.6.log-expected.json index 4fb3b4e92b0..dbafda2b3df 100644 --- a/filebeat/module/redis/log/test/redis-windows-2.4.6.log-expected.json +++ b/filebeat/module/redis/log/test/redis-windows-2.4.6.log-expected.json @@ -1,7 +1,14 @@ [ { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "notice", @@ -10,8 +17,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "warning", @@ -20,8 +34,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "notice", @@ -30,8 +51,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -40,8 +68,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -50,8 +85,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -60,8 +102,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -70,8 +119,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -80,8 +136,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -90,8 +153,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -100,8 +170,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -110,8 +187,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -120,8 +204,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -130,8 +221,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -140,8 +238,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -150,8 +255,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -160,8 +272,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -170,8 +289,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -180,8 +306,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -190,8 +323,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -200,8 +340,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -210,8 +357,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -220,8 +374,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -230,8 +391,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -240,8 +408,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -250,8 +425,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -260,8 +442,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -270,8 +459,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -280,8 +476,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -290,8 +493,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -300,8 +510,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -310,8 +527,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -320,8 +544,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", @@ -330,8 +561,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "verbose", diff --git a/filebeat/module/redis/log/test/test.log-expected.json b/filebeat/module/redis/log/test/test.log-expected.json index b74b64a93ed..cee22b55c3b 100644 --- a/filebeat/module/redis/log/test/test.log-expected.json +++ b/filebeat/module/redis/log/test/test.log-expected.json @@ -1,7 +1,14 @@ [ { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "notice", @@ -12,8 +19,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "debug", @@ -22,8 +36,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.level": "notice", @@ -32,8 +53,15 @@ "service.type": "redis" }, { + "event.category": [ + "database" + ], "event.dataset": "redis.log", + "event.kind": "event", "event.module": "redis", + "event.type": [ + "info" + ], "fileset.name": "log", "input.type": "log", "log.offset": 250, From 1eb303261f9d175d27011c5361a2fa41a8ec40ad Mon Sep 17 00:00:00 2001 From: kaiyan-sheng Date: Mon, 27 Apr 2020 09:46:31 -0600 Subject: [PATCH 030/116] [Metricbeat] allow partial region and zone in googlecloud module config (#17913) * allow partial region and zone in googlecloud module config * add wildcard support in region and zone --- CHANGELOG.next.asciidoc | 1 + metricbeat/docs/modules/googlecloud.asciidoc | 76 ++++++++++++++++++- x-pack/metricbeat/metricbeat.reference.yml | 9 +++ .../module/googlecloud/_meta/config.yml | 9 +++ .../module/googlecloud/_meta/docs.asciidoc | 74 ++++++++++++++++-- .../stackdriver/metrics_requester.go | 25 +++--- .../stackdriver/metrics_requester_test.go | 58 ++++++-------- .../modules.d/googlecloud.yml.disabled | 9 +++ 8 files changed, 206 insertions(+), 55 deletions(-) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index ad59347f35e..f3c8f6d274e 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -348,6 +348,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Reference kubernetes manifests mount data directory from the host when running metricbeat as daemonset, so data persist between executions in the same node. {pull}17429[17429] - Add more detailed error messages, system tests and small refactoring to the service metricset in windows. {pull}17725[17725] - Stack Monitoring modules now auto-configure required metricsets when `xpack.enabled: true` is set. {issue}16471[[16471] {pull}17609[17609] +- Allow partial region and zone name in googlecloud module config. {pull}17913[17913] - Add aggregation aligner as a config parameter for googlecloud stackdriver metricset. {issue}17141[[17141] {pull}17719[17719] - Move the perfmon metricset to GA. {issue}16608[16608] {pull}17879[17879] diff --git a/metricbeat/docs/modules/googlecloud.asciidoc b/metricbeat/docs/modules/googlecloud.asciidoc index 6e3d7e881d5..e323cdc1650 100644 --- a/metricbeat/docs/modules/googlecloud.asciidoc +++ b/metricbeat/docs/modules/googlecloud.asciidoc @@ -16,9 +16,20 @@ Note: extra GCP charges on Stackdriver Monitoring API requests will be generated == Module config and parameters This is a list of the possible module parameters you can tune: -* *zone*: A single string with the zone you want to monitor like "us-central1-a". If you need to fetch from multiple regions, you have to setup a different configuration for each (but you don't need a new instance of Metricbeat running) - -* *region*: A single string with the region you want to monitor like "us-central1". This will enable monitoring for all zones under this region. +* *zone*: A single string with the zone you want to monitor like `us-central1-a`. +Or you can specific a partial zone name like `us-central1-`, which will monitor +all zones start with `us-central1-`: `us-central1-a`, `us-central1-b`, +`us-central1-c` and `us-central1-f`. +Please see https://cloud.google.com/compute/docs/regions-zones#available[GCP zones] +for zones that are available in GCP. + +* *region*: A single string with the region you want to monitor like `us-central1`. +This will enable monitoring for all zones under this region. Or you can specific +a partial region name like `us-east`, which will monitor all regions start with +`us-east`: `us-east1` and `us-east4`. If both region and zone are configured, +only region will be used. +Please see https://cloud.google.com/compute/docs/regions-zones#available[GCP regions] +for regions that are available in GCP. * *project_id*: A single string with your GCP Project ID @@ -28,6 +39,56 @@ This is a list of the possible module parameters you can tune: * *period*: A single time duration specified for this module collection frequency. +[float] +== Example configuration +* `compute` metricset is enabled to collect metrics from `us-central1-a` zone +in `elastic-observability` project. ++ +[source,yaml] +---- +- module: googlecloud + metricsets: + - compute + zone: "us-central1-a" + project_id: "elastic-observability" + credentials_file_path: "your JSON credentials file path" + exclude_labels: false + period: 60s +---- + +* `compute` and `pubsub` metricsets are enabled to collect metrics from all zones +under `us-central1` region in `elastic-observability` project. ++ +[source,yaml] +---- +- module: googlecloud + metricsets: + - compute + - pubsub + region: "us-central1" + project_id: "elastic-observability" + credentials_file_path: "your JSON credentials file path" + exclude_labels: false + period: 60s +---- + +* `compute` metricset is enabled to collect metrics from all regions starts with +`us-west` in `elastic-observability` project, which includes all zones under +`us-west1`, `us-west2`, `us-west3` and `us-west4`. ++ +[source,yaml] +---- +- module: googlecloud + metricsets: + - compute + - pubsub + region: "us-west" + project_id: "elastic-observability" + credentials_file_path: "your JSON credentials file path" + exclude_labels: false + period: 60s +---- + [float] == Authentication, authorization and permissions. Authentication and authorization in Google Cloud Platform can be achieved in many ways. For the current version of the Google Cloud Platform module for Metricbeat, the only supported method is using Service Account JSON files. A typical JSON with a private key looks like this: @@ -133,6 +194,15 @@ metricbeat.modules: credentials_file_path: "your JSON credentials file path" exclude_labels: false period: 300s + +- module: googlecloud + metricsets: + - compute + region: "us-" + project_id: "your project id" + credentials_file_path: "your JSON credentials file path" + exclude_labels: false + period: 60s ---- [float] diff --git a/x-pack/metricbeat/metricbeat.reference.yml b/x-pack/metricbeat/metricbeat.reference.yml index 3e751957bd3..3fba65dbee6 100644 --- a/x-pack/metricbeat/metricbeat.reference.yml +++ b/x-pack/metricbeat/metricbeat.reference.yml @@ -523,6 +523,15 @@ metricbeat.modules: exclude_labels: false period: 300s +- module: googlecloud + metricsets: + - compute + region: "us-" + project_id: "your project id" + credentials_file_path: "your JSON credentials file path" + exclude_labels: false + period: 60s + #------------------------------- Graphite Module ------------------------------- - module: graphite metricsets: ["server"] diff --git a/x-pack/metricbeat/module/googlecloud/_meta/config.yml b/x-pack/metricbeat/module/googlecloud/_meta/config.yml index e717a98ee6d..640fd87bc5a 100644 --- a/x-pack/metricbeat/module/googlecloud/_meta/config.yml +++ b/x-pack/metricbeat/module/googlecloud/_meta/config.yml @@ -25,3 +25,12 @@ credentials_file_path: "your JSON credentials file path" exclude_labels: false period: 300s + +- module: googlecloud + metricsets: + - compute + region: "us-" + project_id: "your project id" + credentials_file_path: "your JSON credentials file path" + exclude_labels: false + period: 60s diff --git a/x-pack/metricbeat/module/googlecloud/_meta/docs.asciidoc b/x-pack/metricbeat/module/googlecloud/_meta/docs.asciidoc index a02a1b80979..5a1e4ed62f3 100644 --- a/x-pack/metricbeat/module/googlecloud/_meta/docs.asciidoc +++ b/x-pack/metricbeat/module/googlecloud/_meta/docs.asciidoc @@ -6,18 +6,82 @@ Note: extra GCP charges on Stackdriver Monitoring API requests will be generated == Module config and parameters This is a list of the possible module parameters you can tune: -* *zone*: A single string with the zone you want to monitor like "us-central1-a". If you need to fetch from multiple regions, you have to setup a different configuration for each (but you don't need a new instance of Metricbeat running) - -* *region*: A single string with the region you want to monitor like "us-central1". This will enable monitoring for all zones under this region. +* *zone*: A single string with the zone you want to monitor like `us-central1-a`. +Or you can specific a partial zone name like `us-central1-` or `us-central1-*`, +which will monitor all zones start with `us-central1-`: `us-central1-a`, +`us-central1-b`, `us-central1-c` and `us-central1-f`. +Please see https://cloud.google.com/compute/docs/regions-zones#available[GCP zones] +for zones that are available in GCP. + +* *region*: A single string with the region you want to monitor like `us-central1`. +This will enable monitoring for all zones under this region. Or you can specific +a partial region name like `us-east` or `us-east*`, which will monitor all regions start with +`us-east`: `us-east1` and `us-east4`. If both region and zone are configured, +only region will be used. +Please see https://cloud.google.com/compute/docs/regions-zones#available[GCP regions] +for regions that are available in GCP. * *project_id*: A single string with your GCP Project ID -* *credentials_file_path*: A single string pointing to the JSON file path reachable by Metricbeat that you have created using IAM. +* *credentials_file_path*: A single string pointing to the JSON file path +reachable by Metricbeat that you have created using IAM. -* *exclude_labels*: (`true`/`false` default `false`) Do not extract extra labels and metadata information from Metricsets and fetch metrics onlly. At the moment, *labels and metadata extraction is only supported* in Compute Metricset. +* *exclude_labels*: (`true`/`false` default `false`) Do not extract extra labels +and metadata information from metricsets and fetch metrics only. At the moment, +*labels and metadata extraction is only supported* in `compute` metricset. * *period*: A single time duration specified for this module collection frequency. +[float] +== Example configuration +* `compute` metricset is enabled to collect metrics from `us-central1-a` zone +in `elastic-observability` project. ++ +[source,yaml] +---- +- module: googlecloud + metricsets: + - compute + zone: "us-central1-a" + project_id: "elastic-observability" + credentials_file_path: "your JSON credentials file path" + exclude_labels: false + period: 60s +---- + +* `compute` and `pubsub` metricsets are enabled to collect metrics from all zones +under `us-central1` region in `elastic-observability` project. ++ +[source,yaml] +---- +- module: googlecloud + metricsets: + - compute + - pubsub + region: "us-central1" + project_id: "elastic-observability" + credentials_file_path: "your JSON credentials file path" + exclude_labels: false + period: 60s +---- + +* `compute` metricset is enabled to collect metrics from all regions starts with +`us-west` in `elastic-observability` project, which includes all zones under +`us-west1`, `us-west2`, `us-west3` and `us-west4`. ++ +[source,yaml] +---- +- module: googlecloud + metricsets: + - compute + - pubsub + region: "us-west" + project_id: "elastic-observability" + credentials_file_path: "your JSON credentials file path" + exclude_labels: false + period: 60s +---- + [float] == Authentication, authorization and permissions. Authentication and authorization in Google Cloud Platform can be achieved in many ways. For the current version of the Google Cloud Platform module for Metricbeat, the only supported method is using Service Account JSON files. A typical JSON with a private key looks like this: diff --git a/x-pack/metricbeat/module/googlecloud/stackdriver/metrics_requester.go b/x-pack/metricbeat/module/googlecloud/stackdriver/metrics_requester.go index edfd52a64e3..11b04e5a1b2 100644 --- a/x-pack/metricbeat/module/googlecloud/stackdriver/metrics_requester.go +++ b/x-pack/metricbeat/module/googlecloud/stackdriver/metrics_requester.go @@ -8,6 +8,7 @@ import ( "context" "fmt" "regexp" + "strings" "sync" "time" @@ -69,18 +70,6 @@ func (r *stackdriverMetricsRequester) Metric(ctx context.Context, metricType str return } -func constructFilter(m string, region string, zone string) string { - filter := fmt.Sprintf(`metric.type="%s" AND resource.labels.zone = `, m) - // If region is specified, use region as filter resource label. - // If region is empty but zone is given, use zone instead. - if region != "" { - filter += fmt.Sprintf(`starts_with("%s")`, region) - } else if zone != "" { - filter += fmt.Sprintf(`"%s"`, zone) - } - return filter -} - func (r *stackdriverMetricsRequester) Metrics(ctx context.Context, stackDriverConfigs []stackDriverConfig, metricsMeta map[string]metricMeta) ([]timeSeriesWithAligner, error) { var lock sync.Mutex var wg sync.WaitGroup @@ -134,9 +123,17 @@ func (r *stackdriverMetricsRequester) getFilterForMetric(m string) (f string) { "both are provided, only use region", r.config.Region, r.config.Zone) } if r.config.Region != "" { - f = fmt.Sprintf(`%s AND resource.labels.zone = starts_with("%s")`, f, r.config.Region) + region := r.config.Region + if strings.HasSuffix(r.config.Region, "*") { + region = strings.TrimSuffix(r.config.Region, "*") + } + f = fmt.Sprintf(`%s AND resource.labels.zone = starts_with("%s")`, f, region) } else if r.config.Zone != "" { - f = fmt.Sprintf(`%s AND resource.labels.zone = "%s"`, f, r.config.Zone) + zone := r.config.Zone + if strings.HasSuffix(r.config.Zone, "*") { + zone = strings.TrimSuffix(r.config.Zone, "*") + } + f = fmt.Sprintf(`%s AND resource.labels.zone = starts_with("%s")`, f, zone) } } return diff --git a/x-pack/metricbeat/module/googlecloud/stackdriver/metrics_requester_test.go b/x-pack/metricbeat/module/googlecloud/stackdriver/metrics_requester_test.go index 1ed10814b76..f7aff666c0f 100644 --- a/x-pack/metricbeat/module/googlecloud/stackdriver/metrics_requester_test.go +++ b/x-pack/metricbeat/module/googlecloud/stackdriver/metrics_requester_test.go @@ -14,38 +14,6 @@ import ( "github.com/elastic/beats/v7/libbeat/logp" ) -func TestStringInSlice(t *testing.T) { - cases := []struct { - title string - m string - region string - zone string - expectedFilter string - }{ - { - "construct filter with zone", - "compute.googleapis.com/instance/cpu/utilization", - "", - "us-east1-b", - "metric.type=\"compute.googleapis.com/instance/cpu/utilization\" AND resource.labels.zone = \"us-east1-b\"", - }, - { - "construct filter with region", - "compute.googleapis.com/instance/cpu/utilization", - "us-east1", - "", - "metric.type=\"compute.googleapis.com/instance/cpu/utilization\" AND resource.labels.zone = starts_with(\"us-east1\")", - }, - } - - for _, c := range cases { - t.Run(c.title, func(t *testing.T) { - filter := constructFilter(c.m, c.region, c.zone) - assert.Equal(t, c.expectedFilter, filter) - }) - } -} - func TestGetFilterForMetric(t *testing.T) { var logger = logp.NewLogger("test") cases := []struct { @@ -58,7 +26,7 @@ func TestGetFilterForMetric(t *testing.T) { "compute service with zone in config", "compute.googleapis.com/firewall/dropped_bytes_count", stackdriverMetricsRequester{config: config{Zone: "us-central1-a"}}, - "metric.type=\"compute.googleapis.com/firewall/dropped_bytes_count\" AND resource.labels.zone = \"us-central1-a\"", + "metric.type=\"compute.googleapis.com/firewall/dropped_bytes_count\" AND resource.labels.zone = starts_with(\"us-central1-a\")", }, { "pubsub service with zone in config", @@ -96,6 +64,30 @@ func TestGetFilterForMetric(t *testing.T) { stackdriverMetricsRequester{config: config{Region: "us-central1", Zone: "us-central1-a"}, logger: logger}, "metric.type=\"compute.googleapis.com/firewall/dropped_bytes_count\" AND resource.labels.zone = starts_with(\"us-central1\")", }, + { + "compute uptime with partial region", + "compute.googleapis.com/instance/uptime", + stackdriverMetricsRequester{config: config{Region: "us-west"}, logger: logger}, + "metric.type=\"compute.googleapis.com/instance/uptime\" AND resource.labels.zone = starts_with(\"us-west\")", + }, + { + "compute uptime with partial zone", + "compute.googleapis.com/instance/uptime", + stackdriverMetricsRequester{config: config{Zone: "us-west1-"}, logger: logger}, + "metric.type=\"compute.googleapis.com/instance/uptime\" AND resource.labels.zone = starts_with(\"us-west1-\")", + }, + { + "compute uptime with wildcard in region", + "compute.googleapis.com/instance/uptime", + stackdriverMetricsRequester{config: config{Region: "us-*"}, logger: logger}, + "metric.type=\"compute.googleapis.com/instance/uptime\" AND resource.labels.zone = starts_with(\"us-\")", + }, + { + "compute uptime with wildcard in zone", + "compute.googleapis.com/instance/uptime", + stackdriverMetricsRequester{config: config{Zone: "us-west1-*"}, logger: logger}, + "metric.type=\"compute.googleapis.com/instance/uptime\" AND resource.labels.zone = starts_with(\"us-west1-\")", + }, } for _, c := range cases { diff --git a/x-pack/metricbeat/modules.d/googlecloud.yml.disabled b/x-pack/metricbeat/modules.d/googlecloud.yml.disabled index 392b64718d8..cd49fdc146f 100644 --- a/x-pack/metricbeat/modules.d/googlecloud.yml.disabled +++ b/x-pack/metricbeat/modules.d/googlecloud.yml.disabled @@ -28,3 +28,12 @@ credentials_file_path: "your JSON credentials file path" exclude_labels: false period: 300s + +- module: googlecloud + metricsets: + - compute + region: "us-" + project_id: "your project id" + credentials_file_path: "your JSON credentials file path" + exclude_labels: false + period: 60s From 7f037bf7b3cd59366933daaf96ea4ba2871b1bac Mon Sep 17 00:00:00 2001 From: Jaime Soriano Pastor Date: Mon, 27 Apr 2020 18:20:25 +0200 Subject: [PATCH 031/116] Disable repository expiration checks in Journalbeat builds (#18005) --- journalbeat/magefile.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/journalbeat/magefile.go b/journalbeat/magefile.go index d119e2e7554..58fdf91dfbe 100644 --- a/journalbeat/magefile.go +++ b/journalbeat/magefile.go @@ -193,7 +193,15 @@ func installDependencies(arch string, pkgs ...string) error { return err } - params := append([]string{"install", "-y", "--no-install-recommends"}, pkgs...) + params := append([]string{"install", "-y", + "--no-install-recommends", + + // Journalbeat is built with old versions of Debian that don't update + // their repositories, so they have expired keys. + // Allow unauthenticated packages. + // This was not enough: "-o", "Acquire::Check-Valid-Until=false", + "--allow-unauthenticated", + }, pkgs...) return sh.Run("apt-get", params...) } From eb2dc26aeac615019dda35a2039e1fcebc47f2a2 Mon Sep 17 00:00:00 2001 From: Andrew Cholakian Date: Mon, 27 Apr 2020 16:11:23 -0500 Subject: [PATCH 032/116] [Heartbeat] Add Additional ECS tls.* fields (#17687) Work in support of https://github.com/elastic/uptime/issues/161 This patch adds additional ECS [TLS](https://www.elastic.co/guide/en/ecs/current/ecs-tls.html) and [x509](https://github.com/elastic/ecs/pull/762) fields. Note that we are blocked on the x509 fields which are not yet merged into ECS. Sample output of the `tls.*` fields with this patch is below. Note the somewhat strange nesting of data in `issuer` and `subject`. This is per the ECS spec, but a bit awkward. We may want to break this data out into the more specific ECS `x509` type in the future. For UI work we are likely fine to parse this on the client and display the CN section in most cases. I did break out the CN into its own field in `x509.subject/issuer.common_name`. However, if we do want to aggregate on issuer in the future it's good to have the full distinguished name to do that on. This PR also refactors some `libbeat` code around parsing TLS versions and adds test coverage there as well. ```json { "tls": { "certificate_not_valid_after": "2020-07-16T03:15:39Z", "certificate_not_valid_before": "2019-08-16T01:40:25Z", "server": { "hash": { "sha1": "b7b4b89ef0d0caf39d223736f0fdbb03c7b426f1", "sha256": "12b00d04db0db8caa302bfde043e88f95baceb91e86ac143e93830b4bbec726d" }, "x509": { "issuer": { "common_name": "GlobalSign CloudSSL CA - SHA256 - G3", "distinguished_name": "CN=GlobalSign CloudSSL CA - SHA256 - G3,O=GlobalSign nv-sa,C=BE" }, "not_after": "2020-07-16T03:15:39Z", "not_before": "2019-08-16T01:40:25Z", "public_key_algorithm": "RSA", "public_key_size": 2048, "serial_number": "26610543540289562361990401194", "signature_algorithm": "SHA256-RSA", "subject": { "common_name": "r2.shared.global.fastly.net", "distinguished_name": "CN=r2.shared.global.fastly.net,O=Fastly\\, Inc.,L=San Francisco,ST=California,C=US" } } } } } ``` ## How to test this PR locally Run against TLS/Non-TLS endpoints --- CHANGELOG.next.asciidoc | 2 +- heartbeat/_meta/fields.common.yml | 8 + heartbeat/docs/fields.asciidoc | 201 ++++++++- heartbeat/hbtest/hbtestutil.go | 60 ++- heartbeat/include/fields.go | 2 +- .../active/dialchain/_meta/fields.yml | 112 ++++- heartbeat/monitors/active/dialchain/tls.go | 62 +-- .../monitors/active/dialchain/tls_test.go | 144 ------- .../active/dialchain/tlsmeta/tlsmeta.go | 133 ++++++ .../active/dialchain/tlsmeta/tlsmeta_test.go | 398 ++++++++++++++++++ .../monitors/active/fixtures/expired.cert | 23 + .../monitors/active/fixtures/expired.key | 28 ++ heartbeat/monitors/active/http/http_test.go | 48 ++- heartbeat/monitors/active/http/task.go | 11 +- heartbeat/monitors/active/tcp/tcp.go | 8 +- heartbeat/monitors/active/tcp/tls_test.go | 31 ++ heartbeat/reason/reason.go | 3 + libbeat/common/transport/tlscommon/types.go | 4 + .../common/transport/tlscommon/versions.go | 12 +- .../transport/tlscommon/versions_default.go | 28 +- .../transport/tlscommon/versions_test.go | 77 ++++ libbeat/mapping/field.go | 2 +- 22 files changed, 1155 insertions(+), 242 deletions(-) delete mode 100644 heartbeat/monitors/active/dialchain/tls_test.go create mode 100644 heartbeat/monitors/active/dialchain/tlsmeta/tlsmeta.go create mode 100644 heartbeat/monitors/active/dialchain/tlsmeta/tlsmeta_test.go create mode 100644 heartbeat/monitors/active/fixtures/expired.cert create mode 100644 heartbeat/monitors/active/fixtures/expired.key create mode 100644 libbeat/common/transport/tlscommon/versions_test.go diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index f3c8f6d274e..b1f70eea949 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -279,7 +279,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d *Heartbeat* - Allow a list of status codes for HTTP checks. {pull}15587[15587] - +- Add additional ECS compatible fields for TLS information. {pull}17687[17687] *Journalbeat* diff --git a/heartbeat/_meta/fields.common.yml b/heartbeat/_meta/fields.common.yml index 28a721494f9..8db94d8a56c 100644 --- a/heartbeat/_meta/fields.common.yml +++ b/heartbeat/_meta/fields.common.yml @@ -17,11 +17,19 @@ type: keyword description: > The monitors configured name + multi_fields: + - name: text + type: text + analyzer: simple - name: id type: keyword description: > The monitors full job ID as used by heartbeat. + multi_fields: + - name: text + type: text + analyzer: simple - name: duration type: group diff --git a/heartbeat/docs/fields.asciidoc b/heartbeat/docs/fields.asciidoc index b288eec1788..f52d1205662 100644 --- a/heartbeat/docs/fields.asciidoc +++ b/heartbeat/docs/fields.asciidoc @@ -215,6 +215,13 @@ type: keyword -- +*`monitor.name.text`*:: ++ +-- +type: text + +-- + *`monitor.id`*:: + -- @@ -225,6 +232,13 @@ type: keyword -- +*`monitor.id.text`*:: ++ +-- +type: text + +-- + [float] === duration @@ -7824,7 +7838,10 @@ TLS layer related fields. *`tls.certificate_not_valid_before`*:: + -- -Earliest time at which the connection's certificates are valid. + +deprecated:[7.8.0] + +Deprecated in favor of `tls.server.x509.not_before`. Earliest time at which the connection's certificates are valid. type: date @@ -7833,7 +7850,10 @@ type: date *`tls.certificate_not_valid_after`*:: + -- -Latest time at which the connection's certificates are valid. + +deprecated:[7.8.0] + +Deprecated in favor of `tls.server.x509.not_after`. Latest time at which the connection's certificates are valid. type: date @@ -7862,3 +7882,180 @@ type: long -- +[float] +=== server + +Detailed x509 certificate metadata + + + +*`tls.server.x509.alternative_names`*:: ++ +-- +List of subject alternative names (SAN). Name types vary by certificate authority and certificate type but commonly contain IP addresses, DNS names (and wildcards), and email addresses. + +type: keyword + +example: *.elastic.co + +-- + + +*`tls.server.x509.issuer.common_name`*:: ++ +-- +List of common name (CN) of issuing certificate authority. + +type: keyword + +example: DigiCert SHA2 High Assurance Server CA + +-- + +*`tls.server.x509.issuer.common_name.text`*:: ++ +-- +type: wildcard + +-- + +*`tls.server.x509.issuer.distinguished_name`*:: ++ +-- +Distinguished name (DN) of issuing certificate authority. + +type: keyword + +example: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert SHA2 High Assurance Server CA + +-- + +*`tls.server.x509.not_after`*:: ++ +-- +Time at which the certificate is no longer considered valid. + +type: date + +example: 2020-07-16 03:15:39 + +-- + +*`tls.server.x509.not_before`*:: ++ +-- +Time at which the certificate is first considered valid. + +type: date + +example: 2019-08-16 01:40:25 + +-- + +*`tls.server.x509.public_key_algorithm`*:: ++ +-- +Algorithm used to generate the public key. + +type: keyword + +example: RSA + +-- + +*`tls.server.x509.public_key_curve`*:: ++ +-- +The curve used by the elliptic curve public key algorithm. This is algorithm specific. + +type: keyword + +example: nistp521 + +-- + +*`tls.server.x509.public_key_exponent`*:: ++ +-- +Exponent used to derive the public key. This is algorithm specific. + +type: long + +example: 65537 + +-- + +*`tls.server.x509.public_key_size`*:: ++ +-- +The size of the public key space in bits. + +type: long + +example: 2048 + +-- + +*`tls.server.x509.serial_number`*:: ++ +-- +Unique serial number issued by the certificate authority. For consistency, if this value is alphanumeric, it should be formatted without colons and uppercase characters. + +type: keyword + +example: 55FBB9C7DEBF09809D12CCAA + +-- + +*`tls.server.x509.signature_algorithm`*:: ++ +-- +Identifier for certificate signature algorithm. Recommend using names found in Go Lang Crypto library (See https://github.com/golang/go/blob/go1.14/src/crypto/x509/x509.go#L337-L353). + +type: keyword + +example: SHA256-RSA + +-- + + +*`tls.server.x509.subject.subject.common_name`*:: ++ +-- +List of common names (CN) of subject. + +type: keyword + +example: r2.shared.global.fastly.net + +-- + +*`tls.server.x509.subject.subject.common_name.text`*:: ++ +-- +type: wildcard + +-- + +*`tls.server.x509.subject.subject.distinguished_name`*:: ++ +-- +Distinguished name (DN) of the certificate subject entity. + +type: keyword + +example: C=US, ST=California, L=San Francisco, O=Fastly, Inc., CN=r2.shared.global.fastly.net + +-- + +*`tls.server.x509.version_number`*:: ++ +-- +Version of x509 format. + +type: keyword + +example: 3 + +-- + diff --git a/heartbeat/hbtest/hbtestutil.go b/heartbeat/hbtest/hbtestutil.go index 548bc42a0eb..246104638e9 100644 --- a/heartbeat/hbtest/hbtestutil.go +++ b/heartbeat/hbtest/hbtestutil.go @@ -18,10 +18,12 @@ package hbtest import ( + "crypto/tls" "crypto/x509" "fmt" "io" "io/ioutil" + "net" "net/http" "net/http/httptest" "net/url" @@ -29,6 +31,10 @@ import ( "strconv" "strings" "testing" + "time" + + "github.com/elastic/beats/v7/heartbeat/monitors/active/dialchain/tlsmeta" + "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/heartbeat/hbtestllext" @@ -107,13 +113,25 @@ func ServerPort(server *httptest.Server) (uint16, error) { // TLSChecks validates the given x509 cert at the given position. func TLSChecks(chainIndex, certIndex int, certificate *x509.Certificate) validator.Validator { - return lookslike.MustCompile(map[string]interface{}{ - "tls": map[string]interface{}{ - "rtt.handshake.us": isdef.IsDuration, - "certificate_not_valid_before": certificate.NotBefore, - "certificate_not_valid_after": certificate.NotAfter, - }, - }) + expected := common.MapStr{} + // This function is well tested independently, so we just test that things match up here. + tlsmeta.AddTLSMetadata(expected, tls.ConnectionState{ + Version: tls.VersionTLS13, + HandshakeComplete: true, + CipherSuite: tls.TLS_AES_128_GCM_SHA256, + ServerName: certificate.Subject.CommonName, + PeerCertificates: []*x509.Certificate{certificate}, + }, time.Duration(1)) + + expected.Put("tls.rtt.handshake.us", isdef.IsDuration) + + return lookslike.MustCompile(expected) +} + +func TLSCertChecks(certificate *x509.Certificate) validator.Validator { + expected := common.MapStr{} + tlsmeta.AddCertMetadata(expected, []*x509.Certificate{certificate}) + return lookslike.MustCompile(expected) } // BaseChecks creates a skima.Validator that represents the "monitor" field present @@ -196,6 +214,14 @@ func ErrorChecks(msgSubstr string, errType string) validator.Validator { }) } +func ExpiredCertChecks(cert *x509.Certificate) validator.Validator { + msg := x509.CertificateInvalidError{Cert: cert, Reason: x509.Expired}.Error() + return lookslike.Compose( + ErrorChecks(msg, "io"), + TLSCertChecks(cert), + ) +} + // RespondingTCPChecks creates a skima.Validator that represents the "tcp" field present // in all heartbeat events that use a Tcp connection as part of their DialChain func RespondingTCPChecks() validator.Validator { @@ -215,3 +241,23 @@ func CertToTempFile(t *testing.T, cert *x509.Certificate) *os.File { certFile.WriteString(x509util.CertToPEMString(cert)) return certFile } + +func StartHTTPSServer(t *testing.T, tlsCert tls.Certificate) (host string, port string, cert *x509.Certificate, doClose func() error) { + cert, err := x509.ParseCertificate(tlsCert.Certificate[0]) + require.NoError(t, err) + + // No need to start a real server, since this is invalid, we just + l, err := tls.Listen("tcp", "127.0.0.1:0", &tls.Config{ + Certificates: []tls.Certificate{tlsCert}, + }) + require.NoError(t, err) + + srv := &http.Server{Handler: HelloWorldHandler(200)} + go func() { + srv.Serve(l) + }() + + host, port, err = net.SplitHostPort(l.Addr().String()) + require.NoError(t, err) + return host, port, cert, srv.Close +} diff --git a/heartbeat/include/fields.go b/heartbeat/include/fields.go index e21fe351e87..601fcb201b9 100644 --- a/heartbeat/include/fields.go +++ b/heartbeat/include/fields.go @@ -32,5 +32,5 @@ func init() { // AssetFieldsYml returns asset data. // This is the base64 encoded gzipped contents of fields.yml. func AssetFieldsYml() string { - return "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" + return "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" } diff --git a/heartbeat/monitors/active/dialchain/_meta/fields.yml b/heartbeat/monitors/active/dialchain/_meta/fields.yml index 5d4991a9704..a234429103a 100644 --- a/heartbeat/monitors/active/dialchain/_meta/fields.yml +++ b/heartbeat/monitors/active/dialchain/_meta/fields.yml @@ -34,10 +34,12 @@ fields: - name: certificate_not_valid_before type: date - description: Earliest time at which the connection's certificates are valid. + deprecated: 7.8.0 + description: Deprecated in favor of `tls.server.x509.not_before`. Earliest time at which the connection's certificates are valid. - name: certificate_not_valid_after + deprecated: 7.8.0 type: date - description: Latest time at which the connection's certificates are valid. + description: Deprecated in favor of `tls.server.x509.not_after`. Latest time at which the connection's certificates are valid. - name: rtt type: group description: > @@ -52,4 +54,110 @@ - name: us type: long description: Duration in microseconds + - name: server + type: group + description: Detailed x509 certificate metadata + fields: + - name: x509 + type: group + fields: + - name: alternative_names + type: keyword + ignore_above: 1024 + description: List of subject alternative names (SAN). Name types vary by certificate + authority and certificate type but commonly contain IP addresses, DNS names + (and wildcards), and email addresses. + example: '*.elastic.co' + default_field: false + - name: issuer + type: group + fields: + - name: common_name + type: keyword + ignore_above: 1024 + description: List of common name (CN) of issuing certificate authority. + example: DigiCert SHA2 High Assurance Server CA + default_field: false + multi_fields: + - name: text + type: wildcard + - name: distinguished_name + type: keyword + ignore_above: 1024 + description: Distinguished name (DN) of issuing certificate authority. + example: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert SHA2 High Assurance + Server CA + default_field: false + - name: not_after + type: date + description: Time at which the certificate is no longer considered valid. + example: 2020-07-16 03:15:39+00:00 + default_field: false + - name: not_before + type: date + description: Time at which the certificate is first considered valid. + example: 2019-08-16 01:40:25+00:00 + default_field: false + - name: public_key_algorithm + type: keyword + ignore_above: 1024 + description: Algorithm used to generate the public key. + example: RSA + default_field: false + - name: public_key_curve + type: keyword + ignore_above: 1024 + description: The curve used by the elliptic curve public key algorithm. This + is algorithm specific. + example: nistp521 + default_field: false + - name: public_key_exponent + type: long + description: Exponent used to derive the public key. This is algorithm specific. + example: 65537 + default_field: false + - name: public_key_size + type: long + description: The size of the public key space in bits. + example: 2048 + default_field: false + - name: serial_number + type: keyword + ignore_above: 1024 + description: Unique serial number issued by the certificate authority. For consistency, + if this value is alphanumeric, it should be formatted without colons and uppercase + characters. + example: 55FBB9C7DEBF09809D12CCAA + default_field: false + - name: signature_algorithm + type: keyword + ignore_above: 1024 + description: Identifier for certificate signature algorithm. Recommend using + names found in Go Lang Crypto library (See https://github.com/golang/go/blob/go1.14/src/crypto/x509/x509.go#L337-L353). + example: SHA256-RSA + default_field: false + - name: subject + type: group + fields: + - name: subject.common_name + type: keyword + ignore_above: 1024 + description: List of common names (CN) of subject. + example: r2.shared.global.fastly.net + default_field: false + multi_fields: + - name: text + type: wildcard + - name: subject.distinguished_name + type: keyword + ignore_above: 1024 + description: Distinguished name (DN) of the certificate subject entity. + example: C=US, ST=California, L=San Francisco, O=Fastly, Inc., CN=r2.shared.global.fastly.net + default_field: false + - name: version_number + type: keyword + ignore_above: 1024 + description: Version of x509 format. + example: 3 + default_field: false diff --git a/heartbeat/monitors/active/dialchain/tls.go b/heartbeat/monitors/active/dialchain/tls.go index 6fd2b43c27f..b4b2c006dfb 100644 --- a/heartbeat/monitors/active/dialchain/tls.go +++ b/heartbeat/monitors/active/dialchain/tls.go @@ -19,27 +19,19 @@ package dialchain import ( cryptoTLS "crypto/tls" - "crypto/x509" "fmt" "net" "time" - "github.com/elastic/beats/v7/heartbeat/look" + "github.com/elastic/beats/v7/heartbeat/monitors/active/dialchain/tlsmeta" "github.com/elastic/beats/v7/libbeat/beat" - "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/libbeat/common/transport" "github.com/elastic/beats/v7/libbeat/common/transport/tlscommon" ) // TLSLayer configures the TLS layer in a DialerChain. -// -// The layer will update the active event with: -// -// { -// "tls": { -// "rtt": { "handshake": { "us": ... }} -// } -// } +// The layer will update the active event with the TLS RTT and +// crypto/cert details. func TLSLayer(cfg *tlscommon.TLSConfig, to time.Duration) Layer { return func(event *beat.Event, next transport.Dialer) (transport.Dialer, error) { var timer timer @@ -58,56 +50,12 @@ func TLSLayer(cfg *tlscommon.TLSConfig, to time.Duration) Layer { if !ok { panic(fmt.Sprintf("TLS afterDial received a non-tls connection %t. This should never happen", conn)) } - - // TODO: extract TLS connection parameters from connection object. + connState := tlsConn.ConnectionState() timer.stop() - event.PutValue("tls.rtt.handshake", look.RTT(timer.duration())) - addCertMetdata(event.Fields, tlsConn.ConnectionState().PeerCertificates) + tlsmeta.AddTLSMetadata(event.Fields, connState, timer.duration()) return conn, nil }), nil } } - -func addCertMetdata(fields common.MapStr, certs []*x509.Certificate) { - // The behavior here might seem strange. We *always* set a notBefore, but only optionally set a notAfter. - // Why might we do this? - // The root cause is that the x509.Certificate type uses time.Time for these fields instead of *time.Time - // so we have no way to know if the user actually set these fields. The x509 RFC says that only one of the - // two fields must be set. Most tools (including openssl and go's certgen) always set both. BECAUSE WHY NOT - // - // In the wild, however, there are certs missing one of these two fields. - // So, what's the correct behavior here? We cannot know if a field was omitted due to the lack of nullability. - // So, in this case, we try to do what people will want 99.99999999999999999% of the time. - // People might set notBefore to go's zero date intentionally when creating certs. So, we always set that - // field, even if we find a zero value. - // However, it would be weird to set notAfter to the zero value. That could invalidate a cert that was intended - // to be valid forever. So, in that case, we treat the zero value as non-existent. - // This is why notBefore is a time.Time and notAfter is a *time.Time - var chainNotValidBefore time.Time - var chainNotValidAfter *time.Time - - // We need the zero date later - var zeroTime time.Time - - // Here we compute the minimal bounds during which this certificate chain is valid - // To do this correctly, we take the maximum NotBefore and the minimum NotAfter. - // This *should* always wind up being the terminal cert in the chain, but we should - // compute this correctly. - for _, cert := range certs { - if chainNotValidBefore.Before(cert.NotBefore) { - chainNotValidBefore = cert.NotBefore - } - - if cert.NotAfter != zeroTime && (chainNotValidAfter == nil || chainNotValidAfter.After(cert.NotAfter)) { - chainNotValidAfter = &cert.NotAfter - } - } - - fields.Put("tls.certificate_not_valid_before", chainNotValidBefore) - - if chainNotValidAfter != nil { - fields.Put("tls.certificate_not_valid_after", *chainNotValidAfter) - } -} diff --git a/heartbeat/monitors/active/dialchain/tls_test.go b/heartbeat/monitors/active/dialchain/tls_test.go deleted file mode 100644 index 8df41fbd3b0..00000000000 --- a/heartbeat/monitors/active/dialchain/tls_test.go +++ /dev/null @@ -1,144 +0,0 @@ -// Licensed to Elasticsearch B.V. under one or more contributor -// license agreements. See the NOTICE file distributed with -// this work for additional information regarding copyright -// ownership. Elasticsearch B.V. licenses this file to you under -// the Apache License, Version 2.0 (the "License"); you may -// not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -package dialchain - -import ( - "crypto/x509" - "crypto/x509/pkix" - "math/big" - "testing" - "time" - - "github.com/stretchr/testify/assert" - - "github.com/elastic/beats/v7/libbeat/common" -) - -func Test_addCertMetdata(t *testing.T) { - goodNotBefore := time.Now().Add(-time.Hour) - goodNotAfter := time.Now().Add(time.Hour) - goodCert := x509.Certificate{ - SerialNumber: big.NewInt(1), - Subject: pkix.Name{ - Organization: []string{"Acme Co"}, - }, - NotBefore: goodNotBefore, - NotAfter: goodNotAfter, - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - BasicConstraintsValid: true, - } - - expiredNotAfter := time.Now().Add(-time.Hour) - expiredCert := x509.Certificate{ - SerialNumber: big.NewInt(1), - Subject: pkix.Name{ - Organization: []string{"Acme Co"}, - }, - NotBefore: goodNotBefore, - NotAfter: expiredNotAfter, - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - BasicConstraintsValid: true, - } - - missingNotBeforeCert := x509.Certificate{ - SerialNumber: big.NewInt(1), - Subject: pkix.Name{ - Organization: []string{"Acme Co"}, - }, - NotAfter: goodNotAfter, - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - BasicConstraintsValid: true, - } - - missingNotAfterCert := x509.Certificate{ - SerialNumber: big.NewInt(1), - Subject: pkix.Name{ - Organization: []string{"Acme Co"}, - }, - NotBefore: goodNotBefore, - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - BasicConstraintsValid: true, - } - - // notBefore is intentionally not a pointer type because go certificates don't have nullable time types - // we cheat a bit and make not after nullable because there's no valid reason to create a cert with go's zero - // time. - // see the addCertMetadata function for more info on this. - type expected struct { - notBefore time.Time - notAfter *time.Time - } - tests := []struct { - name string - certs []*x509.Certificate - expected expected - }{ - { - "Valid cert", - []*x509.Certificate{&goodCert}, - expected{ - notBefore: goodNotBefore, - notAfter: &goodNotAfter, - }, - }, - { - "Expired cert", - []*x509.Certificate{&expiredCert}, - expected{ - notBefore: goodNotBefore, - notAfter: &expiredNotAfter, - }, - }, - { - "Missing not before", - []*x509.Certificate{&missingNotBeforeCert}, - expected{ - notAfter: &goodNotAfter, - }, - }, - { - "Missing not after", - []*x509.Certificate{&missingNotAfterCert}, - expected{ - notBefore: goodNotBefore, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - event := common.MapStr{} - addCertMetdata(event, tt.certs) - v, err := event.GetValue("tls.certificate_not_valid_before") - assert.NoError(t, err) - assert.Equal(t, tt.expected.notBefore, v) - - if tt.expected.notAfter != nil { - v, err := event.GetValue("tls.certificate_not_valid_after") - assert.NoError(t, err) - assert.Equal(t, *tt.expected.notAfter, v) - } else { - ok, _ := event.HasKey("tls.certificate_not_valid_after") - assert.False(t, ok, "event should not have not after %v", event) - } - }) - } -} diff --git a/heartbeat/monitors/active/dialchain/tlsmeta/tlsmeta.go b/heartbeat/monitors/active/dialchain/tlsmeta/tlsmeta.go new file mode 100644 index 00000000000..686da2a3241 --- /dev/null +++ b/heartbeat/monitors/active/dialchain/tlsmeta/tlsmeta.go @@ -0,0 +1,133 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package tlsmeta + +import ( + dsa2 "crypto/dsa" + "crypto/ecdsa" + "crypto/rsa" + "crypto/sha1" + "crypto/sha256" + cryptoTLS "crypto/tls" + "crypto/x509" + "fmt" + "time" + + "github.com/elastic/beats/v7/heartbeat/look" + "github.com/elastic/beats/v7/libbeat/common" + "github.com/elastic/beats/v7/libbeat/common/transport/tlscommon" +) + +func AddTLSMetadata(fields common.MapStr, connState cryptoTLS.ConnectionState, duration time.Duration) { + fields.Put("tls.established", true) + fields.Put("tls.rtt.handshake", look.RTT(duration)) + versionDetails := tlscommon.TLSVersion(connState.Version).Details() + // The only situation in which versionDetails would be nil is if an unknown TLS version were to be + // encountered. Not filling the fields here makes sense, since there's no standard 'unknown' value. + if versionDetails != nil { + fields.Put("tls.version_protocol", versionDetails.Protocol) + fields.Put("tls.version", versionDetails.Version) + } + + if connState.NegotiatedProtocol != "" { + fields.Put("tls.next_protocol", connState.NegotiatedProtocol) + } + fields.Put("tls.cipher", tlscommon.ResolveCipherSuite(connState.CipherSuite)) + + AddCertMetadata(fields, connState.PeerCertificates) +} + +func AddCertMetadata(fields common.MapStr, certs []*x509.Certificate) { + hostCert := certs[0] + + x509Fields := common.MapStr{} + serverFields := common.MapStr{"x509": x509Fields} + tlsFields := common.MapStr{"server": serverFields} + + serverFields.Put("hash.sha1", fmt.Sprintf("%x", sha1.Sum(hostCert.Raw))) + serverFields.Put("hash.sha256", fmt.Sprintf("%x", sha256.Sum256(hostCert.Raw))) + + x509Fields.Put("issuer.common_name", hostCert.Issuer.CommonName) + x509Fields.Put("issuer.distinguished_name", hostCert.Issuer.String()) + x509Fields.Put("subject.common_name", hostCert.Subject.CommonName) + x509Fields.Put("subject.distinguished_name", hostCert.Subject.String()) + x509Fields.Put("serial_number", hostCert.SerialNumber.String()) + x509Fields.Put("signature_algorithm", hostCert.SignatureAlgorithm.String()) + x509Fields.Put("public_key_algorithm", hostCert.PublicKeyAlgorithm.String()) + if rsaKey, ok := hostCert.PublicKey.(*rsa.PublicKey); ok { + sizeInBits := rsaKey.Size() * 8 + x509Fields.Put("public_key_size", sizeInBits) + x509Fields.Put("public_key_exponent", rsaKey.E) + } else if dsaKey, ok := hostCert.PublicKey.(*dsa2.PublicKey); ok { + if dsaKey.Parameters.P != nil { + x509Fields.Put("public_key_size", len(dsaKey.P.Bytes())*8) + } else { + x509Fields.Put("public_key_size", len(dsaKey.P.Bytes())*8) + } + } else if ecdsa, ok := hostCert.PublicKey.(*ecdsa.PublicKey); ok { + x509Fields.Put("public_key_curve", ecdsa.Curve.Params().Name) + } + + chainNotBefore, chainNotAfter := calculateCertTimestamps(certs) + // Legacy non-ECS field + tlsFields.Put("certificate_not_valid_before", chainNotBefore) + x509Fields.Put("not_before", chainNotBefore) + if chainNotAfter != nil { + // Legacy non-ECS field + tlsFields.Put("certificate_not_valid_after", *chainNotAfter) + x509Fields.Put("not_after", *chainNotAfter) + } + + fields.DeepUpdate(common.MapStr{"tls": tlsFields}) +} + +func calculateCertTimestamps(certs []*x509.Certificate) (chainNotBefore time.Time, chainNotAfter *time.Time) { + // The behavior here might seem strange. We *always* set a notBefore, but only optionally set a notAfter. + // Why might we do this? + // The root cause is that the x509.Certificate type uses time.Time for these tlsFields instead of *time.Time + // so we have no way to know if the user actually set these tlsFields. The x509 RFC says that only one of the + // two tlsFields must be set. Most tools (including openssl and go's certgen) always set both. BECAUSE WHY NOT + // + // In the wild, however, there are certs missing one of these two tlsFields. + // So, what's the correct behavior here? We cannot know if a field was omitted due to the lack of nullability. + // So, in this case, we try to do what people will want 99.99999999999999999% of the time. + // People might set notBefore to go's zero date intentionally when creating certs. So, we always set that + // field, even if we find a zero value. + // However, it would be weird to set notAfter to the zero value. That could invalidate a cert that was intended + // to be valid forever. So, in that case, we treat the zero value as non-existent. + // This is why notBefore is a time.Time and notAfter is a *time.Time + + // We need the zero date later + var zeroTime time.Time + + // Here we compute the minimal bounds during which this certificate chain is valid + // To do this correctly, we take the maximum NotBefore and the minimum NotAfter. + // This *should* always wind up being the terminal cert in the chain, but we should + // compute this correctly. + for _, cert := range certs { + if chainNotBefore.Before(cert.NotBefore) { + chainNotBefore = cert.NotBefore + } + + if cert.NotAfter != zeroTime && (chainNotAfter == nil || chainNotAfter.After(cert.NotAfter)) { + chainNotAfter = &cert.NotAfter + } + } + + return +} diff --git a/heartbeat/monitors/active/dialchain/tlsmeta/tlsmeta_test.go b/heartbeat/monitors/active/dialchain/tlsmeta/tlsmeta_test.go new file mode 100644 index 00000000000..609fbbb413d --- /dev/null +++ b/heartbeat/monitors/active/dialchain/tlsmeta/tlsmeta_test.go @@ -0,0 +1,398 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package tlsmeta + +import ( + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" + "testing" + "time" + + "github.com/elastic/go-lookslike" + "github.com/elastic/go-lookslike/testslike" + + "github.com/stretchr/testify/require" + + "github.com/elastic/beats/v7/heartbeat/look" + "github.com/elastic/beats/v7/libbeat/common" +) + +// Tests for the non-cert fields +func TestAddTLSMetadata(t *testing.T) { + // We always test with this one cert because addCertificateMetadata + // is tested in detail elsewhere + certs := []*x509.Certificate{parseCert(t, elasticCert)} + certMetadata := common.MapStr{} + AddCertMetadata(certMetadata, certs) + + scenarios := []struct { + name string + connState tls.ConnectionState + duration time.Duration + expected common.MapStr + }{ + { + "simple TLSv1.1", + tls.ConnectionState{ + Version: tls.VersionTLS11, + HandshakeComplete: true, + PeerCertificates: certs, + CipherSuite: tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + ServerName: "example.net", + }, + time.Duration(1), + common.MapStr{ + "established": true, + "rtt": common.MapStr{"handshake": look.RTT(time.Duration(1))}, + "version_protocol": "tls", + "version": "1.1", + "cipher": "ECDHE-ECDSA-AES-256-CBC-SHA", + }, + }, + { + "TLSv1.2 with next_protocol", + tls.ConnectionState{ + Version: tls.VersionTLS12, + HandshakeComplete: true, + PeerCertificates: certs, + CipherSuite: tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + ServerName: "example.net", + NegotiatedProtocol: "h2", + }, + time.Duration(1), + common.MapStr{ + "established": true, + "rtt": common.MapStr{"handshake": look.RTT(time.Duration(1))}, + "version_protocol": "tls", + "version": "1.2", + "cipher": "ECDHE-ECDSA-AES-256-CBC-SHA", + "next_protocol": "h2", + }, + }, + } + + for _, s := range scenarios { + t.Run(s.name, func(t *testing.T) { + // Nest under the TLS namespace to match actual output + expected := common.MapStr{"tls": s.expected} + + // Always add in the cert metadata since we test that in other test funcs, not here + expected.DeepUpdate(certMetadata) + + fields := common.MapStr{} + AddTLSMetadata(fields, s.connState, s.duration) + require.Equal(t, expected, fields) + }) + } +} + +func TestAddCertMetadata(t *testing.T) { + cert := parseCert(t, elasticCert) + chainCert := parseCert(t, elasticChainCert) + certNotBefore, err := time.Parse(time.RFC3339, "2019-08-16T01:40:25Z") + require.NoError(t, err) + certNotAfter, err := time.Parse(time.RFC3339, "2020-07-16T03:15:39Z") + require.NoError(t, err) + + expectedFields := lookslike.Strict(lookslike.MustCompile(map[string]interface{}{ + "certificate_not_valid_after": certNotAfter, + "certificate_not_valid_before": certNotBefore, + "server": common.MapStr{ + "hash": common.MapStr{ + "sha1": "b7b4b89ef0d0caf39d223736f0fdbb03c7b426f1", + "sha256": "12b00d04db0db8caa302bfde043e88f95baceb91e86ac143e93830b4bbec726d", + }, + "x509": common.MapStr{ + "issuer": common.MapStr{ + "common_name": "GlobalSign CloudSSL CA - SHA256 - G3", + "distinguished_name": "CN=GlobalSign CloudSSL CA - SHA256 - G3,O=GlobalSign nv-sa,C=BE", + }, + "subject": common.MapStr{ + "common_name": "r2.shared.global.fastly.net", + "distinguished_name": "CN=r2.shared.global.fastly.net,O=Fastly\\, Inc.,L=San Francisco,ST=California,C=US", + }, + "not_after": certNotAfter, + "not_before": certNotBefore, + "serial_number": "26610543540289562361990401194", + "signature_algorithm": "SHA256-RSA", + "public_key_algorithm": "RSA", + "public_key_size": 2048, + "public_key_exponent": 65537, + }, + }, + })) + + scenarios := []struct { + name string + certs []*x509.Certificate + }{ + { + "single cert fields should all be present", + []*x509.Certificate{cert}, + }, + { + "cert chain should still show single cert fields", + []*x509.Certificate{cert, chainCert}, + }, + } + + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + fields := common.MapStr{} + AddCertMetadata(fields, scenario.certs) + tls, err := fields.GetValue("tls") + require.NoError(t, err) + testslike.Test(t, expectedFields, tls) + }) + } +} + +// TestCertExpirationMetadata exhaustively tests not before / not after calculation. +func TestCertExpirationMetadata(t *testing.T) { + goodNotBefore := time.Now().Add(-time.Hour) + goodNotAfter := time.Now().Add(time.Hour) + goodCert := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + Organization: []string{"Acme Co"}, + }, + NotBefore: goodNotBefore, + NotAfter: goodNotAfter, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + expiredNotAfter := time.Now().Add(-time.Hour) + expiredCert := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + Organization: []string{"Acme Co"}, + }, + NotBefore: goodNotBefore, + NotAfter: expiredNotAfter, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + missingNotBeforeCert := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + Organization: []string{"Acme Co"}, + }, + NotAfter: goodNotAfter, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + missingNotAfterCert := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + Organization: []string{"Acme Co"}, + }, + NotBefore: goodNotBefore, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + // notBefore is intentionally not a pointer type because go certificates don't have nullable time types + // we cheat a bit and make not after nullable because there's no valid reason to create a cert with go's zero + // time. + // see the AddCertMetadata function for more info on this. + type expected struct { + notBefore time.Time + notAfter *time.Time + } + tests := []struct { + name string + certs []*x509.Certificate + expected expected + }{ + { + "Valid cert", + []*x509.Certificate{&goodCert}, + expected{ + notBefore: goodNotBefore, + notAfter: &goodNotAfter, + }, + }, + { + "Expired cert", + []*x509.Certificate{&expiredCert}, + expected{ + notBefore: goodNotBefore, + notAfter: &expiredNotAfter, + }, + }, + { + "Missing not before", + []*x509.Certificate{&missingNotBeforeCert}, + expected{ + notAfter: &goodNotAfter, + }, + }, + { + "Missing not after", + []*x509.Certificate{&missingNotAfterCert}, + expected{ + notBefore: goodNotBefore, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + notBefore, notAfter := calculateCertTimestamps(tt.certs) + + require.Equal(t, tt.expected.notBefore, notBefore) + if tt.expected.notAfter != nil { + require.Equal(t, tt.expected.notAfter, notAfter) + } else { + require.Nil(t, notAfter) + } + }) + } +} + +func parseCert(t *testing.T, pemStr string) *x509.Certificate { + block, _ := pem.Decode([]byte(elasticCert)) + if block == nil { + require.Fail(t, "Test cert could not be parsed") + } + cert, err := x509.ParseCertificate(block.Bytes) + require.NoError(t, err) + return cert +} + +var elasticCert = `-----BEGIN CERTIFICATE----- +MIIPLzCCDhegAwIBAgIMVfu5x96/CYCdEsyqMA0GCSqGSIb3DQEBCwUAMFcxCzAJ +BgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS0wKwYDVQQDEyRH +bG9iYWxTaWduIENsb3VkU1NMIENBIC0gU0hBMjU2IC0gRzMwHhcNMTkwODE2MDE0 +MDI1WhcNMjAwNzE2MDMxNTM5WjB3MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2Fs +aWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEVMBMGA1UECgwMRmFzdGx5 +LCBJbmMuMSQwIgYDVQQDDBtyMi5zaGFyZWQuZ2xvYmFsLmZhc3RseS5uZXQwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCnvoHpOqA6CM06MlGViMGMFC4G +YFFEe03GQ5jG3uEUbMNPbl0MSxaWle5xZOVaPcIrV7qyE5yKKDv1fT1e8EkwR+3t +nTK4k2QvH6dPtSPlGHVIjBtS17gM939eZvpvUPxmUc5Ov9cbWgsuStqgFpFjnPBV +R0LqD6YekvS9oXG+4GrNZnQ0wJYF0dbos+E7lRSdniDf/Ul9rF4WAzAEoQYau8pe +eIPlJy8rVrDEgqfCQabYXrLaG68EHHMGadY2EX0yyI/SZh9AU8RdatNHBwj42LGP +9dp3fyEv14usJPGuLVy+8I7TMckQPpPB+NLFECJMwRRfciPjibw1MMSYTOWnAgMB +AAGjggvZMIIL1TAOBgNVHQ8BAf8EBAMCBaAwgYoGCCsGAQUFBwEBBH4wfDBCBggr +BgEFBQcwAoY2aHR0cDovL3NlY3VyZS5nbG9iYWxzaWduLmNvbS9jYWNlcnQvY2xv +dWRzc2xzaGEyZzMuY3J0MDYGCCsGAQUFBzABhipodHRwOi8vb2NzcDIuZ2xvYmFs +c2lnbi5jb20vY2xvdWRzc2xzaGEyZzMwVgYDVR0gBE8wTTBBBgkrBgEEAaAyARQw +NDAyBggrBgEFBQcCARYmaHR0cHM6Ly93d3cuZ2xvYmFsc2lnbi5jb20vcmVwb3Np +dG9yeS8wCAYGZ4EMAQICMAkGA1UdEwQCMAAwgglrBgNVHREEggliMIIJXoIbcjIu +c2hhcmVkLmdsb2JhbC5mYXN0bHkubmV0ghEqLmFtcGlmeW11c2ljLmNvbYIPKi5h +cGkuZ2lwaHkuY29tghAqLmFwcC5yb213b2QuY29tghAqLmF3YXl0cmF2ZWwuY29t +ghIqLmJmaWZsYXJlbGl2ZS5jb22CECouYmlsbGluZ2FybS5jb22CCiouYnJhemUu +ZXWCFSouY2FsZ2FyeXN0YW1wZWRlLmNvbYIQKi5jZG4udHJpbGxlci5jb4INKi5j +aXR5bWFwcy5pb4IOKi5kZWFsZXJvbi5jb22CDSouZG92ZW1lZC5jb22CDCouZWxh +c3RpYy5jb4IPKi5mZmhhbmRiYWxsLmZyghEqLmZsZXhzaG9wcGVyLmNvbYIPKi5m +bGlwcC1hZHMuY29tghcqLmZsb3JpZGFldmVyYmxhZGVzLmNvbYIYKi5mb2N1c3Jp +dGUtbm92YXRpb24uY29tghAqLmZyZXNoYm9va3MuY29tggsqLmdpcGh5LmNvbYIV +Ki5pZGFob3N0ZWVsaGVhZHMuY29tghAqLmludGVyYWN0bm93LnR2ghEqLmtjbWF2 +ZXJpY2tzLmNvbYIMKi5rb21ldHMuY29tghEqLm1lZGlhLmdpcGh5LmNvbYIKKi5t +bnRkLm5ldIIMKi5uYXNjYXIuY29tghUqLm9tbmlnb25wcm9zdWl0ZS5jb22CHSou +b3JsYW5kb3NvbGFyYmVhcnNob2NrZXkuY29tggwqLnByZWlzMjQuZGWCDSoucWEu +bW50ZC5uZXSCEyoucmV2ZXJiLWFzc2V0cy5jb22CDCoucmV2ZXJiLmNvbYIMKi5y +b213b2QuY29tghMqLnNjb290ZXJsb3VuZ2UuY29tghgqLnN0YWdpbmcuYmlsbGlu +Z2Vudi5jb22CFiouc3RhZ2luZy5mcmVzaGVudi5jb22CEiouc3dhbXByYWJiaXRz +LmNvbYILKi52ZXJzZS5jb22CDSoudmlkeWFyZC5jb22CDioudmlld2VkaXQuY29t +ghEqLnZvdGVub3cubmJjLmNvbYIMKi52b3Rlbm93LnR2ggsqLndheWluLmNvbYIb +Ki53ZXN0bWluc3Rlcmtlbm5lbGNsdWIub3Jngg9hbXBpZnltdXNpYy5jb22CE2Fw +aS5yZXZlcmJzaXRlcy5jb22CGGFwaS5zdGFnaW5nLmZyZXNoZW52LmNvbYIbYXBp +LnN0YWdpbmcucmV2ZXJic2l0ZXMuY29tgg5hd2F5dHJhdmVsLmNvbYIQYmZpZmxh +cmVsaXZlLmNvbYITYmZsLXRlc3QuYWJjLmdvLmNvbYIOYmZsLmFiYy5nby5jb22C +CGJyYXplLmV1gh5jZG4taW1hZ2VzLmZsaXBwZW50ZXJwcmlzZS5uZXSCF2Nkbi5m +bGlwcGVudGVycHJpc2UubmV0ghJjb3Ntb3NtYWdhemluZS5jb22CDGRlYWxlcm9u +LmNvbYILZG92ZW1lZC5jb22CHWR3dHN2b3RlLWxpdmUtdGVzdC5hYmMuZ28uY29t +ghhkd3Rzdm90ZS1saXZlLmFiYy5nby5jb22CGGR3dHN2b3RlLXRlc3QuYWJjLmdv +LmNvbYITZHd0c3ZvdGUuYWJjLmdvLmNvbYIKZWxhc3RpYy5jb4IMZW1haWwua2du +LmlvghJmLmNsb3VkLmdpdGh1Yi5jb22CHWZhbmJvb3N0LXRlc3QuZmlhZm9ybXVs +YWUuY29tghhmYW5ib29zdC5maWFmb3JtdWxhZS5jb22CDWZmaGFuZGJhbGwuZnKC +D2ZsZXhzaG9wcGVyLmNvbYIVZmxvcmlkYWV2ZXJibGFkZXMuY29tgglnaXBoeS5j +b22CFWdvLmNvbmNhY2FmbGVhZ3VlLmNvbYIcZ28uY29uY2FjYWZuYXRpb25zbGVh +Z3VlLmNvbYIGZ3BoLmlzghNpZGFob3N0ZWVsaGVhZHMuY29tghNpZG9sdm90ZS5h +YmMuZ28uY29tgg1pbmZyb250LnNwb3J0gg5pbnRlcmFjdG5vdy50doIPa2NtYXZl +cmlja3MuY29tggprb21ldHMuY29tghptYWlsLmRldmVsb3BtZW50LmJyYXplLmNv +bYIWbWFuY2hlc3Rlcm1vbmFyY2hzLmNvbYIWbWVkaWEud29ya2FuZG1vbmV5LmNv +bYIXbXkuc3RhZ2luZy5mcmVzaGVudi5jb22CG29ybGFuZG9zb2xhcmJlYXJzaG9j +a2V5LmNvbYIUcGNhLXRlc3QuZW9ubGluZS5jb22CD3BjYS5lb25saW5lLmNvbYIh +cGxmcGwtZmFzdGx5LnN0YWdpbmcuaXNtZ2FtZXMuY29tggpwcmVpczI0LmRlghRw +cmVtaWVyZXNwZWFrZXJzLmNvbYILcWEudGVub3IuY2+CDHFhLnRlbm9yLmNvbYIe +cm9ib3RpYy1jb29rLnNlY3JldGNkbi1zdGcubmV0ghFzY29vdGVybG91bmdlLmNv +bYIac3RhZ2luZy13d3cuZWFzYS5ldXJvcGEuZXWCGHN0YWdpbmcuZGFpbHkuc3F1 +aXJ0Lm9yZ4IUc3RhZ2luZy5mcmVzaGVudi5jb22CEHN3YW1wcmFiYml0cy5jb22C +CHRlbm9yLmNvggl0ZW5vci5jb22CFnRyYWNrLnN3ZWVuZXktbWFpbC5jb22CEHVh +dC5mcmVzaGVudi5jb22CE3VuaWZvcm1zaW5zdG9jay5jb22CF3VzZXJzLnByZW1p +ZXJsZWFndWUuY29tghF1dGFoZ3JpenpsaWVzLmNvbYIJdmVyc2UuY29tggt2aWR5 +YXJkLmNvbYIMdmlld2VkaXQuY29tggp2b3Rlbm93LnR2ggl3YXlpbi5jb22CGXdl +c3RtaW5zdGVya2VubmVsY2x1Yi5vcmeCEXd3dy5jaGlxdWVsbGUuY29tghB3d3cu +Y2hpcXVlbGxlLnNlghJ3d3cuZWFzYS5ldXJvcGEuZXWCGnd3dy5pc3JhZWxuYXRp +b25hbG5ld3MuY29tghh3d3cua29nYW5pbnRlcm5ldC5jb20uYXWCDHd3dy50ZW5v +ci5jb4INd3d3LnRlbm9yLmNvbYIUd3d3LnVhdC5mcmVzaGVudi5jb22CF3d3dy51 +bmlmb3Jtc2luc3RvY2suY29tghV3d3cudXRhaGdyaXp6bGllcy5jb20wHQYDVR0l +BBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB8GA1UdIwQYMBaAFKkrh+HOJEc7G7/P +hTcCVZ0NlFjmMB0GA1UdDgQWBBQ7SJi8MbyN4XPx+T1QVj4sLHjhDjCCAQMGCisG +AQQB1nkCBAIEgfQEgfEA7wB1AId1v+dZfPiMQ5lfvfNu/1aNR1Y2/0q1YMG06v9e +oIMPAAABbJgVTmEAAAQDAEYwRAIgeYcRKQDCMIBnswrwBvmmSpCFWhjGl+zabCpo +E3R9nJcCIBaAx/TYKESO7iz+hU6bq7Dwzo0QpTIvho4ZdFfSAAHMAHYAsh4FzIui +zYogTodm+Su5iiUgZ2va+nDnsklTLe+LkF4AAAFsmBVLIQAABAMARzBFAiAfLUaq +ukt75a1pySCxrreQ/+/IAdyOSqXqbH1tZNKlTAIhALlcthwbBCfSSNEjTeJWOXss +clzGt9zAk256uboF0iFLMA0GCSqGSIb3DQEBCwUAA4IBAQCZXc5cmMCeqIVsRnRH +KsuGlT6tP2NdsK1+b9dJguP0zbQoxLg5qBMjRGjDo8BpGOni5mJmRJYDQ/GHKP/d +bd+n/4BDD5jI5/rtl43D+Y1G3S5tCRX/3s+At1LJcuaVRmvnywfE9OLXpI84SWtU +AainsxdCYcvopTOZG9UwkjyuEBV3tVsiQkhRSAzYStM75caRWer2pP7i3AwKNv29 +DDSHahXxUyjgAbD2XQojODT/AltEvuqcSrB2cRGXultLmJXFNDEQ5Om4GcjAk75D +pzNLvZuaXHwWoYdm+YTwdPwuZhWe9TxMYlpZbQR8dux2QXRfARF07Vi0+gOzPE9V +RG7L +-----END CERTIFICATE-----` + +var elasticChainCert = `-----BEGIN CERTIFICATE----- +MIIEizCCA3OgAwIBAgIORvCM288sVGbvMwHdXzQwDQYJKoZIhvcNAQELBQAwVzEL +MAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsT +B1Jvb3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw0xNTA4MTkw +MDAwMDBaFw0yNTA4MTkwMDAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBH +bG9iYWxTaWduIG52LXNhMS0wKwYDVQQDEyRHbG9iYWxTaWduIENsb3VkU1NMIENB +IC0gU0hBMjU2IC0gRzMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCj +wHXhMpjl2a6EfI3oI19GlVtMoiVw15AEhYDJtfSKZU2Sy6XEQqC2eSUx7fGFIM0T +UT1nrJdNaJszhlyzey2q33egYdH1PPua/NPVlMrJHoAbkJDIrI32YBecMbjFYaLi +blclCG8kmZnPlL/Hi2uwH8oU+hibbBB8mSvaSmPlsk7C/T4QC0j0dwsv8JZLOu69 +Nd6FjdoTDs4BxHHT03fFCKZgOSWnJ2lcg9FvdnjuxURbRb0pO+LGCQ+ivivc41za +Wm+O58kHa36hwFOVgongeFxyqGy+Z2ur5zPZh/L4XCf09io7h+/awkfav6zrJ2R7 +TFPrNOEvmyBNVBJrfSi9AgMBAAGjggFTMIIBTzAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBIGA1UdEwEB/wQIMAYBAf8CAQAw +HQYDVR0OBBYEFKkrh+HOJEc7G7/PhTcCVZ0NlFjmMB8GA1UdIwQYMBaAFGB7ZhpF +DZfKiVAvfQTNNKj//P1LMD0GCCsGAQUFBwEBBDEwLzAtBggrBgEFBQcwAYYhaHR0 +cDovL29jc3AuZ2xvYmFsc2lnbi5jb20vcm9vdHIxMDMGA1UdHwQsMCowKKAmoCSG +Imh0dHA6Ly9jcmwuZ2xvYmFsc2lnbi5jb20vcm9vdC5jcmwwVgYDVR0gBE8wTTAL +BgkrBgEEAaAyARQwPgYGZ4EMAQICMDQwMgYIKwYBBQUHAgEWJmh0dHBzOi8vd3d3 +Lmdsb2JhbHNpZ24uY29tL3JlcG9zaXRvcnkvMA0GCSqGSIb3DQEBCwUAA4IBAQCi +HWmKCo7EFIMqKhJNOSeQTvCNrNKWYkc2XpLR+sWTtTcHZSnS9FNQa8n0/jT13bgd ++vzcFKxWlCecQqoETbftWNmZ0knmIC/Tp3e4Koka76fPhi3WU+kLk5xOq9lF7qSE +hf805A7Au6XOX5WJhXCqwV3szyvT2YPfA8qBpwIyt3dhECVO2XTz2XmCtSZwtFK8 +jzPXiq4Z0PySrS+6PKBIWEde/SBWlSDBch2rZpmk1Xg3SBufskw3Z3r9QtLTVp7T +HY7EDGiWtkdREPd76xUJZPX58GMWLT3fI0I6k2PMq69PVwbH/hRVYs4nERnh9ELt +IjBrNRpKBYCkZd/My2/Q +-----END CERTIFICATE-----` diff --git a/heartbeat/monitors/active/fixtures/expired.cert b/heartbeat/monitors/active/fixtures/expired.cert new file mode 100644 index 00000000000..e39ad893bd6 --- /dev/null +++ b/heartbeat/monitors/active/fixtures/expired.cert @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID3zCCAsegAwIBAgIUS+ahW2wxDZ1bT/qYnenS8jrXUcAwDQYJKoZIhvcNAQEL +BQAwfzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk1OMRQwEgYDVQQHDAtNaW5uZWFw +b2xpczEVMBMGA1UECgwMRWxhc3RpYywgSW5jMRQwEgYDVQQLDAtFbmdpbmVlcmlu +ZzEgMB4GA1UEAwwXZXhwaXJlZHRlc3QuZXhhbXBsZS5uZXQwHhcNMjAwNDIxMTQw +MDE0WhcNMjAwNDIyMTQwMDE0WjB/MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTU4x +FDASBgNVBAcMC01pbm5lYXBvbGlzMRUwEwYDVQQKDAxFbGFzdGljLCBJbmMxFDAS +BgNVBAsMC0VuZ2luZWVyaW5nMSAwHgYDVQQDDBdleHBpcmVkdGVzdC5leGFtcGxl +Lm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKh1iS5EZ7bDSKgW +R3JXAepMIaEewMSdbaoBtuNQb48XJGwI0mudF983a7JxGCSfw9mhVYa4YsSv79UE +XomGrWVrS01Cmf1VRIOmxevWMPhvnE6UH+5VxKUBk5ooNSty4iHkDFy2i5WWjxiv +de6Xqnn/dVQhuT/sW+rU/grCsGcdUwqsWnC547ekqiYRTtyZrdh+U0KRKqy5iBlH +9Woua+CnXmsD7+4MgGekErg9XLRHYveLOmLucbNlAIlRyfMDZL1RlXufcGwhzItz +JNM9N0NJ5bwrpuP0RYlYbbMYal+b1Tn2e8qkMm88hniQkuu69kUpKeewIOr62vIK +tI273GECAwEAAaNTMFEwHQYDVR0OBBYEFKgd6wQcgIdUSjtJREObD+R3q3MPMB8G +A1UdIwQYMBaAFKgd6wQcgIdUSjtJREObD+R3q3MPMA8GA1UdEwEB/wQFMAMBAf8w +DQYJKoZIhvcNAQELBQADggEBADkBqmCUcvVTqu5IIZ5PLz40jdg2luaDHEA6I2Ga +1ioabETfQhXeaNJflojYm0Bzsy2aneVLGM2KaZ76wN0yvib3MZ4miu4C/mDsR3bB +wq7/CAK2AcJXv1jk0vIrK6DhZfA2HaelBkQ8UHwWK7AO+JmS6jozIt1vySwPI1E7 +lMFWbs3bmsSmunj3+66XS2XguUKzFwUIAEOfsPFqT2OMsPIa7weUWuCV/zMi7fuB +HbgVouYvMTve8wx7+ozDk6CyvlRlx20xwdOvXaH3JILw7gTQWcAEWZLcB2ct1Zks +UTtbIAjBV6s0Pm/2/6MxxkDCVVUpwXiiKBRkHxzkgoH7TQw= +-----END CERTIFICATE----- diff --git a/heartbeat/monitors/active/fixtures/expired.key b/heartbeat/monitors/active/fixtures/expired.key new file mode 100644 index 00000000000..2a11440f7aa --- /dev/null +++ b/heartbeat/monitors/active/fixtures/expired.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCodYkuRGe2w0io +FkdyVwHqTCGhHsDEnW2qAbbjUG+PFyRsCNJrnRffN2uycRgkn8PZoVWGuGLEr+/V +BF6Jhq1la0tNQpn9VUSDpsXr1jD4b5xOlB/uVcSlAZOaKDUrcuIh5AxctouVlo8Y +r3Xul6p5/3VUIbk/7Fvq1P4KwrBnHVMKrFpwueO3pKomEU7cma3YflNCkSqsuYgZ +R/VqLmvgp15rA+/uDIBnpBK4PVy0R2L3izpi7nGzZQCJUcnzA2S9UZV7n3BsIcyL +cyTTPTdDSeW8K6bj9EWJWG2zGGpfm9U59nvKpDJvPIZ4kJLruvZFKSnnsCDq+try +CrSNu9xhAgMBAAECggEBAIc32QYvWESmWeK6B11rI5lqxK+snLT1XLpSp/esb++e +dtjU9/nzXd8JgEP6bZOwPiepTZpW1MjmJA+Lc0rWtMYsqoP4ityDHfzC2CmmgyZX +iFK2qS7I35BHRLA/x/X5QDRN9fJRgJdxA6mf5Xy/dtJ4UDhY3XbHBTzo/IWsoqYQ +4V3WBQYMGlhBArCoOx07pwc9NMTnXwpfe4rUdm3EaGGpe/9JT08JcTyFZfFUeFT1 +lfSYo5i+xPOCQ/FcC5GfWdciyY0c8ej8iwdxZb0kPI4hBu36+D6zD+YoNoC3CQTb +MecRFQ0MeTTuUMCdzFWtg+2FWnJucaLiaK9fKbVzi7UCgYEA0BAlfUdXdeDYMlW3 +2ReeOgH32bchPYwn2UvHYkIhhDp40STVw3BYQ0Zj9yJQXLFaoY1SFhwRJR1kpbSd +IfME/IzR/oMFvRUNQEPQZVH0Mg9FWIXLdXlV4qbU3AyA2r4x+VUCt3jp1n/5rG7g +cmoKBdCXNUAhK30bRGTdXB06Fp8CgYEAz0V+IlkGyDKcyCkja0ypA3AdSod/43az +7HMS3nf32hOFpgQuEtVYZc3NW/rdJFPksnRd6+RlD2nIoHZEa+adl2gESjGH2asw +nhxP/Pr4m8PGZF5BwdILRTVFukf5yrM6g63FgrgA9d+QdCsqoqrctItRyCgcfpL4 +XYXEKVWELP8CgYATxbUKVsFE/n0NK4AxLUFoGc/H7NNH2g3fZIgqGka9HiFlHq8B +x6dbnVDap3QjmucV+ywV1cz7TfPGm4djNoj+xxMdsK3W7i04MjmXp1Yhe7oHU4+m +NkWnKFuKHdYQ84okO6Pqc58lNzwu2sqRlOom60+zS8jbLSRuN3ehzVU72QKBgGm0 +qCo+Ou44maqfCFg9hWiicd3Dkt5feE0bNsFMb5PBJwTO1ux175ojxhqlqshPHLBC +FnAqT7v3mAD1r9lTiIVh3+YysnS5EJdiGw0KtWVDB9fCFkkRpPvLul7RPDw7AZmM +MtGCo8LBHHuSVDEXcG2HK9MnWbjXnWCcyrjFyx3jAoGAYsNGYm+OBr16NNsPtx3S +nRQJz9wqB2mIqNU8rRSjd5EUp03jhHiTEN9DT6iEnLGaTDBUgD2RlPvEVGk1N7FT +nh9tLtg2ytWIC/P+QrKwzdUUa00MSswTxRS3Cmy459UbLBiPgHBJ2h1G7gsiHPOt +erJWqYJ8DXvLzCPdMVzQxj8= +-----END PRIVATE KEY----- diff --git a/heartbeat/monitors/active/http/http_test.go b/heartbeat/monitors/active/http/http_test.go index 2db563d0044..86e55b4536c 100644 --- a/heartbeat/monitors/active/http/http_test.go +++ b/heartbeat/monitors/active/http/http_test.go @@ -22,6 +22,7 @@ import ( "crypto/x509" "fmt" "io/ioutil" + "net" "net/http" "net/http/httptest" "net/url" @@ -48,13 +49,13 @@ import ( "github.com/elastic/go-lookslike/validator" ) -func testRequest(t *testing.T, testURL string, useUrls bool) *beat.Event { - return testTLSRequest(t, testURL, useUrls, nil) +func sendSimpleTLSRequest(t *testing.T, testURL string, useUrls bool) *beat.Event { + return sendTLSRequest(t, testURL, useUrls, nil) } -// testTLSRequest tests the given request. certPath is optional, if given +// sendTLSRequest tests the given request. certPath is optional, if given // an empty string no cert will be set. -func testTLSRequest(t *testing.T, testURL string, useUrls bool, extraConfig map[string]interface{}) *beat.Event { +func sendTLSRequest(t *testing.T, testURL string, useUrls bool, extraConfig map[string]interface{}) *beat.Event { configSrc := map[string]interface{}{ "timeout": "1s", } @@ -92,7 +93,7 @@ func testTLSRequest(t *testing.T, testURL string, useUrls bool, extraConfig map[ func checkServer(t *testing.T, handlerFunc http.HandlerFunc, useUrls bool) (*httptest.Server, *beat.Event) { server := httptest.NewServer(handlerFunc) defer server.Close() - event := testRequest(t, server.URL, useUrls) + event := sendSimpleTLSRequest(t, server.URL, useUrls) return server, event } @@ -220,13 +221,6 @@ var downStatuses = []int{ http.StatusNetworkAuthenticationRequired, } -func serverHostname(t *testing.T, server *httptest.Server) string { - surl, err := url.Parse(server.URL) - require.NoError(t, err) - - return surl.Hostname() -} - func TestUpStatuses(t *testing.T) { for _, status := range upStatuses { status := status @@ -347,7 +341,7 @@ func runHTTPSServerCheck( // we give it a few attempts to see if the server can come up before we run the real assertions. var event *beat.Event for i := 0; i < 10; i++ { - event = testTLSRequest(t, server.URL, false, mergedExtraConfig) + event = sendTLSRequest(t, server.URL, false, mergedExtraConfig) if v, err := event.GetValue("monitor.status"); err == nil && reflect.DeepEqual(v, "up") { break } @@ -373,6 +367,30 @@ func TestHTTPSServer(t *testing.T) { runHTTPSServerCheck(t, server, nil) } +func TestExpiredHTTPSServer(t *testing.T) { + tlsCert, err := tls.LoadX509KeyPair("../fixtures/expired.cert", "../fixtures/expired.key") + require.NoError(t, err) + host, port, cert, closeSrv := hbtest.StartHTTPSServer(t, tlsCert) + defer closeSrv() + u := &url.URL{Scheme: "https", Host: net.JoinHostPort(host, port)} + + extraConfig := map[string]interface{}{"ssl.certificate_authorities": "../fixtures/expired.cert"} + event := sendTLSRequest(t, u.String(), true, extraConfig) + + testslike.Test( + t, + lookslike.Strict(lookslike.Compose( + hbtest.BaseChecks("127.0.0.1", "down", "http"), + hbtest.RespondingTCPChecks(), + hbtest.SummaryChecks(0, 1), + hbtest.ExpiredCertChecks(cert), + hbtest.URLChecks(t, &url.URL{Scheme: "https", Host: net.JoinHostPort(host, port)}), + // No HTTP fields expected because we fail at the TCP level + )), + event.Fields, + ) +} + func TestHTTPSx509Auth(t *testing.T) { wd, err := os.Getwd() require.NoError(t, err) @@ -418,7 +436,7 @@ func TestConnRefusedJob(t *testing.T) { url := fmt.Sprintf("http://%s:%d", ip, port) - event := testRequest(t, url, false) + event := sendSimpleTLSRequest(t, url, false) testslike.Test( t, @@ -440,7 +458,7 @@ func TestUnreachableJob(t *testing.T) { port := uint16(1234) url := fmt.Sprintf("http://%s:%d", ip, port) - event := testRequest(t, url, false) + event := sendSimpleTLSRequest(t, url, false) testslike.Test( t, diff --git a/heartbeat/monitors/active/http/task.go b/heartbeat/monitors/active/http/task.go index 65d2f1ae62c..2c227c1d89a 100644 --- a/heartbeat/monitors/active/http/task.go +++ b/heartbeat/monitors/active/http/task.go @@ -20,15 +20,19 @@ package http import ( "bytes" "context" + "crypto/x509" "fmt" "io/ioutil" "net" "net/http" + "net/url" "strconv" "strings" "sync" "time" + "github.com/elastic/beats/v7/heartbeat/monitors/active/dialchain/tlsmeta" + "github.com/elastic/beats/v7/heartbeat/eventext" "github.com/elastic/beats/v7/heartbeat/look" "github.com/elastic/beats/v7/heartbeat/monitors" @@ -232,11 +236,16 @@ func execPing( // Send the HTTP request. We don't immediately return on error since // we may want to add additional fields to contextualize the error. start, resp, errReason := execRequest(client, req) - // If we have no response object or an error was set there probably was an IO error, we can skip the rest of the logic // since that logic is for adding metadata relating to completed HTTP transactions that have errored // in other ways if resp == nil || errReason != nil { + if urlErr, ok := errReason.Unwrap().(*url.Error); ok { + if certErr, ok := urlErr.Err.(x509.CertificateInvalidError); ok { + tlsmeta.AddCertMetadata(event.Fields, []*x509.Certificate{certErr.Cert}) + } + } + return start, time.Now(), errReason } diff --git a/heartbeat/monitors/active/tcp/tcp.go b/heartbeat/monitors/active/tcp/tcp.go index 05c687dd65b..26f96d2e010 100644 --- a/heartbeat/monitors/active/tcp/tcp.go +++ b/heartbeat/monitors/active/tcp/tcp.go @@ -18,18 +18,19 @@ package tcp import ( + "crypto/x509" "net" "net/url" "time" "github.com/elastic/beats/v7/heartbeat/eventext" "github.com/elastic/beats/v7/heartbeat/look" - "github.com/elastic/beats/v7/heartbeat/reason" - "github.com/elastic/beats/v7/heartbeat/monitors" "github.com/elastic/beats/v7/heartbeat/monitors/active/dialchain" + "github.com/elastic/beats/v7/heartbeat/monitors/active/dialchain/tlsmeta" "github.com/elastic/beats/v7/heartbeat/monitors/jobs" "github.com/elastic/beats/v7/heartbeat/monitors/wrappers" + "github.com/elastic/beats/v7/heartbeat/reason" "github.com/elastic/beats/v7/libbeat/beat" "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/libbeat/common/transport" @@ -226,6 +227,9 @@ func (jf *jobFactory) execDialer( conn, err := dialer.Dial("tcp", addr) if err != nil { debugf("dial failed with: %v", err) + if certErr, ok := err.(x509.CertificateInvalidError); ok { + tlsmeta.AddCertMetadata(event.Fields, []*x509.Certificate{certErr.Cert}) + } return reason.IOFailed(err) } defer conn.Close() diff --git a/heartbeat/monitors/active/tcp/tls_test.go b/heartbeat/monitors/active/tcp/tls_test.go index 0628b1694b4..ff4cd569db5 100644 --- a/heartbeat/monitors/active/tcp/tls_test.go +++ b/heartbeat/monitors/active/tcp/tls_test.go @@ -18,12 +18,14 @@ package tcp import ( + "crypto/tls" "crypto/x509" "net" "net/http" "net/http/httptest" "net/url" "os" + "strconv" "testing" "time" @@ -112,6 +114,35 @@ func TestTLSInvalidCert(t *testing.T) { ) } +func TestTLSExpiredCert(t *testing.T) { + certFile := "../fixtures/expired.cert" + tlsCert, err := tls.LoadX509KeyPair(certFile, "../fixtures/expired.key") + require.NoError(t, err) + + ip, portStr, cert, closeSrv := hbtest.StartHTTPSServer(t, tlsCert) + defer closeSrv() + + portInt, err := strconv.Atoi(portStr) + port := uint16(portInt) + require.NoError(t, err) + + host := "localhost" + event := testTLSTCPCheck(t, host, port, certFile, monitors.NewStdResolver()) + + testslike.Test( + t, + lookslike.Strict(lookslike.Compose( + hbtest.RespondingTCPChecks(), + hbtest.BaseChecks(ip, "down", "tcp"), + hbtest.SummaryChecks(0, 1), + hbtest.SimpleURLChecks(t, "ssl", host, port), + hbtest.ResolveChecks(ip), + hbtest.ExpiredCertChecks(cert), + )), + event.Fields, + ) +} + func setupTLSTestServer(t *testing.T) (ip string, port uint16, cert *x509.Certificate, certFile *os.File, teardown func()) { // Start up a TLS Server server, port, err := setupServer(t, func(handler http.Handler) (*httptest.Server, error) { diff --git a/heartbeat/reason/reason.go b/heartbeat/reason/reason.go index 677a87a8971..ad1823af8e3 100644 --- a/heartbeat/reason/reason.go +++ b/heartbeat/reason/reason.go @@ -22,6 +22,7 @@ import "github.com/elastic/beats/v7/libbeat/common" type Reason interface { error Type() string + Unwrap() error } type ValidateError struct { @@ -47,9 +48,11 @@ func IOFailed(err error) Reason { } func (e ValidateError) Error() string { return e.err.Error() } +func (e ValidateError) Unwrap() error { return e.err } func (ValidateError) Type() string { return "validate" } func (e IOError) Error() string { return e.err.Error() } +func (e IOError) Unwrap() error { return e.err } func (IOError) Type() string { return "io" } func FailError(typ string, err error) common.MapStr { diff --git a/libbeat/common/transport/tlscommon/types.go b/libbeat/common/transport/tlscommon/types.go index 3fb96712b16..93cdf95464e 100644 --- a/libbeat/common/transport/tlscommon/types.go +++ b/libbeat/common/transport/tlscommon/types.go @@ -65,6 +65,10 @@ var tlsCipherSuites = map[string]tlsCipherSuite{ "RSA-AES-128-GCM-SHA256": tlsCipherSuite(tls.TLS_RSA_WITH_AES_128_GCM_SHA256), "RSA-AES-256-CBC-SHA": tlsCipherSuite(tls.TLS_RSA_WITH_AES_256_CBC_SHA), "RSA-AES-256-GCM-SHA384": tlsCipherSuite(tls.TLS_RSA_WITH_AES_256_GCM_SHA384), + + "TLS-AES-128-GCM-SHA256": tlsCipherSuite(tls.TLS_AES_128_GCM_SHA256), + "TLS-AES-256-GCM-SHA384": tlsCipherSuite(tls.TLS_AES_256_GCM_SHA384), + "TLS-CHACHA20-POLY1305-SHA256": tlsCipherSuite(tls.TLS_CHACHA20_POLY1305_SHA256), } var tlsCipherSuitesInverse = make(map[tlsCipherSuite]string, len(tlsCipherSuites)) diff --git a/libbeat/common/transport/tlscommon/versions.go b/libbeat/common/transport/tlscommon/versions.go index 3ab3dd5a8f0..a589f0af3cd 100644 --- a/libbeat/common/transport/tlscommon/versions.go +++ b/libbeat/common/transport/tlscommon/versions.go @@ -23,12 +23,20 @@ import "fmt" type TLSVersion uint16 func (v TLSVersion) String() string { - if s, ok := tlsProtocolVersionsInverse[v]; ok { - return s + if details := v.Details(); details != nil { + return details.Combined } return "unknown" } +// Details returns a a ProtocolAndVersions struct containing detailed version metadata. +func (v TLSVersion) Details() *TLSVersionDetails { + if found, ok := tlsInverseLookup[v]; ok { + return &found + } + return nil +} + //Unpack transforms the string into a constant. func (v *TLSVersion) Unpack(s string) error { version, found := tlsProtocolVersions[s] diff --git a/libbeat/common/transport/tlscommon/versions_default.go b/libbeat/common/transport/tlscommon/versions_default.go index 057c5c59cd4..77eff7375eb 100644 --- a/libbeat/common/transport/tlscommon/versions_default.go +++ b/libbeat/common/transport/tlscommon/versions_default.go @@ -19,7 +19,9 @@ package tlscommon -import "crypto/tls" +import ( + "crypto/tls" +) // Define all the possible TLS version. const ( @@ -61,10 +63,22 @@ var tlsProtocolVersions = map[string]TLSVersion{ "TLSv1.3": TLSVersion13, } -var tlsProtocolVersionsInverse = map[TLSVersion]string{ - TLSVersionSSL30: "SSLv3", - TLSVersion10: "TLSv1.0", - TLSVersion11: "TLSv1.1", - TLSVersion12: "TLSv1.2", - TLSVersion13: "TLSv1.3", +// Intended for ECS's tls.version_protocol_field, which does not include +// numeric version and should be lower case +type TLSVersionDetails struct { + Version string + Protocol string + Combined string +} + +func (pv TLSVersionDetails) String() string { + return pv.Combined +} + +var tlsInverseLookup = map[TLSVersion]TLSVersionDetails{ + TLSVersionSSL30: TLSVersionDetails{Version: "3.0", Protocol: "ssl", Combined: "SSLv3"}, + TLSVersion10: TLSVersionDetails{Version: "1.0", Protocol: "tls", Combined: "TLSv1.0"}, + TLSVersion11: TLSVersionDetails{Version: "1.1", Protocol: "tls", Combined: "TLSv1.1"}, + TLSVersion12: TLSVersionDetails{Version: "1.2", Protocol: "tls", Combined: "TLSv1.2"}, + TLSVersion13: TLSVersionDetails{Version: "1.3", Protocol: "tls", Combined: "TLSv1.3"}, } diff --git a/libbeat/common/transport/tlscommon/versions_test.go b/libbeat/common/transport/tlscommon/versions_test.go new file mode 100644 index 00000000000..b1251109b05 --- /dev/null +++ b/libbeat/common/transport/tlscommon/versions_test.go @@ -0,0 +1,77 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package tlscommon + +import ( + "crypto/tls" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestTLSVersion(t *testing.T) { + // These tests are a bit verbose, but given the sensitivity to changes here, it's not a bad idea. + tests := []struct { + name string + v uint16 + want *TLSVersionDetails + }{ + { + "unknown", + 0x0, + nil, + }, + { + "SSLv3", + tls.VersionSSL30, + &TLSVersionDetails{Version: "3.0", Protocol: "ssl", Combined: "SSLv3"}, + }, + { + "TLSv1.0", + tls.VersionTLS10, + &TLSVersionDetails{Version: "1.0", Protocol: "tls", Combined: "TLSv1.0"}, + }, + { + "TLSv1.1", + tls.VersionTLS11, + &TLSVersionDetails{Version: "1.1", Protocol: "tls", Combined: "TLSv1.1"}, + }, + { + "TLSv1.2", + tls.VersionTLS12, + &TLSVersionDetails{Version: "1.2", Protocol: "tls", Combined: "TLSv1.2"}, + }, + { + "TLSv1.3", + tls.VersionTLS13, + &TLSVersionDetails{Version: "1.3", Protocol: "tls", Combined: "TLSv1.3"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tv := TLSVersion(tt.v) + require.Equal(t, tt.want, tv.Details()) + if tt.want == nil { + require.Equal(t, tt.want, tv.Details()) + require.Equal(t, tt.name, "unknown") + } else { + require.Equal(t, tt.name, tv.String()) + } + }) + } +} diff --git a/libbeat/mapping/field.go b/libbeat/mapping/field.go index 7b2ba52e618..79f771bb7dd 100644 --- a/libbeat/mapping/field.go +++ b/libbeat/mapping/field.go @@ -124,7 +124,7 @@ func (f *Field) Validate() error { func (f *Field) validateType() error { switch strings.ToLower(f.Type) { - case "text", "keyword": + case "text", "keyword", "wildcard": return stringType.validate(f.Format) case "long", "integer", "short", "byte", "double", "float", "half_float", "scaled_float": return numberType.validate(f.Format) From f24f7449eb8633d3e377b56565a311fef7238a56 Mon Sep 17 00:00:00 2001 From: Jaime Soriano Pastor Date: Tue, 28 Apr 2020 10:45:43 +0200 Subject: [PATCH 033/116] Review dependency patterns collection in Jenkins (#18004) Fix dependency checking for generators, looking now for dependencies of the generators code itself (beatgen) and Metricbeat beater. Stop looking for dependencies of libbeat in all beats, this is redundant as they are already transitive dependencies. Run collection of dependecies in the host instead of using docker, what is slightly faster. --- Jenkinsfile | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 5761f41b0f8..27ddf9dfe03 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -40,13 +40,13 @@ pipeline { steps { deleteDir() gitCheckout(basedir: "${BASE_DIR}") + stash allowEmpty: true, name: 'source', useDefaultExcludes: false dir("${BASE_DIR}"){ loadConfigEnvVars() } whenTrue(params.debug){ dumpFilteredEnvironment() } - stash allowEmpty: true, name: 'source', useDefaultExcludes: false } } stage('Lint'){ @@ -853,7 +853,6 @@ def isChangedOSSCode(patterns) { "^\\.ci/.*", ] allPatterns.addAll(patterns) - allPatterns.addAll(getVendorPatterns('libbeat')) return isChanged(allPatterns) } @@ -868,7 +867,6 @@ def isChangedXPackCode(patterns) { "^\\.ci/.*", ] allPatterns.addAll(patterns) - allPatterns.addAll(getVendorPatterns('x-pack/libbeat')) return isChanged(allPatterns) } @@ -876,6 +874,10 @@ def loadConfigEnvVars(){ def empty = [] env.GO_VERSION = readFile(".go-version").trim() + withEnv(["HOME=${env.WORKSPACE}"]) { + sh(label: "Install Go ${env.GO_VERSION}", script: ".ci/scripts/install-go.sh") + } + // Libbeat is the core framework of Beats. It has no additional dependencies // on other projects in the Beats repository. env.BUILD_LIBBEAT = isChangedOSSCode(empty) @@ -934,17 +936,25 @@ def loadConfigEnvVars(){ // involved. env.BUILD_KUBERNETES = isChanged(["^deploy/kubernetes/.*"]) - env.BUILD_GENERATOR = isChangedOSSCode(getVendorPatterns('generator')) + def generatorPatterns = ['^generator/.*'] + generatorPatterns.addAll(getVendorPatterns('generator/common/beatgen')) + generatorPatterns.addAll(getVendorPatterns('metricbeat/beater')) + env.BUILD_GENERATOR = isChangedOSSCode(generatorPatterns) } /** This method grab the dependencies of a Go module and transform them on regexp */ def getVendorPatterns(beatName){ + def os = goos() + def goRoot = "${env.WORKSPACE}/.gvm/versions/go${GO_VERSION}.${os}.amd64" def output = "" - docker.image("golang:${GO_VERSION}").inside{ + + withEnv([ + "HOME=${env.WORKSPACE}/${env.BASE_DIR}", + "PATH=${env.WORKSPACE}/bin:${goRoot}/bin:${env.PATH}", + ]) { output = sh(label: 'Get vendor dependency patterns', returnStdout: true, script: """ - export HOME=${WORKSPACE}/${BASE_DIR} go list -mod=vendor -f '{{ .ImportPath }}{{ "\\n" }}{{ join .Deps "\\n" }}' ./${beatName}\ |awk '{print \$1"/.*"}'\ |sed -e "s#github.com/elastic/beats/v7/##g" From 164beb0a9c7bdacea31a1a0635bd8a9f1e96e5d4 Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Tue, 28 Apr 2020 05:08:12 -0700 Subject: [PATCH 034/116] Update stale references to _xpack to refer to _license instead (#18030) * Update stale references to _xpack to refer to _license instead * Adding CHANGELOG entry --- CHANGELOG.next.asciidoc | 1 + x-pack/libbeat/licenser/elastic_fetcher.go | 12 ++++++------ x-pack/libbeat/licenser/es_callback.go | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index b1f70eea949..4f10509a123 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -77,6 +77,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Do not rotate log files on startup when interval is configured and rotateonstartup is disabled. {pull}17613[17613] - Fix goroutine leak and Elasticsearch output file descriptor leak when output reloading is in use. {issue}10491[10491] {pull}17381[17381] - Fix `setup.dashboards.index` setting not working. {pull}17749[17749] +- Fix Elasticsearch license endpoint URL referenced in error message. {issue}17880[17880] {pull}18030[18030] *Auditbeat* diff --git a/x-pack/libbeat/licenser/elastic_fetcher.go b/x-pack/libbeat/licenser/elastic_fetcher.go index 6ffa5f6fa37..b2c855df6b7 100644 --- a/x-pack/libbeat/licenser/elastic_fetcher.go +++ b/x-pack/libbeat/licenser/elastic_fetcher.go @@ -18,9 +18,9 @@ import ( "github.com/elastic/beats/v7/libbeat/logp" ) -const xPackURL = "/_license" +const licenseURL = "/_license" -// params defaults query parameters to send to the '_xpack' endpoint by default we only need +// params defaults query parameters to send to the '_license' endpoint by default we only need // machine parseable data. var params = map[string]string{ "human": "false", @@ -88,12 +88,12 @@ func NewElasticFetcher(client esclient) *ElasticFetcher { return &ElasticFetcher{client: client, log: logp.NewLogger("elasticfetcher")} } -// Fetch retrieves the license information from an Elasticsearch Client, it will call the `_xpack` -// end point and will return a parsed license. If the `_xpack` endpoint is unreacheable we will +// Fetch retrieves the license information from an Elasticsearch Client, it will call the `_license` +// endpoint and will return a parsed license. If the `_license` endpoint is unreacheable we will // return the OSS License otherwise we return an error. func (f *ElasticFetcher) Fetch() (*License, error) { - status, body, err := f.client.Request("GET", xPackURL, "", params, nil) - // When we are running an OSS release of elasticsearch the _xpack endpoint will return a 405, + status, body, err := f.client.Request("GET", licenseURL, "", params, nil) + // When we are running an OSS release of elasticsearch the _license endpoint will return a 405, // "Method Not Allowed", so we return the default OSS license. if status == http.StatusBadRequest { f.log.Debug("Received 'Bad request' (400) response from server, fallback to OSS license") diff --git a/x-pack/libbeat/licenser/es_callback.go b/x-pack/libbeat/licenser/es_callback.go index 0ca99db8f5f..06cc055083c 100644 --- a/x-pack/libbeat/licenser/es_callback.go +++ b/x-pack/libbeat/licenser/es_callback.go @@ -30,7 +30,7 @@ func Enforce(name string, checks ...CheckFunc) { license, err := fetcher.Fetch() if err != nil { - return errors.Wrapf(err, "cannot retrieve the elasticsearch license from the /_xpack endpoint, "+ + return errors.Wrapf(err, "cannot retrieve the elasticsearch license from the /_license endpoint, "+ "%s requires the default distribution of Elasticsearch. Please make the endpoint accessible "+ "to %s so it can verify the license.", name, name) } From 09fd4df169324d4e02b2f3bf3c074d7501f21968 Mon Sep 17 00:00:00 2001 From: premendrasingh Date: Tue, 28 Apr 2020 05:51:15 -0700 Subject: [PATCH 035/116] LIBBEAT: Enhancement replace_string processor for replacing strings values of fields. (#17342) This PR is to add a replace processor. This processor takes in a field name, search string and replacement string. Searches field value for pattern and replaces it with replacement string. --- CHANGELOG.next.asciidoc | 1 + .../processors/actions/docs/replace.asciidoc | 49 ++++ libbeat/processors/actions/replace.go | 118 +++++++++ libbeat/processors/actions/replace_test.go | 248 ++++++++++++++++++ 4 files changed, 416 insertions(+) create mode 100644 libbeat/processors/actions/docs/replace.asciidoc create mode 100644 libbeat/processors/actions/replace.go create mode 100644 libbeat/processors/actions/replace_test.go diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 4f10509a123..a0e555166cc 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -200,6 +200,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Update RPM packages contained in Beat Docker images. {issue}17035[17035] - Update supported versions of `redis` output. {pull}17198[17198] - Update documentation for system.process.memory fields to include clarification on Windows os's. {pull}17268[17268] +- Add `replace` processor for replacing string values of fields. {pull}17342[17342] - Add optional regex based cid extractor to `add_kubernetes_metadata` processor. {pull}17360[17360] - Add `urldecode` processor to for decoding URL-encoded fields. {pull}17505[17505] - Add support for AWS IAM `role_arn` in credentials config. {pull}17658[17658] {issue}12464[12464] diff --git a/libbeat/processors/actions/docs/replace.asciidoc b/libbeat/processors/actions/docs/replace.asciidoc new file mode 100644 index 00000000000..3faf3e0bcce --- /dev/null +++ b/libbeat/processors/actions/docs/replace.asciidoc @@ -0,0 +1,49 @@ +[[replace-fields]] +=== Replace fields from events + +++++ +replace +++++ + +The `replace` processor takes a list of fields to replace the field value +matching a pattern with replacement string. Under the `fields` key, each entry +contains a `field: field-name`, `pattern: regex-pattern` and +`replacement: replacement-string`, where: + +* `field` is the original field name +* `pattern` is regex pattern to match field's value +* `replacement` is the replacement string to use for updating the field's value + +The `replace` processor cannot be used to replace value with a completely new value. + +TIP: You can replace field value to truncate part of field value or replace +it with a new string. It can also be used for masking PII information. + +Following example will change path from /usr/bin to /usr/local/bin + +[source,yaml] +------- +processors: +- replace: + fields: + - field: "file.path" + pattern: "/usr/" + replacement: "/usr/local/" + ignore_missing: false + fail_on_error: true +------- + +The `replace` processor has following configuration settings: + +`ignore_missing`:: (Optional) If set to true, no error is logged in case a specifiedfield +is missing. Default is `false`. + +`fail_on_error`:: (Optional) If set to true, in case of an error the replacement of +field values is stopped and the original event is returned. If set to false, replacement +continues even if an error occurs during replacement. Default is `true`. + +See <> for a list of supported conditions. + +You can specify multiple `ignore_missing` processors under the `processors` +section. + diff --git a/libbeat/processors/actions/replace.go b/libbeat/processors/actions/replace.go new file mode 100644 index 00000000000..37245817050 --- /dev/null +++ b/libbeat/processors/actions/replace.go @@ -0,0 +1,118 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package actions + +import ( + "fmt" + "regexp" + + "github.com/pkg/errors" + + "github.com/elastic/beats/v7/libbeat/beat" + "github.com/elastic/beats/v7/libbeat/common" + "github.com/elastic/beats/v7/libbeat/logp" + "github.com/elastic/beats/v7/libbeat/processors" + "github.com/elastic/beats/v7/libbeat/processors/checks" + jsprocessor "github.com/elastic/beats/v7/libbeat/processors/script/javascript/module/processor" +) + +type replaceString struct { + config replaceStringConfig +} + +type replaceStringConfig struct { + Fields []replaceConfig `config:"fields"` + IgnoreMissing bool `config:"ignore_missing"` + FailOnError bool `config:"fail_on_error"` +} + +type replaceConfig struct { + Field string `config:"field"` + Pattern *regexp.Regexp `config:"pattern"` + Replacement string `config:"replacement"` +} + +func init() { + processors.RegisterPlugin("replace", + checks.ConfigChecked(NewReplaceString, + checks.RequireFields("fields"))) + + jsprocessor.RegisterPlugin("Replace", NewReplaceString) +} + +// NewReplaceString returns a new replace processor. +func NewReplaceString(c *common.Config) (processors.Processor, error) { + config := replaceStringConfig{ + IgnoreMissing: false, + FailOnError: true, + } + err := c.Unpack(&config) + if err != nil { + return nil, fmt.Errorf("failed to unpack the replace configuration: %s", err) + } + + f := &replaceString{ + config: config, + } + return f, nil +} + +func (f *replaceString) Run(event *beat.Event) (*beat.Event, error) { + var backup common.MapStr + // Creates a copy of the event to revert in case of failure + if f.config.FailOnError { + backup = event.Fields.Clone() + } + + for _, field := range f.config.Fields { + err := f.replaceField(field.Field, field.Pattern, field.Replacement, event.Fields) + if err != nil { + errMsg := fmt.Errorf("Failed to replace fields in processor: %s", err) + logp.Debug("replace", errMsg.Error()) + if f.config.FailOnError { + event.Fields = backup + event.PutValue("error.message", errMsg.Error()) + return event, err + } + } + } + + return event, nil +} + +func (f *replaceString) replaceField(field string, pattern *regexp.Regexp, replacement string, fields common.MapStr) error { + currentValue, err := fields.GetValue(field) + if err != nil { + // Ignore ErrKeyNotFound errors + if f.config.IgnoreMissing && errors.Cause(err) == common.ErrKeyNotFound { + return nil + } + return fmt.Errorf("could not fetch value for key: %s, Error: %s", field, err) + } + + updatedString := pattern.ReplaceAllString(currentValue.(string), replacement) + _, err = fields.Put(field, updatedString) + if err != nil { + return fmt.Errorf("could not put value: %s: %v, %v", replacement, currentValue, err) + } + return nil +} + +func (f *replaceString) String() string { + return "replace=" + fmt.Sprintf("%+v", f.config.Fields) +} diff --git a/libbeat/processors/actions/replace_test.go b/libbeat/processors/actions/replace_test.go new file mode 100644 index 00000000000..e54d16c5012 --- /dev/null +++ b/libbeat/processors/actions/replace_test.go @@ -0,0 +1,248 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package actions + +import ( + "reflect" + "regexp" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/elastic/beats/v7/libbeat/beat" + "github.com/elastic/beats/v7/libbeat/common" +) + +func TestReplaceRun(t *testing.T) { + var tests = []struct { + description string + Fields []replaceConfig + IgnoreMissing bool + FailOnError bool + Input common.MapStr + Output common.MapStr + error bool + }{ + { + description: "simple field replacing", + Fields: []replaceConfig{ + { + Field: "f", + Pattern: regexp.MustCompile(`a`), + Replacement: "b", + }, + }, + Input: common.MapStr{ + "f": "abc", + }, + Output: common.MapStr{ + "f": "bbc", + }, + error: false, + IgnoreMissing: false, + FailOnError: true, + }, + { + description: "Add one more hierarchy to event", + Fields: []replaceConfig{ + { + Field: "f.b", + Pattern: regexp.MustCompile(`a`), + Replacement: "b", + }, + }, + Input: common.MapStr{ + "f": common.MapStr{ + "b": "abc", + }, + }, + Output: common.MapStr{ + "f": common.MapStr{ + "b": "bbc", + }, + }, + error: false, + IgnoreMissing: false, + FailOnError: true, + }, + { + description: "replace two fields at the same time.", + Fields: []replaceConfig{ + { + Field: "f", + Pattern: regexp.MustCompile(`a.*c`), + Replacement: "cab", + }, + { + Field: "g", + Pattern: regexp.MustCompile(`ef`), + Replacement: "oor", + }, + }, + Input: common.MapStr{ + "f": "abbbc", + "g": "def", + }, + Output: common.MapStr{ + "f": "cab", + "g": "door", + }, + error: false, + IgnoreMissing: false, + FailOnError: true, + }, + { + description: "test missing fields", + Fields: []replaceConfig{ + { + Field: "f", + Pattern: regexp.MustCompile(`abc`), + Replacement: "xyz", + }, + { + Field: "g", + Pattern: regexp.MustCompile(`def`), + Replacement: "", + }, + }, + Input: common.MapStr{ + "m": "abc", + "n": "def", + }, + Output: common.MapStr{ + "m": "abc", + "n": "def", + "error": common.MapStr{ + "message": "Failed to replace fields in processor: could not fetch value for key: f, Error: key not found", + }, + }, + error: true, + IgnoreMissing: false, + FailOnError: true, + }, + } + + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + f := &replaceString{ + config: replaceStringConfig{ + Fields: test.Fields, + IgnoreMissing: test.IgnoreMissing, + FailOnError: test.FailOnError, + }, + } + event := &beat.Event{ + Fields: test.Input, + } + + newEvent, err := f.Run(event) + if !test.error { + assert.Nil(t, err) + } else { + assert.NotNil(t, err) + } + + assert.True(t, reflect.DeepEqual(newEvent.Fields, test.Output)) + }) + } +} + +func TestReplaceField(t *testing.T) { + var tests = []struct { + Field string + Pattern *regexp.Regexp + Replacement string + ignoreMissing bool + failOnError bool + Input common.MapStr + Output common.MapStr + error bool + description string + }{ + { + description: "replace part of field value with another string", + Field: "f", + Pattern: regexp.MustCompile(`a`), + Replacement: "b", + Input: common.MapStr{ + "f": "abc", + }, + Output: common.MapStr{ + "f": "bbc", + }, + error: false, + failOnError: true, + ignoreMissing: false, + }, + { + description: "Add hierarchy to event and replace", + Field: "f.b", + Pattern: regexp.MustCompile(`a`), + Replacement: "b", + Input: common.MapStr{ + "f": common.MapStr{ + "b": "abc", + }, + }, + Output: common.MapStr{ + "f": common.MapStr{ + "b": "bbc", + }, + }, + error: false, + ignoreMissing: false, + failOnError: true, + }, + { + description: "try replacing value of missing fields in event", + Field: "f", + Pattern: regexp.MustCompile(`abc`), + Replacement: "xyz", + Input: common.MapStr{ + "m": "abc", + "n": "def", + }, + Output: common.MapStr{ + "m": "abc", + "n": "def", + }, + error: true, + ignoreMissing: false, + failOnError: true, + }, + } + + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + + f := &replaceString{ + config: replaceStringConfig{ + IgnoreMissing: test.ignoreMissing, + FailOnError: test.failOnError, + }, + } + + err := f.replaceField(test.Field, test.Pattern, test.Replacement, test.Input) + if err != nil { + assert.Equal(t, test.error, true) + } + + assert.True(t, reflect.DeepEqual(test.Input, test.Output)) + }) + } +} From 77f0d20703650f60ec8957ed3bc620b927429c82 Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Tue, 28 Apr 2020 09:04:34 -0400 Subject: [PATCH 036/116] Fix generated metricbeat so create-metricset works. (#18020) * Fix generated metricbeat so create-metricset works. * Fix mage target add target to x-pack/metricbeat. --- .../_templates/metricbeat/{beat}/magefile.go | 3 ++ metricbeat/Makefile | 2 +- metricbeat/magefile.go | 29 +--------- .../mage/target/metricset/metricset.go | 54 +++++++++++++++++++ x-pack/metricbeat/Makefile | 5 ++ x-pack/metricbeat/magefile.go | 2 + 6 files changed, 67 insertions(+), 28 deletions(-) create mode 100644 metricbeat/scripts/mage/target/metricset/metricset.go diff --git a/generator/_templates/metricbeat/{beat}/magefile.go b/generator/_templates/metricbeat/{beat}/magefile.go index b1b78829ee1..934276e633b 100644 --- a/generator/_templates/metricbeat/{beat}/magefile.go +++ b/generator/_templates/metricbeat/{beat}/magefile.go @@ -16,6 +16,9 @@ import ( "github.com/elastic/beats/v7/dev-tools/mage/target/unittest" "github.com/elastic/beats/v7/generator/common/beatgen" metricbeat "github.com/elastic/beats/v7/metricbeat/scripts/mage" + + // mage:import + _ "github.com/elastic/beats/v7/metricbeat/scripts/mage/target/metricset" ) func init() { diff --git a/metricbeat/Makefile b/metricbeat/Makefile index 9cbf88c3a59..7a05a566775 100644 --- a/metricbeat/Makefile +++ b/metricbeat/Makefile @@ -3,6 +3,6 @@ ES_BEATS ?= .. include $(ES_BEATS)/dev-tools/make/mage.mk # Creates a new metricset. Requires the params MODULE and METRICSET -.PHONY: create-metricset +.PHONY: create-metricset create-metricset: mage createMetricset diff --git a/metricbeat/magefile.go b/metricbeat/magefile.go index 0281c6892ba..b46dc9c7957 100644 --- a/metricbeat/magefile.go +++ b/metricbeat/magefile.go @@ -27,7 +27,6 @@ import ( "time" "github.com/magefile/mage/mg" - "github.com/magefile/mage/sh" devtools "github.com/elastic/beats/v7/dev-tools/mage" metricbeat "github.com/elastic/beats/v7/metricbeat/scripts/mage" @@ -48,6 +47,8 @@ import ( "github.com/elastic/beats/v7/dev-tools/mage/target/unittest" // mage:import _ "github.com/elastic/beats/v7/dev-tools/mage/target/compose" + // mage:import + _ "github.com/elastic/beats/v7/metricbeat/scripts/mage/target/metricset" ) func init() { @@ -197,29 +198,3 @@ func PythonIntegTest(ctx context.Context) error { return devtools.PythonNoseTest(devtools.DefaultPythonTestIntegrationArgs()) }, devtools.ListMatchingEnvVars("NOSE_")...) } - -// CreateMetricset creates a new metricset. -// -// Required ENV variables: -// * MODULE: Name of the module -// * METRICSET: Name of the metricset -func CreateMetricset(ctx context.Context) error { - ve, err := devtools.PythonVirtualenv() - if err != nil { - return err - } - python, err := devtools.LookVirtualenvPath(ve, "python") - if err != nil { - return err - } - path, err := os.Getwd() - if err != nil { - return err - } - - _, err = sh.Exec( - map[string]string{}, os.Stdout, os.Stderr, python, "scripts/create_metricset.py", - "--path", path, "--module", os.Getenv("MODULE"), "--metricset", os.Getenv("METRICSET"), - ) - return err -} diff --git a/metricbeat/scripts/mage/target/metricset/metricset.go b/metricbeat/scripts/mage/target/metricset/metricset.go new file mode 100644 index 00000000000..b1899e9352a --- /dev/null +++ b/metricbeat/scripts/mage/target/metricset/metricset.go @@ -0,0 +1,54 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package metricset + +import ( + "os" + "path/filepath" + + "github.com/magefile/mage/sh" + + devtools "github.com/elastic/beats/v7/dev-tools/mage" +) + +// CreateMetricset creates a new metricset. +// +// Required ENV variables: +// * MODULE: Name of the module +// * METRICSET: Name of the metricset +func CreateMetricset() error { + ve, err := devtools.PythonVirtualenv() + if err != nil { + return err + } + python, err := devtools.LookVirtualenvPath(ve, "python") + if err != nil { + return err + } + beatsDir, err := devtools.ElasticBeatsDir() + if err != nil { + return err + } + scriptPath := filepath.Join(beatsDir, "metricbeat", "scripts", "create_metricset.py") + + _, err = sh.Exec( + map[string]string{}, os.Stdout, os.Stderr, python, scriptPath, + "--path", devtools.CWD(), "--module", os.Getenv("MODULE"), "--metricset", os.Getenv("METRICSET"), + ) + return err +} diff --git a/x-pack/metricbeat/Makefile b/x-pack/metricbeat/Makefile index 019d3b9309a..c77d4c68517 100644 --- a/x-pack/metricbeat/Makefile +++ b/x-pack/metricbeat/Makefile @@ -1,3 +1,8 @@ ES_BEATS ?= ../.. include $(ES_BEATS)/dev-tools/make/mage.mk + +# Creates a new metricset. Requires the params MODULE and METRICSET +.PHONY: create-metricset +create-metricset: + mage createMetricset diff --git a/x-pack/metricbeat/magefile.go b/x-pack/metricbeat/magefile.go index ffd40949d70..992e4d6d1fa 100644 --- a/x-pack/metricbeat/magefile.go +++ b/x-pack/metricbeat/magefile.go @@ -25,6 +25,8 @@ import ( "github.com/elastic/beats/v7/dev-tools/mage/target/unittest" // mage:import "github.com/elastic/beats/v7/dev-tools/mage/target/test" + // mage:import + _ "github.com/elastic/beats/v7/metricbeat/scripts/mage/target/metricset" ) func init() { From 0df53d32ad8c4edde7c3f7397762c73c6634b56a Mon Sep 17 00:00:00 2001 From: Michal Pristas Date: Tue, 28 Apr 2020 15:36:14 +0200 Subject: [PATCH 037/116] [Agent] Allow CLI paths override (#17781) [Agent] Allow CLI paths override (#17781) --- x-pack/elastic-agent/CHANGELOG.asciidoc | 1 + x-pack/elastic-agent/main_test.go | 12 ++++-- .../pkg/agent/application/global_config.go | 41 ++++++++++++++++--- x-pack/elastic-agent/pkg/agent/cmd/common.go | 18 ++++---- x-pack/elastic-agent/pkg/agent/cmd/enroll.go | 10 ++--- x-pack/elastic-agent/pkg/agent/cmd/run.go | 9 ++-- 6 files changed, 64 insertions(+), 27 deletions(-) diff --git a/x-pack/elastic-agent/CHANGELOG.asciidoc b/x-pack/elastic-agent/CHANGELOG.asciidoc index 9cdaa81432c..ed928e1d688 100644 --- a/x-pack/elastic-agent/CHANGELOG.asciidoc +++ b/x-pack/elastic-agent/CHANGELOG.asciidoc @@ -38,4 +38,5 @@ - Expose stream.* variables in events {pull}17468[17468] - Monitoring configuration reloadable {pull}17855[17855] - Pack ECS metadata to request payload send to fleet {pull}17894[17894] +- Allow CLI overrides of paths {pull}17781[17781] - Enable Filebeat input: S3, Azureeventhub, cloudfoundry, httpjson, netflow, o365audit. {pull}17909[17909] diff --git a/x-pack/elastic-agent/main_test.go b/x-pack/elastic-agent/main_test.go index cdced7ea677..79e2a9dc719 100644 --- a/x-pack/elastic-agent/main_test.go +++ b/x-pack/elastic-agent/main_test.go @@ -8,17 +8,21 @@ import ( "flag" "testing" - // Just using this a place holder. - "github.com/elastic/beats/v7/x-pack/filebeat/cmd" + "github.com/spf13/cobra" ) var systemTest *bool func init() { testing.Init() + + cmd := &cobra.Command{ + Use: "elastic-agent [subcommand]", + } + systemTest = flag.Bool("systemTest", false, "Set to true when running system tests") - cmd.RootCmd.PersistentFlags().AddGoFlag(flag.CommandLine.Lookup("systemTest")) - cmd.RootCmd.PersistentFlags().AddGoFlag(flag.CommandLine.Lookup("test.coverprofile")) + cmd.PersistentFlags().AddGoFlag(flag.CommandLine.Lookup("systemTest")) + cmd.PersistentFlags().AddGoFlag(flag.CommandLine.Lookup("test.coverprofile")) } // Test started when the test binary is started. Only calls main. diff --git a/x-pack/elastic-agent/pkg/agent/application/global_config.go b/x-pack/elastic-agent/pkg/agent/application/global_config.go index 44e9f2772ff..a08a59b0912 100644 --- a/x-pack/elastic-agent/pkg/agent/application/global_config.go +++ b/x-pack/elastic-agent/pkg/agent/application/global_config.go @@ -8,33 +8,64 @@ import ( "os" "path/filepath" + "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/errors" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/config" ) var ( - homePath string - dataPath string + homePath string + dataPath string + overwrites *common.Config ) func init() { homePath = retrieveExecutablePath() dataPath = retrieveDataPath() + overwrites = common.NewConfig() + common.ConfigOverwriteFlag(nil, overwrites, "path.home", "path.home", "", "Agent root path") + common.ConfigOverwriteFlag(nil, overwrites, "path.data", "path.data", "", "Data path contains Agent managed binaries") +} + +// HomePath returns home path where. +func HomePath() string { + if val, err := overwrites.String("path.home", -1); err == nil { + return val + } + + return homePath +} + +// DataPath returns data path where. +func DataPath() string { + if val, err := overwrites.String("path.data", -1); err == nil { + return val + } + + return dataPath } // InjectAgentConfig injects config to a provided configuration. func InjectAgentConfig(c *config.Config) error { - globalConfig := AgentGlobalConfig() + globalConfig := agentGlobalConfig() if err := c.Merge(globalConfig); err != nil { return errors.New("failed to inject agent global config", err, errors.TypeConfig) } + return injectOverwrites(c) +} + +func injectOverwrites(c *config.Config) error { + if err := c.Merge(overwrites); err != nil { + return errors.New("failed to inject agent overwrites", err, errors.TypeConfig) + } + return nil } -// AgentGlobalConfig gets global config used for resolution of variables inside configuration +// agentGlobalConfig gets global config used for resolution of variables inside configuration // such as ${path.data}. -func AgentGlobalConfig() map[string]interface{} { +func agentGlobalConfig() map[string]interface{} { return map[string]interface{}{ "path": map[string]interface{}{ "data": dataPath, diff --git a/x-pack/elastic-agent/pkg/agent/cmd/common.go b/x-pack/elastic-agent/pkg/agent/cmd/common.go index 0189f8e408d..956b7324d3c 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/common.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/common.go @@ -5,30 +5,30 @@ package cmd import ( + "flag" "fmt" "os" "path/filepath" "github.com/spf13/cobra" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/basecmd" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/cli" ) -var defaultConfig = "elastic-agent.yml" +const defaultConfig = "elastic-agent.yml" type globalFlags struct { - PathConfigFile string PathConfig string - PathData string - PathHome string - PathLogs string + PathConfigFile string FlagStrictPerms bool } +// Config returns path which identifies configuration file. func (f *globalFlags) Config() string { if len(f.PathConfigFile) == 0 { - return filepath.Join(f.PathHome, defaultConfig) + return filepath.Join(application.HomePath(), defaultConfig) } return f.PathConfigFile } @@ -50,11 +50,11 @@ func NewCommandWithArgs(args []string, streams *cli.IOStreams) *cobra.Command { flags := &globalFlags{} + cmd.PersistentFlags().AddGoFlag(flag.CommandLine.Lookup("path.home")) + cmd.PersistentFlags().AddGoFlag(flag.CommandLine.Lookup("path.data")) + cmd.PersistentFlags().StringVarP(&flags.PathConfigFile, "", "c", defaultConfig, fmt.Sprintf(`Configuration file, relative to path.config (default "%s")`, defaultConfig)) - cmd.PersistentFlags().StringVarP(&flags.PathHome, "path.home", "", "", "Home path") cmd.PersistentFlags().StringVarP(&flags.PathConfig, "path.config", "", "${path.home}", "Configuration path") - cmd.PersistentFlags().StringVarP(&flags.PathData, "path.data", "", "${path.home}/data", "Data path") - cmd.PersistentFlags().StringVarP(&flags.PathLogs, "path.logs", "", "${path.home}/logs", "Logs path") cmd.PersistentFlags().BoolVarP(&flags.FlagStrictPerms, "strict.perms", "", true, "Strict permission checking on config files") // Add version. diff --git a/x-pack/elastic-agent/pkg/agent/cmd/enroll.go b/x-pack/elastic-agent/pkg/agent/cmd/enroll.go index a2a7ee48d22..abc5efb3b90 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/enroll.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/enroll.go @@ -46,13 +46,13 @@ func newEnrollCommandWithArgs(flags *globalFlags, _ []string, streams *cli.IOStr func enroll(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags, args []string) error { warn.PrintNotGA(streams.Out) - - config, err := config.LoadYAML(flags.PathConfigFile) + pathConfigFile := flags.Config() + config, err := config.LoadYAML(pathConfigFile) if err != nil { return errors.New(err, - fmt.Sprintf("could not read configuration file %s", flags.PathConfigFile), + fmt.Sprintf("could not read configuration file %s", pathConfigFile), errors.TypeFilesystem, - errors.M(errors.MetaKeyPath, flags.PathConfigFile)) + errors.M(errors.MetaKeyPath, pathConfigFile)) } force, _ := cmd.Flags().GetBool("force") @@ -95,7 +95,7 @@ func enroll(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags, args c, err := application.NewEnrollCmd( logger, &options, - flags.PathConfigFile, + pathConfigFile, ) if err != nil { diff --git a/x-pack/elastic-agent/pkg/agent/cmd/run.go b/x-pack/elastic-agent/pkg/agent/cmd/run.go index f0196ba7875..db199e2b47d 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/run.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/run.go @@ -33,12 +33,13 @@ func newRunCommandWithArgs(flags *globalFlags, _ []string, streams *cli.IOStream } func run(flags *globalFlags, streams *cli.IOStreams) error { - config, err := config.LoadYAML(flags.PathConfigFile) + pathConfigFile := flags.Config() + config, err := config.LoadYAML(pathConfigFile) if err != nil { return errors.New(err, - fmt.Sprintf("could not read configuration file %s", flags.PathConfigFile), + fmt.Sprintf("could not read configuration file %s", pathConfigFile), errors.TypeFilesystem, - errors.M(errors.MetaKeyPath, flags.PathConfigFile)) + errors.M(errors.MetaKeyPath, pathConfigFile)) } logger, err := logger.NewFromConfig(config) @@ -46,7 +47,7 @@ func run(flags *globalFlags, streams *cli.IOStreams) error { return err } - app, err := application.New(logger, flags.PathConfigFile) + app, err := application.New(logger, pathConfigFile) if err != nil { return err } From ed7fa2a499263101496ebdfe4ff41f81b8aeb795 Mon Sep 17 00:00:00 2001 From: tomdoherty <31742246+tomdoherty@users.noreply.github.com> Date: Tue, 28 Apr 2020 14:53:01 +0100 Subject: [PATCH 038/116] Add domain state metricset to kvm module (#17673) This allows tracking KVM transitions of state and consolidate resource usage with machine state. Signed-off-by: Tom Doherty --- CHANGELOG.asciidoc | 1 + metricbeat/docs/fields.asciidoc | 44 +++++ metricbeat/docs/modules/kvm.asciidoc | 6 +- metricbeat/docs/modules/kvm/status.asciidoc | 24 +++ metricbeat/docs/modules_list.asciidoc | 3 +- metricbeat/include/list_common.go | 1 + metricbeat/metricbeat.reference.yml | 2 +- .../module/kvm/_meta/config.reference.yml | 2 +- metricbeat/module/kvm/_meta/config.yml | 1 + metricbeat/module/kvm/fields.go | 2 +- metricbeat/module/kvm/status/_meta/data.json | 28 +++ .../module/kvm/status/_meta/docs.asciidoc | 1 + metricbeat/module/kvm/status/_meta/fields.yml | 23 +++ metricbeat/module/kvm/status/status.go | 161 ++++++++++++++++++ metricbeat/module/kvm/status/status_test.go | 69 ++++++++ metricbeat/modules.d/kvm.yml.disabled | 1 + x-pack/metricbeat/metricbeat.reference.yml | 2 +- 17 files changed, 365 insertions(+), 6 deletions(-) create mode 100644 metricbeat/docs/modules/kvm/status.asciidoc create mode 100644 metricbeat/module/kvm/status/_meta/data.json create mode 100644 metricbeat/module/kvm/status/_meta/docs.asciidoc create mode 100644 metricbeat/module/kvm/status/_meta/fields.yml create mode 100644 metricbeat/module/kvm/status/status.go create mode 100644 metricbeat/module/kvm/status/status_test.go diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index b3fa34b8b9b..3da2b9c4fcc 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -30,6 +30,7 @@ https://github.com/elastic/beats/compare/v7.5.0...v7.5.1[View commits] - Fix docker network stats when multiple interfaces are configured. {issue}14586[14586] {pull}14825[14825] - Fix ListMetrics pagination in aws module. {issue}14926[14926] {pull}14942[14942] - Fix CPU count in docker/cpu in cases where no `online_cpus` are reported {pull}15070[15070] +- Add domain state to kvm module {pull}17673[17673] [[release-notes-7.5.0]] === Beats version 7.5.0 diff --git a/metricbeat/docs/fields.asciidoc b/metricbeat/docs/fields.asciidoc index 04c431efd59..0aaafa9488b 100644 --- a/metricbeat/docs/fields.asciidoc +++ b/metricbeat/docs/fields.asciidoc @@ -25865,6 +25865,50 @@ type: long Domain name +type: keyword + +-- + +[float] +=== status + +status + + + +[float] +=== stat + +Memory stat + + + +*`kvm.status.stat.state`*:: ++ +-- +domain state + + +type: keyword + +-- + +*`kvm.status.id`*:: ++ +-- +Domain id + + +type: long + +-- + +*`kvm.status.name`*:: ++ +-- +Domain name + + type: keyword -- diff --git a/metricbeat/docs/modules/kvm.asciidoc b/metricbeat/docs/modules/kvm.asciidoc index f8373184ba1..bc29173f280 100644 --- a/metricbeat/docs/modules/kvm.asciidoc +++ b/metricbeat/docs/modules/kvm.asciidoc @@ -20,7 +20,7 @@ in <>. Here is an example configuration: ---- metricbeat.modules: - module: kvm - metricsets: ["dommemstat"] + metricsets: ["dommemstat", "status"] enabled: true period: 10s hosts: ["unix:///var/run/libvirt/libvirt-sock"] @@ -39,5 +39,9 @@ The following metricsets are available: * <> +* <> + include::kvm/dommemstat.asciidoc[] +include::kvm/status.asciidoc[] + diff --git a/metricbeat/docs/modules/kvm/status.asciidoc b/metricbeat/docs/modules/kvm/status.asciidoc new file mode 100644 index 00000000000..5fea3653349 --- /dev/null +++ b/metricbeat/docs/modules/kvm/status.asciidoc @@ -0,0 +1,24 @@ +//// +This file is generated! See scripts/mage/docs_collector.go +//// + +[[metricbeat-metricset-kvm-status]] +=== kvm status metricset + +beta[] + +include::../../../module/kvm/status/_meta/docs.asciidoc[] + +This is a default metricset. If the host module is unconfigured, this metricset is enabled by default. + +==== Fields + +For a description of each field in the metricset, see the +<> section. + +Here is an example document generated by this metricset: + +[source,json] +---- +include::../../../module/kvm/status/_meta/data.json[] +---- diff --git a/metricbeat/docs/modules_list.asciidoc b/metricbeat/docs/modules_list.asciidoc index fe03abd62a3..bacbcb00906 100644 --- a/metricbeat/docs/modules_list.asciidoc +++ b/metricbeat/docs/modules_list.asciidoc @@ -168,7 +168,8 @@ This file is generated! See scripts/mage/docs_collector.go |<> |<> |<> beta[] |image:./images/icon-no.png[No prebuilt dashboards] | -.1+| .1+| |<> beta[] +.2+| .2+| |<> beta[] +|<> beta[] |<> |image:./images/icon-no.png[No prebuilt dashboards] | .2+| .2+| |<> |<> diff --git a/metricbeat/include/list_common.go b/metricbeat/include/list_common.go index f15d6a4be16..ced01fa0d57 100644 --- a/metricbeat/include/list_common.go +++ b/metricbeat/include/list_common.go @@ -92,6 +92,7 @@ import ( _ "github.com/elastic/beats/v7/metricbeat/module/kibana/status" _ "github.com/elastic/beats/v7/metricbeat/module/kvm" _ "github.com/elastic/beats/v7/metricbeat/module/kvm/dommemstat" + _ "github.com/elastic/beats/v7/metricbeat/module/kvm/status" _ "github.com/elastic/beats/v7/metricbeat/module/logstash" _ "github.com/elastic/beats/v7/metricbeat/module/logstash/node" _ "github.com/elastic/beats/v7/metricbeat/module/logstash/node_stats" diff --git a/metricbeat/metricbeat.reference.yml b/metricbeat/metricbeat.reference.yml index 6de1e04436a..bdbb3ae87ba 100644 --- a/metricbeat/metricbeat.reference.yml +++ b/metricbeat/metricbeat.reference.yml @@ -556,7 +556,7 @@ metricbeat.modules: #--------------------------------- Kvm Module --------------------------------- - module: kvm - metricsets: ["dommemstat"] + metricsets: ["dommemstat", "status"] enabled: true period: 10s hosts: ["unix:///var/run/libvirt/libvirt-sock"] diff --git a/metricbeat/module/kvm/_meta/config.reference.yml b/metricbeat/module/kvm/_meta/config.reference.yml index 79f754a52cb..84e584787e1 100644 --- a/metricbeat/module/kvm/_meta/config.reference.yml +++ b/metricbeat/module/kvm/_meta/config.reference.yml @@ -1,5 +1,5 @@ - module: kvm - metricsets: ["dommemstat"] + metricsets: ["dommemstat", "status"] enabled: true period: 10s hosts: ["unix:///var/run/libvirt/libvirt-sock"] diff --git a/metricbeat/module/kvm/_meta/config.yml b/metricbeat/module/kvm/_meta/config.yml index 2df123ca14d..78509f63aa4 100644 --- a/metricbeat/module/kvm/_meta/config.yml +++ b/metricbeat/module/kvm/_meta/config.yml @@ -1,5 +1,6 @@ - module: kvm #metricsets: # - dommemstat + # - status period: 10s hosts: ["unix:///var/run/libvirt/libvirt-sock"] diff --git a/metricbeat/module/kvm/fields.go b/metricbeat/module/kvm/fields.go index 456017804b4..4f509af839a 100644 --- a/metricbeat/module/kvm/fields.go +++ b/metricbeat/module/kvm/fields.go @@ -32,5 +32,5 @@ func init() { // AssetKvm returns asset data. // This is the base64 encoded gzipped contents of module/kvm. func AssetKvm() string { - return "eJyskDFuwzAMRXed4iN7LqChU9ceQq3YQLBoGrLsQrcvnEZFotByh3Lw4G+//8gzBioWw8oGyCFHsjgNK58MkCiSm8ninbIzgKf5I4UpBxktXgyA7T+w+CWSAT4DRT/ba3DG6JgqeJtcJrK4JFmm2xuF9wi5B3lhJp6zy7+Rxtzl3iKN0u5Zp1V5FGogPaFDrZ95I5ZUdLDucu+zPZW4Og1UviR59YtDs8Zur6uqrC4uPZco4+V/RLSmahGel+3UH1a/CrswPlO79+/f/q+dV/R3AAAA///hct15" + return "eJzckzFuxCAQRXtO8bX9XoAiVdocgoTJCpkxFmBHvn3k7BLZeBZHStLsFC485v0HZs7oaNboJlZAdtmTxqmb+KSASJ5MIo1XykYBltJbdEN2odd4UgCWdeBgR08KeHfkbdJfjTN6w1TAS+V5II1LDONweyPwtpA1yAZm4pRN/m5JzLvcW0ui1PssVatshSpIS+hQ61ovxCHOMlh2WfssT6FdnDqaP0K04heHZpXdvayiMhk/tlx86C9/IyIlFQu332wj/jD6ObBx/Z7aPP/22f80c4Ne378x/WYYdoTHGIRl2b9Ogr3+FSnnQe/eZwAAAP//6Q5sWg==" } diff --git a/metricbeat/module/kvm/status/_meta/data.json b/metricbeat/module/kvm/status/_meta/data.json new file mode 100644 index 00000000000..fb7fd3c3595 --- /dev/null +++ b/metricbeat/module/kvm/status/_meta/data.json @@ -0,0 +1,28 @@ +{ + "@timestamp": "2017-10-12T08:05:34.853Z", + "agent": { + "hostname": "host.example.com", + "name": "host.example.com" + }, + "event":{ + "dataset":"kvm.status", + "module":"kvm", + "duration":4012216 + }, + "metricset":{ + "name":"status" + }, + "service":{ + "address":"unix:///var/run/libvirt/libvirt-sock", + "type":"kvm" + }, + "kvm":{ + "status":{ + "stat":{ + "state":"running" + }, + "id":1, + "name":"generic-2" + } + } +} diff --git a/metricbeat/module/kvm/status/_meta/docs.asciidoc b/metricbeat/module/kvm/status/_meta/docs.asciidoc new file mode 100644 index 00000000000..b94f0f0f147 --- /dev/null +++ b/metricbeat/module/kvm/status/_meta/docs.asciidoc @@ -0,0 +1 @@ +This is the status metricset of the module kvm. diff --git a/metricbeat/module/kvm/status/_meta/fields.yml b/metricbeat/module/kvm/status/_meta/fields.yml new file mode 100644 index 00000000000..9f75085f2cf --- /dev/null +++ b/metricbeat/module/kvm/status/_meta/fields.yml @@ -0,0 +1,23 @@ +- name: status + type: group + description: > + status + release: beta + fields: + - name: stat + type: group + description: > + Memory stat + fields: + - name: state + type: keyword + description: > + domain state + - name: id + type: long + description: > + Domain id + - name: name + type: keyword + description: > + Domain name diff --git a/metricbeat/module/kvm/status/status.go b/metricbeat/module/kvm/status/status.go new file mode 100644 index 00000000000..eab1b947d7f --- /dev/null +++ b/metricbeat/module/kvm/status/status.go @@ -0,0 +1,161 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package status + +import ( + "net" + "net/url" + "time" + + "github.com/pkg/errors" + + "github.com/digitalocean/go-libvirt" + "github.com/digitalocean/go-libvirt/libvirttest" + + "github.com/elastic/beats/v7/libbeat/common" + "github.com/elastic/beats/v7/libbeat/common/cfgwarn" + "github.com/elastic/beats/v7/metricbeat/mb" +) + +// init registers the MetricSet with the central registry as soon as the program +// starts. The New function will be called later to instantiate an instance of +// the MetricSet for each host defined in the module's configuration. After the +// MetricSet has been created then Fetch will begin to be called periodically. +func init() { + mb.Registry.MustAddMetricSet("kvm", "status", New, + mb.DefaultMetricSet(), + ) +} + +// MetricSet holds any configuration or state information. It must implement +// the mb.MetricSet interface. And this is best achieved by embedding +// mb.BaseMetricSet because it implements all of the required mb.MetricSet +// interface methods except for Fetch. +type MetricSet struct { + mb.BaseMetricSet + Timeout time.Duration + HostURL *url.URL +} + +// New creates a new instance of the MetricSet. New is responsible for unpacking +// any MetricSet specific configuration options if there are any. +func New(base mb.BaseMetricSet) (mb.MetricSet, error) { + cfgwarn.Beta("The kvm status metricset is beta.") + u, err := url.Parse(base.HostData().URI) + if err != nil { + return nil, err + } + + return &MetricSet{ + BaseMetricSet: base, + Timeout: base.Module().Config().Timeout, + HostURL: u, + }, nil +} + +// Fetch methods implements the data gathering and data conversion to the right +// format. It publishes the event which is then forwarded to the output. In case +// of an error set the Error field of mb.Event or simply call report.Error(). +func (m *MetricSet) Fetch(report mb.ReporterV2) error { + var ( + c net.Conn + err error + ) + + u := m.HostURL + + if u.Scheme == "test" { + // when running tests, a mock Libvirt server is used + c = libvirttest.New() + } else { + address := u.Host + if u.Host == "" { + address = u.Path + } + + c, err = net.DialTimeout(u.Scheme, address, m.Timeout) + if err != nil { + return errors.Wrapf(err, "cannot connect to %v", u) + } + } + + defer c.Close() + + l := libvirt.New(c) + if err = l.Connect(); err != nil { + return errors.Wrap(err, "error connecting to libvirtd") + } + defer func() { + if err = l.Disconnect(); err != nil { + msg := errors.Wrap(err, "failed to disconnect") + report.Error(msg) + m.Logger().Error(msg) + } + }() + + domains, err := l.Domains() + if err != nil { + return errors.Wrap(err, "error listing domains") + } + + for _, d := range domains { + state, err := l.DomainState(d.Name) + if err != nil { + continue + } + reported := report.Event(mb.Event{ + ModuleFields: common.MapStr{ + "id": d.ID, + "name": d.Name, + }, + MetricSetFields: common.MapStr{ + "stat": common.MapStr{ + "state": getDomainStateName(state), + }, + }, + }) + if !reported { + return nil + } + } + + return nil +} + +func getDomainStateName(tag libvirt.DomainState) string { + switch tag { + case 0: + return "no state" + case 1: + return "running" + case 2: + return "blocked" + case 3: + return "paused" + case 4: + return "shutdown" + case 5: + return "shutoff" + case 6: + return "crashed" + case 7: + return "suspended" + default: + return "unidentified" + } +} diff --git a/metricbeat/module/kvm/status/status_test.go b/metricbeat/module/kvm/status/status_test.go new file mode 100644 index 00000000000..e484cd775be --- /dev/null +++ b/metricbeat/module/kvm/status/status_test.go @@ -0,0 +1,69 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package status + +import ( + "testing" + + "github.com/digitalocean/go-libvirt/libvirttest" + "github.com/stretchr/testify/assert" + + mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing" +) + +func TestFetchEventContents(t *testing.T) { + conn := libvirttest.New() + + f := mbtest.NewReportingMetricSetV2Error(t, getConfig(conn)) + + events, errs := mbtest.ReportingFetchV2Error(f) + if len(errs) > 0 { + t.Fatal(errs) + } + if len(events) == 0 { + t.Fatal("no events received") + } + + for _, e := range events { + if e.Error != nil { + t.Fatalf("received error: %+v", e.Error) + } + } + if len(events) == 0 { + t.Fatal("received no events") + } + + e := events[0] + + t.Logf("%s/%s event: %+v", f.Module().Name(), f.Name(), e) + + statName, err := e.MetricSetFields.GetValue("stat.state") + if err == nil { + assert.EqualValues(t, statName.(string), "running") + } else { + t.Errorf("error while getting value from event: %v", err) + } +} + +func getConfig(conn *libvirttest.MockLibvirt) map[string]interface{} { + return map[string]interface{}{ + "module": "kvm", + "metricsets": []string{"status"}, + "hosts": []string{"test://" + conn.RemoteAddr().String() + ":123"}, + } +} diff --git a/metricbeat/modules.d/kvm.yml.disabled b/metricbeat/modules.d/kvm.yml.disabled index 878e279b969..8450e1afc6d 100644 --- a/metricbeat/modules.d/kvm.yml.disabled +++ b/metricbeat/modules.d/kvm.yml.disabled @@ -4,5 +4,6 @@ - module: kvm #metricsets: # - dommemstat + # - status period: 10s hosts: ["unix:///var/run/libvirt/libvirt-sock"] diff --git a/x-pack/metricbeat/metricbeat.reference.yml b/x-pack/metricbeat/metricbeat.reference.yml index 3fba65dbee6..d736b844393 100644 --- a/x-pack/metricbeat/metricbeat.reference.yml +++ b/x-pack/metricbeat/metricbeat.reference.yml @@ -843,7 +843,7 @@ metricbeat.modules: #--------------------------------- Kvm Module --------------------------------- - module: kvm - metricsets: ["dommemstat"] + metricsets: ["dommemstat", "status"] enabled: true period: 10s hosts: ["unix:///var/run/libvirt/libvirt-sock"] From 41b9f8679e05573a4283d6fb7590bcacae3e2171 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Tue, 28 Apr 2020 15:10:55 +0100 Subject: [PATCH 039/116] ci: comment PRs with the build status (#17971) --- Jenkinsfile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Jenkinsfile b/Jenkinsfile index 27ddf9dfe03..c2b5c94fed6 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -593,6 +593,11 @@ pipeline { } } } + post { + cleanup { + notifyBuildResult(prComment: true) + } + } } def makeTarget(String context, String target, boolean clean = true) { From 3df04660a67aa9c303400726e1cf440ae667aa13 Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Tue, 28 Apr 2020 15:07:17 -0400 Subject: [PATCH 040/116] Set the es_beats path when calling create_metricset.py. (#18059) --- metricbeat/scripts/mage/target/metricset/metricset.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/metricbeat/scripts/mage/target/metricset/metricset.go b/metricbeat/scripts/mage/target/metricset/metricset.go index b1899e9352a..46bd2171295 100644 --- a/metricbeat/scripts/mage/target/metricset/metricset.go +++ b/metricbeat/scripts/mage/target/metricset/metricset.go @@ -48,7 +48,8 @@ func CreateMetricset() error { _, err = sh.Exec( map[string]string{}, os.Stdout, os.Stderr, python, scriptPath, - "--path", devtools.CWD(), "--module", os.Getenv("MODULE"), "--metricset", os.Getenv("METRICSET"), + "--path", devtools.CWD(), "--es_beats", beatsDir, + "--module", os.Getenv("MODULE"), "--metricset", os.Getenv("METRICSET"), ) return err } From 0524005985cd44c89105ee4a775cab088f54a738 Mon Sep 17 00:00:00 2001 From: Andrew Stucki Date: Tue, 28 Apr 2020 15:13:00 -0400 Subject: [PATCH 041/116] [Filebeat] Update docs for unix sockets (#18009) * update docs for unix sockets * Update filebeat/docs/inputs/input-unix.asciidoc Co-Authored-By: Andrew Kroh * Add socket cleanup code, and socket ownership modification * rearrange imports * updated docs * Switch to group chown and chmod only * Fix docs * Bypass refusal check for windows due to Windows unix socket buf for FileMode Co-authored-by: Andrew Kroh --- .../inputs/input-common-unix-options.asciidoc | 16 ++++ filebeat/docs/inputs/input-syslog.asciidoc | 2 + filebeat/docs/inputs/input-unix.asciidoc | 35 ++++++++ filebeat/input/syslog/config.go | 3 + filebeat/input/unix/input.go | 2 + filebeat/inputsource/unix/config.go | 2 + filebeat/inputsource/unix/server.go | 84 +++++++++++++++++ filebeat/inputsource/unix/server_test.go | 90 +++++++++++++++++++ libbeat/common/seccomp/policy_linux_386.go | 1 + libbeat/common/seccomp/policy_linux_amd64.go | 1 + 10 files changed, 236 insertions(+) create mode 100644 filebeat/docs/inputs/input-unix.asciidoc diff --git a/filebeat/docs/inputs/input-common-unix-options.asciidoc b/filebeat/docs/inputs/input-common-unix-options.asciidoc index 443fe761274..f73278944a6 100644 --- a/filebeat/docs/inputs/input-common-unix-options.asciidoc +++ b/filebeat/docs/inputs/input-common-unix-options.asciidoc @@ -16,6 +16,22 @@ The maximum size of the message received over the socket. The default is `20MiB` The path to the Unix socket that will receive event streams. +[float] +[id="{beatname_lc}-input-{type}-unix-group"] +==== `group` + +The group ownership of the Unix socket that will be created by Filebeat. +The default is the primary group name for the user Filebeat is running as. +This option is ignored on Windows. + +[float] +[id="{beatname_lc}-input-{type}-unix-mode"] +==== `mode` + +The file mode of the Unix socket that will be created by Filebeat. This is +expected to be a file mode as an octal string. The default value is the system +default (generally `0755`). + [float] [id="{beatname_lc}-input-{type}-unix-line-delimiter"] ==== `line_delimiter` diff --git a/filebeat/docs/inputs/input-syslog.asciidoc b/filebeat/docs/inputs/input-syslog.asciidoc index 0c360a03f7f..f9a24c04b81 100644 --- a/filebeat/docs/inputs/input-syslog.asciidoc +++ b/filebeat/docs/inputs/input-syslog.asciidoc @@ -51,6 +51,8 @@ include::../inputs/input-common-tcp-options.asciidoc[] ===== Protocol `unix`: +beta[] + include::../inputs/input-common-unix-options.asciidoc[] [id="{beatname_lc}-input-{type}-common-options"] diff --git a/filebeat/docs/inputs/input-unix.asciidoc b/filebeat/docs/inputs/input-unix.asciidoc new file mode 100644 index 00000000000..a2f445159b7 --- /dev/null +++ b/filebeat/docs/inputs/input-unix.asciidoc @@ -0,0 +1,35 @@ +:type: unix + +[id="{beatname_lc}-input-{type}"] +=== Unix input + +beta[] + +++++ +Unix +++++ + +Use the `unix` input to read events over a stream-oriented Unix domain socket. + +Example configuration: + +["source","yaml",subs="attributes"] +---- +{beatname_lc}.inputs: +- type: unix + max_message_size: 10MiB + path: "/var/run/filebeat.sock" +---- + + +==== Configuration options + +The `unix` input supports the following configuration options plus the +<<{beatname_lc}-input-{type}-common-options>> described later. + +include::../inputs/input-common-unix-options.asciidoc[] + +[id="{beatname_lc}-input-{type}-common-options"] +include::../inputs/input-common-options.asciidoc[] + +:type!: diff --git a/filebeat/input/syslog/config.go b/filebeat/input/syslog/config.go index 5b6ac1452b4..ff009bfb1dd 100644 --- a/filebeat/input/syslog/config.go +++ b/filebeat/input/syslog/config.go @@ -30,6 +30,7 @@ import ( "github.com/elastic/beats/v7/filebeat/inputsource/udp" "github.com/elastic/beats/v7/filebeat/inputsource/unix" "github.com/elastic/beats/v7/libbeat/common" + "github.com/elastic/beats/v7/libbeat/common/cfgwarn" "github.com/elastic/beats/v7/libbeat/logp" ) @@ -98,6 +99,8 @@ func factory( return tcp.New(&config.Config, factory) case unix.Name: + cfgwarn.Beta("Syslog Unix socket support is beta.") + config := defaultUnix if err := cfg.Unpack(&config); err != nil { return nil, err diff --git a/filebeat/input/unix/input.go b/filebeat/input/unix/input.go index 12c091f00da..19609cb5ab8 100644 --- a/filebeat/input/unix/input.go +++ b/filebeat/input/unix/input.go @@ -30,6 +30,7 @@ import ( "github.com/elastic/beats/v7/filebeat/inputsource/unix" "github.com/elastic/beats/v7/libbeat/beat" "github.com/elastic/beats/v7/libbeat/common" + "github.com/elastic/beats/v7/libbeat/common/cfgwarn" "github.com/elastic/beats/v7/libbeat/logp" ) @@ -56,6 +57,7 @@ func NewInput( connector channel.Connector, context input.Context, ) (input.Input, error) { + cfgwarn.Beta("Unix socket support is beta.") out, err := connector.ConnectWith(cfg, beat.ClientConfig{ Processing: beat.ProcessingConfig{ diff --git a/filebeat/inputsource/unix/config.go b/filebeat/inputsource/unix/config.go index 79b2a43dd08..5051ab86e75 100644 --- a/filebeat/inputsource/unix/config.go +++ b/filebeat/inputsource/unix/config.go @@ -30,6 +30,8 @@ const Name = "unix" // Config exposes the unix configuration. type Config struct { Path string `config:"path"` + Group *string `config:"group"` + Mode *string `config:"mode"` Timeout time.Duration `config:"timeout" validate:"nonzero,positive"` MaxMessageSize cfgtype.ByteSize `config:"max_message_size" validate:"nonzero,positive"` MaxConnections int `config:"max_connections"` diff --git a/filebeat/inputsource/unix/server.go b/filebeat/inputsource/unix/server.go index 965a3300282..ee9a0f4564d 100644 --- a/filebeat/inputsource/unix/server.go +++ b/filebeat/inputsource/unix/server.go @@ -20,10 +20,16 @@ package unix import ( "fmt" "net" + "os" + "os/user" + "runtime" + "strconv" + "github.com/pkg/errors" "golang.org/x/net/netutil" "github.com/elastic/beats/v7/filebeat/inputsource/common" + "github.com/elastic/beats/v7/libbeat/logp" ) // Server represent a unix server @@ -55,13 +61,91 @@ func New( } func (s *Server) createServer() (net.Listener, error) { + if err := s.cleanupStaleSocket(); err != nil { + return nil, err + } + l, err := net.Listen("unix", s.config.Path) if err != nil { return nil, err } + if err := s.setSocketOwnership(); err != nil { + return nil, err + } + + if err := s.setSocketMode(); err != nil { + return nil, err + } + if s.config.MaxConnections > 0 { return netutil.LimitListener(l, s.config.MaxConnections), nil } return l, nil } + +func (s *Server) cleanupStaleSocket() error { + path := s.config.Path + info, err := os.Lstat(path) + if err != nil { + // If the file does not exist, then the cleanup can be considered successful. + if os.IsNotExist(err) { + return nil + } + return errors.Wrapf(err, "cannot lstat unix socket file at location %s", path) + } + + if runtime.GOOS != "windows" { + // see https://github.com/golang/go/issues/33357 for context on Windows socket file attributes bug + if info.Mode()&os.ModeSocket == 0 { + return fmt.Errorf("refusing to remove file at location %s, it is not a socket", path) + } + } + + if err := os.Remove(path); err != nil { + return errors.Wrapf(err, "cannot remove existing unix socket file at location %s", path) + } + + return nil +} + +func (s *Server) setSocketOwnership() error { + if s.config.Group != nil { + if runtime.GOOS == "windows" { + logp.NewLogger("unix").Warn("windows does not support the 'group' configuration option, ignoring") + return nil + } + g, err := user.LookupGroup(*s.config.Group) + if err != nil { + return err + } + gid, err := strconv.Atoi(g.Gid) + if err != nil { + return err + } + return os.Chown(s.config.Path, -1, gid) + } + return nil +} + +func (s *Server) setSocketMode() error { + if s.config.Mode != nil { + mode, err := parseFileMode(*s.config.Mode) + if err != nil { + return err + } + return os.Chmod(s.config.Path, mode) + } + return nil +} + +func parseFileMode(mode string) (os.FileMode, error) { + parsed, err := strconv.ParseUint(mode, 8, 32) + if err != nil { + return 0, err + } + if parsed > 0777 { + return 0, errors.New("invalid file mode") + } + return os.FileMode(parsed), nil +} diff --git a/filebeat/inputsource/unix/server_test.go b/filebeat/inputsource/unix/server_test.go index 36e75c757e9..a9043d14a8e 100644 --- a/filebeat/inputsource/unix/server_test.go +++ b/filebeat/inputsource/unix/server_test.go @@ -23,7 +23,10 @@ import ( "math/rand" "net" "os" + "os/user" "path/filepath" + "runtime" + "strconv" "strings" "testing" "time" @@ -35,6 +38,7 @@ import ( "github.com/elastic/beats/v7/filebeat/inputsource" netcommon "github.com/elastic/beats/v7/filebeat/inputsource/common" "github.com/elastic/beats/v7/libbeat/common" + "github.com/elastic/beats/v7/libbeat/common/file" "github.com/elastic/beats/v7/libbeat/logp" ) @@ -207,6 +211,92 @@ func TestReceiveEventsAndMetadata(t *testing.T) { } } +func TestSocketOwnershipAndMode(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("changing socket ownership is only supported on non-windows") + return + } + + groups, err := os.Getgroups() + require.NoError(t, err) + + if len(groups) <= 1 { + t.Skip("no group that we can change to") + return + } + + group, err := user.LookupGroupId(strconv.Itoa(groups[1])) + require.NoError(t, err) + + path := filepath.Join(os.TempDir(), "test.sock") + cfg, _ := common.NewConfigFrom(map[string]interface{}{ + "path": path, + "group": group.Name, + "mode": "0740", + }) + config := defaultConfig + err = cfg.Unpack(&config) + require.NoError(t, err) + + factory := netcommon.SplitHandlerFactory(netcommon.FamilyUnix, logp.NewLogger("test"), MetadataCallback, nil, netcommon.SplitFunc([]byte("\n"))) + server, err := New(&config, factory) + require.NoError(t, err) + err = server.Start() + require.NoError(t, err) + defer server.Stop() + + info, err := file.Lstat(path) + require.NoError(t, err) + require.NotEqual(t, 0, info.Mode()&os.ModeSocket) + require.Equal(t, os.FileMode(0740), info.Mode().Perm()) + gid, err := info.GID() + require.NoError(t, err) + require.Equal(t, group.Gid, strconv.Itoa(gid)) +} + +func TestSocketCleanup(t *testing.T) { + path := filepath.Join(os.TempDir(), "test.sock") + mockStaleSocket, err := net.Listen("unix", path) + require.NoError(t, err) + defer mockStaleSocket.Close() + + cfg, _ := common.NewConfigFrom(map[string]interface{}{ + "path": path, + }) + config := defaultConfig + require.NoError(t, cfg.Unpack(&config)) + factory := netcommon.SplitHandlerFactory(netcommon.FamilyUnix, logp.NewLogger("test"), MetadataCallback, nil, netcommon.SplitFunc([]byte("\n"))) + server, err := New(&config, factory) + require.NoError(t, err) + err = server.Start() + require.NoError(t, err) + server.Stop() +} + +func TestSocketCleanupRefusal(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("skipping due to windows FileAttributes bug https://github.com/golang/go/issues/33357") + return + } + path := filepath.Join(os.TempDir(), "test.sock") + f, err := os.Create(path) + require.NoError(t, err) + require.NoError(t, f.Close()) + defer os.Remove(path) + + cfg, _ := common.NewConfigFrom(map[string]interface{}{ + "path": path, + }) + config := defaultConfig + require.NoError(t, cfg.Unpack(&config)) + factory := netcommon.SplitHandlerFactory(netcommon.FamilyUnix, logp.NewLogger("test"), MetadataCallback, nil, netcommon.SplitFunc([]byte("\n"))) + server, err := New(&config, factory) + require.NoError(t, err) + err = server.Start() + require.Error(t, err) + require.Contains(t, err.Error(), "refusing to remove file at location") +} + func TestReceiveNewEventsConcurrently(t *testing.T) { workers := 4 eventsCount := 100 diff --git a/libbeat/common/seccomp/policy_linux_386.go b/libbeat/common/seccomp/policy_linux_386.go index 76b24714cac..acbc69ddd1f 100644 --- a/libbeat/common/seccomp/policy_linux_386.go +++ b/libbeat/common/seccomp/policy_linux_386.go @@ -32,6 +32,7 @@ func init() { "access", "brk", "chmod", + "chown", "clock_gettime", "clone", "close", diff --git a/libbeat/common/seccomp/policy_linux_amd64.go b/libbeat/common/seccomp/policy_linux_amd64.go index 92b5fbe488a..bf1e4bc31c5 100644 --- a/libbeat/common/seccomp/policy_linux_amd64.go +++ b/libbeat/common/seccomp/policy_linux_amd64.go @@ -35,6 +35,7 @@ func init() { "bind", "brk", "chmod", + "chown", "clock_gettime", "clone", "close", From 2b686e59b6538156efbba51b213d9c893ec74526 Mon Sep 17 00:00:00 2001 From: kaiyan-sheng Date: Tue, 28 Apr 2020 13:42:28 -0600 Subject: [PATCH 042/116] Document all ECS fields in fields.epr.yml files for aws Filebeat module (#17990) * Document all ECS fields in fields.epr.yml files --- filebeat/docs/fields.asciidoc | 12 ++ .../aws/cloudtrail/_meta/fields.epr.yml | 45 +++++++ .../module/aws/cloudwatch/_meta/fields.yml | 4 + .../module/aws/cloudwatch/ingest/pipeline.yml | 4 +- .../test/cloudwatch_ec2.log-expected.json | 18 ++- .../module/aws/ec2/_meta/fields.epr.yml | 3 + .../module/aws/elb/_meta/fields.epr.yml | 78 +++++++++++ .../filebeat/module/aws/elb/_meta/fields.yml | 2 +- x-pack/filebeat/module/aws/fields.go | 2 +- .../module/aws/s3access/_meta/fields.epr.yml | 90 +++++++++++++ .../module/aws/vpcflow/_meta/fields.epr.yml | 123 ++++++++++++++++++ 11 files changed, 371 insertions(+), 10 deletions(-) create mode 100644 x-pack/filebeat/module/aws/cloudtrail/_meta/fields.epr.yml create mode 100644 x-pack/filebeat/module/aws/ec2/_meta/fields.epr.yml create mode 100644 x-pack/filebeat/module/aws/elb/_meta/fields.epr.yml create mode 100644 x-pack/filebeat/module/aws/s3access/_meta/fields.epr.yml create mode 100644 x-pack/filebeat/module/aws/vpcflow/_meta/fields.epr.yml diff --git a/filebeat/docs/fields.asciidoc b/filebeat/docs/fields.asciidoc index b1412d99429..bc2db15417d 100644 --- a/filebeat/docs/fields.asciidoc +++ b/filebeat/docs/fields.asciidoc @@ -1405,6 +1405,17 @@ type: boolean Fields for AWS CloudWatch logs. + +*`aws.cloudwatch.message`*:: ++ +-- +CloudWatch log message. + + +type: text + +-- + [float] === ec2 @@ -1664,6 +1675,7 @@ type: keyword -- The error reason if the executed action failed. + type: keyword -- diff --git a/x-pack/filebeat/module/aws/cloudtrail/_meta/fields.epr.yml b/x-pack/filebeat/module/aws/cloudtrail/_meta/fields.epr.yml new file mode 100644 index 00000000000..91c417b502a --- /dev/null +++ b/x-pack/filebeat/module/aws/cloudtrail/_meta/fields.epr.yml @@ -0,0 +1,45 @@ +- name: event.action + type: keyword + description: The action captured by the event. +- name: event.original + type: keyword + description: Raw text message of entire event. Used to demonstrate log integrity. +- name: user.name + type: keyword + description: Short name or login of the user. +- name: user.id + type: keyword + description: Unique identifier of the user. +- name: cloud.account.id + type: keyword + description: The cloud account or organization id used to identify different entities in a multi-tenant environment. +- name: event.provider + type: keyword + description: Source of the event. +- name: cloud.region + type: keyword + description: Region in which this host is running. +- name: source.address + type: keyword + description: Some event source addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the .address field. +- name: source.ip + type: ip + description: IP address of the source (IPv4 or IPv6). +- name: user_agent.device.name + type: keyword + description: Name of the device. +- name: user_agent.name + type: keyword + description: Name of the user agent. +- name: user_agent.original + type: keyword + description: Unparsed user_agent string. +- name: related.user + type: keyword + description: All the user names seen on your event. +- name: event.kind + type: keyword + description: Event kind (e.g. event, alert, metric, state, pipeline_error, signal) +- name: event.type + type: keyword + description: Event severity (e.g. info, error) diff --git a/x-pack/filebeat/module/aws/cloudwatch/_meta/fields.yml b/x-pack/filebeat/module/aws/cloudwatch/_meta/fields.yml index 844c13309d6..7d80e27ed15 100644 --- a/x-pack/filebeat/module/aws/cloudwatch/_meta/fields.yml +++ b/x-pack/filebeat/module/aws/cloudwatch/_meta/fields.yml @@ -5,3 +5,7 @@ description: > Fields for AWS CloudWatch logs. fields: + - name: message + type: text + description: > + CloudWatch log message. diff --git a/x-pack/filebeat/module/aws/cloudwatch/ingest/pipeline.yml b/x-pack/filebeat/module/aws/cloudwatch/ingest/pipeline.yml index d1f65f3ba85..ff7e20d1c3d 100644 --- a/x-pack/filebeat/module/aws/cloudwatch/ingest/pipeline.yml +++ b/x-pack/filebeat/module/aws/cloudwatch/ingest/pipeline.yml @@ -4,8 +4,8 @@ processors: - grok: field: message patterns: - - "%{TIMESTAMP_ISO8601:_tmp.timestamp} %{SYSLOGTIMESTAMP:_tmp.syslog_timestamp} %{GREEDYDATA:message}" - - "%{TIMESTAMP_ISO8601:_tmp.timestamp} %{GREEDYDATA:message}" + - "%{TIMESTAMP_ISO8601:_tmp.timestamp} %{SYSLOGTIMESTAMP:_tmp.syslog_timestamp} %{GREEDYDATA:aws.cloudwatch.message}" + - "%{TIMESTAMP_ISO8601:_tmp.timestamp} %{GREEDYDATA:aws.cloudwatch.message}" - date: field: '_tmp.timestamp' diff --git a/x-pack/filebeat/module/aws/cloudwatch/test/cloudwatch_ec2.log-expected.json b/x-pack/filebeat/module/aws/cloudwatch/test/cloudwatch_ec2.log-expected.json index 11d33c51e0b..bdc8b0c3a72 100644 --- a/x-pack/filebeat/module/aws/cloudwatch/test/cloudwatch_ec2.log-expected.json +++ b/x-pack/filebeat/module/aws/cloudwatch/test/cloudwatch_ec2.log-expected.json @@ -1,62 +1,68 @@ [ { "@timestamp": "2020-02-20T07:01:01.000Z", + "aws.cloudwatch.message": "ip-172-31-81-156 systemd: Stopping User Slice of root.", "event.dataset": "aws.cloudwatch", "event.module": "aws", "fileset.name": "cloudwatch", "input.type": "log", "log.offset": 0, - "message": "ip-172-31-81-156 systemd: Stopping User Slice of root.", + "message": "2020-02-20T07:01:01.000Z Feb 20 07:01:01 ip-172-31-81-156 systemd: Stopping User Slice of root.", "service.type": "aws" }, { "@timestamp": "2020-02-20T07:02:18.000Z", + "aws.cloudwatch.message": "ip-172-31-81-156 dhclient[3000]: XMT: Solicit on eth0, interval 125240ms.", "event.dataset": "aws.cloudwatch", "event.module": "aws", "fileset.name": "cloudwatch", "input.type": "log", "log.offset": 96, - "message": "ip-172-31-81-156 dhclient[3000]: XMT: Solicit on eth0, interval 125240ms.", + "message": "2020-02-20T07:02:18.000Z Feb 20 07:02:18 ip-172-31-81-156 dhclient[3000]: XMT: Solicit on eth0, interval 125240ms.", "service.type": "aws" }, { "@timestamp": "2020-02-20T07:02:37.000Z", + "aws.cloudwatch.message": "ip-172-31-81-156 dhclient[2898]: DHCPREQUEST on eth0 to 172.31.80.1 port 67 (xid=0x4575af22)", "event.dataset": "aws.cloudwatch", "event.module": "aws", "fileset.name": "cloudwatch", "input.type": "log", "log.offset": 211, - "message": "ip-172-31-81-156 dhclient[2898]: DHCPREQUEST on eth0 to 172.31.80.1 port 67 (xid=0x4575af22)", + "message": "2020-02-20T07:02:37.000Z Feb 20 07:02:37 ip-172-31-81-156 dhclient[2898]: DHCPREQUEST on eth0 to 172.31.80.1 port 67 (xid=0x4575af22)", "service.type": "aws" }, { "@timestamp": "2020-02-20T07:02:37.000Z", + "aws.cloudwatch.message": "ip-172-31-81-156 dhclient[2898]: DHCPACK from 172.31.80.1 (xid=0x4575af22)", "event.dataset": "aws.cloudwatch", "event.module": "aws", "fileset.name": "cloudwatch", "input.type": "log", "log.offset": 345, - "message": "ip-172-31-81-156 dhclient[2898]: DHCPACK from 172.31.80.1 (xid=0x4575af22)", + "message": "2020-02-20T07:02:37.000Z Feb 20 07:02:37 ip-172-31-81-156 dhclient[2898]: DHCPACK from 172.31.80.1 (xid=0x4575af22)", "service.type": "aws" }, { "@timestamp": "2020-02-20T07:02:37.000Z", + "aws.cloudwatch.message": "ip-172-31-81-156 dhclient[2898]: bound to 172.31.81.156 -- renewal in 1599 seconds.", "event.dataset": "aws.cloudwatch", "event.module": "aws", "fileset.name": "cloudwatch", "input.type": "log", "log.offset": 461, - "message": "ip-172-31-81-156 dhclient[2898]: bound to 172.31.81.156 -- renewal in 1599 seconds.", + "message": "2020-02-20T07:02:37.000Z Feb 20 07:02:37 ip-172-31-81-156 dhclient[2898]: bound to 172.31.81.156 -- renewal in 1599 seconds.", "service.type": "aws" }, { "@timestamp": "2020-02-20T07:02:37.000Z", + "aws.cloudwatch.message": "ip-172-31-81-156 ec2net: [get_meta] Trying to get http://169.254.169.254/latest/meta-data/network/interfaces/macs/12:e2:a9:95:8b:97/local-ipv4s", "event.dataset": "aws.cloudwatch", "event.module": "aws", "fileset.name": "cloudwatch", "input.type": "log", "log.offset": 586, - "message": "ip-172-31-81-156 ec2net: [get_meta] Trying to get http://169.254.169.254/latest/meta-data/network/interfaces/macs/12:e2:a9:95:8b:97/local-ipv4s", + "message": "2020-02-20T07:02:37.000Z Feb 20 07:02:37 ip-172-31-81-156 ec2net: [get_meta] Trying to get http://169.254.169.254/latest/meta-data/network/interfaces/macs/12:e2:a9:95:8b:97/local-ipv4s", "service.type": "aws" } ] \ No newline at end of file diff --git a/x-pack/filebeat/module/aws/ec2/_meta/fields.epr.yml b/x-pack/filebeat/module/aws/ec2/_meta/fields.epr.yml new file mode 100644 index 00000000000..3a22e7a7e80 --- /dev/null +++ b/x-pack/filebeat/module/aws/ec2/_meta/fields.epr.yml @@ -0,0 +1,3 @@ +- name: process.name + type: keyword + description: Process name. diff --git a/x-pack/filebeat/module/aws/elb/_meta/fields.epr.yml b/x-pack/filebeat/module/aws/elb/_meta/fields.epr.yml new file mode 100644 index 00000000000..f548842e70f --- /dev/null +++ b/x-pack/filebeat/module/aws/elb/_meta/fields.epr.yml @@ -0,0 +1,78 @@ +- name: destination.domain + type: keyword + description: Destination domain. +- name: event.start + type: date + description: event.start contains the date when the event started or when the activity was first observed. +- name: destination.bytes + type: long + description: Bytes sent from the destination to the source. +- name: http.response.status_code + type: long + description: HTTP response status code. +- name: http.request.body.bytes + type: long + description: Size in bytes of the request body. +- name: http.response.body.bytes + type: long + description: Size in bytes of the response body. +- name: http.request.method + type: keyword + description: HTTP request method. +- name: http.request.referrer + type: keyword + description: Referrer for this HTTP request. +- name: http.version + type: keyword + description: HTTP version. +- name: user_agent.original + type: keyword + description: Unparsed user_agent string. +- name: cloud.provider + type: keyword + description: Name of the cloud provider. Example values are aws, azure, gcp, or digitalocean. +- name: event.kind + type: keyword + description: Event kind (e.g. event, alert, metric, state, pipeline_error, sig +- name: event.category + type: keyword + description: Event category (e.g. database) +- name: event.outcome + type: keyword + description: This is one of four ECS Categorization Fields, and indicates the lowest level in the ECS category hierarchy. +- name: tracing.trace.id + type: keyword + description: Unique identifier of the trace. +- name: event.end + type: date + description: event.end contains the date when the event ended or when the activity was last observed. +- name: source.ip + type: ip + description: IP address of the source. +- name: source.as.number + type: long + description: Unique number allocated to the autonomous system. The autonomous system number (ASN) uniquely identifies each network on the Internet. +- name: source.as.organization.name + type: keyword + description: Organization name. +- name: source.geo.city_name + type: keyword + description: City name. +- name: source.geo.continent_name + type: keyword + description: Name of the continent. +- name: source.geo.country_iso_code + type: keyword + description: Country ISO code. +- name: source.geo.location + type: geo_point + description: Longitude and latitude. +- name: source.geo.region_iso_code + type: keyword + description: Region ISO code. +- name: source.geo.region_name + type: keyword + description: Region name. +- name: source.port + type: long + description: Port of the source. diff --git a/x-pack/filebeat/module/aws/elb/_meta/fields.yml b/x-pack/filebeat/module/aws/elb/_meta/fields.yml index 8e2597c2d09..9ddfb123901 100644 --- a/x-pack/filebeat/module/aws/elb/_meta/fields.yml +++ b/x-pack/filebeat/module/aws/elb/_meta/fields.yml @@ -98,5 +98,5 @@ The URL used if a redirection action was executed. - name: error.reason type: keyword - description: + description: > The error reason if the executed action failed. diff --git a/x-pack/filebeat/module/aws/fields.go b/x-pack/filebeat/module/aws/fields.go index b2ac0ac3729..22308cf2722 100644 --- a/x-pack/filebeat/module/aws/fields.go +++ b/x-pack/filebeat/module/aws/fields.go @@ -19,5 +19,5 @@ func init() { // AssetAws returns asset data. // This is the base64 encoded gzipped contents of module/aws. func AssetAws() string { - return "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" + return "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" } diff --git a/x-pack/filebeat/module/aws/s3access/_meta/fields.epr.yml b/x-pack/filebeat/module/aws/s3access/_meta/fields.epr.yml new file mode 100644 index 00000000000..5f5693a8279 --- /dev/null +++ b/x-pack/filebeat/module/aws/s3access/_meta/fields.epr.yml @@ -0,0 +1,90 @@ +- name: related.user + type: keyword + description: All the user names seen on your event. +- name: related.ip + type: ip + description: All of the IPs seen on your event. +- name: client.ip + type: ip + description: IP address of the client. +- name: client.address + type: keyword + description: Some event client addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the .address field. +- name: client.user.id + type: keyword + description: Unique identifiers of the user. +- name: event.id + type: keyword + description: Unique ID to describe the event. +- name: event.action + type: keyword + description: The action captured by the event. +- name: http.response.status_code + type: long + description: HTTP response status code. +- name: event.outcome + type: keyword + description: This is one of four ECS Categorization Fields, and indicates the lowest level in the ECS category hierarchy. +- name: event.code + type: keyword + description: Identification code for this event, if one exists. +- name: event.duration + type: long + description: Duration of the event in nanoseconds. +- name: http.request.referrer + type: keyword + description: Referrer for this HTTP request. +- name: tls.cipher + type: keyword + description: String indicating the cipher used during the current connection. +- name: tls.version + type: keyword + description: Numeric part of the version parsed from the original string. +- name: tls.version_protocol + type: keyword + description: Normalized lowercase protocol name parsed from original string. +- name: cloud.provider + type: keyword + description: Name of the cloud provider. Example values are aws, azure, gcp, or digitalocean. +- name: event.kind + type: keyword + description: Event kind (e.g. event, alert, metric, state, pipeline_error, signal) +- name: geo.city_name + type: keyword + description: City name. +- name: geo.continent_name + type: keyword + description: Name of the continent. +- name: geo.country_iso_code + type: keyword + description: Country ISO code. +- name: geo.location + type: geo_point + description: Longitude and latitude. +- name: geo.region_iso_code + type: keyword + description: Region ISO code. +- name: geo.region_name + type: keyword + description: Region name. +- name: user_agent.device.name + type: keyword + description: Name of the device. +- name: user_agent.name + type: keyword + description: Name of the user agent. +- name: user_agent.original + type: keyword + description: Unparsed user_agent string. +- name: user_agent.os.full + type: keyword + description: Operating system name, including the version or code name. +- name: user_agent.os.name + type: keyword + description: Operating system name, without the version. +- name: user_agent.os.version + type: keyword + description: Operating system version as a raw string. +- name: user_agent.version + type: keyword + description: Version of the user agent. diff --git a/x-pack/filebeat/module/aws/vpcflow/_meta/fields.epr.yml b/x-pack/filebeat/module/aws/vpcflow/_meta/fields.epr.yml new file mode 100644 index 00000000000..7293e8090ff --- /dev/null +++ b/x-pack/filebeat/module/aws/vpcflow/_meta/fields.epr.yml @@ -0,0 +1,123 @@ +- name: event.start + type: date + description: event.start contains the date when the event started or when the activity was first observed. +- name: event.end + type: date + description: event.end contains the date when the event ended or when the activity was last observed. +- name: destination.geo.continent_name + type: keyword + description: Name of the continent. +- name: destination.geo.country_iso_code + type: keyword + description: Country ISO code. +- name: destination.geo.location + type: geo_point + description: Longitude and latitude. +- name: destination.ip + type: ip + description: IP address of the destination. +- name: destination.address + type: keyword + description: Some event destination addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the .address field. +- name: destination.port + type: long + description: Port of the destination. +- name: event.category + type: keyword + description: Event category (e.g. database) +- name: event.outcome + type: keyword + description: This is one of four ECS Categorization Fields, and indicates the lowest level in the ECS category hierarchy. +- name: event.type + type: keyword + description: Event severity (e.g. info, error) +- name: source.as.number + type: long + description: Unique number allocated to the autonomous system. The autonomous system number (ASN) uniquely identifies each network on the Internet. +- name: source.as.organization.name + type: keyword + description: Organization name. +- name: destination.as.number + type: long + description: Unique number allocated to the autonomous system. The autonomous system number (ASN) uniquely identifies each network on the Internet. +- name: destination.as.organization.name + type: keyword + description: Organization name. +- name: event.original + type: keyword + description: Raw text message of entire event. Used to demonstrate log integrity. +- name: cloud.account.id + type: keyword + description: The cloud account or organization id used to identify different entities in a multi-tenant environment. +- name: cloud.instance.id + type: keyword + description: Instance ID of the host machine. +- name: cloud.provider + type: keyword + description: Name of the cloud provider. +- name: related.ip + type: ip + description: All of the IPs seen on your event. +- name: event.kind + type: keyword + description: Event kind (e.g. event, alert, metric, state, pipeline_error, signal) +- name: cloud.account.id + type: keyword + description: The cloud account or organization id used to identify different entities in a multi-tenant environment. +- name: network.bytes + type: long + description: Total bytes transferred in both directions. +- name: network.community_id + type: keyword + description: A hash of source and destination IPs and ports, as well as the protocol used in a communication. This is a tool-agnostic standard to identify flows. +- name: network.iana_number + type: keyword + description: IANA Protocol Number (https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml). Standardized list of protocols. This aligns well with NetFlow and sFlow related logs which use the IANA Protocol Number. +- name: network.packets + type: long + description: Total packets transferred in both directions. +- name: network.transport + type: keyword + description: Same as network.iana_number, but instead using the Keyword name of the transport layer (udp, tcp, ipv6-icmp, etc.) +- name: network.type + type: keyword + description: In the OSI Model this would be the Network Layer. ipv4, ipv6, ipsec, pim, etc +- name: source.address + type: keyword + description: Some event source addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the .address field. +- name: source.as.number + type: long + description: Unique number allocated to the autonomous system. The autonomous system number (ASN) uniquely identifies each network on the Internet. +- name: source.as.organization.name + type: keyword + description: Organization name. +- name: source.bytes + type: long + description: Bytes sent from the source to the destination. +- name: source.geo.city_name + type: keyword + description: City name. +- name: source.geo.continent_name + type: keyword + description: Name of the continent. +- name: source.geo.country_iso_code + type: keyword + description: Country ISO code. +- name: source.geo.location + type: geo_point + description: Longitude and latitude. +- name: source.geo.region_iso_code + type: keyword + description: Region ISO code. +- name: source.geo.region_name + type: keyword + description: Region name. +- name: source.ip + type: ip + description: IP address of the source (IPv4 or IPv6). +- name: source.packets + type: long + description: Packets sent from the source to the destination. +- name: source.port + type: long + description: Port of the source. From 7d16a0f6db1c5847adcca44b4299601960fb86ed Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Tue, 28 Apr 2020 17:58:33 -0400 Subject: [PATCH 043/116] Fix update target in metricbeat magefile.go to include collectDocs target (#18062) * Fix update target in metricbeat magefile.go to include collectDocs target. * Add make doc to makefile shim, use collectall instead of collectdocs in update target. --- dev-tools/make/mage.mk | 8 ++++++++ metricbeat/docs/modules/googlecloud.asciidoc | 15 +++++++++------ metricbeat/magefile.go | 3 ++- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/dev-tools/make/mage.mk b/dev-tools/make/mage.mk index e1e634c27bd..6b210832006 100644 --- a/dev-tools/make/mage.mk +++ b/dev-tools/make/mage.mk @@ -67,3 +67,11 @@ update: mage .PHONY: crosscompile crosscompile: mage mage crossBuild + +.PHONY: docs +docs: + mage docs + +.PHONY: docs-preview +docs-preview: + PREVIEW=1 $(MAKE) docs diff --git a/metricbeat/docs/modules/googlecloud.asciidoc b/metricbeat/docs/modules/googlecloud.asciidoc index e323cdc1650..3dcf9b20db9 100644 --- a/metricbeat/docs/modules/googlecloud.asciidoc +++ b/metricbeat/docs/modules/googlecloud.asciidoc @@ -17,15 +17,15 @@ Note: extra GCP charges on Stackdriver Monitoring API requests will be generated This is a list of the possible module parameters you can tune: * *zone*: A single string with the zone you want to monitor like `us-central1-a`. -Or you can specific a partial zone name like `us-central1-`, which will monitor -all zones start with `us-central1-`: `us-central1-a`, `us-central1-b`, -`us-central1-c` and `us-central1-f`. +Or you can specific a partial zone name like `us-central1-` or `us-central1-*`, +which will monitor all zones start with `us-central1-`: `us-central1-a`, +`us-central1-b`, `us-central1-c` and `us-central1-f`. Please see https://cloud.google.com/compute/docs/regions-zones#available[GCP zones] for zones that are available in GCP. * *region*: A single string with the region you want to monitor like `us-central1`. This will enable monitoring for all zones under this region. Or you can specific -a partial region name like `us-east`, which will monitor all regions start with +a partial region name like `us-east` or `us-east*`, which will monitor all regions start with `us-east`: `us-east1` and `us-east4`. If both region and zone are configured, only region will be used. Please see https://cloud.google.com/compute/docs/regions-zones#available[GCP regions] @@ -33,9 +33,12 @@ for regions that are available in GCP. * *project_id*: A single string with your GCP Project ID -* *credentials_file_path*: A single string pointing to the JSON file path reachable by Metricbeat that you have created using IAM. +* *credentials_file_path*: A single string pointing to the JSON file path +reachable by Metricbeat that you have created using IAM. -* *exclude_labels*: (`true`/`false` default `false`) Do not extract extra labels and metadata information from Metricsets and fetch metrics onlly. At the moment, *labels and metadata extraction is only supported* in Compute Metricset. +* *exclude_labels*: (`true`/`false` default `false`) Do not extract extra labels +and metadata information from metricsets and fetch metrics only. At the moment, +*labels and metadata extraction is only supported* in `compute` metricset. * *period*: A single time duration specified for this module collection frequency. diff --git a/metricbeat/magefile.go b/metricbeat/magefile.go index b46dc9c7957..cf7f3154ad8 100644 --- a/metricbeat/magefile.go +++ b/metricbeat/magefile.go @@ -143,7 +143,8 @@ func moduleFieldsGo() error { // Update is an alias for running fields, dashboards, config. func Update() { - mg.SerialDeps(Fields, Dashboards, Config, + mg.SerialDeps( + Fields, Dashboards, Config, CollectAll, metricbeat.PrepareModulePackagingOSS, metricbeat.GenerateOSSMetricbeatModuleIncludeListGo) } From b552dd830f3a37a65b68170bc4916bc8f27ddb48 Mon Sep 17 00:00:00 2001 From: Lee Hinman <57081003+leehinman@users.noreply.github.com> Date: Tue, 28 Apr 2020 20:51:40 -0500 Subject: [PATCH 044/116] [Filebeat] Improve ECS categorization field mappings for zeek module (#17738) * Improve ECS categorization field mappings for zeek module - capture_loss + convert pipeline to yaml + event.kind + event.type - connection + convert pipeline to yaml + event.kind + event.category + event.type + related.ip - dce_rpc + convert pipeline to yaml + event.kind + event.category + event.type + event.action + related.ip + source.geo + destination.geo - dhcp + convert pipeline to yaml + event.kind + event.category + event.type + related.ip - dnp3 + convert pipeline to yaml + event.kind + event.category + event.type + event.action + related.ip + source.geo + destination.geo + source.as + destiantion.as - dns + event.kind + event.category + event.type + event.outcome + dns.question.top_level_domain + related.ip - dpd + convert pipeline to yaml + event.kind + event.category + event.type + source.geo + destiantion.geo + source.as + destiantion.as + related.ip - files + convert pipeline to yaml + event.kind + event.category + event.type + file.mime_type + file.name + file.hash.md5 + file.hash.sha1 + file.hash.sha256 + client.ip + server.ip + related.ip + related.hash - ftp + convert pipeline to yaml + event.kind + event.category + event.type + event.action + user.name + file.mime_type + file.size + related.ip + related.user + source.geo + destination.geo - http + convert pipeline to yaml + event.kind + event.category + event.type + event.outcome + event.action + url.port type to number + http.request.method lowercase + related.ip + related.users - intel + event.kind + event.type + related.ip - irc + convert pipeline to yaml + event.kind + event.category + event.type + event.action + user.name + file.name + file.size + file.mime_type + related.ip + related.user + source.geo + destination.geo - kerberos + convert pipeline to yaml + event.kind + event.category + event.type + event.outcome + event.action + user.name + user.domain + source.geo + destination.geo + related.ip + related.user - modbus + convert pipeline to yaml + event.kind + event.category + event.type + event.outcome + event.action + related.ip + source.geo + destination.geo - mysql + convert pipeline to yaml + event.kind + event.category + event.type + event.outcome + source.geo + destiantion.geo + related.ip - notice + convert pipeline to yaml + event.kind + event.category + event.type + related.ip + file.size + file.mime_type + rule.description + rule.name - ntlm + convert pipeline to yaml + event.kind + event.category + event.type + event.outcome + user.name + user.domain + source.geo + destination.geo + related.ip + related.user - ocsp + convert pipeline to yaml + event.kind + related.hash - pe + convert pipeline to yaml + event.kind + event.category + event.type - radius + convert pipeline to yaml + event.kind + event.category + event.type + event.outcome + user.name + source.geo + destination.geo + related.ip + related.user - rdp + convert pipeline to yaml + event.kind + event.category + event.type + related.ip + source.geo + destination.geo - rfb + convert pipeline to yaml + event.kind + event.category + event.type + related.ip + source.geo + destination.geo - sip + convert pipeline to yaml + event.kind + event.category + event.type + event.outcome + event.action + related.ip + source.geo + destination.geo + url.full - smb_cmd + convert pipeline to yaml + event.kind + event.category + event.type + event.outcome + event.action + related.ip + related.user + source.geo + destination.geo + user.name - smb_files + convert pipeline to yaml + event.kind + event.category + event.type + event.action + related.ip + related.user + source.geo + destination.geo + user.name + file.accessed + file.ctime + file.created + file.mtime + file.path + file.name + file.size - smb_mapping + convert pipeline to yaml + event.kind + event.category + event.type + related.ip + source.geo + destination.geo - smtp + convert pipeline to yaml + event.kind + event.category + event.type + related.ip + source.geo + destination.geo - snmp + convert pipeline to yaml + event.kind + event.category + event.type + related.ip + source.geo + destination.geo - socks + convert pipeline to yaml + event.kind + event.category + event.type + event.outcome + related.ip + source.geo + destination.geo + user.name + related.user - ssh + convert pipeline to yaml + event.kind + event.category + event.type + event.outcome + related.ip + source.geo + destination.geo - ssl + event.kind + event.category + event.type + related.ip - stats + convert pipeline to yaml + event.kind - syslog + convert pipeline to yaml + event.kind + related.ip + source.geo + destination.geo + log.syslog.facility.name + log.syslog.severity.name - traceroute + convert pipeline to yaml + event.kind + event.category + event.type + related.ip + source.geo + destination.geo - tunnel + convert pipeline to yaml + event.kind + event.category + event.type + related.ip + source.geo + destination.geo - weird + convert pipeline to yaml + event.kind + event.category + event.type + related.ip + source.geo + destination.geo + rule.name - x509 + event.kind + event.type Closes #16029 --- CHANGELOG.next.asciidoc | 1 + .../zeek/capture_loss/ingest/pipeline.json | 28 --- .../zeek/capture_loss/ingest/pipeline.yml | 21 ++ .../module/zeek/capture_loss/manifest.yml | 2 +- .../test/capture_loss-json.log-expected.json | 2 + .../zeek/connection/config/connection.yml | 19 +- .../zeek/connection/ingest/pipeline.json | 160 --------------- .../zeek/connection/ingest/pipeline.yml | 187 ++++++++++++++++++ .../module/zeek/connection/manifest.yml | 2 +- .../test/connection-json.log-expected.json | 57 +++++- .../module/zeek/dce_rpc/config/dce_rpc.yml | 18 +- .../module/zeek/dce_rpc/ingest/pipeline.json | 47 ----- .../module/zeek/dce_rpc/ingest/pipeline.yml | 63 ++++++ .../filebeat/module/zeek/dce_rpc/manifest.yml | 2 +- .../test/dce_rpc-json.log-expected.json | 15 ++ .../filebeat/module/zeek/dhcp/config/dhcp.yml | 24 ++- .../module/zeek/dhcp/ingest/pipeline.json | 84 -------- .../module/zeek/dhcp/ingest/pipeline.yml | 27 +++ x-pack/filebeat/module/zeek/dhcp/manifest.yml | 2 +- .../dhcp/test/dhcp-json.log-expected.json | 14 ++ .../filebeat/module/zeek/dnp3/config/dnp3.yml | 20 +- .../module/zeek/dnp3/ingest/pipeline.json | 47 ----- .../module/zeek/dnp3/ingest/pipeline.yml | 64 ++++++ x-pack/filebeat/module/zeek/dnp3/manifest.yml | 2 +- .../dnp3/test/dnp3-json.log-expected.json | 10 + .../filebeat/module/zeek/dns/config/dns.yml | 64 +++++- .../zeek/dns/test/dns-json.log-expected.json | 44 +++++ .../filebeat/module/zeek/dpd/config/dpd.yml | 20 +- .../module/zeek/dpd/ingest/pipeline.json | 47 ----- .../module/zeek/dpd/ingest/pipeline.yml | 63 ++++++ x-pack/filebeat/module/zeek/dpd/manifest.yml | 2 +- .../zeek/dpd/test/dpd-json.log-expected.json | 12 ++ .../module/zeek/files/config/files.yml | 20 +- .../module/zeek/files/ingest/pipeline.json | 57 ------ .../module/zeek/files/ingest/pipeline.yml | 66 +++++++ .../filebeat/module/zeek/files/manifest.yml | 2 +- .../files/test/files-json.log-expected.json | 40 ++++ .../filebeat/module/zeek/ftp/config/ftp.yml | 25 ++- .../module/zeek/ftp/ingest/pipeline.json | 53 ----- .../module/zeek/ftp/ingest/pipeline.yml | 68 +++++++ x-pack/filebeat/module/zeek/ftp/manifest.yml | 2 +- .../zeek/ftp/test/ftp.log-expected.json | 55 ++++++ .../filebeat/module/zeek/http/config/http.yml | 23 ++- .../module/zeek/http/ingest/pipeline.json | 123 ------------ .../module/zeek/http/ingest/pipeline.yml | 82 ++++++++ x-pack/filebeat/module/zeek/http/manifest.yml | 2 +- .../http/test/http-json.log-expected.json | 20 +- .../module/zeek/intel/config/intel.yml | 9 + .../module/zeek/intel/ingest/pipeline.yml | 9 + .../intel/test/intel-json.log-expected.json | 8 + .../filebeat/module/zeek/irc/config/irc.yml | 26 ++- .../module/zeek/irc/ingest/pipeline.json | 47 ----- .../module/zeek/irc/ingest/pipeline.yml | 65 ++++++ x-pack/filebeat/module/zeek/irc/manifest.yml | 2 +- .../zeek/irc/test/irc-json.log-expected.json | 68 +++++++ .../module/zeek/kerberos/config/kerberos.yml | 31 ++- .../module/zeek/kerberos/ingest/pipeline.json | 81 -------- .../module/zeek/kerberos/ingest/pipeline.yml | 90 +++++++++ .../module/zeek/kerberos/manifest.yml | 2 +- .../test/kerberos-json.log-expected.json | 20 ++ .../module/zeek/modbus/config/modbus.yml | 33 +++- .../module/zeek/modbus/ingest/pipeline.json | 47 ----- .../module/zeek/modbus/ingest/pipeline.yml | 63 ++++++ .../filebeat/module/zeek/modbus/manifest.yml | 2 +- .../modbus/test/modbus-json.log-expected.json | 14 ++ .../module/zeek/mysql/config/mysql.yml | 35 +++- .../module/zeek/mysql/ingest/pipeline.json | 47 ----- .../module/zeek/mysql/ingest/pipeline.yml | 83 ++++++++ .../filebeat/module/zeek/mysql/manifest.yml | 2 +- .../mysql/test/mysql-json.log-expected.json | 16 ++ .../module/zeek/notice/config/notice.yml | 22 ++- .../module/zeek/notice/ingest/pipeline.json | 115 ----------- .../module/zeek/notice/ingest/pipeline.yml | 71 +++++++ .../filebeat/module/zeek/notice/manifest.yml | 2 +- .../notice/test/notice-json.log-expected.json | 27 +++ .../filebeat/module/zeek/ntlm/config/ntlm.yml | 37 +++- .../module/zeek/ntlm/ingest/pipeline.json | 47 ----- .../module/zeek/ntlm/ingest/pipeline.yml | 67 +++++++ x-pack/filebeat/module/zeek/ntlm/manifest.yml | 2 +- .../ntlm/test/ntlm-json.log-expected.json | 18 ++ .../filebeat/module/zeek/ocsp/config/ocsp.yml | 4 + .../module/zeek/ocsp/ingest/pipeline.json | 52 ----- .../module/zeek/ocsp/ingest/pipeline.yml | 41 ++++ x-pack/filebeat/module/zeek/ocsp/manifest.yml | 2 +- x-pack/filebeat/module/zeek/pe/config/pe.yml | 8 + .../module/zeek/pe/ingest/pipeline.json | 36 ---- .../module/zeek/pe/ingest/pipeline.yml | 21 ++ x-pack/filebeat/module/zeek/pe/manifest.yml | 2 +- .../zeek/pe/test/pe-json.log-expected.json | 7 + .../module/zeek/radius/config/radius.yml | 21 +- .../module/zeek/radius/ingest/pipeline.json | 47 ----- .../module/zeek/radius/ingest/pipeline.yml | 67 +++++++ .../filebeat/module/zeek/radius/manifest.yml | 2 +- .../radius/test/radius-json.log-expected.json | 18 ++ .../filebeat/module/zeek/rdp/config/rdp.yml | 18 +- .../module/zeek/rdp/ingest/pipeline.json | 55 ------ .../module/zeek/rdp/ingest/pipeline.yml | 68 +++++++ x-pack/filebeat/module/zeek/rdp/manifest.yml | 2 +- .../zeek/rdp/test/rdp-json.log-expected.json | 12 ++ .../filebeat/module/zeek/rfb/config/rfb.yml | 18 +- .../module/zeek/rfb/ingest/pipeline.json | 47 ----- .../module/zeek/rfb/ingest/pipeline.yml | 63 ++++++ x-pack/filebeat/module/zeek/rfb/manifest.yml | 2 +- .../zeek/rfb/test/rfb-json.log-expected.json | 12 ++ .../filebeat/module/zeek/sip/config/sip.yml | 22 ++- .../module/zeek/sip/ingest/pipeline.json | 60 ------ .../module/zeek/sip/ingest/pipeline.yml | 83 ++++++++ x-pack/filebeat/module/zeek/sip/manifest.yml | 2 +- .../zeek/sip/test/sip-json.log-expected.json | 88 +++++++++ .../module/zeek/smb_cmd/config/smb_cmd.yml | 22 ++- .../module/zeek/smb_cmd/ingest/pipeline.json | 53 ----- .../module/zeek/smb_cmd/ingest/pipeline.yml | 82 ++++++++ .../filebeat/module/zeek/smb_cmd/manifest.yml | 2 +- .../test/smb_cmd-json.log-expected.json | 14 ++ .../zeek/smb_files/config/smb_files.yml | 24 ++- .../zeek/smb_files/ingest/pipeline.json | 103 ---------- .../module/zeek/smb_files/ingest/pipeline.yml | 135 +++++++++++++ .../module/zeek/smb_files/manifest.yml | 2 +- .../test/smb_files-json.log-expected.json | 22 +++ .../zeek/smb_mapping/config/smb_mapping.yml | 20 +- .../zeek/smb_mapping/ingest/pipeline.json | 47 ----- .../zeek/smb_mapping/ingest/pipeline.yml | 63 ++++++ .../module/zeek/smb_mapping/manifest.yml | 2 +- .../test/smb_mapping-json.log-expected.json | 12 ++ .../filebeat/module/zeek/smtp/config/smtp.yml | 21 +- .../module/zeek/smtp/ingest/pipeline.json | 63 ------ .../module/zeek/smtp/ingest/pipeline.yml | 69 +++++++ x-pack/filebeat/module/zeek/smtp/manifest.yml | 2 +- .../smtp/test/smtp-json.log-expected.json | 12 ++ .../filebeat/module/zeek/snmp/config/snmp.yml | 20 +- .../module/zeek/snmp/ingest/pipeline.json | 55 ------ .../module/zeek/snmp/ingest/pipeline.yml | 69 +++++++ x-pack/filebeat/module/zeek/snmp/manifest.yml | 2 +- .../snmp/test/snmp-json.log-expected.json | 12 ++ .../module/zeek/socks/config/socks.yml | 21 +- .../module/zeek/socks/ingest/pipeline.json | 53 ----- .../module/zeek/socks/ingest/pipeline.yml | 82 ++++++++ .../filebeat/module/zeek/socks/manifest.yml | 2 +- .../socks/test/socks-json.log-expected.json | 15 ++ .../filebeat/module/zeek/ssh/config/ssh.yml | 18 +- .../module/zeek/ssh/ingest/pipeline.json | 47 ----- .../module/zeek/ssh/ingest/pipeline.yml | 71 +++++++ x-pack/filebeat/module/zeek/ssh/manifest.yml | 2 +- .../zeek/ssh/test/ssh-json.log-expected.json | 13 ++ .../filebeat/module/zeek/ssl/config/ssl.yml | 22 ++- .../module/zeek/ssl/ingest/pipeline.yml | 21 +- .../zeek/ssl/test/ssl-json.log-expected.json | 26 +++ .../module/zeek/stats/ingest/pipeline.json | 28 --- .../module/zeek/stats/ingest/pipeline.yml | 18 ++ .../filebeat/module/zeek/stats/manifest.yml | 2 +- .../stats/test/stats-json.log-expected.json | 1 + .../module/zeek/syslog/config/syslog.yml | 15 +- .../module/zeek/syslog/ingest/pipeline.json | 47 ----- .../module/zeek/syslog/ingest/pipeline.yml | 63 ++++++ .../filebeat/module/zeek/syslog/manifest.yml | 2 +- .../zeek/traceroute/config/traceroute.yml | 14 ++ .../zeek/traceroute/ingest/pipeline.json | 40 ---- .../zeek/traceroute/ingest/pipeline.yml | 63 ++++++ .../module/zeek/traceroute/manifest.yml | 2 +- .../test/traceroute-json.log-expected.json | 17 ++ .../module/zeek/tunnel/config/tunnel.yml | 16 ++ .../module/zeek/tunnel/ingest/pipeline.json | 47 ----- .../module/zeek/tunnel/ingest/pipeline.yml | 63 ++++++ .../filebeat/module/zeek/tunnel/manifest.yml | 2 +- .../tunnel/test/tunnel-json.log-expected.json | 24 +++ .../module/zeek/weird/config/weird.yml | 16 ++ .../module/zeek/weird/ingest/pipeline.json | 49 ----- .../module/zeek/weird/ingest/pipeline.yml | 63 ++++++ .../filebeat/module/zeek/weird/manifest.yml | 2 +- .../weird/test/weird-json.log-expected.json | 20 ++ .../filebeat/module/zeek/x509/config/x509.yml | 6 + .../x509/test/x509-json.log-expected.json | 4 + 172 files changed, 3728 insertions(+), 2113 deletions(-) delete mode 100644 x-pack/filebeat/module/zeek/capture_loss/ingest/pipeline.json create mode 100644 x-pack/filebeat/module/zeek/capture_loss/ingest/pipeline.yml delete mode 100644 x-pack/filebeat/module/zeek/connection/ingest/pipeline.json create mode 100644 x-pack/filebeat/module/zeek/connection/ingest/pipeline.yml delete mode 100644 x-pack/filebeat/module/zeek/dce_rpc/ingest/pipeline.json create mode 100644 x-pack/filebeat/module/zeek/dce_rpc/ingest/pipeline.yml delete mode 100644 x-pack/filebeat/module/zeek/dhcp/ingest/pipeline.json create mode 100644 x-pack/filebeat/module/zeek/dhcp/ingest/pipeline.yml delete mode 100644 x-pack/filebeat/module/zeek/dnp3/ingest/pipeline.json create mode 100644 x-pack/filebeat/module/zeek/dnp3/ingest/pipeline.yml delete mode 100644 x-pack/filebeat/module/zeek/dpd/ingest/pipeline.json create mode 100644 x-pack/filebeat/module/zeek/dpd/ingest/pipeline.yml delete mode 100644 x-pack/filebeat/module/zeek/files/ingest/pipeline.json create mode 100644 x-pack/filebeat/module/zeek/files/ingest/pipeline.yml delete mode 100644 x-pack/filebeat/module/zeek/ftp/ingest/pipeline.json create mode 100644 x-pack/filebeat/module/zeek/ftp/ingest/pipeline.yml delete mode 100644 x-pack/filebeat/module/zeek/http/ingest/pipeline.json create mode 100644 x-pack/filebeat/module/zeek/http/ingest/pipeline.yml delete mode 100644 x-pack/filebeat/module/zeek/irc/ingest/pipeline.json create mode 100644 x-pack/filebeat/module/zeek/irc/ingest/pipeline.yml delete mode 100644 x-pack/filebeat/module/zeek/kerberos/ingest/pipeline.json create mode 100644 x-pack/filebeat/module/zeek/kerberos/ingest/pipeline.yml delete mode 100644 x-pack/filebeat/module/zeek/modbus/ingest/pipeline.json create mode 100644 x-pack/filebeat/module/zeek/modbus/ingest/pipeline.yml delete mode 100644 x-pack/filebeat/module/zeek/mysql/ingest/pipeline.json create mode 100644 x-pack/filebeat/module/zeek/mysql/ingest/pipeline.yml delete mode 100644 x-pack/filebeat/module/zeek/notice/ingest/pipeline.json create mode 100644 x-pack/filebeat/module/zeek/notice/ingest/pipeline.yml delete mode 100644 x-pack/filebeat/module/zeek/ntlm/ingest/pipeline.json create mode 100644 x-pack/filebeat/module/zeek/ntlm/ingest/pipeline.yml delete mode 100644 x-pack/filebeat/module/zeek/ocsp/ingest/pipeline.json create mode 100644 x-pack/filebeat/module/zeek/ocsp/ingest/pipeline.yml delete mode 100644 x-pack/filebeat/module/zeek/pe/ingest/pipeline.json create mode 100644 x-pack/filebeat/module/zeek/pe/ingest/pipeline.yml delete mode 100644 x-pack/filebeat/module/zeek/radius/ingest/pipeline.json create mode 100644 x-pack/filebeat/module/zeek/radius/ingest/pipeline.yml delete mode 100644 x-pack/filebeat/module/zeek/rdp/ingest/pipeline.json create mode 100644 x-pack/filebeat/module/zeek/rdp/ingest/pipeline.yml delete mode 100644 x-pack/filebeat/module/zeek/rfb/ingest/pipeline.json create mode 100644 x-pack/filebeat/module/zeek/rfb/ingest/pipeline.yml delete mode 100644 x-pack/filebeat/module/zeek/sip/ingest/pipeline.json create mode 100644 x-pack/filebeat/module/zeek/sip/ingest/pipeline.yml delete mode 100644 x-pack/filebeat/module/zeek/smb_cmd/ingest/pipeline.json create mode 100644 x-pack/filebeat/module/zeek/smb_cmd/ingest/pipeline.yml delete mode 100644 x-pack/filebeat/module/zeek/smb_files/ingest/pipeline.json create mode 100644 x-pack/filebeat/module/zeek/smb_files/ingest/pipeline.yml delete mode 100644 x-pack/filebeat/module/zeek/smb_mapping/ingest/pipeline.json create mode 100644 x-pack/filebeat/module/zeek/smb_mapping/ingest/pipeline.yml delete mode 100644 x-pack/filebeat/module/zeek/smtp/ingest/pipeline.json create mode 100644 x-pack/filebeat/module/zeek/smtp/ingest/pipeline.yml delete mode 100644 x-pack/filebeat/module/zeek/snmp/ingest/pipeline.json create mode 100644 x-pack/filebeat/module/zeek/snmp/ingest/pipeline.yml delete mode 100644 x-pack/filebeat/module/zeek/socks/ingest/pipeline.json create mode 100644 x-pack/filebeat/module/zeek/socks/ingest/pipeline.yml delete mode 100644 x-pack/filebeat/module/zeek/ssh/ingest/pipeline.json create mode 100644 x-pack/filebeat/module/zeek/ssh/ingest/pipeline.yml delete mode 100644 x-pack/filebeat/module/zeek/stats/ingest/pipeline.json create mode 100644 x-pack/filebeat/module/zeek/stats/ingest/pipeline.yml delete mode 100644 x-pack/filebeat/module/zeek/syslog/ingest/pipeline.json create mode 100644 x-pack/filebeat/module/zeek/syslog/ingest/pipeline.yml delete mode 100644 x-pack/filebeat/module/zeek/traceroute/ingest/pipeline.json create mode 100644 x-pack/filebeat/module/zeek/traceroute/ingest/pipeline.yml delete mode 100644 x-pack/filebeat/module/zeek/tunnel/ingest/pipeline.json create mode 100644 x-pack/filebeat/module/zeek/tunnel/ingest/pipeline.yml delete mode 100644 x-pack/filebeat/module/zeek/weird/ingest/pipeline.json create mode 100644 x-pack/filebeat/module/zeek/weird/ingest/pipeline.yml diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index a0e555166cc..b63bff11ce4 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -277,6 +277,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Improve ECS categorization field mappings in postgresql module. {issue}16177[16177] {pull}17914[17914] - Improve ECS categorization field mappings in rabbitmq module. {issue}16178[16178] {pull}17916[17916] - Improve ECS categorization field mappings in redis module. {issue}16179[16179] {pull}17918[17918] +- Improve ECS categorization field mappings for zeek module. {issue}16029[16029] {pull}17738[17738] *Heartbeat* diff --git a/x-pack/filebeat/module/zeek/capture_loss/ingest/pipeline.json b/x-pack/filebeat/module/zeek/capture_loss/ingest/pipeline.json deleted file mode 100644 index 7d662ab7da1..00000000000 --- a/x-pack/filebeat/module/zeek/capture_loss/ingest/pipeline.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "description": "Pipeline for normalizing Zeek capture_loss.log", - "processors": [ - { - "set": { - "field": "event.created", - "value": "{{_ingest.timestamp}}" - } - }, - { - "date": { - "field": "zeek.capture_loss.ts", - "formats": ["UNIX"] - } - }, - { - "remove": { - "field": "zeek.capture_loss.ts" - } - } - ], - "on_failure" : [{ - "set" : { - "field" : "error.message", - "value" : "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/x-pack/filebeat/module/zeek/capture_loss/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/capture_loss/ingest/pipeline.yml new file mode 100644 index 00000000000..3c6171bc045 --- /dev/null +++ b/x-pack/filebeat/module/zeek/capture_loss/ingest/pipeline.yml @@ -0,0 +1,21 @@ +description: Pipeline for normalizing Zeek capture_loss.log +processors: +- set: + field: event.created + value: '{{_ingest.timestamp}}' +- date: + field: zeek.capture_loss.ts + formats: + - UNIX +- remove: + field: zeek.capture_loss.ts +- set: + field: event.kind + value: metric +- set: + field: event.type + value: info +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/x-pack/filebeat/module/zeek/capture_loss/manifest.yml b/x-pack/filebeat/module/zeek/capture_loss/manifest.yml index 97ae0f09d40..5349b0581c6 100644 --- a/x-pack/filebeat/module/zeek/capture_loss/manifest.yml +++ b/x-pack/filebeat/module/zeek/capture_loss/manifest.yml @@ -11,5 +11,5 @@ var: - name: tags default: [zeek.capture_loss] -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/capture_loss.yml diff --git a/x-pack/filebeat/module/zeek/capture_loss/test/capture_loss-json.log-expected.json b/x-pack/filebeat/module/zeek/capture_loss/test/capture_loss-json.log-expected.json index 0ae18ff9c37..14f20eb3189 100644 --- a/x-pack/filebeat/module/zeek/capture_loss/test/capture_loss-json.log-expected.json +++ b/x-pack/filebeat/module/zeek/capture_loss/test/capture_loss-json.log-expected.json @@ -2,7 +2,9 @@ { "@timestamp": "2019-09-10T16:19:28.465Z", "event.dataset": "zeek.capture_loss", + "event.kind": "metric", "event.module": "zeek", + "event.type": "info", "fileset.name": "capture_loss", "input.type": "log", "log.offset": 0, diff --git a/x-pack/filebeat/module/zeek/connection/config/connection.yml b/x-pack/filebeat/module/zeek/connection/config/connection.yml index 14c5b529708..f91d24f8020 100644 --- a/x-pack/filebeat/module/zeek/connection/config/connection.yml +++ b/x-pack/filebeat/module/zeek/connection/config/connection.yml @@ -75,20 +75,27 @@ processors: ignore_missing: true fail_on_error: false - + - convert: + fields: + - {from: "zeek.session_id", to: "event.id"} + - {from: "source.address", to: "source.ip", type: "ip"} + - {from: "destination.address", to: "destination.ip", type: "ip"} + ignore_missing: true + fail_on_error: false + - add_fields: + target: event + fields: + kind: event + category: + - network {{ if .community_id }} - if: equals.network.transport: icmp then: community_id: fields: - source_ip: source.address - destination_ip: destination.address icmp_type: zeek.connection.icmp.type icmp_code: zeek.connection.icmp.code else: community_id: - fields: - source_ip: source.address - destination_ip: destination.address {{ end }} diff --git a/x-pack/filebeat/module/zeek/connection/ingest/pipeline.json b/x-pack/filebeat/module/zeek/connection/ingest/pipeline.json deleted file mode 100644 index a930fd08ec9..00000000000 --- a/x-pack/filebeat/module/zeek/connection/ingest/pipeline.json +++ /dev/null @@ -1,160 +0,0 @@ -{ - "description": "Pipeline for normalizing Zeek conn.log", - "processors": [ - { - "set": { - "field": "event.created", - "value": "{{_ingest.timestamp}}" - } - }, - { - "date": { - "field": "zeek.connection.ts", - "formats": ["UNIX"] - } - }, - { - "remove": { - "field": "zeek.connection.ts" - } - }, - { - "set": { - "field": "event.id", - "value": "{{zeek.session_id}}", - "if": "ctx.zeek.session_id != null" - } - }, - { - "script": { - "source": "ctx.event.duration = Math.round(ctx.temp.duration * params.scale)", - "params": { - "scale": 1000000000 - }, - "if": "ctx.temp?.duration != null" - } - }, - { - "remove": { - "field": "temp.duration", - "ignore_missing": true - } - }, - { - "script": { - "source": "if (ctx.zeek.connection.local_orig) ctx.tags.add(\"local_orig\");", - "if": "ctx.zeek.connection.local_orig != null" - } - }, - { - "script": { - "source": "if (ctx.zeek.connection.local_resp) ctx.tags.add(\"local_resp\");", - "if": "ctx.zeek.connection.local_resp != null" - } - }, - { - "set": { - "field": "source.ip", - "value": "{{source.address}}" - } - }, - { - "set": { - "field": "destination.ip", - "value": "{{destination.address}}" - } - }, - { - "script": { - "source": "ctx.network.packets = ctx.source.packets + ctx.destination.packets", - "ignore_failure": true - } - }, - { - "script": { - "source": "ctx.network.bytes = ctx.source.bytes + ctx.destination.bytes", - "ignore_failure": true - } - }, - { - "script": { - "source": "if (ctx.zeek.connection.local_orig == true && ctx.zeek.connection.local_resp == true) {ctx.network.direction = \"internal\"} else if (ctx.zeek.connection.local_orig == true && ctx.zeek.connection.local_resp == false) {ctx.network.direction = \"outbound\"} else if (ctx.zeek.connection.local_orig == false && ctx.zeek.connection.local_resp == true) {ctx.network.direction = \"inbound\"} else {ctx.network.direction = \"external\"}" - } - }, - { - "geoip": { - "field": "destination.ip", - "target_field": "destination.geo" - } - }, - { - "geoip": { - "field": "source.ip", - "target_field": "source.geo" - } - }, - { - "geoip": { - "database_file": "GeoLite2-ASN.mmdb", - "field": "source.ip", - "target_field": "source.as", - "properties": [ - "asn", - "organization_name" - ], - "ignore_missing": true - } - }, - { - "geoip": { - "database_file": "GeoLite2-ASN.mmdb", - "field": "destination.ip", - "target_field": "destination.as", - "properties": [ - "asn", - "organization_name" - ], - "ignore_missing": true - } - }, - { - "rename": { - "field": "source.as.asn", - "target_field": "source.as.number", - "ignore_missing": true - } - }, - { - "rename": { - "field": "source.as.organization_name", - "target_field": "source.as.organization.name", - "ignore_missing": true - } - }, - { - "rename": { - "field": "destination.as.asn", - "target_field": "destination.as.number", - "ignore_missing": true - } - }, - { - "rename": { - "field": "destination.as.organization_name", - "target_field": "destination.as.organization.name", - "ignore_missing": true - } - }, - { - "script": { - "source": "if (ctx.zeek.connection.state == \"S0\") {ctx.zeek.connection.state_message = \"Connection attempt seen, no reply.\"} else if (ctx.zeek.connection.state == \"S1\") {ctx.zeek.connection.state_message = \"Connection established, not terminated.\"} else if (ctx.zeek.connection.state == \"SF\") {ctx.zeek.connection.state_message = \"Normal establishment and termination.\"} else if (ctx.zeek.connection.state == \"REJ\") {ctx.zeek.connection.state_message = \"Connection attempt rejected.\"} else if (ctx.zeek.connection.state == \"S2\") {ctx.zeek.connection.state_message = \" Connection established and close attempt by originator seen (but no reply from responder).\"} else if (ctx.zeek.connection.state == \"S3\") {ctx.zeek.connection.state_message = \"Connection established and close attempt by responder seen (but no reply from originator).\"} else if (ctx.zeek.connection.state == \"RSTO\") {ctx.zeek.connection.state_message = \"Connection established, originator aborted (sent a RST).\"} else if (ctx.zeek.connection.state == \"RSTR\") {ctx.zeek.connection.state_message = \"Responder sent a RST.\"} else if (ctx.zeek.connection.state == \"RSTOS0\") {ctx.zeek.connection.state_message = \"Originator sent a SYN followed by a RST, we never saw a SYN-ACK from the responder.\"} else if (ctx.zeek.connection.state == \"RSTRH\") {ctx.zeek.connection.state_message = \"Responder sent a SYN ACK followed by a RST, we never saw a SYN from the (purported) originator.\"} else if (ctx.zeek.connection.state == \"SH\") {ctx.zeek.connection.state_message = \"Originator sent a SYN followed by a FIN, we never saw a SYN ACK from the responder (hence the connection was 'half' open).\"} else if (ctx.zeek.connection.state == \"SHR\") {ctx.zeek.connection.state_message = \"Responder sent a SYN ACK followed by a FIN, we never saw a SYN from the originator.\"} else if (ctx.zeek.connection.state == \"OTH\") {ctx.zeek.connection.state_message = \"No SYN seen, just midstream traffic (a 'partial connection' that was not later closed).\"}" - } - } - ], - "on_failure" : [{ - "set" : { - "field" : "error.message", - "value" : "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/x-pack/filebeat/module/zeek/connection/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/connection/ingest/pipeline.yml new file mode 100644 index 00000000000..b660079324a --- /dev/null +++ b/x-pack/filebeat/module/zeek/connection/ingest/pipeline.yml @@ -0,0 +1,187 @@ +description: Pipeline for normalizing Zeek conn.log +processors: +- set: + field: event.created + value: '{{_ingest.timestamp}}' +- date: + field: zeek.connection.ts + formats: + - UNIX +- remove: + field: zeek.connection.ts +- set: + field: event.id + value: '{{zeek.session_id}}' + if: ctx.zeek.session_id != null +- script: + source: ctx.event.duration = Math.round(ctx.temp.duration * params.scale) + params: + scale: 1000000000 + if: ctx.temp?.duration != null +- remove: + field: temp.duration + ignore_missing: true +- script: + source: if (ctx.zeek.connection.local_orig) ctx.tags.add("local_orig"); + if: ctx.zeek.connection.local_orig != null +- script: + source: if (ctx.zeek.connection.local_resp) ctx.tags.add("local_resp"); + if: ctx.zeek.connection.local_resp != null +- set: + field: source.ip + value: '{{source.address}}' +- append: + field: related.ip + value: '{{source.address}}' +- set: + field: destination.ip + value: '{{destination.address}}' +- append: + field: related.ip + value: '{{destination.address}}' +- script: + source: ctx.network.packets = ctx.source.packets + ctx.destination.packets + ignore_failure: true +- script: + source: ctx.network.bytes = ctx.source.bytes + ctx.destination.bytes + ignore_failure: true +- script: + source: >- + if (ctx?.zeek?.connection?.local_orig == true) { + if (ctx?.zeek?.connection?.local_resp == true) { + ctx.network.direction = "internal"; + } else { + ctx.network.direction = "outbound"; + } + } else { + if (ctx?.zeek?.connection?.local_resp == true) { + ctx.network.direction = "inbound"; + } else { + ctx.network.direction = "external"; + } + } +- geoip: + field: destination.ip + target_field: destination.geo +- geoip: + field: source.ip + target_field: source.geo +- geoip: + database_file: GeoLite2-ASN.mmdb + field: source.ip + target_field: source.as + properties: + - asn + - organization_name + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: destination.ip + target_field: destination.as + properties: + - asn + - organization_name + ignore_missing: true +- rename: + field: source.as.asn + target_field: source.as.number + ignore_missing: true +- rename: + field: source.as.organization_name + target_field: source.as.organization.name + ignore_missing: true +- rename: + field: destination.as.asn + target_field: destination.as.number + ignore_missing: true +- rename: + field: destination.as.organization_name + target_field: destination.as.organization.name + ignore_missing: true +- set: + field: event.kind + value: event +- append: + field: event.category + value: network +- script: + params: + S0: + conn_str: "Connection attempt seen, no reply." + types: + - connection + - start + S1: + conn_str: "Connection established, not terminated." + types: + - connection + - start + SF: + conn_str: "Normal establishment and termination." + types: + - connection + - start + - end + REG: + conn_str: "Connection attempt rejected." + types: + - connection + - start + - denied + S2: + conn_str: "Connection established and close attempt by originator seen (but no reply from responder)." + types: + - connection + - info + S3: + conn_str: "Connection established and close attempt by responder seen (but no reply from originator)." + types: + - connection + - info + RSTO: + conn_str: "Connection established, originator aborted (sent a RST)." + types: + - connection + - info + RSTR: + conn_str: "Responder sent a RST." + types: + - connection + - info + RSTOS0: + conn_str: "Originator sent a SYN followed by a RST, we never saw a SYN-ACK from the responder." + types: + - connection + - info + RSTRH: + conn_str: "Responder sent a SYN ACK followed by a RST, we never saw a SYN from the (purported) originator." + types: + - connection + - info + SH: + conn_str: "Originator sent a SYN followed by a FIN, we never saw a SYN ACK from the responder (hence the connection was 'half' open)." + types: + - connection + - info + SHR: + conn_str: "Responder sent a SYN ACK followed by a FIN, we never saw a SYN from the originator." + types: + - connection + - info + OTH: + conn_str: "No SYN seen, just midstream traffic (a 'partial connection' that was not later closed)." + types: + - connection + - info + source: >- + if (ctx?.zeek?.connection?.state == null) { + return; + } + if (params.containsKey(ctx.zeek.connection.state)) { + ctx.zeek.connection.state_message = params[ctx.zeek.connection.state]["conn_str"]; + ctx.event.type = params[ctx.zeek.connection.state]["types"]; + } +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/x-pack/filebeat/module/zeek/connection/manifest.yml b/x-pack/filebeat/module/zeek/connection/manifest.yml index 0361f0c89fa..0acad34d69c 100644 --- a/x-pack/filebeat/module/zeek/connection/manifest.yml +++ b/x-pack/filebeat/module/zeek/connection/manifest.yml @@ -13,7 +13,7 @@ var: - name: community_id default: true -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/connection.yml requires.processors: diff --git a/x-pack/filebeat/module/zeek/connection/test/connection-json.log-expected.json b/x-pack/filebeat/module/zeek/connection/test/connection-json.log-expected.json index 4e5615a3a51..35a539b1493 100644 --- a/x-pack/filebeat/module/zeek/connection/test/connection-json.log-expected.json +++ b/x-pack/filebeat/module/zeek/connection/test/connection-json.log-expected.json @@ -6,10 +6,20 @@ "destination.ip": "192.168.86.1", "destination.packets": 1, "destination.port": 53, + "event.category": [ + "network", + "network" + ], "event.dataset": "zeek.connection", "event.duration": 76967000, "event.id": "CAcJw21BbVedgFnYH3", + "event.kind": "event", "event.module": "zeek", + "event.type": [ + "connection", + "start", + "end" + ], "fileset.name": "connection", "input.type": "log", "log.offset": 0, @@ -19,6 +29,10 @@ "network.packets": 2, "network.protocol": "dns", "network.transport": "udp", + "related.ip": [ + "192.168.86.167", + "192.168.86.1" + ], "service.type": "zeek", "source.address": "192.168.86.167", "source.bytes": 103, @@ -51,10 +65,20 @@ "destination.ip": "8.8.8.8", "destination.packets": 1, "destination.port": 53, + "event.category": [ + "network", + "network" + ], "event.dataset": "zeek.connection", "event.duration": 76967000, "event.id": "CAcJw21BbVedgFnYH4", + "event.kind": "event", "event.module": "zeek", + "event.type": [ + "connection", + "start", + "end" + ], "fileset.name": "connection", "input.type": "log", "log.offset": 398, @@ -64,6 +88,10 @@ "network.packets": 2, "network.protocol": "dns", "network.transport": "udp", + "related.ip": [ + "192.168.86.167", + "8.8.8.8" + ], "service.type": "zeek", "source.address": "192.168.86.167", "source.bytes": 103, @@ -95,10 +123,20 @@ "destination.ip": "8.8.8.8", "destination.packets": 1, "destination.port": 53, + "event.category": [ + "network", + "network" + ], "event.dataset": "zeek.connection", "event.duration": 76967000, "event.id": "CAcJw21BbVedgFnYH5", + "event.kind": "event", "event.module": "zeek", + "event.type": [ + "connection", + "start", + "end" + ], "fileset.name": "connection", "input.type": "log", "log.offset": 792, @@ -108,6 +146,10 @@ "network.packets": 2, "network.protocol": "dns", "network.transport": "udp", + "related.ip": [ + "4.4.2.2", + "8.8.8.8" + ], "service.type": "zeek", "source.address": "4.4.2.2", "source.as.number": 3356, @@ -137,9 +179,18 @@ "destination.bytes": 0, "destination.ip": "198.51.100.249", "destination.packets": 0, + "event.category": [ + "network", + "network" + ], "event.dataset": "zeek.connection", "event.id": "Cc6NJ3GRlfjE44I3h", + "event.kind": "event", "event.module": "zeek", + "event.type": [ + "connection", + "info" + ], "fileset.name": "connection", "input.type": "log", "log.offset": 1181, @@ -148,6 +199,10 @@ "network.direction": "external", "network.packets": 1, "network.transport": "icmp", + "related.ip": [ + "192.0.2.205", + "198.51.100.249" + ], "service.type": "zeek", "source.address": "192.0.2.205", "source.bytes": 107, @@ -165,4 +220,4 @@ "zeek.connection.state_message": "No SYN seen, just midstream traffic (a 'partial connection' that was not later closed).", "zeek.session_id": "Cc6NJ3GRlfjE44I3h" } -] +] \ No newline at end of file diff --git a/x-pack/filebeat/module/zeek/dce_rpc/config/dce_rpc.yml b/x-pack/filebeat/module/zeek/dce_rpc/config/dce_rpc.yml index e7875bca0df..0ba1b0fc673 100644 --- a/x-pack/filebeat/module/zeek/dce_rpc/config/dce_rpc.yml +++ b/x-pack/filebeat/module/zeek/dce_rpc/config/dce_rpc.yml @@ -36,7 +36,23 @@ processors: ignore_missing: true fail_on_error: false - + - convert: + fields: + - {from: "zeek.session_id", to: "event.id"} + - {from: "source.address", to: "source.ip", type: "ip"} + - {from: "destination.address", to: "destination.ip", type: "ip"} + ignore_missing: true + fail_on_error: false + - add_fields: + target: event + fields: + kind: event + category: + - network + type: + - connection + - protocol + - info {{ if .community_id }} - community_id: {{ end }} diff --git a/x-pack/filebeat/module/zeek/dce_rpc/ingest/pipeline.json b/x-pack/filebeat/module/zeek/dce_rpc/ingest/pipeline.json deleted file mode 100644 index 0f274438186..00000000000 --- a/x-pack/filebeat/module/zeek/dce_rpc/ingest/pipeline.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "description": "Pipeline for normalizing Zeek dce_rpc.log", - "processors": [ - { - "set": { - "field": "event.created", - "value": "{{_ingest.timestamp}}" - } - }, - { - "date": { - "field": "zeek.dce_rpc.ts", - "formats": ["UNIX"] - } - }, - { - "set": { - "field": "event.id", - "value": "{{zeek.session_id}}", - "if": "ctx.zeek.session_id != null" - } - }, - { - "remove": { - "field": "zeek.dce_rpc.ts" - } - }, - { - "set": { - "field": "source.ip", - "value": "{{source.address}}" - } - }, - { - "set": { - "field": "destination.ip", - "value": "{{destination.address}}" - } - } - ], - "on_failure" : [{ - "set" : { - "field" : "error.message", - "value" : "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/x-pack/filebeat/module/zeek/dce_rpc/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/dce_rpc/ingest/pipeline.yml new file mode 100644 index 00000000000..1ecda252cc8 --- /dev/null +++ b/x-pack/filebeat/module/zeek/dce_rpc/ingest/pipeline.yml @@ -0,0 +1,63 @@ +description: Pipeline for normalizing Zeek dce_rpc.log +processors: +- set: + field: event.created + value: '{{_ingest.timestamp}}' +- date: + field: zeek.dce_rpc.ts + formats: + - UNIX +- remove: + field: zeek.dce_rpc.ts +- append: + field: related.ip + value: '{{source.ip}}' +- geoip: + field: source.ip + target_field: source.geo +- geoip: + database_file: GeoLite2-ASN.mmdb + field: source.ip + target_field: source.as + properties: + - asn + - organization_name + ignore_missing: true +- rename: + field: source.as.asn + target_field: source.as.number + ignore_missing: true +- rename: + field: source.as.organization_name + target_field: source.as.organization.name + ignore_missing: true +- append: + field: related.ip + value: '{{destination.ip}}' +- geoip: + field: destination.ip + target_field: destination.geo +- geoip: + database_file: GeoLite2-ASN.mmdb + field: destination.ip + target_field: destination.as + properties: + - asn + - organization_name + ignore_missing: true +- rename: + field: destination.as.asn + target_field: destination.as.number + ignore_missing: true +- rename: + field: destination.as.organization_name + target_field: destination.as.organization.name + ignore_missing: true +- set: + field: event.action + value: '{{zeek.dce_rpc.operation}}' + if: "ctx?.zeek?.dce_rpc?.operation != null" +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/x-pack/filebeat/module/zeek/dce_rpc/manifest.yml b/x-pack/filebeat/module/zeek/dce_rpc/manifest.yml index 853c7084f7e..21ba27eac96 100644 --- a/x-pack/filebeat/module/zeek/dce_rpc/manifest.yml +++ b/x-pack/filebeat/module/zeek/dce_rpc/manifest.yml @@ -13,5 +13,5 @@ var: - name: community_id default: true -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/dce_rpc.yml diff --git a/x-pack/filebeat/module/zeek/dce_rpc/test/dce_rpc-json.log-expected.json b/x-pack/filebeat/module/zeek/dce_rpc/test/dce_rpc-json.log-expected.json index 881f30d1b79..6128801caa7 100644 --- a/x-pack/filebeat/module/zeek/dce_rpc/test/dce_rpc-json.log-expected.json +++ b/x-pack/filebeat/module/zeek/dce_rpc/test/dce_rpc-json.log-expected.json @@ -4,14 +4,29 @@ "destination.address": "172.16.128.202", "destination.ip": "172.16.128.202", "destination.port": 445, + "event.action": "BrowserrQueryOtherDomains", + "event.category": [ + "network" + ], "event.dataset": "zeek.dce_rpc", "event.id": "CsNHVHa1lzFtvJzT8", + "event.kind": "event", "event.module": "zeek", + "event.type": [ + "connection", + "protocol", + "info" + ], "fileset.name": "dce_rpc", "input.type": "log", "log.offset": 0, + "network.community_id": "1:SJNAD5vtzZuhQjGtfaI8svTnyuw=", "network.protocol": "dce_rpc", "network.transport": "tcp", + "related.ip": [ + "172.16.133.6", + "172.16.128.202" + ], "service.type": "zeek", "source.address": "172.16.133.6", "source.ip": "172.16.133.6", diff --git a/x-pack/filebeat/module/zeek/dhcp/config/dhcp.yml b/x-pack/filebeat/module/zeek/dhcp/config/dhcp.yml index 5878c8d7894..97c45a17920 100644 --- a/x-pack/filebeat/module/zeek/dhcp/config/dhcp.yml +++ b/x-pack/filebeat/module/zeek/dhcp/config/dhcp.yml @@ -94,9 +94,27 @@ processors: fields: port: 67 + - convert: + fields: + - {from: "zeek.dhcp.address.client", to: "source.address"} + - {from: "zeek.dhcp.address.client", to: "source.ip", type: "ip"} + - {from: "zeek.dhcp.address.client", to: "client.address"} + - {from: "zeek.dhcp.address.server", to: "destination.address"} + - {from: "zeek.dhcp.address.server", to: "destination.ip", type: "ip"} + - {from: "zeek.dhcp.address.server", to: "server.address"} + - {from: "zeek.dhcp.domain", to: "network.name"} + ignore_missing: true + fail_on_error: false + - add_fields: + target: event + fields: + kind: event + category: + - network + type: + - connection + - protocol + - info {{ if .community_id }} - community_id: - fields: - source.address: zeek.dhcp.address.client - destination.address: zeek.dhcp.address.server {{ end }} diff --git a/x-pack/filebeat/module/zeek/dhcp/ingest/pipeline.json b/x-pack/filebeat/module/zeek/dhcp/ingest/pipeline.json deleted file mode 100644 index 92c1a43dd4a..00000000000 --- a/x-pack/filebeat/module/zeek/dhcp/ingest/pipeline.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "description": "Pipeline for normalizing Zeek dhcp.log", - "processors": [ - { - "set": { - "field": "event.created", - "value": "{{_ingest.timestamp}}" - } - }, - { - "date": { - "field": "zeek.dhcp.ts", - "formats": ["UNIX"] - } - }, - { - "remove": { - "field": "zeek.dhcp.ts" - } - }, - { - "set": { - "field": "event.id", - "value": "{{zeek.session_id}}", - "if": "ctx.zeek.session_id != null" - } - }, - { - "set": { - "field": "source.address", - "value": "{{zeek.dhcp.address.client}}", - "if": "ctx.zeek.dhcp.address?.client != null" - } - }, - { - "set": { - "field": "client.address", - "value": "{{zeek.dhcp.address.client}}", - "if": "ctx.zeek.dhcp.address?.client != null" - } - }, - { - "set": { - "field": "destination.address", - "value": "{{zeek.dhcp.address.server}}", - "if": "ctx.zeek.dhcp.address?.server != null" - } - }, - { - "set": { - "field": "server.address", - "value": "{{zeek.dhcp.address.server}}", - "if": "ctx.zeek.dhcp.address?.server != null" - } - }, - { - "set": { - "field": "source.ip", - "value": "{{source.address}}", - "if": "ctx.source?.address != null" - } - }, - { - "set": { - "field": "destination.ip", - "value": "{{destination.address}}", - "if": "ctx.destination?.address != null" - } - }, - { - "set": { - "field": "network.name", - "value": "{{zeek.dhcp.domain}}", - "if": "ctx.zeek.dhcp.domain != null" - } - } - ], - "on_failure": [{ - "set": { - "field": "error.message", - "value": "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/x-pack/filebeat/module/zeek/dhcp/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/dhcp/ingest/pipeline.yml new file mode 100644 index 00000000000..49df687ecc3 --- /dev/null +++ b/x-pack/filebeat/module/zeek/dhcp/ingest/pipeline.yml @@ -0,0 +1,27 @@ +description: Pipeline for normalizing Zeek dhcp.log +processors: +- set: + field: event.created + value: '{{_ingest.timestamp}}' +- date: + field: zeek.dhcp.ts + formats: + - UNIX +- remove: + field: zeek.dhcp.ts +- set: + field: event.id + value: '{{zeek.session_id}}' + if: ctx.zeek.session_id != null +- append: + field: related.ip + value: '{{source.ip}}' + if: 'ctx?.source?.ip != null' +- append: + field: related.ip + value: '{{destination.ip}}' + if: 'ctx?.destination?.ip != null' +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/x-pack/filebeat/module/zeek/dhcp/manifest.yml b/x-pack/filebeat/module/zeek/dhcp/manifest.yml index a09038725e3..7cb434b1955 100644 --- a/x-pack/filebeat/module/zeek/dhcp/manifest.yml +++ b/x-pack/filebeat/module/zeek/dhcp/manifest.yml @@ -13,5 +13,5 @@ var: - name: community_id default: true -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/dhcp.yml diff --git a/x-pack/filebeat/module/zeek/dhcp/test/dhcp-json.log-expected.json b/x-pack/filebeat/module/zeek/dhcp/test/dhcp-json.log-expected.json index 63fd7367dd8..ec36a36c503 100644 --- a/x-pack/filebeat/module/zeek/dhcp/test/dhcp-json.log-expected.json +++ b/x-pack/filebeat/module/zeek/dhcp/test/dhcp-json.log-expected.json @@ -5,15 +5,29 @@ "destination.address": "192.168.199.254", "destination.ip": "192.168.199.254", "destination.port": 67, + "event.category": [ + "network" + ], "event.dataset": "zeek.dhcp", "event.id": "{0=CmWOt6VWaNGqXYcH6, 1=CLObLo4YHn0u23Tp8a}", + "event.kind": "event", "event.module": "zeek", + "event.type": [ + "connection", + "protocol", + "info" + ], "fileset.name": "dhcp", "input.type": "log", "log.offset": 0, + "network.community_id": "1:HsGjbon+HsK9xnMq+1A32BR9C4Y=", "network.name": "localdomain", "network.protocol": "dhcp", "network.transport": "udp", + "related.ip": [ + "192.168.199.132", + "192.168.199.254" + ], "server.address": "192.168.199.254", "service.type": "zeek", "source.address": "192.168.199.132", diff --git a/x-pack/filebeat/module/zeek/dnp3/config/dnp3.yml b/x-pack/filebeat/module/zeek/dnp3/config/dnp3.yml index 4dec34b4b59..d059b4c79f9 100644 --- a/x-pack/filebeat/module/zeek/dnp3/config/dnp3.yml +++ b/x-pack/filebeat/module/zeek/dnp3/config/dnp3.yml @@ -46,9 +46,23 @@ processors: ignore_missing: true fail_on_error: false + - convert: + fields: + - {from: "zeek.session_id", to: "event.id"} + - {from: "source.address", to: "source.ip", type: "ip"} + - {from: "destination.address", to: "destination.ip", type: "ip"} + ignore_missing: true + fail_on_error: false + - add_fields: + target: event + fields: + kind: event + category: + - network + type: + - connection + - protocol + - info {{ if .community_id }} - community_id: - fields: - source_ip: source.address - destination_ip: destination.address {{ end }} diff --git a/x-pack/filebeat/module/zeek/dnp3/ingest/pipeline.json b/x-pack/filebeat/module/zeek/dnp3/ingest/pipeline.json deleted file mode 100644 index 3f7e3c4baee..00000000000 --- a/x-pack/filebeat/module/zeek/dnp3/ingest/pipeline.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "description": "Pipeline for normalizing Zeek dnp3.log", - "processors": [ - { - "set": { - "field": "event.created", - "value": "{{_ingest.timestamp}}" - } - }, - { - "date": { - "field": "zeek.dnp3.ts", - "formats": ["UNIX"] - } - }, - { - "remove": { - "field": "zeek.dnp3.ts" - } - }, - { - "set": { - "field": "event.id", - "value": "{{zeek.session_id}}", - "if": "ctx.zeek.session_id != null" - } - }, - { - "set": { - "field": "source.ip", - "value": "{{source.address}}" - } - }, - { - "set": { - "field": "destination.ip", - "value": "{{destination.address}}" - } - } - ], - "on_failure" : [{ - "set" : { - "field" : "error.message", - "value" : "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/x-pack/filebeat/module/zeek/dnp3/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/dnp3/ingest/pipeline.yml new file mode 100644 index 00000000000..ad4670dc350 --- /dev/null +++ b/x-pack/filebeat/module/zeek/dnp3/ingest/pipeline.yml @@ -0,0 +1,64 @@ +description: Pipeline for normalizing Zeek dnp3.log +processors: +- set: + field: event.created + value: '{{_ingest.timestamp}}' +- date: + field: zeek.dnp3.ts + formats: + - UNIX +- remove: + field: zeek.dnp3.ts +- set: + field: event.action + value: '{{zeek.dnp3.function.request}}' + if: "ctx?.zeek?.dnp3?.function?.request != null" +- set: + field: event.action + value: '{{zeek.dnp3.function.reply}}' + if: "ctx?.zeek?.dnp3?.function?.reply != null" +- lowercase: + field: event.action + ignore_missing: true +- geoip: + field: destination.ip + target_field: destination.geo +- geoip: + field: source.ip + target_field: source.geo +- geoip: + database_file: GeoLite2-ASN.mmdb + field: source.ip + target_field: source.as + properties: + - asn + - organization_name + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: destination.ip + target_field: destination.as + properties: + - asn + - organization_name + ignore_missing: true +- rename: + field: source.as.asn + target_field: source.as.number + ignore_missing: true +- rename: + field: source.as.organization_name + target_field: source.as.organization.name + ignore_missing: true +- rename: + field: destination.as.asn + target_field: destination.as.number + ignore_missing: true +- rename: + field: destination.as.organization_name + target_field: destination.as.organization.name + ignore_missing: true +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/x-pack/filebeat/module/zeek/dnp3/manifest.yml b/x-pack/filebeat/module/zeek/dnp3/manifest.yml index 73488debb12..98de1c3af82 100644 --- a/x-pack/filebeat/module/zeek/dnp3/manifest.yml +++ b/x-pack/filebeat/module/zeek/dnp3/manifest.yml @@ -13,5 +13,5 @@ var: - name: community_id default: true -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/dnp3.yml diff --git a/x-pack/filebeat/module/zeek/dnp3/test/dnp3-json.log-expected.json b/x-pack/filebeat/module/zeek/dnp3/test/dnp3-json.log-expected.json index 040dabff377..fa386feb1ce 100644 --- a/x-pack/filebeat/module/zeek/dnp3/test/dnp3-json.log-expected.json +++ b/x-pack/filebeat/module/zeek/dnp3/test/dnp3-json.log-expected.json @@ -4,9 +4,19 @@ "destination.address": "127.0.0.1", "destination.ip": "127.0.0.1", "destination.port": 20000, + "event.action": "read", + "event.category": [ + "network" + ], "event.dataset": "zeek.dnp3", "event.id": "CQV6tj1w1t4WzQpHoe", + "event.kind": "event", "event.module": "zeek", + "event.type": [ + "connection", + "protocol", + "info" + ], "fileset.name": "dnp3", "input.type": "log", "log.offset": 0, diff --git a/x-pack/filebeat/module/zeek/dns/config/dns.yml b/x-pack/filebeat/module/zeek/dns/config/dns.yml index 96e67d9f840..7b4c332f5df 100644 --- a/x-pack/filebeat/module/zeek/dns/config/dns.yml +++ b/x-pack/filebeat/module/zeek/dns/config/dns.yml @@ -13,6 +13,11 @@ processors: - decode_json_fields: fields: [event.original] target: zeek.dns + - registered_domain: + ignore_missing: true + ignore_failure: true + field: zeek.dns.query + target_field: dns.question.registered_domain - script: lang: javascript id: zeek_dns_flags @@ -105,12 +110,54 @@ processors: evt.Put("event.duration", rttSec * 1000000000); } + function addTopLevelDomain(evt) { + var rd = evt.Get("dns.question.registered_domain"); + if (!rd) { + return; + } + var firstPeriod = rd.indexOf("."); + if (firstPeriod == -1) { + return; + } + evt.Put("dns.question.top_level_domain", rd.substr(firstPeriod + 1)); + } + + function addEventOutcome(evt) { + var rcode = evt.Get("zeek.dns.rcode"); + if (rcode == null) { + return; + } + if (rcode == 0) { + evt.Put("event.outcome", "success"); + } else { + evt.Put("event.outcome", "failure"); + } + } + + function addRelatedIP(evt) { + var related = []; + var src = evt.Get("zeek.dns.id.orig_h"); + if (src != null) { + related.push(src); + } + var dst = evt.Get("zeek.dns.id.resp_h"); + if (dst != null) { + related.push(dst); + } + if (related.length > 0) { + evt.Put("related.ip", related); + } + } + function process(evt) { addDnsHeaderFlags(evt); addDnsQuestionClass(evt); addDnsAnswers(evt); setDnsType(evt); addEventDuration(evt); + addTopLevelDomain(evt); + addEventOutcome(evt); + addRelatedIP(evt); } - convert: ignore_missing: true @@ -136,13 +183,18 @@ processors: - {from: zeek.dns.query, to: dns.question.name} - {from: zeek.dns.qtype_name, to: dns.question.type} - {from: zeek.dns.rcode_name, to: dns.response_code} - - registered_domain: - ignore_missing: true - ignore_failure: true - field: dns.question.name - target_field: dns.question.registered_domain + - add_fields: + target: event + fields: + kind: event + category: + - network + type: + - connection + - info + - protocol {{ if .community_id }} - - community_id: ~ + - community_id: {{ end }} - timestamp: ignore_missing: true diff --git a/x-pack/filebeat/module/zeek/dns/test/dns-json.log-expected.json b/x-pack/filebeat/module/zeek/dns/test/dns-json.log-expected.json index a8e2cd94b3a..0c01c52e428 100644 --- a/x-pack/filebeat/module/zeek/dns/test/dns-json.log-expected.json +++ b/x-pack/filebeat/module/zeek/dns/test/dns-json.log-expected.json @@ -26,22 +26,37 @@ "dns.question.class": "IN", "dns.question.name": "dd625ffb4fc54735b281862aa1cd6cd4.us-west1.gcp.cloud.es.io", "dns.question.registered_domain": "es.io", + "dns.question.top_level_domain": "io", "dns.question.type": "A", "dns.resolved_ip": [ "35.199.178.4" ], "dns.response_code": "NOERROR", "dns.type": "answer", + "event.category": [ + "network" + ], "event.dataset": "zeek.dns", "event.duration": 76967000, "event.id": "CAcJw21BbVedgFnYH3", + "event.kind": "event", "event.module": "zeek", "event.original": "{\"ts\":1547188415.857497,\"uid\":\"CAcJw21BbVedgFnYH3\",\"id.orig_h\":\"192.168.86.167\",\"id.orig_p\":38339,\"id.resp_h\":\"192.168.86.1\",\"id.resp_p\":53,\"proto\":\"udp\",\"trans_id\":15209,\"rtt\":0.076967,\"query\":\"dd625ffb4fc54735b281862aa1cd6cd4.us-west1.gcp.cloud.es.io\",\"qclass\":1,\"qclass_name\":\"C_INTERNET\",\"qtype\":1,\"qtype_name\":\"A\",\"rcode\":0,\"rcode_name\":\"NOERROR\",\"AA\":false,\"TC\":false,\"RD\":true,\"RA\":true,\"Z\":0,\"answers\":[\"proxy-production-us-west1.gcp.cloud.es.io\",\"proxy-production-us-west1-v1-009.gcp.cloud.es.io\",\"35.199.178.4\"],\"TTLs\":[119.0,119.0,59.0],\"rejected\":false}", + "event.outcome": "success", + "event.type": [ + "connection", + "info", + "protocol" + ], "fileset.name": "dns", "input.type": "log", "log.offset": 0, "network.community_id": "1:Z26DBGVYoBKQ1FT6qfPaAqBnJik=", "network.transport": "udp", + "related.ip": [ + "192.168.86.167", + "192.168.86.1" + ], "service.type": "zeek", "source.address": "192.168.86.167", "source.ip": "192.168.86.167", @@ -84,17 +99,31 @@ "dns.question.class": "IN", "dns.question.name": "_googlecast._tcp.local", "dns.question.registered_domain": "_tcp.local", + "dns.question.top_level_domain": "local", "dns.question.type": "PTR", "dns.type": "query", + "event.category": [ + "network" + ], "event.dataset": "zeek.dns", "event.id": "C19a1k4lTv46YMbeOk", + "event.kind": "event", "event.module": "zeek", "event.original": "{\"ts\":1567095830.680046,\"uid\":\"C19a1k4lTv46YMbeOk\",\"id.orig_h\":\"fe80::4ef:15cf:769f:ff21\",\"id.orig_p\":5353,\"id.resp_h\":\"ff02::fb\",\"id.resp_p\":5353,\"proto\":\"udp\",\"trans_id\":0,\"query\":\"_googlecast._tcp.local\",\"qclass\":1,\"qclass_name\":\"C_INTERNET\",\"qtype\":12,\"qtype_name\":\"PTR\",\"AA\":false,\"TC\":false,\"RD\":false,\"RA\":false,\"Z\":0,\"rejected\":false}", + "event.type": [ + "connection", + "info", + "protocol" + ], "fileset.name": "dns", "input.type": "log", "log.offset": 566, "network.community_id": "1:Jq0sRtlGSMjsvMBE1ZYybbR2tI0=", "network.transport": "udp", + "related.ip": [ + "fe80::4ef:15cf:769f:ff21", + "ff02::fb" + ], "service.type": "zeek", "source.address": "fe80::4ef:15cf:769f:ff21", "source.ip": "fe80::4ef:15cf:769f:ff21", @@ -130,17 +159,32 @@ "dns.id": 0, "dns.question.name": "_googlecast._tcp.local", "dns.question.registered_domain": "_tcp.local", + "dns.question.top_level_domain": "local", "dns.response_code": "NOERROR", "dns.type": "answer", + "event.category": [ + "network" + ], "event.dataset": "zeek.dns", "event.id": "CdiVAw7jJw6gsX5H", + "event.kind": "event", "event.module": "zeek", "event.original": "{\"ts\":1567095830.734329,\"uid\":\"CdiVAw7jJw6gsX5H\",\"id.orig_h\":\"192.168.86.237\",\"id.orig_p\":5353,\"id.resp_h\":\"224.0.0.251\",\"id.resp_p\":5353,\"proto\":\"udp\",\"trans_id\":0,\"query\":\"_googlecast._tcp.local\",\"rcode\":0,\"rcode_name\":\"NOERROR\",\"AA\":true,\"TC\":false,\"RD\":false,\"RA\":false,\"Z\":0,\"answers\":[\"bravia-4k-gb-5c89f865c9d569ab338815b35e3acc56._googlecast._tcp.local\"],\"TTLs\":[120.0],\"rejected\":false}", + "event.outcome": "success", + "event.type": [ + "connection", + "info", + "protocol" + ], "fileset.name": "dns", "input.type": "log", "log.offset": 909, "network.community_id": "1:QIR5YXlirWwWA18ZyY/RnvQoaic=", "network.transport": "udp", + "related.ip": [ + "192.168.86.237", + "224.0.0.251" + ], "service.type": "zeek", "source.address": "192.168.86.237", "source.ip": "192.168.86.237", diff --git a/x-pack/filebeat/module/zeek/dpd/config/dpd.yml b/x-pack/filebeat/module/zeek/dpd/config/dpd.yml index 9e6a0138ef2..0a31b70f6bd 100644 --- a/x-pack/filebeat/module/zeek/dpd/config/dpd.yml +++ b/x-pack/filebeat/module/zeek/dpd/config/dpd.yml @@ -36,10 +36,22 @@ processors: ignore_missing: true fail_on_error: false - + - convert: + fields: + - {from: "source.address", to: "source.ip", type: "ip"} + - {from: "destination.address", to: "destination.ip", type: "ip"} + - {from: "zeek.session_id", to: "event.id"} + ignore_missing: true + fail_on_error: false + - add_fields: + target: event + fields: + kind: event + category: + - network + type: + - connection + - info {{ if .community_id }} - community_id: - fields: - source_ip: source.address - destination_ip: destination.address {{ end }} diff --git a/x-pack/filebeat/module/zeek/dpd/ingest/pipeline.json b/x-pack/filebeat/module/zeek/dpd/ingest/pipeline.json deleted file mode 100644 index 7a8958013fc..00000000000 --- a/x-pack/filebeat/module/zeek/dpd/ingest/pipeline.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "description": "Pipeline for normalizing Zeek dpd.log", - "processors": [ - { - "set": { - "field": "event.created", - "value": "{{_ingest.timestamp}}" - } - }, - { - "date": { - "field": "zeek.dpd.ts", - "formats": ["UNIX"] - } - }, - { - "remove": { - "field": "zeek.dpd.ts" - } - }, - { - "set": { - "field": "event.id", - "value": "{{zeek.session_id}}", - "if": "ctx.zeek.session_id != null" - } - }, - { - "set": { - "field": "source.ip", - "value": "{{source.address}}" - } - }, - { - "set": { - "field": "destination.ip", - "value": "{{destination.address}}" - } - } - ], - "on_failure" : [{ - "set" : { - "field" : "error.message", - "value" : "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/x-pack/filebeat/module/zeek/dpd/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/dpd/ingest/pipeline.yml new file mode 100644 index 00000000000..f30ff172fa8 --- /dev/null +++ b/x-pack/filebeat/module/zeek/dpd/ingest/pipeline.yml @@ -0,0 +1,63 @@ +description: Pipeline for normalizing Zeek dpd.log +processors: +- set: + field: event.created + value: '{{_ingest.timestamp}}' +- date: + field: zeek.dpd.ts + formats: + - UNIX +- remove: + field: zeek.dpd.ts +- geoip: + field: source.ip + target_field: source.geo + ignore_missing: true +- geoip: + field: destination.ip + target_field: destination.geo + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: source.ip + target_field: source.as + properties: + - asn + - organization_name + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: destination.ip + target_field: destination.as + properties: + - asn + - organization_name + ignore_missing: true +- rename: + field: source.as.asn + target_field: source.as.number + ignore_missing: true +- rename: + field: source.as.organization_name + target_field: source.as.organization.name + ignore_missing: true +- rename: + field: destination.as.asn + target_field: destination.as.number + ignore_missing: true +- rename: + field: destination.as.organization_name + target_field: destination.as.organization.name + ignore_missing: true +- append: + field: related.ip + value: "{{source.ip}}" + if: "ctx?.source?.ip != null" +- append: + field: related.ip + value: "{{destination.ip}}" + if: "ctx?.destination?.ip != null" +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/x-pack/filebeat/module/zeek/dpd/manifest.yml b/x-pack/filebeat/module/zeek/dpd/manifest.yml index b331bca2921..aeba0ef31fc 100644 --- a/x-pack/filebeat/module/zeek/dpd/manifest.yml +++ b/x-pack/filebeat/module/zeek/dpd/manifest.yml @@ -13,5 +13,5 @@ var: - name: community_id default: true -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/dpd.yml diff --git a/x-pack/filebeat/module/zeek/dpd/test/dpd-json.log-expected.json b/x-pack/filebeat/module/zeek/dpd/test/dpd-json.log-expected.json index d3f58dbd4e0..0d6173e172e 100644 --- a/x-pack/filebeat/module/zeek/dpd/test/dpd-json.log-expected.json +++ b/x-pack/filebeat/module/zeek/dpd/test/dpd-json.log-expected.json @@ -4,14 +4,26 @@ "destination.address": "192.168.10.10", "destination.ip": "192.168.10.10", "destination.port": 445, + "event.category": [ + "network" + ], "event.dataset": "zeek.dpd", "event.id": "CRrT7S1ccw9H6hzCR", + "event.kind": "event", "event.module": "zeek", + "event.type": [ + "connection", + "info" + ], "fileset.name": "dpd", "input.type": "log", "log.offset": 0, "network.community_id": "1:b+Szw+ia464igf5e+MwW1WUzw9Y=", "network.transport": "tcp", + "related.ip": [ + "192.168.10.31", + "192.168.10.10" + ], "service.type": "zeek", "source.address": "192.168.10.31", "source.ip": "192.168.10.31", diff --git a/x-pack/filebeat/module/zeek/files/config/files.yml b/x-pack/filebeat/module/zeek/files/config/files.yml index 7148b82a481..74259307f41 100644 --- a/x-pack/filebeat/module/zeek/files/config/files.yml +++ b/x-pack/filebeat/module/zeek/files/config/files.yml @@ -15,9 +15,25 @@ processors: fields: - from: "json" to: "zeek.files" - - from: "zeek.files.conn_uids" to: "zeek.files.session_ids" - ignore_missing: true fail_on_error: false + - convert: + fields: + - {from: "zeek.files.mime_type", to: "file.mime_type"} + - {from: "zeek.files.filename", to: "file.name"} + - {from: "zeek.files.total_bytes", to: "file.size"} + - {from: "zeek.files.md5", to: "file.hash.md5"} + - {from: "zeek.files.sha1", to: "file.hash.sha1"} + - {from: "zeek.files.sha256", to: "file.hash.sha256"} + ignore_missing: true + fail_on_error: false + - add_fields: + target: event + fields: + kind: event + category: + - file + type: + - info diff --git a/x-pack/filebeat/module/zeek/files/ingest/pipeline.json b/x-pack/filebeat/module/zeek/files/ingest/pipeline.json deleted file mode 100644 index 1c47b4d0b42..00000000000 --- a/x-pack/filebeat/module/zeek/files/ingest/pipeline.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "description": "Pipeline for normalizing Zeek files.log", - "processors": [ - { - "set": { - "field": "event.created", - "value": "{{_ingest.timestamp}}" - } - }, - { - "date": { - "field": "zeek.files.ts", - "formats": ["UNIX"] - } - }, - { - "remove": { - "field": "zeek.files.ts" - } - }, - { - "script": { - "lang": "painless", - "source": "ctx.zeek.session_id = ctx.zeek.files.session_ids[0];", - "if": "ctx.zeek.files.session_ids != null", - "ignore_failure": true - } - }, - { - "script": { - "lang": "painless", - "source": "ctx.zeek.files.rx_host = ctx.zeek.files.rx_hosts[0]; ctx.zeek.files.remove('rx_hosts');", - "ignore_failure": true - } - }, - { - "script": { - "lang": "painless", - "source": "ctx.zeek.files.tx_host = ctx.zeek.files.tx_hosts[0]; ctx.zeek.files.remove('tx_hosts');", - "ignore_failure": true - } - }, - { - "set": { - "field": "event.id", - "value": "{{zeek.session_id}}", - "if": "ctx.zeek.session_id != null" - } - } - ], - "on_failure": [{ - "set": { - "field": "error.message", - "value": "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/x-pack/filebeat/module/zeek/files/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/files/ingest/pipeline.yml new file mode 100644 index 00000000000..0d5abf9bdda --- /dev/null +++ b/x-pack/filebeat/module/zeek/files/ingest/pipeline.yml @@ -0,0 +1,66 @@ +description: Pipeline for normalizing Zeek files.log +processors: +- set: + field: event.created + value: '{{_ingest.timestamp}}' +- date: + field: zeek.files.ts + formats: + - UNIX +- remove: + field: zeek.files.ts +- script: + lang: painless + source: ctx.zeek.session_id = ctx.zeek.files.session_ids[0]; + if: ctx.zeek.files.session_ids != null + ignore_failure: true +- set: + field: event.id + value: '{{zeek.session_id}}' + if: ctx.zeek.session_id != null +- foreach: + field: zeek.files.tx_hosts + processor: + append: + field: related.ip + value: "{{_ingest._value}}" + ignore_missing: true +- script: + lang: painless + source: ctx.zeek.files.tx_host = ctx.zeek.files.tx_hosts[0]; ctx.zeek.files.remove('tx_hosts'); + ignore_failure: true +- set: + field: server.ip + value: "{{zeek.files.tx_host}}" + if: "ctx?.zeek?.files?.tx_host != null" +- foreach: + field: zeek.files.rx_hosts + processor: + append: + field: related.ip + value: "{{_ingest._value}}" + ignore_missing: true +- script: + lang: painless + source: ctx.zeek.files.rx_host = ctx.zeek.files.rx_hosts[0]; ctx.zeek.files.remove('rx_hosts'); + ignore_failure: true +- set: + field: client.ip + value: "{{zeek.files.rx_host}}" + if: "ctx?.zeek?.files?.rx_host != null" +- append: + field: related.hash + value: "{{file.hash.md5}}" + if: "ctx?.file?.hash?.md5 != null" +- append: + field: related.hash + value: "{{file.hash.sha1}}" + if: "ctx?.file?.hash?.sha1 != null" +- append: + field: related.hash + value: "{{file.hash.sha256}}" + if: "ctx?.file?.hash?.sha256 != null" +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/x-pack/filebeat/module/zeek/files/manifest.yml b/x-pack/filebeat/module/zeek/files/manifest.yml index 68b53467346..bef3d7211b6 100644 --- a/x-pack/filebeat/module/zeek/files/manifest.yml +++ b/x-pack/filebeat/module/zeek/files/manifest.yml @@ -13,7 +13,7 @@ var: - name: community_id default: true -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/files.yml requires.processors: diff --git a/x-pack/filebeat/module/zeek/files/test/files-json.log-expected.json b/x-pack/filebeat/module/zeek/files/test/files-json.log-expected.json index 4cc0e2d38e0..6fc38a5d22a 100644 --- a/x-pack/filebeat/module/zeek/files/test/files-json.log-expected.json +++ b/x-pack/filebeat/module/zeek/files/test/files-json.log-expected.json @@ -1,12 +1,32 @@ [ { "@timestamp": "2019-01-17T01:33:16.636Z", + "client.ip": "10.178.98.102", + "event.category": [ + "file" + ], "event.dataset": "zeek.files", "event.id": "C8I0zn3r9EPbfLgta6", + "event.kind": "event", "event.module": "zeek", + "event.type": [ + "info" + ], + "file.hash.md5": "79e4a9840d7d3a96d7c04fe2434c892e", + "file.hash.sha1": "a8985d3a65e5e5c4b2d7d66d40c6dd2fb19c5436", + "file.mime_type": "application/pkix-cert", "fileset.name": "files", "input.type": "log", "log.offset": 0, + "related.hash": [ + "79e4a9840d7d3a96d7c04fe2434c892e", + "a8985d3a65e5e5c4b2d7d66d40c6dd2fb19c5436" + ], + "related.ip": [ + "35.199.178.4", + "10.178.98.102" + ], + "server.ip": "35.199.178.4", "service.type": "zeek", "tags": [ "zeek.files" @@ -38,12 +58,32 @@ }, { "@timestamp": "2019-01-17T01:33:21.566Z", + "client.ip": "10.178.98.102", + "event.category": [ + "file" + ], "event.dataset": "zeek.files", "event.id": "C6sjVo23iNApLnlAt6", + "event.kind": "event", "event.module": "zeek", + "event.type": [ + "info" + ], + "file.hash.md5": "b9742f12eb97eff531d94f7800c6706c", + "file.hash.sha1": "b88d13fe319d342e7a808ce3a0a1158111fc3c2a", + "file.mime_type": "application/pkix-cert", "fileset.name": "files", "input.type": "log", "log.offset": 452, + "related.hash": [ + "b9742f12eb97eff531d94f7800c6706c", + "b88d13fe319d342e7a808ce3a0a1158111fc3c2a" + ], + "related.ip": [ + "17.134.127.250", + "10.178.98.102" + ], + "server.ip": "17.134.127.250", "service.type": "zeek", "tags": [ "zeek.files" diff --git a/x-pack/filebeat/module/zeek/ftp/config/ftp.yml b/x-pack/filebeat/module/zeek/ftp/config/ftp.yml index 7c9e90cb96a..3e91ace4831 100644 --- a/x-pack/filebeat/module/zeek/ftp/config/ftp.yml +++ b/x-pack/filebeat/module/zeek/ftp/config/ftp.yml @@ -60,10 +60,27 @@ processors: ignore_missing: true fail_on_error: false - + - convert: + fields: + - {from: "zeek.session_id", to: "event.id"} + - {from: "source.address", to: "source.ip", type: "ip"} + - {from: "destination.address", to: "destination.ip", type: "ip"} + - {from: "zeek.ftp.user", to: "user.name"} + - {from: "zeek.ftp.command", to: "event.action"} + - {from: "zeek.ftp.mime.type", to: "file.mime_type"} + - {from: "zeek.ftp.file.size", to: "file.size"} + ignore_missing: true + fail_on_error: false + - add_fields: + target: event + fields: + kind: event + category: + - network + type: + - connection + - info + - protocol {{ if .community_id }} - community_id: - fields: - source_ip: source.address - destination_ip: destination.address {{ end }} diff --git a/x-pack/filebeat/module/zeek/ftp/ingest/pipeline.json b/x-pack/filebeat/module/zeek/ftp/ingest/pipeline.json deleted file mode 100644 index 06b896b53d3..00000000000 --- a/x-pack/filebeat/module/zeek/ftp/ingest/pipeline.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "description": "Pipeline for normalizing Zeek ftp.log", - "processors": [ - { - "set": { - "field": "event.created", - "value": "{{_ingest.timestamp}}" - } - }, - { - "date": { - "field": "zeek.ftp.ts", - "formats": ["UNIX"] - } - }, - { - "remove": { - "field": "zeek.ftp.ts" - } - }, - { - "set": { - "field": "event.id", - "value": "{{zeek.session_id}}", - "if": "ctx.zeek.session_id != null" - } - }, - { - "set": { - "field": "source.ip", - "value": "{{source.address}}" - } - }, - { - "set": { - "field": "destination.ip", - "value": "{{destination.address}}" - } - }, - { - "dot_expander": { - "field": "data_channel.passive", - "path": "zeek.ftp" - } - } - ], - "on_failure" : [{ - "set" : { - "field" : "error.message", - "value" : "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/x-pack/filebeat/module/zeek/ftp/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/ftp/ingest/pipeline.yml new file mode 100644 index 00000000000..7c15dce3ac5 --- /dev/null +++ b/x-pack/filebeat/module/zeek/ftp/ingest/pipeline.yml @@ -0,0 +1,68 @@ +description: Pipeline for normalizing Zeek ftp.log +processors: +- set: + field: event.created + value: '{{_ingest.timestamp}}' +- date: + field: zeek.ftp.ts + formats: + - UNIX +- remove: + field: zeek.ftp.ts +- dot_expander: + field: data_channel.passive + path: zeek.ftp +- append: + field: related.ip + value: "{{source.ip}}" + if: "ctx?.source?.ip != null" +- append: + field: related.ip + value: "{{destination.ip}}" + if: "ctx?.destination?.ip != null" +- append: + field: related.user + value: "{{user.name}}" + if: "ctx?.user?.name != null" +- geoip: + field: destination.ip + target_field: destination.geo +- geoip: + field: source.ip + target_field: source.geo +- geoip: + database_file: GeoLite2-ASN.mmdb + field: source.ip + target_field: source.as + properties: + - asn + - organization_name + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: destination.ip + target_field: destination.as + properties: + - asn + - organization_name + ignore_missing: true +- rename: + field: source.as.asn + target_field: source.as.number + ignore_missing: true +- rename: + field: source.as.organization_name + target_field: source.as.organization.name + ignore_missing: true +- rename: + field: destination.as.asn + target_field: destination.as.number + ignore_missing: true +- rename: + field: destination.as.organization_name + target_field: destination.as.organization.name + ignore_missing: true +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/x-pack/filebeat/module/zeek/ftp/manifest.yml b/x-pack/filebeat/module/zeek/ftp/manifest.yml index 3dd47573af9..cf51575cf84 100644 --- a/x-pack/filebeat/module/zeek/ftp/manifest.yml +++ b/x-pack/filebeat/module/zeek/ftp/manifest.yml @@ -13,5 +13,5 @@ var: - name: community_id default: true -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/ftp.yml diff --git a/x-pack/filebeat/module/zeek/ftp/test/ftp.log-expected.json b/x-pack/filebeat/module/zeek/ftp/test/ftp.log-expected.json index 7de6cc8897c..e6a47bd369e 100644 --- a/x-pack/filebeat/module/zeek/ftp/test/ftp.log-expected.json +++ b/x-pack/filebeat/module/zeek/ftp/test/ftp.log-expected.json @@ -4,15 +4,32 @@ "destination.address": "192.168.1.231", "destination.ip": "192.168.1.231", "destination.port": 21, + "event.action": "EPSV", + "event.category": [ + "network" + ], "event.dataset": "zeek.ftp", "event.id": "CpQoCn3o28tke89zv9", + "event.kind": "event", "event.module": "zeek", + "event.type": [ + "connection", + "info", + "protocol" + ], "fileset.name": "ftp", "input.type": "log", "log.offset": 0, "network.community_id": "1:Szmpl33Czo3dQvU2V4/SrHfmBC0=", "network.protocol": "ftp", "network.transport": "tcp", + "related.ip": [ + "192.168.1.182", + "192.168.1.231" + ], + "related.user": [ + "ftp" + ], "service.type": "zeek", "source.address": "192.168.1.182", "source.ip": "192.168.1.182", @@ -20,6 +37,7 @@ "tags": [ "zeek.ftp" ], + "user.name": "ftp", "zeek.ftp.command": "EPSV", "zeek.ftp.data_channel.originating_host": "192.168.1.182", "zeek.ftp.data_channel.passive": true, @@ -36,15 +54,33 @@ "destination.address": "192.168.1.231", "destination.ip": "192.168.1.231", "destination.port": 21, + "event.action": "RETR", + "event.category": [ + "network" + ], "event.dataset": "zeek.ftp", "event.id": "CpQoCn3o28tke89zv9", + "event.kind": "event", "event.module": "zeek", + "event.type": [ + "connection", + "info", + "protocol" + ], + "file.size": 39424, "fileset.name": "ftp", "input.type": "log", "log.offset": 394, "network.community_id": "1:Szmpl33Czo3dQvU2V4/SrHfmBC0=", "network.protocol": "ftp", "network.transport": "tcp", + "related.ip": [ + "192.168.1.182", + "192.168.1.231" + ], + "related.user": [ + "ftp" + ], "service.type": "zeek", "source.address": "192.168.1.182", "source.ip": "192.168.1.182", @@ -52,6 +88,7 @@ "tags": [ "zeek.ftp" ], + "user.name": "ftp", "zeek.ftp.arg": "ftp://192.168.1.231/resume.doc", "zeek.ftp.command": "RETR", "zeek.ftp.file.size": 39424, @@ -66,15 +103,32 @@ "destination.address": "192.168.1.231", "destination.ip": "192.168.1.231", "destination.port": 21, + "event.action": "STOR", + "event.category": [ + "network" + ], "event.dataset": "zeek.ftp", "event.id": "CpQoCn3o28tke89zv9", + "event.kind": "event", "event.module": "zeek", + "event.type": [ + "connection", + "info", + "protocol" + ], "fileset.name": "ftp", "input.type": "log", "log.offset": 688, "network.community_id": "1:Szmpl33Czo3dQvU2V4/SrHfmBC0=", "network.protocol": "ftp", "network.transport": "tcp", + "related.ip": [ + "192.168.1.182", + "192.168.1.231" + ], + "related.user": [ + "ftp" + ], "service.type": "zeek", "source.address": "192.168.1.182", "source.ip": "192.168.1.182", @@ -82,6 +136,7 @@ "tags": [ "zeek.ftp" ], + "user.name": "ftp", "zeek.ftp.arg": "ftp://192.168.1.231/uploads/README", "zeek.ftp.command": "STOR", "zeek.ftp.password": "ftp", diff --git a/x-pack/filebeat/module/zeek/http/config/http.yml b/x-pack/filebeat/module/zeek/http/config/http.yml index 2c024397018..584160639cb 100644 --- a/x-pack/filebeat/module/zeek/http/config/http.yml +++ b/x-pack/filebeat/module/zeek/http/config/http.yml @@ -68,9 +68,26 @@ processors: ignore_missing: true fail_on_error: false + - convert: + fields: + - {from: "zeek.session_id", to: "event.id"} + - {from: "source.address", to: "source.ip", type: "ip"} + - {from: "destination.address", to: "destination.ip", type: "ip"} + - {from: "destination.port", to: "url.port"} + - {from: "http.request.method", to: "event.action"} + ignore_missing: true + fail_on_error: false + - add_fields: + target: event + fields: + kind: event + category: + - network + - web + type: + - connection + - info + - protocol {{ if .community_id }} - community_id: - fields: - source_ip: source.address - destination_ip: destination.address {{ end }} diff --git a/x-pack/filebeat/module/zeek/http/ingest/pipeline.json b/x-pack/filebeat/module/zeek/http/ingest/pipeline.json deleted file mode 100644 index af771f8c745..00000000000 --- a/x-pack/filebeat/module/zeek/http/ingest/pipeline.json +++ /dev/null @@ -1,123 +0,0 @@ -{ - "description": "Pipeline for normalizing Zeek http.log", - "processors": [ - { - "set": { - "field": "event.created", - "value": "{{_ingest.timestamp}}" - } - }, - { - "date": { - "field": "zeek.http.ts", - "formats": ["UNIX"] - } - }, - { - "remove": { - "field": "zeek.http.ts" - } - }, - { - "set": { - "field": "event.id", - "value": "{{zeek.session_id}}", - "if": "ctx.zeek.session_id != null" - } - }, - { - "set": { - "field": "source.ip", - "value": "{{source.address}}" - } - }, - { - "set": { - "field": "destination.ip", - "value": "{{destination.address}}" - } - }, - { - "set": { - "field": "url.port", - "value": "{{destination.port}}" - } - }, - { - "geoip": { - "field": "destination.ip", - "target_field": "destination.geo" - } - }, - { - "geoip": { - "field": "source.ip", - "target_field": "source.geo" - } - }, - { - "geoip": { - "database_file": "GeoLite2-ASN.mmdb", - "field": "source.ip", - "target_field": "source.as", - "properties": [ - "asn", - "organization_name" - ], - "ignore_missing": true - } - }, - { - "geoip": { - "database_file": "GeoLite2-ASN.mmdb", - "field": "destination.ip", - "target_field": "destination.as", - "properties": [ - "asn", - "organization_name" - ], - "ignore_missing": true - } - }, - { - "rename": { - "field": "source.as.asn", - "target_field": "source.as.number", - "ignore_missing": true - } - }, - { - "rename": { - "field": "source.as.organization_name", - "target_field": "source.as.organization.name", - "ignore_missing": true - } - }, - { - "rename": { - "field": "destination.as.asn", - "target_field": "destination.as.number", - "ignore_missing": true - } - }, - { - "rename": { - "field": "destination.as.organization_name", - "target_field": "destination.as.organization.name", - "ignore_missing": true - } - }, - { - "user_agent": { - "field": "user_agent.original", - "ignore_missing": true - } - } - ], - "on_failure": [{ - "set": { - "field": "error.message", - "value": "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/x-pack/filebeat/module/zeek/http/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/http/ingest/pipeline.yml new file mode 100644 index 00000000000..62ffef0db45 --- /dev/null +++ b/x-pack/filebeat/module/zeek/http/ingest/pipeline.yml @@ -0,0 +1,82 @@ +description: Pipeline for normalizing Zeek http.log +processors: +- set: + field: event.created + value: '{{_ingest.timestamp}}' +- date: + field: zeek.http.ts + formats: + - UNIX +- remove: + field: zeek.http.ts +- geoip: + field: destination.ip + target_field: destination.geo +- geoip: + field: source.ip + target_field: source.geo +- geoip: + database_file: GeoLite2-ASN.mmdb + field: source.ip + target_field: source.as + properties: + - asn + - organization_name + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: destination.ip + target_field: destination.as + properties: + - asn + - organization_name + ignore_missing: true +- rename: + field: source.as.asn + target_field: source.as.number + ignore_missing: true +- rename: + field: source.as.organization_name + target_field: source.as.organization.name + ignore_missing: true +- rename: + field: destination.as.asn + target_field: destination.as.number + ignore_missing: true +- rename: + field: destination.as.organization_name + target_field: destination.as.organization.name + ignore_missing: true +- user_agent: + field: user_agent.original + ignore_missing: true +- lowercase: + field: "http.request.method" + ignore_missing: true +- lowercase: + field: "event.action" + ignore_missing: true +- set: + field: event.outcome + value: success + if: "ctx?.http?.response?.status_code != null && ctx.http.response.status_code < 400" +- set: + field: event.outcome + value: failure + if: "ctx?.http?.response?.status_code != null && ctx.http.response.status_code >= 400" +- append: + field: related.ip + value: "{{source.ip}}" + if: "ctx?.source?.ip != null" +- append: + field: related.ip + value: "{{destination.ip}}" + if: "ctx?.destination?.ip != null" +- append: + field: related.user + value: "{{url.username}}" + if: "ctx?.url?.username != null" +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/x-pack/filebeat/module/zeek/http/manifest.yml b/x-pack/filebeat/module/zeek/http/manifest.yml index a9ceabbaaa1..ddd253bb218 100644 --- a/x-pack/filebeat/module/zeek/http/manifest.yml +++ b/x-pack/filebeat/module/zeek/http/manifest.yml @@ -13,7 +13,7 @@ var: - name: community_id default: true -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/http.yml requires.processors: diff --git a/x-pack/filebeat/module/zeek/http/test/http-json.log-expected.json b/x-pack/filebeat/module/zeek/http/test/http-json.log-expected.json index 20d3fedb1c7..ee72065d771 100644 --- a/x-pack/filebeat/module/zeek/http/test/http-json.log-expected.json +++ b/x-pack/filebeat/module/zeek/http/test/http-json.log-expected.json @@ -13,12 +13,24 @@ "destination.geo.region_name": "California", "destination.ip": "17.253.5.203", "destination.port": 80, + "event.action": "get", + "event.category": [ + "network", + "web" + ], "event.dataset": "zeek.http", "event.id": "CCNp8v1SNzY7v9d1Ih", + "event.kind": "event", "event.module": "zeek", + "event.outcome": "success", + "event.type": [ + "connection", + "info", + "protocol" + ], "fileset.name": "http", "http.request.body.bytes": 0, - "http.request.method": "GET", + "http.request.method": "get", "http.response.body.bytes": 3735, "http.response.status_code": 200, "http.version": "1.1", @@ -26,6 +38,10 @@ "log.offset": 0, "network.community_id": "1:dtBPRfpKEZyg1iOHss95buwv+cw=", "network.transport": "tcp", + "related.ip": [ + "10.178.98.102", + "17.253.5.203" + ], "service.type": "zeek", "source.address": "10.178.98.102", "source.ip": "10.178.98.102", @@ -35,7 +51,7 @@ ], "url.domain": "ocsp.apple.com", "url.original": "/ocsp04-aaica02/ME4wTKADAgEAMEUwQzBBMAkGBSsOAwIaBQAEFNqvF+Za6oA4ceFRLsAWwEInjUhJBBQx6napI3Sl39T97qDBpp7GEQ4R7AIIUP1IOZZ86ns=", - "url.port": "80", + "url.port": 80, "user_agent.device.name": "Other", "user_agent.name": "Other", "user_agent.original": "com.apple.trustd/2.0", diff --git a/x-pack/filebeat/module/zeek/intel/config/intel.yml b/x-pack/filebeat/module/zeek/intel/config/intel.yml index 38fe388bec0..2896ed72db9 100644 --- a/x-pack/filebeat/module/zeek/intel/config/intel.yml +++ b/x-pack/filebeat/module/zeek/intel/config/intel.yml @@ -61,3 +61,12 @@ processors: - zeek.intel.id.orig_p - zeek.intel.id.resp_h - zeek.intel.id.resp_p + - add_fields: + target: event + fields: + kind: alert + type: + - info +{{ if .community_id }} + - community_id: +{{ end }} diff --git a/x-pack/filebeat/module/zeek/intel/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/intel/ingest/pipeline.yml index 512cf67ff93..6a2bd6382ad 100644 --- a/x-pack/filebeat/module/zeek/intel/ingest/pipeline.yml +++ b/x-pack/filebeat/module/zeek/intel/ingest/pipeline.yml @@ -66,6 +66,15 @@ processors: field: destination.as.organization_name target_field: destination.as.organization.name ignore_missing: true + - append: + field: "related.ip" + value: "{{source.ip}}" + if: "ctx?.source?.ip != null" + - append: + field: "related.ip" + value: "{{destination.ip}}" + if: "ctx?.destination?.ip != null" + on_failure: - set: field: error.message diff --git a/x-pack/filebeat/module/zeek/intel/test/intel-json.log-expected.json b/x-pack/filebeat/module/zeek/intel/test/intel-json.log-expected.json index 1b2ac5464bf..d9de4e04efd 100644 --- a/x-pack/filebeat/module/zeek/intel/test/intel-json.log-expected.json +++ b/x-pack/filebeat/module/zeek/intel/test/intel-json.log-expected.json @@ -12,11 +12,19 @@ "destination.ip": "198.41.0.4", "destination.port": 53, "event.dataset": "zeek.intel", + "event.kind": "alert", "event.module": "zeek", "event.original": "{\"ts\":1573030980.989353,\"uid\":\"Ctefoj1tgOPt4D0EK2\",\"id.orig_h\":\"192.168.1.1\",\"id.orig_p\":37598,\"id.resp_h\":\"198.41.0.4\",\"id.resp_p\":53,\"seen.indicator\":\"198.41.0.4\",\"seen.indicator_type\":\"Intel::ADDR\",\"seen.where\":\"Conn::IN_RESP\",\"seen.node\":\"worker-1-2\",\"matched\":[\"Intel::ADDR\"],\"sources\":[\"ETPRO Rep: AbusedTLD Score: 127\"]}", + "event.type": [ + "info" + ], "fileset.name": "intel", "input.type": "log", "log.offset": 0, + "related.ip": [ + "192.168.1.1", + "198.41.0.4" + ], "service.type": "zeek", "source.address": "192.168.1.1", "source.ip": "192.168.1.1", diff --git a/x-pack/filebeat/module/zeek/irc/config/irc.yml b/x-pack/filebeat/module/zeek/irc/config/irc.yml index 1ee45c0dc57..4d5783b8087 100644 --- a/x-pack/filebeat/module/zeek/irc/config/irc.yml +++ b/x-pack/filebeat/module/zeek/irc/config/irc.yml @@ -45,10 +45,28 @@ processors: ignore_missing: true fail_on_error: false - + - convert: + fields: + - {from: "zeek.session_id", to: "event.id"} + - {from: "source.address", to: "source.ip", type: "ip"} + - {from: "destination.address", to: "destination.ip", type: "ip"} + - {from: "zeek.irc.user", to: "user.name"} + - {from: "zeek.irc.command", to: "event.action"} + - {from: "zeek.irc.dcc.file.name", to: "file.name"} + - {from: "zeek.irc.dcc.file.size", to: "file.size"} + - {from: "zeek.irc.dcc.mime_type", to: "file.mime_type"} + ignore_missing: true + fail_on_error: false + - add_fields: + target: event + fields: + kind: event + category: + - network + type: + - connection + - protocol + - info {{ if .community_id }} - community_id: - fields: - source_ip: source.address - destination_ip: destination.address {{ end }} diff --git a/x-pack/filebeat/module/zeek/irc/ingest/pipeline.json b/x-pack/filebeat/module/zeek/irc/ingest/pipeline.json deleted file mode 100644 index 40723512349..00000000000 --- a/x-pack/filebeat/module/zeek/irc/ingest/pipeline.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "description": "Pipeline for normalizing Zeek irc.log", - "processors": [ - { - "set": { - "field": "event.created", - "value": "{{_ingest.timestamp}}" - } - }, - { - "date": { - "field": "zeek.irc.ts", - "formats": ["UNIX"] - } - }, - { - "remove": { - "field": "zeek.irc.ts" - } - }, - { - "set": { - "field": "event.id", - "value": "{{zeek.session_id}}", - "if": "ctx.zeek.session_id != null" - } - }, - { - "set": { - "field": "source.ip", - "value": "{{source.address}}" - } - }, - { - "set": { - "field": "destination.ip", - "value": "{{destination.address}}" - } - } - ], - "on_failure" : [{ - "set" : { - "field" : "error.message", - "value" : "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/x-pack/filebeat/module/zeek/irc/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/irc/ingest/pipeline.yml new file mode 100644 index 00000000000..ec04f4e7c93 --- /dev/null +++ b/x-pack/filebeat/module/zeek/irc/ingest/pipeline.yml @@ -0,0 +1,65 @@ +description: Pipeline for normalizing Zeek irc.log +processors: +- set: + field: event.created + value: '{{_ingest.timestamp}}' +- date: + field: zeek.irc.ts + formats: + - UNIX +- remove: + field: zeek.irc.ts +- append: + field: related.ip + value: "{{source.ip}}" + if: "ctx?.source?.ip != null" +- append: + field: related.ip + value: "{{destination.ip}}" + if: "ctx?.destination?.ip != null" +- append: + field: related.user + value: "{{user.name}}" + if: "ctx?.user?.name != null" +- geoip: + field: destination.ip + target_field: destination.geo +- geoip: + field: source.ip + target_field: source.geo +- geoip: + database_file: GeoLite2-ASN.mmdb + field: source.ip + target_field: source.as + properties: + - asn + - organization_name + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: destination.ip + target_field: destination.as + properties: + - asn + - organization_name + ignore_missing: true +- rename: + field: source.as.asn + target_field: source.as.number + ignore_missing: true +- rename: + field: source.as.organization_name + target_field: source.as.organization.name + ignore_missing: true +- rename: + field: destination.as.asn + target_field: destination.as.number + ignore_missing: true +- rename: + field: destination.as.organization_name + target_field: destination.as.organization.name + ignore_missing: true +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/x-pack/filebeat/module/zeek/irc/manifest.yml b/x-pack/filebeat/module/zeek/irc/manifest.yml index ce7cd7b714e..3bf899fd2c0 100644 --- a/x-pack/filebeat/module/zeek/irc/manifest.yml +++ b/x-pack/filebeat/module/zeek/irc/manifest.yml @@ -13,5 +13,5 @@ var: - name: community_id default: true -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/irc.yml diff --git a/x-pack/filebeat/module/zeek/irc/test/irc-json.log-expected.json b/x-pack/filebeat/module/zeek/irc/test/irc-json.log-expected.json index 2a12e671ea5..245d1154e86 100644 --- a/x-pack/filebeat/module/zeek/irc/test/irc-json.log-expected.json +++ b/x-pack/filebeat/module/zeek/irc/test/irc-json.log-expected.json @@ -2,17 +2,37 @@ { "@timestamp": "2013-12-20T15:44:10.647Z", "destination.address": "38.229.70.20", + "destination.as.number": 23028, + "destination.as.organization.name": "Team Cymru Inc.", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "38.229.70.20", "destination.port": 8000, + "event.action": "USER", + "event.category": [ + "network" + ], "event.dataset": "zeek.irc", "event.id": "CNJBX5FQdL62VUUP1", + "event.kind": "event", "event.module": "zeek", + "event.type": [ + "connection", + "protocol", + "info" + ], "fileset.name": "irc", "input.type": "log", "log.offset": 0, "network.community_id": "1:YdkGov/c+KLtmg7Cf5DLDB4+YdQ=", "network.protocol": "irc", "network.transport": "tcp", + "related.ip": [ + "10.180.156.249", + "38.229.70.20" + ], "service.type": "zeek", "source.address": "10.180.156.249", "source.ip": "10.180.156.249", @@ -28,17 +48,40 @@ { "@timestamp": "2013-12-20T15:44:10.647Z", "destination.address": "38.229.70.20", + "destination.as.number": 23028, + "destination.as.organization.name": "Team Cymru Inc.", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "38.229.70.20", "destination.port": 8000, + "event.action": "NICK", + "event.category": [ + "network" + ], "event.dataset": "zeek.irc", "event.id": "CNJBX5FQdL62VUUP1", + "event.kind": "event", "event.module": "zeek", + "event.type": [ + "connection", + "protocol", + "info" + ], "fileset.name": "irc", "input.type": "log", "log.offset": 206, "network.community_id": "1:YdkGov/c+KLtmg7Cf5DLDB4+YdQ=", "network.protocol": "irc", "network.transport": "tcp", + "related.ip": [ + "10.180.156.249", + "38.229.70.20" + ], + "related.user": [ + "xxxxx" + ], "service.type": "zeek", "source.address": "10.180.156.249", "source.ip": "10.180.156.249", @@ -46,6 +89,7 @@ "tags": [ "zeek.irc" ], + "user.name": "xxxxx", "zeek.irc.addl": "+iw xxxxx XxxxxxXxxx ", "zeek.irc.command": "NICK", "zeek.irc.user": "xxxxx", @@ -55,17 +99,40 @@ { "@timestamp": "2013-12-20T15:44:10.706Z", "destination.address": "38.229.70.20", + "destination.as.number": 23028, + "destination.as.organization.name": "Team Cymru Inc.", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "38.229.70.20", "destination.port": 8000, + "event.action": "JOIN", + "event.category": [ + "network" + ], "event.dataset": "zeek.irc", "event.id": "CNJBX5FQdL62VUUP1", + "event.kind": "event", "event.module": "zeek", + "event.type": [ + "connection", + "protocol", + "info" + ], "fileset.name": "irc", "input.type": "log", "log.offset": 432, "network.community_id": "1:YdkGov/c+KLtmg7Cf5DLDB4+YdQ=", "network.protocol": "irc", "network.transport": "tcp", + "related.ip": [ + "10.180.156.249", + "38.229.70.20" + ], + "related.user": [ + "xxxxx" + ], "service.type": "zeek", "source.address": "10.180.156.249", "source.ip": "10.180.156.249", @@ -73,6 +140,7 @@ "tags": [ "zeek.irc" ], + "user.name": "xxxxx", "zeek.irc.addl": " with channel key: '-'", "zeek.irc.command": "JOIN", "zeek.irc.nick": "molochtest", diff --git a/x-pack/filebeat/module/zeek/kerberos/config/kerberos.yml b/x-pack/filebeat/module/zeek/kerberos/config/kerberos.yml index 4bbcf677b70..28c49507406 100644 --- a/x-pack/filebeat/module/zeek/kerberos/config/kerberos.yml +++ b/x-pack/filebeat/module/zeek/kerberos/config/kerberos.yml @@ -72,10 +72,33 @@ processors: ignore_missing: true fail_on_error: false - + - convert: + fields: + - {from: "zeek.session_id", to: "event.id"} + - {from: "source.address", to: "source.ip", type: "ip"} + - {from: "source.address", to: "client.address"} + - {from: "destination.address", to: "destination.ip", type: "ip"} + - {from: "destination.address", to: "server.address"} + - {from: "zeek.kerberos.request_type", to: "event.action"} + ignore_missing: true + fail_on_error: false + - add_fields: + target: event + fields: + kind: event + category: + - network + type: + - connection + - protocol + - authentication + - dissect: + when: + contains: + zeek.kerberos.client: "/" + tokenizer: "%{user.name}/%{user.domain}" + field: zeek.kerberos.client + target_prefix: "" {{ if .community_id }} - community_id: - fields: - source_ip: source.address - destination_ip: destination.address {{ end }} diff --git a/x-pack/filebeat/module/zeek/kerberos/ingest/pipeline.json b/x-pack/filebeat/module/zeek/kerberos/ingest/pipeline.json deleted file mode 100644 index 988e9b7f2b1..00000000000 --- a/x-pack/filebeat/module/zeek/kerberos/ingest/pipeline.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "description": "Pipeline for normalizing Zeek kerberos.log", - "processors": [ - { - "set": { - "field": "event.created", - "value": "{{_ingest.timestamp}}" - } - }, - { - "date": { - "field": "zeek.kerberos.ts", - "formats": ["UNIX"] - } - }, - { - "remove": { - "field": "zeek.kerberos.ts" - } - }, - { - "set": { - "field": "event.id", - "value": "{{zeek.session_id}}", - "if": "ctx.zeek.session_id != null" - } - }, - { - "set": { - "field": "source.ip", - "value": "{{source.address}}" - } - }, - { - "set": { - "field": "client.address", - "value": "{{source.address}}" - } - }, - { - "set": { - "field": "server.address", - "value": "{{destination.address}}" - } - }, - { - "set": { - "field": "destination.ip", - "value": "{{destination.address}}" - } - }, - { - "script": { - "source": "ctx.zeek.kerberos.valid.days = Math.round( (ctx.zeek.kerberos.valid.until - ctx.zeek.kerberos.valid.from) / 86400 )", - "if": "ctx.zeek.kerberos.valid?.from != null && ctx.zeek.kerberos.valid?.until != null" - } - }, - { - "date": { - "field": "zeek.kerberos.valid.until", - "target_field": "zeek.kerberos.valid.until", - "formats": ["UNIX"], - "if": "ctx.zeek.kerberos.valid?.until != null" - } - }, - { - "date": { - "field": "zeek.kerberos.valid.from", - "target_field": "zeek.kerberos.valid.from", - "formats": ["UNIX"], - "if": "ctx.zeek.kerberos.valid?.from != null" - } - } - ], - "on_failure": [{ - "set": { - "field": "error.message", - "value": "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/x-pack/filebeat/module/zeek/kerberos/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/kerberos/ingest/pipeline.yml new file mode 100644 index 00000000000..05005491115 --- /dev/null +++ b/x-pack/filebeat/module/zeek/kerberos/ingest/pipeline.yml @@ -0,0 +1,90 @@ +description: Pipeline for normalizing Zeek kerberos.log +processors: +- set: + field: event.created + value: '{{_ingest.timestamp}}' +- date: + field: zeek.kerberos.ts + formats: + - UNIX +- remove: + field: zeek.kerberos.ts +- script: + source: "ctx.zeek.kerberos.valid.days = Math.round( (ctx.zeek.kerberos.valid.until - ctx.zeek.kerberos.valid.from) / 86400 )" + if: "ctx.zeek.kerberos.valid?.from != null && ctx.zeek.kerberos.valid?.until != null" +- date: + field: zeek.kerberos.valid.until + target_field: zeek.kerberos.valid.until + formats: + - UNIX + if: ctx.zeek.kerberos.valid?.until != null +- date: + field: zeek.kerberos.valid.from + target_field: zeek.kerberos.valid.from + formats: + - UNIX + if: ctx.zeek.kerberos.valid?.from != null +- set: + field: event.outcome + value: success + if: "ctx?.zeek?.kerberos?.success == true" +- set: + field: event.outcome + value: failure + if: "ctx?.zeek?.kerberos?.success == false" +- geoip: + field: destination.ip + target_field: destination.geo + ignore_missing: true +- geoip: + field: source.ip + target_field: source.geo + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: source.ip + target_field: source.as + properties: + - asn + - organization_name + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: destination.ip + target_field: destination.as + properties: + - asn + - organization_name + ignore_missing: true +- rename: + field: source.as.asn + target_field: source.as.number + ignore_missing: true +- rename: + field: source.as.organization_name + target_field: source.as.organization.name + ignore_missing: true +- rename: + field: destination.as.asn + target_field: destination.as.number + ignore_missing: true +- rename: + field: destination.as.organization_name + target_field: destination.as.organization.name + ignore_missing: true +- append: + field: related.ip + value: "{{source.ip}}" + if: "ctx?.source?.ip != null" +- append: + field: related.ip + value: "{{destination.ip}}" + if: "ctx?.destination?.ip != null" +- append: + field: related.user + value: "{{user.name}}" + if: "ctx?.user?.name != null" +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/x-pack/filebeat/module/zeek/kerberos/manifest.yml b/x-pack/filebeat/module/zeek/kerberos/manifest.yml index a2e040be371..4a94434f1d4 100644 --- a/x-pack/filebeat/module/zeek/kerberos/manifest.yml +++ b/x-pack/filebeat/module/zeek/kerberos/manifest.yml @@ -13,7 +13,7 @@ var: - name: community_id default: true -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/kerberos.yml requires.processors: diff --git a/x-pack/filebeat/module/zeek/kerberos/test/kerberos-json.log-expected.json b/x-pack/filebeat/module/zeek/kerberos/test/kerberos-json.log-expected.json index a09e3ac8a4f..e01e42a4036 100644 --- a/x-pack/filebeat/module/zeek/kerberos/test/kerberos-json.log-expected.json +++ b/x-pack/filebeat/module/zeek/kerberos/test/kerberos-json.log-expected.json @@ -5,15 +5,33 @@ "destination.address": "192.168.10.10", "destination.ip": "192.168.10.10", "destination.port": 88, + "event.action": "TGS", + "event.category": [ + "network" + ], "event.dataset": "zeek.kerberos", "event.id": "C56Flhb4WQBNkfMOl", + "event.kind": "event", "event.module": "zeek", + "event.outcome": "success", + "event.type": [ + "connection", + "protocol", + "authentication" + ], "fileset.name": "kerberos", "input.type": "log", "log.offset": 0, "network.community_id": "1:DW/lSsosl8gZ8pqO9kKMm7cZheQ=", "network.protocol": "kerberos", "network.transport": "tcp", + "related.ip": [ + "192.168.10.31", + "192.168.10.10" + ], + "related.user": [ + "RonHD" + ], "server.address": "192.168.10.10", "service.type": "zeek", "source.address": "192.168.10.31", @@ -22,6 +40,8 @@ "tags": [ "zeek.kerberos" ], + "user.domain": "CONTOSO.LOCAL", + "user.name": "RonHD", "zeek.kerberos.cipher": "aes256-cts-hmac-sha1-96", "zeek.kerberos.client": "RonHD/CONTOSO.LOCAL", "zeek.kerberos.forwardable": true, diff --git a/x-pack/filebeat/module/zeek/modbus/config/modbus.yml b/x-pack/filebeat/module/zeek/modbus/config/modbus.yml index fec2b954224..6dc8c3004d4 100644 --- a/x-pack/filebeat/module/zeek/modbus/config/modbus.yml +++ b/x-pack/filebeat/module/zeek/modbus/config/modbus.yml @@ -39,10 +39,35 @@ processors: ignore_missing: true fail_on_error: false - + - convert: + fields: + - {from: "zeek.session_id", to: "event.id"} + - {from: "source.address", to: "source.ip", type: "ip"} + - {from: "destination.address", to: "destination.ip", type: "ip"} + - {from: "zeek.modbus.function", to: "event.action"} + ignore_missing: true + fail_on_error: false + - add_fields: + target: event + fields: + kind: event + category: + - network + type: + - connection + - protocol + - if: + has_fields: ['zeek.modbus.exception'] + then: + - add_fields: + target: event + fields: + outcome: failure + else: + - add_fields: + target: event + fields: + outcome: success {{ if .community_id }} - community_id: - fields: - source_ip: source.address - destination_ip: destination.address {{ end }} diff --git a/x-pack/filebeat/module/zeek/modbus/ingest/pipeline.json b/x-pack/filebeat/module/zeek/modbus/ingest/pipeline.json deleted file mode 100644 index 78026f2dc87..00000000000 --- a/x-pack/filebeat/module/zeek/modbus/ingest/pipeline.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "description": "Pipeline for normalizing Zeek modbus.log", - "processors": [ - { - "set": { - "field": "event.created", - "value": "{{_ingest.timestamp}}" - } - }, - { - "date": { - "field": "zeek.modbus.ts", - "formats": ["UNIX"] - } - }, - { - "remove": { - "field": "zeek.modbus.ts" - } - }, - { - "set": { - "field": "event.id", - "value": "{{zeek.session_id}}", - "if": "ctx.zeek.session_id != null" - } - }, - { - "set": { - "field": "source.ip", - "value": "{{source.address}}" - } - }, - { - "set": { - "field": "destination.ip", - "value": "{{destination.address}}" - } - } - ], - "on_failure" : [{ - "set" : { - "field" : "error.message", - "value" : "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/x-pack/filebeat/module/zeek/modbus/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/modbus/ingest/pipeline.yml new file mode 100644 index 00000000000..d053a541ef5 --- /dev/null +++ b/x-pack/filebeat/module/zeek/modbus/ingest/pipeline.yml @@ -0,0 +1,63 @@ +description: Pipeline for normalizing Zeek modbus.log +processors: +- set: + field: event.created + value: '{{_ingest.timestamp}}' +- date: + field: zeek.modbus.ts + formats: + - UNIX +- remove: + field: zeek.modbus.ts +- append: + field: related.ip + value: "{{source.ip}}" + if: "ctx?.source?.ip != null" +- append: + field: related.ip + value: "{{destination.ip}}" + if: "ctx?.destination?.ip != null" +- geoip: + field: destination.ip + target_field: destination.geo + ignore_missing: true +- geoip: + field: source.ip + target_field: source.geo + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: source.ip + target_field: source.as + properties: + - asn + - organization_name + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: destination.ip + target_field: destination.as + properties: + - asn + - organization_name + ignore_missing: true +- rename: + field: source.as.asn + target_field: source.as.number + ignore_missing: true +- rename: + field: source.as.organization_name + target_field: source.as.organization.name + ignore_missing: true +- rename: + field: destination.as.asn + target_field: destination.as.number + ignore_missing: true +- rename: + field: destination.as.organization_name + target_field: destination.as.organization.name + ignore_missing: true +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/x-pack/filebeat/module/zeek/modbus/manifest.yml b/x-pack/filebeat/module/zeek/modbus/manifest.yml index 98e51ae2bec..e20412fadc6 100644 --- a/x-pack/filebeat/module/zeek/modbus/manifest.yml +++ b/x-pack/filebeat/module/zeek/modbus/manifest.yml @@ -13,5 +13,5 @@ var: - name: community_id default: true -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/modbus.yml diff --git a/x-pack/filebeat/module/zeek/modbus/test/modbus-json.log-expected.json b/x-pack/filebeat/module/zeek/modbus/test/modbus-json.log-expected.json index 9817176e098..ba9034a3621 100644 --- a/x-pack/filebeat/module/zeek/modbus/test/modbus-json.log-expected.json +++ b/x-pack/filebeat/module/zeek/modbus/test/modbus-json.log-expected.json @@ -4,15 +4,29 @@ "destination.address": "192.168.1.164", "destination.ip": "192.168.1.164", "destination.port": 502, + "event.action": "READ_COILS", + "event.category": [ + "network" + ], "event.dataset": "zeek.modbus", "event.id": "CpIIXl4DFGswmjH2bl", + "event.kind": "event", "event.module": "zeek", + "event.outcome": "success", + "event.type": [ + "connection", + "protocol" + ], "fileset.name": "modbus", "input.type": "log", "log.offset": 0, "network.community_id": "1:jEXbR2FqHyMgLJgyYyFQN3yxbpc=", "network.protocol": "modbus", "network.transport": "tcp", + "related.ip": [ + "192.168.1.10", + "192.168.1.164" + ], "service.type": "zeek", "source.address": "192.168.1.10", "source.ip": "192.168.1.10", diff --git a/x-pack/filebeat/module/zeek/mysql/config/mysql.yml b/x-pack/filebeat/module/zeek/mysql/config/mysql.yml index fcd226131bc..b28262b5bd5 100644 --- a/x-pack/filebeat/module/zeek/mysql/config/mysql.yml +++ b/x-pack/filebeat/module/zeek/mysql/config/mysql.yml @@ -36,10 +36,37 @@ processors: ignore_missing: true fail_on_error: false - + - convert: + fields: + - {from: "zeek.session_id", to: "event.id"} + - {from: "source.address", to: "source.ip", type: "ip"} + - {from: "destination.address", to: "destination.ip", type: "ip"} + - {from: "zeek.mysql.cmd", to: "event.action"} + ignore_missing: true + fail_on_error: false + - add_fields: + target: event + fields: + kind: event + category: + - database + - network + type: + - connection + - protocol + - if: + equals: + zeek.mysql.success: true + then: + - add_fields: + target: event + fields: + outcome: success + else: + - add_fields: + target: event + fields: + outcome: failure {{ if .community_id }} - community_id: - fields: - source_ip: source.address - destination_ip: destination.address {{ end }} diff --git a/x-pack/filebeat/module/zeek/mysql/ingest/pipeline.json b/x-pack/filebeat/module/zeek/mysql/ingest/pipeline.json deleted file mode 100644 index ec55df982d7..00000000000 --- a/x-pack/filebeat/module/zeek/mysql/ingest/pipeline.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "description": "Pipeline for normalizing Zeek mysql.log", - "processors": [ - { - "set": { - "field": "event.created", - "value": "{{_ingest.timestamp}}" - } - }, - { - "date": { - "field": "zeek.mysql.ts", - "formats": ["UNIX"] - } - }, - { - "remove": { - "field": "zeek.mysql.ts" - } - }, - { - "set": { - "field": "event.id", - "value": "{{zeek.session_id}}", - "if": "ctx.zeek.session_id != null" - } - }, - { - "set": { - "field": "source.ip", - "value": "{{source.address}}" - } - }, - { - "set": { - "field": "destination.ip", - "value": "{{destination.address}}" - } - } - ], - "on_failure" : [{ - "set" : { - "field" : "error.message", - "value" : "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/x-pack/filebeat/module/zeek/mysql/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/mysql/ingest/pipeline.yml new file mode 100644 index 00000000000..ca2c6c57172 --- /dev/null +++ b/x-pack/filebeat/module/zeek/mysql/ingest/pipeline.yml @@ -0,0 +1,83 @@ +description: Pipeline for normalizing Zeek mysql.log +processors: +- set: + field: event.created + value: '{{_ingest.timestamp}}' +- date: + field: zeek.mysql.ts + formats: + - UNIX +- remove: + field: zeek.mysql.ts +- append: + field: related.ip + value: "{{source.ip}}" + if: "ctx?.source?.ip != null" +- append: + field: related.ip + value: "{{destination.ip}}" + if: "ctx?.destination?.ip != null" +- geoip: + field: destination.ip + target_field: destination.geo + ignore_missing: true +- geoip: + field: source.ip + target_field: source.geo + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: source.ip + target_field: source.as + properties: + - asn + - organization_name + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: destination.ip + target_field: destination.as + properties: + - asn + - organization_name + ignore_missing: true +- rename: + field: source.as.asn + target_field: source.as.number + ignore_missing: true +- rename: + field: source.as.organization_name + target_field: source.as.organization.name + ignore_missing: true +- rename: + field: destination.as.asn + target_field: destination.as.number + ignore_missing: true +- rename: + field: destination.as.organization_name + target_field: destination.as.organization.name + ignore_missing: true +- append: + field: event.type + value: access + if: "ctx?.zeek?.mysql?.cmd != null && (ctx.zeek.mysql.cmd == 'connect' || ctx.zeek.mysql.cmd == 'connect_out')" +- append: + field: event.type + value: change + if: "ctx?.zeek?.mysql?.cmd != null && (ctx.zeek.mysql.cmd == 'init_db' || ctx.zeek.mysql.cmd == 'change_user' || ctx.zeek.mysql.cmd == 'set_option' || ctx.zeek.mysql.cmd == 'drop_db' || ctx.zeek.mysql.cmd == 'create_db' || ctx.zeek.mysql.cmd == 'process_kill' || ctx.zeek.mysql.cmd == 'delayed_insert')" +- append: + field: event.type + value: info + if: "ctx?.zeek?.mysql?.cmd != null && ctx.zeek.mysql.cmd != 'init_db' && ctx.zeek.mysql.cmd != 'change_user' && ctx.zeek.mysql.cmd != 'set_option' && ctx.zeek.mysql.cmd != 'drop_db' && ctx.zeek.mysql.cmd != 'create_db' && ctx.zeek.mysql.cmd != 'process_kill' && ctx.zeek.mysql.cmd != 'delayed_insert' && ctx.zeek.mysql.cmd != 'connect' && ctx.zeek.mysql.cmd != 'connect_out'" +- append: + field: event.type + value: start + if: "ctx?.zeek?.mysql?.cmd != null && ctx.zeek.mysql.cmd == 'connect'" +- append: + field: event.type + value: end + if: "ctx?.zeek?.mysql?.cmd != null && ctx.zeek.mysql.cmd == 'connect_out'" +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/x-pack/filebeat/module/zeek/mysql/manifest.yml b/x-pack/filebeat/module/zeek/mysql/manifest.yml index a16c6092cc7..1b7ec4edb19 100644 --- a/x-pack/filebeat/module/zeek/mysql/manifest.yml +++ b/x-pack/filebeat/module/zeek/mysql/manifest.yml @@ -13,5 +13,5 @@ var: - name: community_id default: true -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/mysql.yml diff --git a/x-pack/filebeat/module/zeek/mysql/test/mysql-json.log-expected.json b/x-pack/filebeat/module/zeek/mysql/test/mysql-json.log-expected.json index 279b1019404..bf68cae48fe 100644 --- a/x-pack/filebeat/module/zeek/mysql/test/mysql-json.log-expected.json +++ b/x-pack/filebeat/module/zeek/mysql/test/mysql-json.log-expected.json @@ -4,15 +4,31 @@ "destination.address": "192.168.0.254", "destination.ip": "192.168.0.254", "destination.port": 3306, + "event.action": "query", + "event.category": [ + "database", + "network" + ], "event.dataset": "zeek.mysql", "event.id": "C5Hol527kLMUw36hj3", + "event.kind": "event", "event.module": "zeek", + "event.outcome": "success", + "event.type": [ + "connection", + "protocol", + "info" + ], "fileset.name": "mysql", "input.type": "log", "log.offset": 0, "network.community_id": "1:0HUQbshhYbATQXDHv/ysOs0DlZA=", "network.protocol": "mysql", "network.transport": "tcp", + "related.ip": [ + "192.168.0.254", + "192.168.0.254" + ], "service.type": "zeek", "source.address": "192.168.0.254", "source.ip": "192.168.0.254", diff --git a/x-pack/filebeat/module/zeek/notice/config/notice.yml b/x-pack/filebeat/module/zeek/notice/config/notice.yml index 7f5c9c0869c..32ab849b6b5 100644 --- a/x-pack/filebeat/module/zeek/notice/config/notice.yml +++ b/x-pack/filebeat/module/zeek/notice/config/notice.yml @@ -78,9 +78,25 @@ processors: - drop_fields: fields: ["zeek.notice.remote_location", "zeek.notice.f"] + - convert: + fields: + - {from: "zeek.session_id", to: "event.id"} + - {from: "source.address", to: "source.ip", type: "ip"} + - {from: "destination.address", to: "destination.ip", type: "ip"} + - {from: "zeek.notice.file.total_bytes", to: "file.size"} + - {from: "zeek.notice.file.mime_type", to: "file.mime_type"} + - {from: "zeek.notice.note", to: "rule.name"} + - {from: "zeek.notice.msg", to: "rule.description"} + ignore_missing: true + fail_on_error: false + - add_fields: + target: event + fields: + kind: alert + category: + - intrusion_detection + type: + - info {{ if .community_id }} - community_id: - fields: - source_ip: source.address - destination_ip: destination.address {{ end }} diff --git a/x-pack/filebeat/module/zeek/notice/ingest/pipeline.json b/x-pack/filebeat/module/zeek/notice/ingest/pipeline.json deleted file mode 100644 index b343068d6c6..00000000000 --- a/x-pack/filebeat/module/zeek/notice/ingest/pipeline.json +++ /dev/null @@ -1,115 +0,0 @@ -{ - "description": "Pipeline for normalizing Zeek notice.log", - "processors": [ - { - "set": { - "field": "event.created", - "value": "{{_ingest.timestamp}}" - } - }, - { - "date": { - "field": "zeek.notice.ts", - "formats": ["UNIX"] - } - }, - { - "remove": { - "field": "zeek.notice.ts" - } - }, - { - "set": { - "field": "destination.ip", - "value": "{{destination.address}}", - "if": "ctx.destination?.address != null" - } - }, - { - "set": { - "field": "source.ip", - "value": "{{source.address}}", - "if": "ctx.source?.address != null" - } - }, - { - "set": { - "field": "event.id", - "value": "{{zeek.session_id}}", - "if": "ctx.zeek.session_id != null" - } - }, - { - "geoip": { - "field": "destination.ip", - "target_field": "destination.geo", - "ignore_missing": true - } - }, - { - "geoip": { - "field": "source.ip", - "target_field": "source.geo", - "ignore_missing": true - } - }, - { - "geoip": { - "database_file": "GeoLite2-ASN.mmdb", - "field": "source.ip", - "target_field": "source.as", - "properties": [ - "asn", - "organization_name" - ], - "ignore_missing": true - } - }, - { - "geoip": { - "database_file": "GeoLite2-ASN.mmdb", - "field": "destination.ip", - "target_field": "destination.as", - "properties": [ - "asn", - "organization_name" - ], - "ignore_missing": true - } - }, - { - "rename": { - "field": "source.as.asn", - "target_field": "source.as.number", - "ignore_missing": true - } - }, - { - "rename": { - "field": "source.as.organization_name", - "target_field": "source.as.organization.name", - "ignore_missing": true - } - }, - { - "rename": { - "field": "destination.as.asn", - "target_field": "destination.as.number", - "ignore_missing": true - } - }, - { - "rename": { - "field": "destination.as.organization_name", - "target_field": "destination.as.organization.name", - "ignore_missing": true - } - } - ], - "on_failure": [{ - "set": { - "field": "error.message", - "value": "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/x-pack/filebeat/module/zeek/notice/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/notice/ingest/pipeline.yml new file mode 100644 index 00000000000..c4dee6b78f2 --- /dev/null +++ b/x-pack/filebeat/module/zeek/notice/ingest/pipeline.yml @@ -0,0 +1,71 @@ +description: Pipeline for normalizing Zeek notice.log +processors: +- set: + field: event.created + value: '{{_ingest.timestamp}}' +- date: + field: zeek.notice.ts + formats: + - UNIX +- remove: + field: zeek.notice.ts +- geoip: + field: destination.ip + target_field: destination.geo + ignore_missing: true +- geoip: + field: source.ip + target_field: source.geo + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: source.ip + target_field: source.as + properties: + - asn + - organization_name + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: destination.ip + target_field: destination.as + properties: + - asn + - organization_name + ignore_missing: true +- rename: + field: source.as.asn + target_field: source.as.number + ignore_missing: true +- rename: + field: source.as.organization_name + target_field: source.as.organization.name + ignore_missing: true +- rename: + field: destination.as.asn + target_field: destination.as.number + ignore_missing: true +- rename: + field: destination.as.organization_name + target_field: destination.as.organization.name + ignore_missing: true +- append: + field: related.ip + value: "{{source.ip}}" + if: "ctx?.source?.ip != null" +- append: + field: related.ip + value: "{{destination.ip}}" + if: "ctx?.destination?.ip != null" +- append: + field: event.type + value: allowed + if: "ctx?.zeek?.notice?.dropped == false" +- append: + field: event.type + value: denied + if: "ctx?.zeek?.notice?.dropped == true" +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/x-pack/filebeat/module/zeek/notice/manifest.yml b/x-pack/filebeat/module/zeek/notice/manifest.yml index 7b98a8efefc..e2bdf695027 100644 --- a/x-pack/filebeat/module/zeek/notice/manifest.yml +++ b/x-pack/filebeat/module/zeek/notice/manifest.yml @@ -13,7 +13,7 @@ var: - name: community_id default: true -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/notice.yml requires.processors: diff --git a/x-pack/filebeat/module/zeek/notice/test/notice-json.log-expected.json b/x-pack/filebeat/module/zeek/notice/test/notice-json.log-expected.json index 58a59ab4d7b..a5838e9f3f1 100644 --- a/x-pack/filebeat/module/zeek/notice/test/notice-json.log-expected.json +++ b/x-pack/filebeat/module/zeek/notice/test/notice-json.log-expected.json @@ -1,11 +1,24 @@ [ { "@timestamp": "2011-11-04T19:44:35.879Z", + "event.category": [ + "intrusion_detection" + ], "event.dataset": "zeek.notice", + "event.kind": "alert", "event.module": "zeek", + "event.type": [ + "info", + "allowed" + ], "fileset.name": "notice", "input.type": "log", "log.offset": 0, + "related.ip": [ + "172.16.238.1" + ], + "rule.description": "172.16.238.1 appears to be guessing SSH passwords (seen in 30 connections).", + "rule.name": "SSH::Password_Guessing", "service.type": "zeek", "source.address": "172.16.238.1", "source.ip": "172.16.238.1", @@ -32,11 +45,25 @@ "destination.geo.region_iso_code": "DE-HE", "destination.geo.region_name": "Hesse", "destination.ip": "207.154.238.205", + "event.category": [ + "intrusion_detection" + ], "event.dataset": "zeek.notice", + "event.kind": "alert", "event.module": "zeek", + "event.type": [ + "info", + "allowed" + ], "fileset.name": "notice", "input.type": "log", "log.offset": 357, + "related.ip": [ + "8.42.77.171", + "207.154.238.205" + ], + "rule.description": "8.42.77.171 scanned at least 15 unique ports of host 207.154.238.205 in 0m0s", + "rule.name": "Scan::Port_Scan", "service.type": "zeek", "source.address": "8.42.77.171", "source.as.number": 393552, diff --git a/x-pack/filebeat/module/zeek/ntlm/config/ntlm.yml b/x-pack/filebeat/module/zeek/ntlm/config/ntlm.yml index 76cfecaaf54..55a6795b6fa 100644 --- a/x-pack/filebeat/module/zeek/ntlm/config/ntlm.yml +++ b/x-pack/filebeat/module/zeek/ntlm/config/ntlm.yml @@ -48,10 +48,39 @@ processors: ignore_missing: true fail_on_error: false - + - convert: + fields: + - {from: "zeek.session_id", to: "event.id"} + - {from: "source.address", to: "source.ip", type: "ip"} + - {from: "destination.address", to: "destination.ip", type: "ip"} + - {from: "zeek.ntlm.username", to: "user.name"} + - {from: "zeek.ntlm.domain", to: "user.domain"} + - add_fields: + target: event + fields: + kind: event + category: + - authentication + - network + type: + - info + - connection + - if: + equals: + zeek.ntlm.success: true + then: + - add_fields: + target: event + fields: + outcome: success + - if: + equals: + zeek.ntlm.success: false + then: + - add_fields: + target: event + fields: + outcome: failure {{ if .community_id }} - community_id: - fields: - source_ip: source.address - destination_ip: destination.address {{ end }} diff --git a/x-pack/filebeat/module/zeek/ntlm/ingest/pipeline.json b/x-pack/filebeat/module/zeek/ntlm/ingest/pipeline.json deleted file mode 100644 index 680ea8815e0..00000000000 --- a/x-pack/filebeat/module/zeek/ntlm/ingest/pipeline.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "description": "Pipeline for normalizing Zeek ntlm.log", - "processors": [ - { - "set": { - "field": "event.created", - "value": "{{_ingest.timestamp}}" - } - }, - { - "date": { - "field": "zeek.ntlm.ts", - "formats": ["UNIX"] - } - }, - { - "remove": { - "field": "zeek.ntlm.ts" - } - }, - { - "set": { - "field": "event.id", - "value": "{{zeek.session_id}}", - "if": "ctx.zeek.session_id != null" - } - }, - { - "set": { - "field": "source.ip", - "value": "{{source.address}}" - } - }, - { - "set": { - "field": "destination.ip", - "value": "{{destination.address}}" - } - } - ], - "on_failure" : [{ - "set" : { - "field" : "error.message", - "value" : "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/x-pack/filebeat/module/zeek/ntlm/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/ntlm/ingest/pipeline.yml new file mode 100644 index 00000000000..9f76d461392 --- /dev/null +++ b/x-pack/filebeat/module/zeek/ntlm/ingest/pipeline.yml @@ -0,0 +1,67 @@ +description: Pipeline for normalizing Zeek ntlm.log +processors: +- set: + field: event.created + value: '{{_ingest.timestamp}}' +- date: + field: zeek.ntlm.ts + formats: + - UNIX +- remove: + field: zeek.ntlm.ts +- append: + field: related.ip + value: "{{source.ip}}" + if: "ctx?.source?.ip != null" +- append: + field: related.ip + value: "{{destination.ip}}" + if: "ctx?.destination?.ip != null" +- append: + field: related.user + value: "{{user.name}}" + if: "ctx?.user?.name != null" +- geoip: + field: destination.ip + target_field: destination.geo + ignore_missing: true +- geoip: + field: source.ip + target_field: source.geo + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: source.ip + target_field: source.as + properties: + - asn + - organization_name + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: destination.ip + target_field: destination.as + properties: + - asn + - organization_name + ignore_missing: true +- rename: + field: source.as.asn + target_field: source.as.number + ignore_missing: true +- rename: + field: source.as.organization_name + target_field: source.as.organization.name + ignore_missing: true +- rename: + field: destination.as.asn + target_field: destination.as.number + ignore_missing: true +- rename: + field: destination.as.organization_name + target_field: destination.as.organization.name + ignore_missing: true +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/x-pack/filebeat/module/zeek/ntlm/manifest.yml b/x-pack/filebeat/module/zeek/ntlm/manifest.yml index 0248af27d3b..545bef85aaa 100644 --- a/x-pack/filebeat/module/zeek/ntlm/manifest.yml +++ b/x-pack/filebeat/module/zeek/ntlm/manifest.yml @@ -13,5 +13,5 @@ var: - name: community_id default: true -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/ntlm.yml diff --git a/x-pack/filebeat/module/zeek/ntlm/test/ntlm-json.log-expected.json b/x-pack/filebeat/module/zeek/ntlm/test/ntlm-json.log-expected.json index 90aebbec10b..c85d3127476 100644 --- a/x-pack/filebeat/module/zeek/ntlm/test/ntlm-json.log-expected.json +++ b/x-pack/filebeat/module/zeek/ntlm/test/ntlm-json.log-expected.json @@ -4,15 +4,31 @@ "destination.address": "192.168.10.31", "destination.ip": "192.168.10.31", "destination.port": 445, + "event.category": [ + "authentication", + "network" + ], "event.dataset": "zeek.ntlm", "event.id": "CHphiNUKDC20fsy09", + "event.kind": "event", "event.module": "zeek", + "event.type": [ + "info", + "connection" + ], "fileset.name": "ntlm", "input.type": "log", "log.offset": 0, "network.community_id": "1:zxnXAE/Cme5fQhh6sJLs7GItc08=", "network.protocol": "ntlm", "network.transport": "tcp", + "related.ip": [ + "192.168.10.50", + "192.168.10.31" + ], + "related.user": [ + "JeffV" + ], "service.type": "zeek", "source.address": "192.168.10.50", "source.ip": "192.168.10.50", @@ -20,6 +36,8 @@ "tags": [ "zeek.ntlm" ], + "user.domain": "contoso.local", + "user.name": "JeffV", "zeek.ntlm.domain": "contoso.local", "zeek.ntlm.hostname": "ybaARon55QykXrgu", "zeek.ntlm.server.name.dns": "Victim-PC.contoso.local", diff --git a/x-pack/filebeat/module/zeek/ocsp/config/ocsp.yml b/x-pack/filebeat/module/zeek/ocsp/config/ocsp.yml index a6a74d6d05e..f6298a36d1e 100644 --- a/x-pack/filebeat/module/zeek/ocsp/config/ocsp.yml +++ b/x-pack/filebeat/module/zeek/ocsp/config/ocsp.yml @@ -56,3 +56,7 @@ processors: ignore_missing: true fail_on_error: false + - add_fields: + target: event + fields: + kind: event diff --git a/x-pack/filebeat/module/zeek/ocsp/ingest/pipeline.json b/x-pack/filebeat/module/zeek/ocsp/ingest/pipeline.json deleted file mode 100644 index e56642bd4a8..00000000000 --- a/x-pack/filebeat/module/zeek/ocsp/ingest/pipeline.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "description": "Pipeline for normalizing Zeek ocsp.log", - "processors": [ - { - "set": { - "field": "event.created", - "value": "{{_ingest.timestamp}}" - } - }, - { - "date": { - "field": "zeek.ocsp.ts", - "formats": ["UNIX"] - } - }, - { - "remove": { - "field": "zeek.ocsp.ts" - } - }, - { - "date": { - "field": "zeek.ocsp.revoke.date", - "target_field": "zeek.ocsp.revoke.date", - "formats": ["UNIX"], - "if": "ctx.zeek.ocsp.revoke?.date != null" - } - }, - { - "date": { - "field": "zeek.ocsp.update.this", - "target_field": "zeek.ocsp.update.this", - "formats": ["UNIX"], - "if": "ctx.zeek.ocsp.update?.this != null" - } - }, - { - "date": { - "field": "zeek.ocsp.update.next", - "target_field": "zeek.ocsp.update.next", - "formats": ["UNIX"], - "if": "ctx.zeek.ocsp.update?.next != null" - } - } - ], - "on_failure" : [{ - "set" : { - "field" : "error.message", - "value" : "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/x-pack/filebeat/module/zeek/ocsp/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/ocsp/ingest/pipeline.yml new file mode 100644 index 00000000000..63a878825d7 --- /dev/null +++ b/x-pack/filebeat/module/zeek/ocsp/ingest/pipeline.yml @@ -0,0 +1,41 @@ +description: Pipeline for normalizing Zeek ocsp.log +processors: +- set: + field: event.created + value: '{{_ingest.timestamp}}' +- date: + field: zeek.ocsp.ts + formats: + - UNIX +- remove: + field: zeek.ocsp.ts +- date: + field: zeek.ocsp.revoke.date + target_field: zeek.ocsp.revoke.date + formats: + - UNIX + if: ctx.zeek.ocsp.revoke?.date != null +- date: + field: zeek.ocsp.update.this + target_field: zeek.ocsp.update.this + formats: + - UNIX + if: ctx.zeek.ocsp.update?.this != null +- date: + field: zeek.ocsp.update.next + target_field: zeek.ocsp.update.next + formats: + - UNIX + if: ctx.zeek.ocsp.update?.next != null +- append: + field: related.hash + value: "{{zeek.ocsp.issuerNameHash}}" + if: "ctx?.zeek?.ocsp?.issuerNameHash != null" +- append: + field: related.hash + value: "{{zeek.ocsp.issuerKeyHash}}" + if: "ctx?.zeek?.ocsp?.issuerKeyHash != null" +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/x-pack/filebeat/module/zeek/ocsp/manifest.yml b/x-pack/filebeat/module/zeek/ocsp/manifest.yml index 739873d645f..35bcfccdcb6 100644 --- a/x-pack/filebeat/module/zeek/ocsp/manifest.yml +++ b/x-pack/filebeat/module/zeek/ocsp/manifest.yml @@ -11,5 +11,5 @@ var: - name: tags default: [zeek.ocsp] -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/ocsp.yml diff --git a/x-pack/filebeat/module/zeek/pe/config/pe.yml b/x-pack/filebeat/module/zeek/pe/config/pe.yml index ee4c78bb8cc..cf5f54396ad 100644 --- a/x-pack/filebeat/module/zeek/pe/config/pe.yml +++ b/x-pack/filebeat/module/zeek/pe/config/pe.yml @@ -21,3 +21,11 @@ processors: ignore_missing: true fail_on_error: false + - add_fields: + target: event + fields: + kind: event + category: + - file + type: + - info diff --git a/x-pack/filebeat/module/zeek/pe/ingest/pipeline.json b/x-pack/filebeat/module/zeek/pe/ingest/pipeline.json deleted file mode 100644 index f950772464c..00000000000 --- a/x-pack/filebeat/module/zeek/pe/ingest/pipeline.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "description": "Pipeline for normalizing Zeek pe.log", - "processors": [ - { - "set": { - "field": "event.created", - "value": "{{_ingest.timestamp}}" - } - }, - { - "date": { - "field": "zeek.pe.ts", - "formats": ["UNIX"] - } - }, - { - "remove": { - "field": "zeek.pe.ts" - } - }, - { - "date": { - "field": "zeek.pe.compile_time", - "target_field": "zeek.pe.compile_time", - "formats": ["UNIX"], - "if": "ctx.zeek.pe.compile_time != null" - } - } - ], - "on_failure" : [{ - "set" : { - "field" : "error.message", - "value" : "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/x-pack/filebeat/module/zeek/pe/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/pe/ingest/pipeline.yml new file mode 100644 index 00000000000..6a7fa7dca87 --- /dev/null +++ b/x-pack/filebeat/module/zeek/pe/ingest/pipeline.yml @@ -0,0 +1,21 @@ +description: Pipeline for normalizing Zeek pe.log +processors: +- set: + field: event.created + value: '{{_ingest.timestamp}}' +- date: + field: zeek.pe.ts + formats: + - UNIX +- remove: + field: zeek.pe.ts +- date: + field: zeek.pe.compile_time + target_field: zeek.pe.compile_time + formats: + - UNIX + if: ctx.zeek.pe.compile_time != null +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/x-pack/filebeat/module/zeek/pe/manifest.yml b/x-pack/filebeat/module/zeek/pe/manifest.yml index 02a352c5dfd..16dfe2e4634 100644 --- a/x-pack/filebeat/module/zeek/pe/manifest.yml +++ b/x-pack/filebeat/module/zeek/pe/manifest.yml @@ -13,5 +13,5 @@ var: - name: community_id default: true -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/pe.yml diff --git a/x-pack/filebeat/module/zeek/pe/test/pe-json.log-expected.json b/x-pack/filebeat/module/zeek/pe/test/pe-json.log-expected.json index ccad0e8e2fc..3356f0ef793 100644 --- a/x-pack/filebeat/module/zeek/pe/test/pe-json.log-expected.json +++ b/x-pack/filebeat/module/zeek/pe/test/pe-json.log-expected.json @@ -1,8 +1,15 @@ [ { "@timestamp": "2017-10-09T16:13:19.578Z", + "event.category": [ + "file" + ], "event.dataset": "zeek.pe", + "event.kind": "event", "event.module": "zeek", + "event.type": [ + "info" + ], "fileset.name": "pe", "input.type": "log", "log.offset": 0, diff --git a/x-pack/filebeat/module/zeek/radius/config/radius.yml b/x-pack/filebeat/module/zeek/radius/config/radius.yml index fdbb468450c..38338b1c84f 100644 --- a/x-pack/filebeat/module/zeek/radius/config/radius.yml +++ b/x-pack/filebeat/module/zeek/radius/config/radius.yml @@ -36,10 +36,23 @@ processors: ignore_missing: true fail_on_error: false - + - convert: + fields: + - {from: "zeek.session_id", to: "event.id"} + - {from: "source.address", to: "source.ip", type: "ip"} + - {from: "destination.address", to: "destination.ip", type: "ip"} + - {from: "zeek.radius.username", to: "user.name"} + - {from: "zeek.radius.result", to: "event.outcome"} + - add_fields: + target: event + fields: + kind: event + category: + - authentication + - network + type: + - info + - connection {{ if .community_id }} - community_id: - fields: - source_ip: source.address - destination_ip: destination.address {{ end }} diff --git a/x-pack/filebeat/module/zeek/radius/ingest/pipeline.json b/x-pack/filebeat/module/zeek/radius/ingest/pipeline.json deleted file mode 100644 index 72f645dd651..00000000000 --- a/x-pack/filebeat/module/zeek/radius/ingest/pipeline.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "description": "Pipeline for normalizing Zeek radius.log", - "processors": [ - { - "set": { - "field": "event.created", - "value": "{{_ingest.timestamp}}" - } - }, - { - "date": { - "field": "zeek.radius.ts", - "formats": ["UNIX"] - } - }, - { - "remove": { - "field": "zeek.radius.ts" - } - }, - { - "set": { - "field": "event.id", - "value": "{{zeek.session_id}}", - "if": "ctx.zeek.session_id != null" - } - }, - { - "set": { - "field": "source.ip", - "value": "{{source.address}}" - } - }, - { - "set": { - "field": "destination.ip", - "value": "{{destination.address}}" - } - } - ], - "on_failure" : [{ - "set" : { - "field" : "error.message", - "value" : "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/x-pack/filebeat/module/zeek/radius/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/radius/ingest/pipeline.yml new file mode 100644 index 00000000000..c69dfaefbb4 --- /dev/null +++ b/x-pack/filebeat/module/zeek/radius/ingest/pipeline.yml @@ -0,0 +1,67 @@ +description: Pipeline for normalizing Zeek radius.log +processors: +- set: + field: event.created + value: '{{_ingest.timestamp}}' +- date: + field: zeek.radius.ts + formats: + - UNIX +- remove: + field: zeek.radius.ts +- append: + field: related.ip + value: "{{source.ip}}" + if: "ctx?.source?.ip != null" +- append: + field: related.ip + value: "{{destination.ip}}" + if: "ctx?.destination?.ip != null" +- geoip: + field: destination.ip + target_field: destination.geo + ignore_missing: true +- geoip: + field: source.ip + target_field: source.geo + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: source.ip + target_field: source.as + properties: + - asn + - organization_name + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: destination.ip + target_field: destination.as + properties: + - asn + - organization_name + ignore_missing: true +- rename: + field: source.as.asn + target_field: source.as.number + ignore_missing: true +- rename: + field: source.as.organization_name + target_field: source.as.organization.name + ignore_missing: true +- rename: + field: destination.as.asn + target_field: destination.as.number + ignore_missing: true +- rename: + field: destination.as.organization_name + target_field: destination.as.organization.name + ignore_missing: true +- append: + field: related.user + value: "{{user.name}}" + if: "ctx?.user?.name != null" +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/x-pack/filebeat/module/zeek/radius/manifest.yml b/x-pack/filebeat/module/zeek/radius/manifest.yml index 505abcbbbd6..f881f404d7a 100644 --- a/x-pack/filebeat/module/zeek/radius/manifest.yml +++ b/x-pack/filebeat/module/zeek/radius/manifest.yml @@ -13,5 +13,5 @@ var: - name: community_id default: true -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/radius.yml diff --git a/x-pack/filebeat/module/zeek/radius/test/radius-json.log-expected.json b/x-pack/filebeat/module/zeek/radius/test/radius-json.log-expected.json index 9b4ddfa91f2..894b85f435f 100644 --- a/x-pack/filebeat/module/zeek/radius/test/radius-json.log-expected.json +++ b/x-pack/filebeat/module/zeek/radius/test/radius-json.log-expected.json @@ -4,15 +4,32 @@ "destination.address": "10.0.0.100", "destination.ip": "10.0.0.100", "destination.port": 1812, + "event.category": [ + "authentication", + "network" + ], "event.dataset": "zeek.radius", "event.id": "CRe9VD3flCDWbPmpIh", + "event.kind": "event", "event.module": "zeek", + "event.outcome": "success", + "event.type": [ + "info", + "connection" + ], "fileset.name": "radius", "input.type": "log", "log.offset": 0, "network.community_id": "1:3SdDgWXPnheV2oGfVmxQjfwtr8E=", "network.protocol": "radius", "network.transport": "udp", + "related.ip": [ + "10.0.0.1", + "10.0.0.100" + ], + "related.user": [ + "John.McGuirk" + ], "service.type": "zeek", "source.address": "10.0.0.1", "source.ip": "10.0.0.1", @@ -20,6 +37,7 @@ "tags": [ "zeek.radius" ], + "user.name": "John.McGuirk", "zeek.radius.mac": "00:14:22:e9:54:5e", "zeek.radius.result": "success", "zeek.radius.username": "John.McGuirk", diff --git a/x-pack/filebeat/module/zeek/rdp/config/rdp.yml b/x-pack/filebeat/module/zeek/rdp/config/rdp.yml index d9dac8f2e9b..b9b19e79dd7 100644 --- a/x-pack/filebeat/module/zeek/rdp/config/rdp.yml +++ b/x-pack/filebeat/module/zeek/rdp/config/rdp.yml @@ -69,10 +69,20 @@ processors: ignore_missing: true fail_on_error: false - + - convert: + fields: + - {from: "zeek.session_id", to: "event.id"} + - {from: "source.address", to: "source.ip", type: "ip"} + - {from: "destination.address", to: "destination.ip", type: "ip"} + - add_fields: + target: event + fields: + kind: event + category: + - network + type: + - protocol + - info {{ if .community_id }} - community_id: - fields: - source_ip: source.address - destination_ip: destination.address {{ end }} diff --git a/x-pack/filebeat/module/zeek/rdp/ingest/pipeline.json b/x-pack/filebeat/module/zeek/rdp/ingest/pipeline.json deleted file mode 100644 index ae56b98801f..00000000000 --- a/x-pack/filebeat/module/zeek/rdp/ingest/pipeline.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "description": "Pipeline for normalizing Zeek rdp.log", - "processors": [ - { - "set": { - "field": "event.created", - "value": "{{_ingest.timestamp}}" - } - }, - { - "date": { - "field": "zeek.rdp.ts", - "formats": ["UNIX"] - } - }, - { - "remove": { - "field": "zeek.rdp.ts" - } - }, - { - "set": { - "field": "event.id", - "value": "{{zeek.session_id}}", - "if": "ctx.zeek.session_id != null" - } - }, - { - "convert": { - "field": "zeek.rdp.ssl", - "target_field": "tls.established", - "type": "boolean", - "ignore_missing": true - } - }, - { - "set": { - "field": "source.ip", - "value": "{{source.address}}" - } - }, - { - "set": { - "field": "destination.ip", - "value": "{{destination.address}}" - } - } - ], - "on_failure" : [{ - "set" : { - "field" : "error.message", - "value" : "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/x-pack/filebeat/module/zeek/rdp/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/rdp/ingest/pipeline.yml new file mode 100644 index 00000000000..d6b70dd92e6 --- /dev/null +++ b/x-pack/filebeat/module/zeek/rdp/ingest/pipeline.yml @@ -0,0 +1,68 @@ +description: Pipeline for normalizing Zeek rdp.log +processors: +- set: + field: event.created + value: '{{_ingest.timestamp}}' +- date: + field: zeek.rdp.ts + formats: + - UNIX +- remove: + field: zeek.rdp.ts +- convert: + field: zeek.rdp.ssl + target_field: tls.established + type: boolean + ignore_missing: true +- geoip: + field: destination.ip + target_field: destination.geo + ignore_missing: true +- geoip: + field: source.ip + target_field: source.geo + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: source.ip + target_field: source.as + properties: + - asn + - organization_name + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: destination.ip + target_field: destination.as + properties: + - asn + - organization_name + ignore_missing: true +- rename: + field: source.as.asn + target_field: source.as.number + ignore_missing: true +- rename: + field: source.as.organization_name + target_field: source.as.organization.name + ignore_missing: true +- rename: + field: destination.as.asn + target_field: destination.as.number + ignore_missing: true +- rename: + field: destination.as.organization_name + target_field: destination.as.organization.name + ignore_missing: true +- append: + field: related.ip + value: "{{source.ip}}" + if: "ctx?.source?.ip != null" +- append: + field: related.ip + value: "{{destination.ip}}" + if: "ctx?.destination?.ip != null" +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/x-pack/filebeat/module/zeek/rdp/manifest.yml b/x-pack/filebeat/module/zeek/rdp/manifest.yml index 044352bb2fd..b0c76c9f3a3 100644 --- a/x-pack/filebeat/module/zeek/rdp/manifest.yml +++ b/x-pack/filebeat/module/zeek/rdp/manifest.yml @@ -13,5 +13,5 @@ var: - name: community_id default: true -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/rdp.yml diff --git a/x-pack/filebeat/module/zeek/rdp/test/rdp-json.log-expected.json b/x-pack/filebeat/module/zeek/rdp/test/rdp-json.log-expected.json index 6d39caef60b..878eb3e2050 100644 --- a/x-pack/filebeat/module/zeek/rdp/test/rdp-json.log-expected.json +++ b/x-pack/filebeat/module/zeek/rdp/test/rdp-json.log-expected.json @@ -4,15 +4,27 @@ "destination.address": "192.168.131.131", "destination.ip": "192.168.131.131", "destination.port": 3389, + "event.category": [ + "network" + ], "event.dataset": "zeek.rdp", "event.id": "C2PcYV7D3ntaHm056", + "event.kind": "event", "event.module": "zeek", + "event.type": [ + "protocol", + "info" + ], "fileset.name": "rdp", "input.type": "log", "log.offset": 0, "network.community_id": "1:PsQu6lSZioPVi0A5K7UaeGsVqS0=", "network.protocol": "rdp", "network.transport": "tcp", + "related.ip": [ + "192.168.131.1", + "192.168.131.131" + ], "service.type": "zeek", "source.address": "192.168.131.1", "source.ip": "192.168.131.1", diff --git a/x-pack/filebeat/module/zeek/rfb/config/rfb.yml b/x-pack/filebeat/module/zeek/rfb/config/rfb.yml index 61e984131cd..f9a2618b02b 100644 --- a/x-pack/filebeat/module/zeek/rfb/config/rfb.yml +++ b/x-pack/filebeat/module/zeek/rfb/config/rfb.yml @@ -54,10 +54,20 @@ processors: ignore_missing: true fail_on_error: false - + - convert: + fields: + - {from: "zeek.session_id", to: "event.id"} + - {from: "source.address", to: "source.ip", type: "ip"} + - {from: "destination.address", to: "destination.ip", type: "ip"} + - add_fields: + target: event + fields: + kind: event + category: + - network + type: + - connection + - info {{ if .community_id }} - community_id: - fields: - source_ip: source.address - destination_ip: destination.address {{ end }} diff --git a/x-pack/filebeat/module/zeek/rfb/ingest/pipeline.json b/x-pack/filebeat/module/zeek/rfb/ingest/pipeline.json deleted file mode 100644 index 14ae112ffea..00000000000 --- a/x-pack/filebeat/module/zeek/rfb/ingest/pipeline.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "description": "Pipeline for normalizing Zeek rfb.log", - "processors": [ - { - "set": { - "field": "event.created", - "value": "{{_ingest.timestamp}}" - } - }, - { - "date": { - "field": "zeek.rfb.ts", - "formats": ["UNIX"] - } - }, - { - "remove": { - "field": "zeek.rfb.ts" - } - }, - { - "set": { - "field": "event.id", - "value": "{{zeek.session_id}}", - "if": "ctx.zeek.session_id != null" - } - }, - { - "set": { - "field": "source.ip", - "value": "{{source.address}}" - } - }, - { - "set": { - "field": "destination.ip", - "value": "{{destination.address}}" - } - } - ], - "on_failure" : [{ - "set" : { - "field" : "error.message", - "value" : "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/x-pack/filebeat/module/zeek/rfb/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/rfb/ingest/pipeline.yml new file mode 100644 index 00000000000..8cf2cebdf4d --- /dev/null +++ b/x-pack/filebeat/module/zeek/rfb/ingest/pipeline.yml @@ -0,0 +1,63 @@ +description: Pipeline for normalizing Zeek rfb.log +processors: +- set: + field: event.created + value: '{{_ingest.timestamp}}' +- date: + field: zeek.rfb.ts + formats: + - UNIX +- remove: + field: zeek.rfb.ts +- append: + field: related.ip + value: "{{source.ip}}" + if: "ctx?.source?.ip != null" +- append: + field: related.ip + value: "{{destination.ip}}" + if: "ctx?.destination?.ip != null" +- geoip: + field: destination.ip + target_field: destination.geo + ignore_missing: true +- geoip: + field: source.ip + target_field: source.geo + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: source.ip + target_field: source.as + properties: + - asn + - organization_name + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: destination.ip + target_field: destination.as + properties: + - asn + - organization_name + ignore_missing: true +- rename: + field: source.as.asn + target_field: source.as.number + ignore_missing: true +- rename: + field: source.as.organization_name + target_field: source.as.organization.name + ignore_missing: true +- rename: + field: destination.as.asn + target_field: destination.as.number + ignore_missing: true +- rename: + field: destination.as.organization_name + target_field: destination.as.organization.name + ignore_missing: true +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/x-pack/filebeat/module/zeek/rfb/manifest.yml b/x-pack/filebeat/module/zeek/rfb/manifest.yml index 2f96e4f618e..2b9daaab107 100644 --- a/x-pack/filebeat/module/zeek/rfb/manifest.yml +++ b/x-pack/filebeat/module/zeek/rfb/manifest.yml @@ -13,5 +13,5 @@ var: - name: community_id default: true -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/rfb.yml diff --git a/x-pack/filebeat/module/zeek/rfb/test/rfb-json.log-expected.json b/x-pack/filebeat/module/zeek/rfb/test/rfb-json.log-expected.json index c860f5377e3..83b5544b655 100644 --- a/x-pack/filebeat/module/zeek/rfb/test/rfb-json.log-expected.json +++ b/x-pack/filebeat/module/zeek/rfb/test/rfb-json.log-expected.json @@ -4,15 +4,27 @@ "destination.address": "192.168.1.10", "destination.ip": "192.168.1.10", "destination.port": 5900, + "event.category": [ + "network" + ], "event.dataset": "zeek.rfb", "event.id": "CXoIzM3wH3fUwXtKN1", + "event.kind": "event", "event.module": "zeek", + "event.type": [ + "connection", + "info" + ], "fileset.name": "rfb", "input.type": "log", "log.offset": 0, "network.community_id": "1:AtPVA5phuztnwqMfO/2142WXVdY=", "network.protocol": "rfb", "network.transport": "tcp", + "related.ip": [ + "192.168.1.123", + "192.168.1.10" + ], "service.type": "zeek", "source.address": "192.168.1.123", "source.ip": "192.168.1.123", diff --git a/x-pack/filebeat/module/zeek/sip/config/sip.yml b/x-pack/filebeat/module/zeek/sip/config/sip.yml index bd22de69672..c94dbe5e40e 100644 --- a/x-pack/filebeat/module/zeek/sip/config/sip.yml +++ b/x-pack/filebeat/module/zeek/sip/config/sip.yml @@ -72,10 +72,24 @@ processors: ignore_missing: true fail_on_error: false - + - convert: + fields: + - {from: "zeek.session_id", to: "event.id"} + - {from: "source.address", to: "source.ip", type: "ip"} + - {from: "destination.address", to: "destination.ip", type: "ip"} + - {from: "zeek.sip.sequence.method", to: "event.action"} + - {from: "zeek.sip.uri", to: "url.full"} + ignore_missing: true + fail_on_error: false + - add_fields: + target: event + fields: + kind: event + category: + - network + type: + - connection + - protocol {{ if .community_id }} - community_id: - fields: - source_ip: source.address - destination_ip: destination.address {{ end }} diff --git a/x-pack/filebeat/module/zeek/sip/ingest/pipeline.json b/x-pack/filebeat/module/zeek/sip/ingest/pipeline.json deleted file mode 100644 index c3b7eab58fb..00000000000 --- a/x-pack/filebeat/module/zeek/sip/ingest/pipeline.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "description": "Pipeline for normalizing Zeek sip.log", - "processors": [ - { - "set": { - "field": "event.created", - "value": "{{_ingest.timestamp}}" - } - }, - { - "date": { - "field": "zeek.sip.ts", - "formats": ["UNIX"] - } - }, - { - "remove": { - "field": "zeek.sip.ts" - } - }, - { - "set": { - "field": "event.id", - "value": "{{zeek.session_id}}", - "if": "ctx.zeek.session_id != null" - } - }, - { - "set": { - "field": "source.ip", - "value": "{{source.address}}" - } - }, - { - "set": { - "field": "destination.ip", - "value": "{{destination.address}}" - } - }, - { - "grok": { - "field": "zeek.sip.seq", - "patterns": ["%{NUMBER:zeek.sip.sequence.number}"], - "ignore_missing": true - } - }, - { - "remove": { - "field": "zeek.sip.seq", - "ignore_missing": true - } - } - ], - "on_failure" : [{ - "set" : { - "field" : "error.message", - "value" : "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/x-pack/filebeat/module/zeek/sip/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/sip/ingest/pipeline.yml new file mode 100644 index 00000000000..9982cb82d87 --- /dev/null +++ b/x-pack/filebeat/module/zeek/sip/ingest/pipeline.yml @@ -0,0 +1,83 @@ +description: Pipeline for normalizing Zeek sip.log +processors: +- set: + field: event.created + value: '{{_ingest.timestamp}}' +- date: + field: zeek.sip.ts + formats: + - UNIX +- remove: + field: zeek.sip.ts +- grok: + field: zeek.sip.seq + patterns: + - '%{NUMBER:zeek.sip.sequence.number}' + ignore_missing: true +- remove: + field: zeek.sip.seq + ignore_missing: true +- geoip: + field: destination.ip + target_field: destination.geo + ignore_missing: true +- geoip: + field: source.ip + target_field: source.geo + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: source.ip + target_field: source.as + properties: + - asn + - organization_name + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: destination.ip + target_field: destination.as + properties: + - asn + - organization_name + ignore_missing: true +- rename: + field: source.as.asn + target_field: source.as.number + ignore_missing: true +- rename: + field: source.as.organization_name + target_field: source.as.organization.name + ignore_missing: true +- rename: + field: destination.as.asn + target_field: destination.as.number + ignore_missing: true +- rename: + field: destination.as.organization_name + target_field: destination.as.organization.name + ignore_missing: true +- append: + field: related.ip + value: "{{source.ip}}" + if: "ctx?.source?.ip != null" +- append: + field: related.ip + value: "{{destination.ip}}" + if: "ctx?.destination?.ip != null" +- append: + field: event.type + value: error + if: "ctx?.zeek?.sip?.status?.code != null && ctx.zeek.sip.status.code >= 400" +- set: + field: event.outcome + value: failure + if: "ctx?.zeek?.sip?.status?.code != null && ctx.zeek.sip.status.code >= 400" +- set: + field: event.outcome + value: success + if: "ctx?.zeek?.sip?.status?.code != null && ctx.zeek.sip.status.code < 400" +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/x-pack/filebeat/module/zeek/sip/manifest.yml b/x-pack/filebeat/module/zeek/sip/manifest.yml index 8b022a943af..8da0cc443dd 100644 --- a/x-pack/filebeat/module/zeek/sip/manifest.yml +++ b/x-pack/filebeat/module/zeek/sip/manifest.yml @@ -13,5 +13,5 @@ var: - name: community_id default: true -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/sip.yml diff --git a/x-pack/filebeat/module/zeek/sip/test/sip-json.log-expected.json b/x-pack/filebeat/module/zeek/sip/test/sip-json.log-expected.json index c24f5405435..79b38a0717d 100644 --- a/x-pack/filebeat/module/zeek/sip/test/sip-json.log-expected.json +++ b/x-pack/filebeat/module/zeek/sip/test/sip-json.log-expected.json @@ -2,17 +2,38 @@ { "@timestamp": "2013-02-26T22:02:39.055Z", "destination.address": "74.63.41.218", + "destination.as.number": 29791, + "destination.as.organization.name": "Internap Corporation", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "74.63.41.218", "destination.port": 5060, + "event.action": "REGISTER", + "event.category": [ + "network" + ], "event.dataset": "zeek.sip", "event.id": "CPRLCB4eWHdjP852Bk", + "event.kind": "event", "event.module": "zeek", + "event.outcome": "failure", + "event.type": [ + "connection", + "protocol", + "error" + ], "fileset.name": "sip", "input.type": "log", "log.offset": 0, "network.community_id": "1:t8Jl0amIXPHemzxKgsLjtkB+ewo=", "network.protocol": "sip", "network.transport": "udp", + "related.ip": [ + "172.16.133.19", + "74.63.41.218" + ], "service.type": "zeek", "source.address": "172.16.133.19", "source.ip": "172.16.133.19", @@ -20,6 +41,7 @@ "tags": [ "zeek.sip" ], + "url.full": "sip:newyork.voip.ms:5060", "zeek.session_id": "CPRLCB4eWHdjP852Bk", "zeek.sip.call_id": "8694cd7e-976e4fc3-d76f6e38@172.16.133.19", "zeek.sip.request.body_length": 0, @@ -45,24 +67,57 @@ { "@timestamp": "2005-01-14T17:58:02.965Z", "destination.address": "200.57.7.195", + "destination.as.number": 18734, + "destination.as.organization.name": "Operbes, S.A. de C.V.", + "destination.geo.city_name": "Mexico City", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "MX", + "destination.geo.location.lat": 19.4357, + "destination.geo.location.lon": -99.1438, + "destination.geo.region_iso_code": "MX-CMX", + "destination.geo.region_name": "Mexico City", "destination.ip": "200.57.7.195", "destination.port": 5060, + "event.action": "INVITE", + "event.category": [ + "network" + ], "event.dataset": "zeek.sip", "event.id": "ComJz236lSOcuOmix3", + "event.kind": "event", "event.module": "zeek", + "event.outcome": "success", + "event.type": [ + "connection", + "protocol" + ], "fileset.name": "sip", "input.type": "log", "log.offset": 805, "network.community_id": "1:U/Makwsc8lm6pVKLfRMzoNTI++0=", "network.protocol": "sip", "network.transport": "udp", + "related.ip": [ + "200.57.7.204", + "200.57.7.195" + ], "service.type": "zeek", "source.address": "200.57.7.204", + "source.as.number": 18734, + "source.as.organization.name": "Operbes, S.A. de C.V.", + "source.geo.city_name": "Mexico City", + "source.geo.continent_name": "North America", + "source.geo.country_iso_code": "MX", + "source.geo.location.lat": 19.4357, + "source.geo.location.lon": -99.1438, + "source.geo.region_iso_code": "MX-CMX", + "source.geo.region_name": "Mexico City", "source.ip": "200.57.7.204", "source.port": 5061, "tags": [ "zeek.sip" ], + "url.full": "sip:francisco@bestel.com:55060", "zeek.session_id": "ComJz236lSOcuOmix3", "zeek.sip.call_id": "12013223@200.57.7.195", "zeek.sip.request.body_length": 229, @@ -91,24 +146,57 @@ { "@timestamp": "2005-01-14T17:58:07.022Z", "destination.address": "200.57.7.195", + "destination.as.number": 18734, + "destination.as.organization.name": "Operbes, S.A. de C.V.", + "destination.geo.city_name": "Mexico City", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "MX", + "destination.geo.location.lat": 19.4357, + "destination.geo.location.lon": -99.1438, + "destination.geo.region_iso_code": "MX-CMX", + "destination.geo.region_name": "Mexico City", "destination.ip": "200.57.7.195", "destination.port": 5060, + "event.action": "REGISTER", + "event.category": [ + "network" + ], "event.dataset": "zeek.sip", "event.id": "CJZDWgixtwqXctWEg", + "event.kind": "event", "event.module": "zeek", + "event.outcome": "success", + "event.type": [ + "connection", + "protocol" + ], "fileset.name": "sip", "input.type": "log", "log.offset": 1654, "network.community_id": "1:0hvHF/bh5wFKg7nfRXxsno4F198=", "network.protocol": "sip", "network.transport": "udp", + "related.ip": [ + "200.57.7.205", + "200.57.7.195" + ], "service.type": "zeek", "source.address": "200.57.7.205", + "source.as.number": 18734, + "source.as.organization.name": "Operbes, S.A. de C.V.", + "source.geo.city_name": "Mexico City", + "source.geo.continent_name": "North America", + "source.geo.country_iso_code": "MX", + "source.geo.location.lat": 19.4357, + "source.geo.location.lon": -99.1438, + "source.geo.region_iso_code": "MX-CMX", + "source.geo.region_name": "Mexico City", "source.ip": "200.57.7.205", "source.port": 5061, "tags": [ "zeek.sip" ], + "url.full": "sip:Verso.com", "zeek.session_id": "CJZDWgixtwqXctWEg", "zeek.sip.call_id": "46E1C3CB36304F84A020CF6DD3F96461@Verso.com", "zeek.sip.request.body_length": 0, diff --git a/x-pack/filebeat/module/zeek/smb_cmd/config/smb_cmd.yml b/x-pack/filebeat/module/zeek/smb_cmd/config/smb_cmd.yml index d9839c7dc16..ada63493d6f 100644 --- a/x-pack/filebeat/module/zeek/smb_cmd/config/smb_cmd.yml +++ b/x-pack/filebeat/module/zeek/smb_cmd/config/smb_cmd.yml @@ -78,10 +78,24 @@ processors: ignore_missing: true fail_on_error: false - + - convert: + fields: + - {from: "zeek.session_id", to: "event.id"} + - {from: "source.address", to: "source.ip", type: "ip"} + - {from: "destination.address", to: "destination.ip", type: "ip"} + - {from: "zeek.smb_cmd.command", to: "event.action"} + - {from: "zeek.smb_cmd.username", to: "user.name"} + ignore_missing: true + fail_on_error: false + - add_fields: + target: event + fields: + kind: event + category: + - network + type: + - connection + - protocol {{ if .community_id }} - community_id: - fields: - source_ip: source.address - destination_ip: destination.address {{ end }} diff --git a/x-pack/filebeat/module/zeek/smb_cmd/ingest/pipeline.json b/x-pack/filebeat/module/zeek/smb_cmd/ingest/pipeline.json deleted file mode 100644 index 6b1f7f1b2af..00000000000 --- a/x-pack/filebeat/module/zeek/smb_cmd/ingest/pipeline.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "description": "Pipeline for normalizing Zeek smb_cmd.log", - "processors": [ - { - "set": { - "field": "event.created", - "value": "{{_ingest.timestamp}}" - } - }, - { - "date": { - "field": "zeek.smb_cmd.ts", - "formats": ["UNIX"] - } - }, - { - "remove": { - "field": "zeek.smb_cmd.ts" - } - }, - { - "set": { - "field": "event.id", - "value": "{{zeek.session_id}}", - "if": "ctx.zeek.session_id != null" - } - }, - { - "remove": { - "field": "zeek.smb_cmd.referenced_file", - "ignore_missing": true - } - }, - { - "set": { - "field": "source.ip", - "value": "{{source.address}}" - } - }, - { - "set": { - "field": "destination.ip", - "value": "{{destination.address}}" - } - } - ], - "on_failure" : [{ - "set" : { - "field" : "error.message", - "value" : "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/x-pack/filebeat/module/zeek/smb_cmd/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/smb_cmd/ingest/pipeline.yml new file mode 100644 index 00000000000..838e9f2e8bc --- /dev/null +++ b/x-pack/filebeat/module/zeek/smb_cmd/ingest/pipeline.yml @@ -0,0 +1,82 @@ +description: Pipeline for normalizing Zeek smb_cmd.log +processors: +- set: + field: event.created + value: '{{_ingest.timestamp}}' +- date: + field: zeek.smb_cmd.ts + formats: + - UNIX +- remove: + field: zeek.smb_cmd.ts +- remove: + field: zeek.smb_cmd.referenced_file + ignore_missing: true +- geoip: + field: destination.ip + target_field: destination.geo + ignore_missing: true +- geoip: + field: source.ip + target_field: source.geo + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: source.ip + target_field: source.as + properties: + - asn + - organization_name + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: destination.ip + target_field: destination.as + properties: + - asn + - organization_name + ignore_missing: true +- rename: + field: source.as.asn + target_field: source.as.number + ignore_missing: true +- rename: + field: source.as.organization_name + target_field: source.as.organization.name + ignore_missing: true +- rename: + field: destination.as.asn + target_field: destination.as.number + ignore_missing: true +- rename: + field: destination.as.organization_name + target_field: destination.as.organization.name + ignore_missing: true +- append: + field: related.ip + value: "{{source.ip}}" + if: "ctx?.source?.ip != null" +- append: + field: related.ip + value: "{{destination.ip}}" + if: "ctx?.destination?.ip != null" +- append: + field: related.user + value: "{{user.name}}" + if: "ctx?.user?.name != null" +- append: + field: event.type + value: error + if: "ctx?.zeek?.smb_cmd?.status != null && ctx.zeek.smb_cmd.status.toLowerCase() != 'success'" +- set: + field: event.outcome + value: success + if: "ctx?.zeek?.smb_cmd?.status != null && ctx.zeek.smb_cmd.status.toLowerCase() == 'success'" +- set: + field: event.outcome + value: failure + if: "ctx?.zeek?.smb_cmd?.status != null && ctx.zeek.smb_cmd.status.toLowerCase() != 'success'" +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/x-pack/filebeat/module/zeek/smb_cmd/manifest.yml b/x-pack/filebeat/module/zeek/smb_cmd/manifest.yml index 089269869e8..a4ad3a78ce1 100644 --- a/x-pack/filebeat/module/zeek/smb_cmd/manifest.yml +++ b/x-pack/filebeat/module/zeek/smb_cmd/manifest.yml @@ -13,5 +13,5 @@ var: - name: community_id default: true -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/smb_cmd.yml diff --git a/x-pack/filebeat/module/zeek/smb_cmd/test/smb_cmd-json.log-expected.json b/x-pack/filebeat/module/zeek/smb_cmd/test/smb_cmd-json.log-expected.json index 872ce4a8238..e18caef3fd2 100644 --- a/x-pack/filebeat/module/zeek/smb_cmd/test/smb_cmd-json.log-expected.json +++ b/x-pack/filebeat/module/zeek/smb_cmd/test/smb_cmd-json.log-expected.json @@ -4,15 +4,29 @@ "destination.address": "172.16.128.202", "destination.ip": "172.16.128.202", "destination.port": 445, + "event.action": "NT_CREATE_ANDX", + "event.category": [ + "network" + ], "event.dataset": "zeek.smb_cmd", "event.id": "CbT8mpAXseu6Pt4R7", + "event.kind": "event", "event.module": "zeek", + "event.outcome": "success", + "event.type": [ + "connection", + "protocol" + ], "fileset.name": "smb_cmd", "input.type": "log", "log.offset": 0, "network.community_id": "1:SJNAD5vtzZuhQjGtfaI8svTnyuw=", "network.protocol": "smb", "network.transport": "tcp", + "related.ip": [ + "172.16.133.6", + "172.16.128.202" + ], "service.type": "zeek", "source.address": "172.16.133.6", "source.ip": "172.16.133.6", diff --git a/x-pack/filebeat/module/zeek/smb_files/config/smb_files.yml b/x-pack/filebeat/module/zeek/smb_files/config/smb_files.yml index ed5d4cdecbb..8ab5ee36395 100644 --- a/x-pack/filebeat/module/zeek/smb_files/config/smb_files.yml +++ b/x-pack/filebeat/module/zeek/smb_files/config/smb_files.yml @@ -36,10 +36,26 @@ processors: ignore_missing: true fail_on_error: false - + - convert: + fields: + - {from: "zeek.session_id", to: "event.id"} + - {from: "source.address", to: "source.ip", type: "ip"} + - {from: "destination.address", to: "destination.ip", type: "ip"} + - {from: "zeek.smb_files.action", to: "event.action"} + - {from: "zeek.smb_files.name", to: "file.name"} + - {from: "zeek.smb_files.size", to: "file.size"} + ignore_missing: true + fail_on_error: false + - add_fields: + target: event + fields: + kind: event + category: + - network + - file + type: + - connection + - protocol {{ if .community_id }} - community_id: - fields: - source_ip: source.address - destination_ip: destination.address {{ end }} diff --git a/x-pack/filebeat/module/zeek/smb_files/ingest/pipeline.json b/x-pack/filebeat/module/zeek/smb_files/ingest/pipeline.json deleted file mode 100644 index b4cfcfaa5b1..00000000000 --- a/x-pack/filebeat/module/zeek/smb_files/ingest/pipeline.json +++ /dev/null @@ -1,103 +0,0 @@ -{ - "description": "Pipeline for normalizing Zeek smb_files.log", - "processors": [ - { - "set": { - "field": "event.created", - "value": "{{_ingest.timestamp}}" - } - }, - { - "date": { - "field": "zeek.smb_files.ts", - "formats": ["UNIX"] - } - }, - { - "remove": { - "field": "zeek.smb_files.ts" - } - }, - { - "set": { - "field": "event.id", - "value": "{{zeek.session_id}}", - "if": "ctx.zeek.session_id != null" - } - }, - { - "set": { - "field": "source.ip", - "value": "{{source.address}}" - } - }, - { - "set": { - "field": "destination.ip", - "value": "{{destination.address}}" - } - }, - { - "dot_expander": { - "field": "times.accessed", - "path": "zeek.smb_files" - } - }, - { - "dot_expander": { - "field": "times.changed", - "path": "zeek.smb_files" - } - }, - { - "dot_expander": { - "field": "times.created", - "path": "zeek.smb_files" - } - }, - { - "dot_expander": { - "field": "times.modified", - "path": "zeek.smb_files" - } - }, - { - "date": { - "field": "zeek.smb_files.times.accessed", - "target_field": "zeek.smb_files.times.accessed", - "formats": ["UNIX"], - "if": "ctx.zeek.smb_files.times?.accessed != null" - } - }, - { - "date": { - "field": "zeek.smb_files.times.changed", - "target_field": "zeek.smb_files.times.changed", - "formats": ["UNIX"], - "if": "ctx.zeek.smb_files.times?.accessed != null" - } - }, - { - "date": { - "field": "zeek.smb_files.times.created", - "target_field": "zeek.smb_files.times.created", - "formats": ["UNIX"], - "if": "ctx.zeek.smb_files.times?.accessed != null" - } - }, - { - "date": { - "field": "zeek.smb_files.times.modified", - "target_field": "zeek.smb_files.times.modified", - "formats": ["UNIX"], - "if": "ctx.zeek.smb_files.times?.accessed != null" - } - } - ], - "on_failure": [{ - "set": { - "field": "error.message", - "value": "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/x-pack/filebeat/module/zeek/smb_files/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/smb_files/ingest/pipeline.yml new file mode 100644 index 00000000000..b2c7f52a29b --- /dev/null +++ b/x-pack/filebeat/module/zeek/smb_files/ingest/pipeline.yml @@ -0,0 +1,135 @@ +description: Pipeline for normalizing Zeek smb_files.log +processors: +- set: + field: event.created + value: '{{_ingest.timestamp}}' +- date: + field: zeek.smb_files.ts + formats: + - UNIX +- remove: + field: zeek.smb_files.ts +- dot_expander: + field: times.accessed + path: zeek.smb_files +- dot_expander: + field: times.changed + path: zeek.smb_files +- dot_expander: + field: times.created + path: zeek.smb_files +- dot_expander: + field: times.modified + path: zeek.smb_files +- date: + field: zeek.smb_files.times.accessed + target_field: zeek.smb_files.times.accessed + formats: + - UNIX + if: ctx.zeek.smb_files.times?.accessed != null +- set: + field: file.accessed + value: "{{zeek.smb_files.times.accessed}}" + if: "ctx?.zeek?.smb_files?.times?.accessed != null" +- date: + field: zeek.smb_files.times.changed + target_field: zeek.smb_files.times.changed + formats: + - UNIX + if: ctx.zeek.smb_files.times?.accessed != null +- set: + field: file.ctime + value: "{{zeek.smb_files.times.changed}}" + if: "ctx?.zeek?.smb_files?.times?.changed != null" +- date: + field: zeek.smb_files.times.created + target_field: zeek.smb_files.times.created + formats: + - UNIX + if: ctx.zeek.smb_files.times?.accessed != null +- set: + field: file.created + value: "{{zeek.smb_files.times.created}}" + if: "ctx?.zeek?.smb_files?.times?.created != null" +- date: + field: zeek.smb_files.times.modified + target_field: zeek.smb_files.times.modified + formats: + - UNIX + if: ctx.zeek.smb_files.times?.accessed != null +- set: + field: file.mtime + value: "{{zeek.smb_files.times.modified}}" + if: "ctx?.zeek?.smb_files?.times?.modified != null" +- geoip: + field: destination.ip + target_field: destination.geo + ignore_missing: true +- geoip: + field: source.ip + target_field: source.geo + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: source.ip + target_field: source.as + properties: + - asn + - organization_name + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: destination.ip + target_field: destination.as + properties: + - asn + - organization_name + ignore_missing: true +- rename: + field: source.as.asn + target_field: source.as.number + ignore_missing: true +- rename: + field: source.as.organization_name + target_field: source.as.organization.name + ignore_missing: true +- rename: + field: destination.as.asn + target_field: destination.as.number + ignore_missing: true +- rename: + field: destination.as.organization_name + target_field: destination.as.organization.name + ignore_missing: true +- append: + field: related.ip + value: "{{source.ip}}" + if: "ctx?.source?.ip != null" +- append: + field: related.ip + value: "{{destination.ip}}" + if: "ctx?.destination?.ip != null" +- append: + field: related.user + value: "{{user.name}}" + if: "ctx?.user?.name != null" +- set: + field: file.path + value: "{{zeek.smb_files.path}}\\{{zeek.smb_files.name}}" + if: "ctx?.zeek?.smb_files?.path != null && ctx?.zeek?.smb_files?.name != null" +- append: + field: event.type + value: deletion + if: "ctx?.zeek?.smb_files?.action == 'SMB::FILE_DELETE'" +- append: + field: event.type + value: change + if: "ctx?.zeek?.smb_files?.action == 'SMB::FILE_RENAME' || ctx?.zeek?.smb_files?.action == 'SMB::FILE_SET_ATTRIBUTE'" +- append: + field: event.type + value: info + if: "ctx?.zeek?.smb_files?.action != null && ctx.zeek.smb_files != 'SMB::FILE_DELETE' && ctx.zeek.smb_files != 'SMB::FILE_RENAME' && ctx.zeek.smb_files != 'SMB::FILE_SET_ATTRIBUTE'" +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/x-pack/filebeat/module/zeek/smb_files/manifest.yml b/x-pack/filebeat/module/zeek/smb_files/manifest.yml index 154b445e765..f59a04153a5 100644 --- a/x-pack/filebeat/module/zeek/smb_files/manifest.yml +++ b/x-pack/filebeat/module/zeek/smb_files/manifest.yml @@ -13,5 +13,5 @@ var: - name: community_id default: true -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/smb_files.yml diff --git a/x-pack/filebeat/module/zeek/smb_files/test/smb_files-json.log-expected.json b/x-pack/filebeat/module/zeek/smb_files/test/smb_files-json.log-expected.json index fc7b8496d08..c7d5ab98b78 100644 --- a/x-pack/filebeat/module/zeek/smb_files/test/smb_files-json.log-expected.json +++ b/x-pack/filebeat/module/zeek/smb_files/test/smb_files-json.log-expected.json @@ -4,15 +4,37 @@ "destination.address": "192.168.10.30", "destination.ip": "192.168.10.30", "destination.port": 445, + "event.action": "SMB::FILE_OPEN", + "event.category": [ + "network", + "file" + ], "event.dataset": "zeek.smb_files", "event.id": "C9YAaEzWLL62yWMn5", + "event.kind": "event", "event.module": "zeek", + "event.type": [ + "connection", + "protocol", + "info" + ], + "file.accessed": "2017-10-09T16:13:19.607Z", + "file.created": "2017-10-09T16:13:19.607Z", + "file.ctime": "2017-10-09T16:13:19.607Z", + "file.mtime": "2017-10-09T16:13:19.607Z", + "file.name": "PSEXESVC.exe", + "file.path": "\\\\\\\\admin-pc\\\\ADMIN$\\PSEXESVC.exe", + "file.size": 0, "fileset.name": "smb_files", "input.type": "log", "log.offset": 0, "network.community_id": "1:k308wDxRMx/FIEzeh+YwD86zgoA=", "network.protocol": "smb", "network.transport": "tcp", + "related.ip": [ + "192.168.10.31", + "192.168.10.30" + ], "service.type": "zeek", "source.address": "192.168.10.31", "source.ip": "192.168.10.31", diff --git a/x-pack/filebeat/module/zeek/smb_mapping/config/smb_mapping.yml b/x-pack/filebeat/module/zeek/smb_mapping/config/smb_mapping.yml index 72ea3647344..0d0934c62c8 100644 --- a/x-pack/filebeat/module/zeek/smb_mapping/config/smb_mapping.yml +++ b/x-pack/filebeat/module/zeek/smb_mapping/config/smb_mapping.yml @@ -36,10 +36,22 @@ processors: ignore_missing: true fail_on_error: false - + - convert: + fields: + - {from: "zeek.session_id", to: "event.id"} + - {from: "source.address", to: "source.ip", type: "ip"} + - {from: "destination.address", to: "destination.ip", type: "ip"} + ignore_missing: true + fail_on_error: false + - add_fields: + target: event + fields: + kind: event + category: + - network + type: + - connection + - protocol {{ if .community_id }} - community_id: - fields: - source_ip: source.address - destination_ip: destination.address {{ end }} diff --git a/x-pack/filebeat/module/zeek/smb_mapping/ingest/pipeline.json b/x-pack/filebeat/module/zeek/smb_mapping/ingest/pipeline.json deleted file mode 100644 index c15ad371ed3..00000000000 --- a/x-pack/filebeat/module/zeek/smb_mapping/ingest/pipeline.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "description": "Pipeline for normalizing Zeek smb_mapping.log", - "processors": [ - { - "set": { - "field": "event.created", - "value": "{{_ingest.timestamp}}" - } - }, - { - "date": { - "field": "zeek.smb_mapping.ts", - "formats": ["UNIX"] - } - }, - { - "remove": { - "field": "zeek.smb_mapping.ts" - } - }, - { - "set": { - "field": "event.id", - "value": "{{zeek.session_id}}", - "if": "ctx.zeek.session_id != null" - } - }, - { - "set": { - "field": "source.ip", - "value": "{{source.address}}" - } - }, - { - "set": { - "field": "destination.ip", - "value": "{{destination.address}}" - } - } - ], - "on_failure": [{ - "set": { - "field": "error.message", - "value": "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/x-pack/filebeat/module/zeek/smb_mapping/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/smb_mapping/ingest/pipeline.yml new file mode 100644 index 00000000000..b5752120267 --- /dev/null +++ b/x-pack/filebeat/module/zeek/smb_mapping/ingest/pipeline.yml @@ -0,0 +1,63 @@ +description: Pipeline for normalizing Zeek smb_mapping.log +processors: +- set: + field: event.created + value: '{{_ingest.timestamp}}' +- date: + field: zeek.smb_mapping.ts + formats: + - UNIX +- remove: + field: zeek.smb_mapping.ts +- geoip: + field: destination.ip + target_field: destination.geo + ignore_missing: true +- geoip: + field: source.ip + target_field: source.geo + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: source.ip + target_field: source.as + properties: + - asn + - organization_name + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: destination.ip + target_field: destination.as + properties: + - asn + - organization_name + ignore_missing: true +- rename: + field: source.as.asn + target_field: source.as.number + ignore_missing: true +- rename: + field: source.as.organization_name + target_field: source.as.organization.name + ignore_missing: true +- rename: + field: destination.as.asn + target_field: destination.as.number + ignore_missing: true +- rename: + field: destination.as.organization_name + target_field: destination.as.organization.name + ignore_missing: true +- append: + field: related.ip + value: "{{source.ip}}" + if: "ctx?.source?.ip != null" +- append: + field: related.ip + value: "{{destination.ip}}" + if: "ctx?.destination?.ip != null" +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/x-pack/filebeat/module/zeek/smb_mapping/manifest.yml b/x-pack/filebeat/module/zeek/smb_mapping/manifest.yml index 403d2951c0c..7382e529b27 100644 --- a/x-pack/filebeat/module/zeek/smb_mapping/manifest.yml +++ b/x-pack/filebeat/module/zeek/smb_mapping/manifest.yml @@ -13,5 +13,5 @@ var: - name: community_id default: true -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/smb_mapping.yml diff --git a/x-pack/filebeat/module/zeek/smb_mapping/test/smb_mapping-json.log-expected.json b/x-pack/filebeat/module/zeek/smb_mapping/test/smb_mapping-json.log-expected.json index fbd3dd29693..71efd1e51ac 100644 --- a/x-pack/filebeat/module/zeek/smb_mapping/test/smb_mapping-json.log-expected.json +++ b/x-pack/filebeat/module/zeek/smb_mapping/test/smb_mapping-json.log-expected.json @@ -4,15 +4,27 @@ "destination.address": "192.168.10.30", "destination.ip": "192.168.10.30", "destination.port": 445, + "event.category": [ + "network" + ], "event.dataset": "zeek.smb_mapping", "event.id": "C9YAaEzWLL62yWMn5", + "event.kind": "event", "event.module": "zeek", + "event.type": [ + "connection", + "protocol" + ], "fileset.name": "smb_mapping", "input.type": "log", "log.offset": 0, "network.community_id": "1:k308wDxRMx/FIEzeh+YwD86zgoA=", "network.protocol": "smb", "network.transport": "tcp", + "related.ip": [ + "192.168.10.31", + "192.168.10.30" + ], "service.type": "zeek", "source.address": "192.168.10.31", "source.ip": "192.168.10.31", diff --git a/x-pack/filebeat/module/zeek/smtp/config/smtp.yml b/x-pack/filebeat/module/zeek/smtp/config/smtp.yml index af4855948ea..fc8c3b0074f 100644 --- a/x-pack/filebeat/module/zeek/smtp/config/smtp.yml +++ b/x-pack/filebeat/module/zeek/smtp/config/smtp.yml @@ -45,10 +45,23 @@ processors: ignore_missing: true fail_on_error: false - + - convert: + fields: + - {from: "zeek.session_id", to: "event.id"} + - {from: "source.address", to: "source.ip", type: "ip"} + - {from: "destination.address", to: "destination.ip", type: "ip"} + - {from: "zeek.smtp.tls", to: "tls.established", type: boolean} + ignore_missing: true + fail_on_error: false + - add_fields: + target: event + fields: + kind: event + category: + - network + type: + - connection + - protocol {{ if .community_id }} - community_id: - fields: - source_ip: source.address - destination_ip: destination.address {{ end }} diff --git a/x-pack/filebeat/module/zeek/smtp/ingest/pipeline.json b/x-pack/filebeat/module/zeek/smtp/ingest/pipeline.json deleted file mode 100644 index 44bc0b189aa..00000000000 --- a/x-pack/filebeat/module/zeek/smtp/ingest/pipeline.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "description": "Pipeline for normalizing Zeek smtp.log", - "processors": [ - { - "set": { - "field": "event.created", - "value": "{{_ingest.timestamp}}" - } - }, - { - "date": { - "field": "zeek.smtp.ts", - "formats": ["UNIX"] - } - }, - { - "remove": { - "field": "zeek.smtp.ts" - } - }, - { - "set": { - "field": "event.id", - "value": "{{zeek.session_id}}", - "if": "ctx.zeek.session_id != null" - } - }, - { - "set": { - "field": "source.ip", - "value": "{{source.address}}" - } - }, - { - "set": { - "field": "destination.ip", - "value": "{{destination.address}}" - } - }, - { - "convert": { - "field": "zeek.smtp.tls", - "target_field": "tls.established", - "type": "boolean", - "ignore_missing": true - } - }, - { - "date": { - "field": "zeek.smtp.date", - "target_field": "zeek.smtp.date", - "formats": ["EEE, d MMM yyyy HH:mm:ss Z"], - "if": "ctx.zeek.smtp.date != null" - } - } - ], - "on_failure" : [{ - "set" : { - "field" : "error.message", - "value" : "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/x-pack/filebeat/module/zeek/smtp/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/smtp/ingest/pipeline.yml new file mode 100644 index 00000000000..4424d3674ff --- /dev/null +++ b/x-pack/filebeat/module/zeek/smtp/ingest/pipeline.yml @@ -0,0 +1,69 @@ +description: Pipeline for normalizing Zeek smtp.log +processors: +- set: + field: event.created + value: '{{_ingest.timestamp}}' +- date: + field: zeek.smtp.ts + formats: + - UNIX +- remove: + field: zeek.smtp.ts +- date: + field: zeek.smtp.date + target_field: zeek.smtp.date + formats: + - EEE, d MMM yyyy HH:mm:ss Z + if: ctx.zeek.smtp.date != null +- geoip: + field: destination.ip + target_field: destination.geo + ignore_missing: true +- geoip: + field: source.ip + target_field: source.geo + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: source.ip + target_field: source.as + properties: + - asn + - organization_name + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: destination.ip + target_field: destination.as + properties: + - asn + - organization_name + ignore_missing: true +- rename: + field: source.as.asn + target_field: source.as.number + ignore_missing: true +- rename: + field: source.as.organization_name + target_field: source.as.organization.name + ignore_missing: true +- rename: + field: destination.as.asn + target_field: destination.as.number + ignore_missing: true +- rename: + field: destination.as.organization_name + target_field: destination.as.organization.name + ignore_missing: true +- append: + field: related.ip + value: "{{source.ip}}" + if: "ctx?.source?.ip != null" +- append: + field: related.ip + value: "{{destination.ip}}" + if: "ctx?.destination?.ip != null" +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/x-pack/filebeat/module/zeek/smtp/manifest.yml b/x-pack/filebeat/module/zeek/smtp/manifest.yml index 489c984b1c4..6d69b3b5e3e 100644 --- a/x-pack/filebeat/module/zeek/smtp/manifest.yml +++ b/x-pack/filebeat/module/zeek/smtp/manifest.yml @@ -13,5 +13,5 @@ var: - name: community_id default: true -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/smtp.yml diff --git a/x-pack/filebeat/module/zeek/smtp/test/smtp-json.log-expected.json b/x-pack/filebeat/module/zeek/smtp/test/smtp-json.log-expected.json index 3d4bd56ac4a..61e1be27bf6 100644 --- a/x-pack/filebeat/module/zeek/smtp/test/smtp-json.log-expected.json +++ b/x-pack/filebeat/module/zeek/smtp/test/smtp-json.log-expected.json @@ -4,15 +4,27 @@ "destination.address": "192.168.1.9", "destination.ip": "192.168.1.9", "destination.port": 25, + "event.category": [ + "network" + ], "event.dataset": "zeek.smtp", "event.id": "CWWzPB3RjqhFf528c", + "event.kind": "event", "event.module": "zeek", + "event.type": [ + "connection", + "protocol" + ], "fileset.name": "smtp", "input.type": "log", "log.offset": 0, "network.community_id": "1:38H0puTqOoHT/5r2bKFUVSXifQw=", "network.protocol": "smtp", "network.transport": "tcp", + "related.ip": [ + "192.168.1.10", + "192.168.1.9" + ], "service.type": "zeek", "source.address": "192.168.1.10", "source.ip": "192.168.1.10", diff --git a/x-pack/filebeat/module/zeek/snmp/config/snmp.yml b/x-pack/filebeat/module/zeek/snmp/config/snmp.yml index 76ff0c05f93..3431a990e0f 100644 --- a/x-pack/filebeat/module/zeek/snmp/config/snmp.yml +++ b/x-pack/filebeat/module/zeek/snmp/config/snmp.yml @@ -48,10 +48,22 @@ processors: ignore_missing: true fail_on_error: false - + - convert: + fields: + - {from: "zeek.session_id", to: "event.id"} + - {from: "source.address", to: "source.ip", type: "ip"} + - {from: "destination.address", to: "destination.ip", type: "ip"} + ignore_missing: true + fail_on_error: false + - add_fields: + target: event + fields: + kind: event + category: + - network + type: + - connection + - protocol {{ if .community_id }} - community_id: - fields: - source_ip: source.address - destination_ip: destination.address {{ end }} diff --git a/x-pack/filebeat/module/zeek/snmp/ingest/pipeline.json b/x-pack/filebeat/module/zeek/snmp/ingest/pipeline.json deleted file mode 100644 index 646b7edf845..00000000000 --- a/x-pack/filebeat/module/zeek/snmp/ingest/pipeline.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "description": "Pipeline for normalizing Zeek snmp.log", - "processors": [ - { - "set": { - "field": "event.created", - "value": "{{_ingest.timestamp}}" - } - }, - { - "date": { - "field": "zeek.snmp.ts", - "formats": ["UNIX"] - } - }, - { - "remove": { - "field": "zeek.snmp.ts" - } - }, - { - "set": { - "field": "event.id", - "value": "{{zeek.session_id}}", - "if": "ctx.zeek.session_id != null" - } - }, - { - "set": { - "field": "source.ip", - "value": "{{source.address}}" - } - }, - { - "set": { - "field": "destination.ip", - "value": "{{destination.address}}" - } - }, - { - "date": { - "field": "zeek.snmp.up_since", - "target_field": "zeek.snmp.up_since", - "formats": ["UNIX"], - "if": "ctx.zeek.snmp.up_since != null" - } - } - ], - "on_failure" : [{ - "set" : { - "field" : "error.message", - "value" : "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/x-pack/filebeat/module/zeek/snmp/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/snmp/ingest/pipeline.yml new file mode 100644 index 00000000000..f0070ef790d --- /dev/null +++ b/x-pack/filebeat/module/zeek/snmp/ingest/pipeline.yml @@ -0,0 +1,69 @@ +description: Pipeline for normalizing Zeek snmp.log +processors: +- set: + field: event.created + value: '{{_ingest.timestamp}}' +- date: + field: zeek.snmp.ts + formats: + - UNIX +- remove: + field: zeek.snmp.ts +- date: + field: zeek.snmp.up_since + target_field: zeek.snmp.up_since + formats: + - UNIX + if: ctx.zeek.snmp.up_since != null +- geoip: + field: destination.ip + target_field: destination.geo + ignore_missing: true +- geoip: + field: source.ip + target_field: source.geo + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: source.ip + target_field: source.as + properties: + - asn + - organization_name + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: destination.ip + target_field: destination.as + properties: + - asn + - organization_name + ignore_missing: true +- rename: + field: source.as.asn + target_field: source.as.number + ignore_missing: true +- rename: + field: source.as.organization_name + target_field: source.as.organization.name + ignore_missing: true +- rename: + field: destination.as.asn + target_field: destination.as.number + ignore_missing: true +- rename: + field: destination.as.organization_name + target_field: destination.as.organization.name + ignore_missing: true +- append: + field: related.ip + value: "{{source.ip}}" + if: "ctx?.source?.ip != null" +- append: + field: related.ip + value: "{{destination.ip}}" + if: "ctx?.destination?.ip != null" +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/x-pack/filebeat/module/zeek/snmp/manifest.yml b/x-pack/filebeat/module/zeek/snmp/manifest.yml index c11cd0b3491..b980b6fb82e 100644 --- a/x-pack/filebeat/module/zeek/snmp/manifest.yml +++ b/x-pack/filebeat/module/zeek/snmp/manifest.yml @@ -13,5 +13,5 @@ var: - name: community_id default: true -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/snmp.yml diff --git a/x-pack/filebeat/module/zeek/snmp/test/snmp-json.log-expected.json b/x-pack/filebeat/module/zeek/snmp/test/snmp-json.log-expected.json index 44cd6c16319..65345db7957 100644 --- a/x-pack/filebeat/module/zeek/snmp/test/snmp-json.log-expected.json +++ b/x-pack/filebeat/module/zeek/snmp/test/snmp-json.log-expected.json @@ -4,15 +4,27 @@ "destination.address": "192.168.1.1", "destination.ip": "192.168.1.1", "destination.port": 161, + "event.category": [ + "network" + ], "event.dataset": "zeek.snmp", "event.id": "CnKW1B4w9fpRa6Nkf2", + "event.kind": "event", "event.module": "zeek", + "event.type": [ + "connection", + "protocol" + ], "fileset.name": "snmp", "input.type": "log", "log.offset": 0, "network.community_id": "1:X15ey/8/tEH+tlelK6P+GfgwBPc=", "network.protocol": "snmp", "network.transport": "udp", + "related.ip": [ + "192.168.1.2", + "192.168.1.1" + ], "service.type": "zeek", "source.address": "192.168.1.2", "source.ip": "192.168.1.2", diff --git a/x-pack/filebeat/module/zeek/socks/config/socks.yml b/x-pack/filebeat/module/zeek/socks/config/socks.yml index 5bf93e22f91..ddbcd51d0b0 100644 --- a/x-pack/filebeat/module/zeek/socks/config/socks.yml +++ b/x-pack/filebeat/module/zeek/socks/config/socks.yml @@ -45,10 +45,23 @@ processors: ignore_missing: true fail_on_error: false - + - convert: + fields: + - {from: "zeek.session_id", to: "event.id"} + - {from: "source.address", to: "source.ip", type: "ip"} + - {from: "destination.address", to: "destination.ip", type: "ip"} + - {from: "zeek.socks.user", to: "user.name"} + ignore_missing: true + fail_on_error: false + - add_fields: + target: event + fields: + kind: event + category: + - network + type: + - connection + - protocol {{ if .community_id }} - community_id: - fields: - source_ip: source.address - destination_ip: destination.address {{ end }} diff --git a/x-pack/filebeat/module/zeek/socks/ingest/pipeline.json b/x-pack/filebeat/module/zeek/socks/ingest/pipeline.json deleted file mode 100644 index eabb2837d82..00000000000 --- a/x-pack/filebeat/module/zeek/socks/ingest/pipeline.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "description": "Pipeline for normalizing Zeek socks.log", - "processors": [ - { - "set": { - "field": "event.created", - "value": "{{_ingest.timestamp}}" - } - }, - { - "date": { - "field": "zeek.socks.ts", - "formats": ["UNIX"] - } - }, - { - "remove": { - "field": "zeek.socks.ts" - } - }, - { - "set": { - "field": "event.id", - "value": "{{zeek.session_id}}", - "if": "ctx.zeek.session_id != null" - } - }, - { - "set": { - "field": "source.ip", - "value": "{{source.address}}" - } - }, - { - "set": { - "field": "destination.ip", - "value": "{{destination.address}}" - } - }, - { - "dot_expander": { - "field": "bound.host", - "path": "zeek.socks" - } - } - ], - "on_failure" : [{ - "set" : { - "field" : "error.message", - "value" : "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/x-pack/filebeat/module/zeek/socks/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/socks/ingest/pipeline.yml new file mode 100644 index 00000000000..04a84b13177 --- /dev/null +++ b/x-pack/filebeat/module/zeek/socks/ingest/pipeline.yml @@ -0,0 +1,82 @@ +description: Pipeline for normalizing Zeek socks.log +processors: +- set: + field: event.created + value: '{{_ingest.timestamp}}' +- date: + field: zeek.socks.ts + formats: + - UNIX +- remove: + field: zeek.socks.ts +- dot_expander: + field: bound.host + path: zeek.socks +- geoip: + field: destination.ip + target_field: destination.geo + ignore_missing: true +- geoip: + field: source.ip + target_field: source.geo + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: source.ip + target_field: source.as + properties: + - asn + - organization_name + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: destination.ip + target_field: destination.as + properties: + - asn + - organization_name + ignore_missing: true +- rename: + field: source.as.asn + target_field: source.as.number + ignore_missing: true +- rename: + field: source.as.organization_name + target_field: source.as.organization.name + ignore_missing: true +- rename: + field: destination.as.asn + target_field: destination.as.number + ignore_missing: true +- rename: + field: destination.as.organization_name + target_field: destination.as.organization.name + ignore_missing: true +- append: + field: related.ip + value: "{{source.ip}}" + if: "ctx?.source?.ip != null" +- append: + field: related.ip + value: "{{destination.ip}}" + if: "ctx?.destination?.ip != null" +- append: + field: related.user + value: "{{user.name}}" + if: "ctx?.user?.name != null" +- append: + field: event.type + value: error + if: "ctx?.zeek?.socks?.status != null && ctx.zeek.socks.status != 'succeeded'" +- append: + field: event.outcome + value: success + if: "ctx?.zeek?.socks?.status != null && ctx.zeek.socks.status == 'succeeded'" +- append: + field: event.outcome + value: failure + if: "ctx?.zeek?.socks?.status != null && ctx.zeek.socks.status != 'succeeded'" +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/x-pack/filebeat/module/zeek/socks/manifest.yml b/x-pack/filebeat/module/zeek/socks/manifest.yml index c24b9aae6db..68fea837fde 100644 --- a/x-pack/filebeat/module/zeek/socks/manifest.yml +++ b/x-pack/filebeat/module/zeek/socks/manifest.yml @@ -13,5 +13,5 @@ var: - name: community_id default: true -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/socks.yml diff --git a/x-pack/filebeat/module/zeek/socks/test/socks-json.log-expected.json b/x-pack/filebeat/module/zeek/socks/test/socks-json.log-expected.json index cf2a629e475..c8172d23d1a 100644 --- a/x-pack/filebeat/module/zeek/socks/test/socks-json.log-expected.json +++ b/x-pack/filebeat/module/zeek/socks/test/socks-json.log-expected.json @@ -4,15 +4,30 @@ "destination.address": "127.0.0.1", "destination.ip": "127.0.0.1", "destination.port": 8080, + "event.category": [ + "network" + ], "event.dataset": "zeek.socks", "event.id": "Cmz4Cb4qCw1hGqYw1c", + "event.kind": "event", "event.module": "zeek", + "event.outcome": [ + "success" + ], + "event.type": [ + "connection", + "protocol" + ], "fileset.name": "socks", "input.type": "log", "log.offset": 0, "network.community_id": "1:1Hp/o0hOC62lAwrV+a0ZKDE3rrs=", "network.protocol": "socks", "network.transport": "tcp", + "related.ip": [ + "127.0.0.1", + "127.0.0.1" + ], "service.type": "zeek", "source.address": "127.0.0.1", "source.ip": "127.0.0.1", diff --git a/x-pack/filebeat/module/zeek/ssh/config/ssh.yml b/x-pack/filebeat/module/zeek/ssh/config/ssh.yml index f463b62e895..e33f4e0e29e 100644 --- a/x-pack/filebeat/module/zeek/ssh/config/ssh.yml +++ b/x-pack/filebeat/module/zeek/ssh/config/ssh.yml @@ -57,10 +57,20 @@ processors: ignore_missing: true fail_on_error: false - + - convert: + fields: + - {from: "zeek.session_id", to: "event.id"} + - {from: "source.address", to: "source.ip", type: "ip"} + - {from: "destination.address", to: "destination.ip", type: "ip"} + - add_fields: + target: event + fields: + kind: event + category: + - network + type: + - connection + - protocol {{ if .community_id }} - community_id: - fields: - source_ip: source.address - destination_ip: destination.address {{ end }} diff --git a/x-pack/filebeat/module/zeek/ssh/ingest/pipeline.json b/x-pack/filebeat/module/zeek/ssh/ingest/pipeline.json deleted file mode 100644 index 2eefd208860..00000000000 --- a/x-pack/filebeat/module/zeek/ssh/ingest/pipeline.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "description": "Pipeline for normalizing Zeek ssh.log", - "processors": [ - { - "set": { - "field": "event.created", - "value": "{{_ingest.timestamp}}" - } - }, - { - "date": { - "field": "zeek.ssh.ts", - "formats": ["UNIX"] - } - }, - { - "remove": { - "field": "zeek.ssh.ts" - } - }, - { - "set": { - "field": "event.id", - "value": "{{zeek.session_id}}", - "if": "ctx.zeek.session_id != null" - } - }, - { - "set": { - "field": "source.ip", - "value": "{{source.address}}" - } - }, - { - "set": { - "field": "destination.ip", - "value": "{{destination.address}}" - } - } - ], - "on_failure" : [{ - "set" : { - "field" : "error.message", - "value" : "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/x-pack/filebeat/module/zeek/ssh/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/ssh/ingest/pipeline.yml new file mode 100644 index 00000000000..019a44b89e0 --- /dev/null +++ b/x-pack/filebeat/module/zeek/ssh/ingest/pipeline.yml @@ -0,0 +1,71 @@ +description: Pipeline for normalizing Zeek ssh.log +processors: +- set: + field: event.created + value: '{{_ingest.timestamp}}' +- date: + field: zeek.ssh.ts + formats: + - UNIX +- remove: + field: zeek.ssh.ts +- geoip: + field: destination.ip + target_field: destination.geo + ignore_missing: true +- geoip: + field: source.ip + target_field: source.geo + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: source.ip + target_field: source.as + properties: + - asn + - organization_name + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: destination.ip + target_field: destination.as + properties: + - asn + - organization_name + ignore_missing: true +- rename: + field: source.as.asn + target_field: source.as.number + ignore_missing: true +- rename: + field: source.as.organization_name + target_field: source.as.organization.name + ignore_missing: true +- rename: + field: destination.as.asn + target_field: destination.as.number + ignore_missing: true +- rename: + field: destination.as.organization_name + target_field: destination.as.organization.name + ignore_missing: true +- append: + field: related.ip + value: "{{source.ip}}" + if: "ctx?.source?.ip != null" +- append: + field: related.ip + value: "{{destination.ip}}" + if: "ctx?.destination?.ip != null" +- set: + field: event.outcome + value: failure + if: "ctx?.zeek?.ssh?.auth?.success != null && ctx.zeek.ssh.auth.success == false" +- set: + field: event.outcome + value: success + if: "ctx?.zeek?.ssh?.auth?.success != null && ctx.zeek.ssh.auth.success == true" +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/x-pack/filebeat/module/zeek/ssh/manifest.yml b/x-pack/filebeat/module/zeek/ssh/manifest.yml index da635a43771..60249e25c21 100644 --- a/x-pack/filebeat/module/zeek/ssh/manifest.yml +++ b/x-pack/filebeat/module/zeek/ssh/manifest.yml @@ -13,5 +13,5 @@ var: - name: community_id default: true -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/ssh.yml diff --git a/x-pack/filebeat/module/zeek/ssh/test/ssh-json.log-expected.json b/x-pack/filebeat/module/zeek/ssh/test/ssh-json.log-expected.json index 8ab4788abc7..343aa7392e5 100644 --- a/x-pack/filebeat/module/zeek/ssh/test/ssh-json.log-expected.json +++ b/x-pack/filebeat/module/zeek/ssh/test/ssh-json.log-expected.json @@ -4,15 +4,28 @@ "destination.address": "192.168.1.1", "destination.ip": "192.168.1.1", "destination.port": 22, + "event.category": [ + "network" + ], "event.dataset": "zeek.ssh", "event.id": "CajWfz1b3qnnWT0BU9", + "event.kind": "event", "event.module": "zeek", + "event.outcome": "failure", + "event.type": [ + "connection", + "protocol" + ], "fileset.name": "ssh", "input.type": "log", "log.offset": 0, "network.community_id": "1:42tg9bemt74qgrdvJOy2n5Veg4A=", "network.protocol": "ssh", "network.transport": "tcp", + "related.ip": [ + "192.168.1.2", + "192.168.1.1" + ], "service.type": "zeek", "source.address": "192.168.1.2", "source.ip": "192.168.1.2", diff --git a/x-pack/filebeat/module/zeek/ssl/config/ssl.yml b/x-pack/filebeat/module/zeek/ssl/config/ssl.yml index 878267f549a..88bfcc4b53e 100644 --- a/x-pack/filebeat/module/zeek/ssl/config/ssl.yml +++ b/x-pack/filebeat/module/zeek/ssl/config/ssl.yml @@ -56,10 +56,24 @@ processors: ignore_missing: true fail_on_error: false - + - convert: + fields: + - {from: "zeek.session_id", to: "event.id"} + - {from: "source.address", to: "source.ip", type: "ip"} + - {from: "source.address", to: "client.address"} + - {from: "destination.address", to: "destination.ip", type: "ip"} + - {from: "destination.address", to: "server.address"} + ignore_missing: true + fail_on_error: false + - add_fields: + target: event + fields: + kind: event + category: + - network + kind: + - connection + - protocol {{ if .community_id }} - community_id: - fields: - source_ip: source.address - destination_ip: destination.address {{ end }} diff --git a/x-pack/filebeat/module/zeek/ssl/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/ssl/ingest/pipeline.yml index 2a5ebf4ce7a..bbeaa24d1bd 100644 --- a/x-pack/filebeat/module/zeek/ssl/ingest/pipeline.yml +++ b/x-pack/filebeat/module/zeek/ssl/ingest/pipeline.yml @@ -10,22 +10,14 @@ processors: - UNIX - remove: field: zeek.ssl.ts -- set: - field: event.id - value: '{{zeek.session_id}}' - if: ctx.zeek.session_id != null -- set: - field: source.ip - value: '{{source.address}}' -- set: - field: destination.ip - value: '{{destination.address}}' - geoip: field: destination.ip target_field: destination.geo + ignore_missing: true - geoip: field: source.ip target_field: source.geo + ignore_missing: true - geoip: database_file: GeoLite2-ASN.mmdb field: source.ip @@ -248,7 +240,14 @@ processors: ctx.tls.version = parts[1].substring(0,1) + "." + parts[1].substring(1); } ctx.tls.version_protocol = parts[0].toLowerCase(); - +- append: + field: related.ip + value: "{{source.ip}}" + if: "ctx?.source?.ip != null" +- append: + field: related.ip + value: "{{destination.ip}}" + if: "ctx?.destination?.ip != null" on_failure: - set: field: error.message diff --git a/x-pack/filebeat/module/zeek/ssl/test/ssl-json.log-expected.json b/x-pack/filebeat/module/zeek/ssl/test/ssl-json.log-expected.json index d7d7ac33ff9..526a43a350b 100644 --- a/x-pack/filebeat/module/zeek/ssl/test/ssl-json.log-expected.json +++ b/x-pack/filebeat/module/zeek/ssl/test/ssl-json.log-expected.json @@ -1,6 +1,7 @@ [ { "@timestamp": "2019-01-17T01:32:16.805Z", + "client.address": "10.178.98.102", "destination.address": "35.199.178.4", "destination.as.number": 15169, "destination.as.organization.name": "Google LLC", @@ -13,14 +14,26 @@ "destination.geo.region_name": "California", "destination.ip": "35.199.178.4", "destination.port": 9243, + "event.category": [ + "network" + ], "event.dataset": "zeek.ssl", "event.id": "CAOvs1BMFCX2Eh0Y3", + "event.kind": [ + "connection", + "protocol" + ], "event.module": "zeek", "fileset.name": "ssl", "input.type": "log", "log.offset": 0, "network.community_id": "1:1PMhYqOKBIyRAQeMbg/pWiJ198g=", "network.transport": "tcp", + "related.ip": [ + "10.178.98.102", + "35.199.178.4" + ], + "server.address": "35.199.178.4", "service.type": "zeek", "source.address": "10.178.98.102", "source.ip": "10.178.98.102", @@ -59,6 +72,7 @@ }, { "@timestamp": "2019-01-17T01:32:16.805Z", + "client.address": "10.178.98.102", "destination.address": "35.199.178.4", "destination.as.number": 15169, "destination.as.organization.name": "Google LLC", @@ -71,14 +85,26 @@ "destination.geo.region_name": "California", "destination.ip": "35.199.178.4", "destination.port": 9243, + "event.category": [ + "network" + ], "event.dataset": "zeek.ssl", "event.id": "C3mki91FnnNtm0u1ok", + "event.kind": [ + "connection", + "protocol" + ], "event.module": "zeek", "fileset.name": "ssl", "input.type": "log", "log.offset": 635, "network.community_id": "1:zYbLmqRN6PLPB067HNAiAQISqvI=", "network.transport": "tcp", + "related.ip": [ + "10.178.98.102", + "35.199.178.4" + ], + "server.address": "35.199.178.4", "service.type": "zeek", "source.address": "10.178.98.102", "source.ip": "10.178.98.102", diff --git a/x-pack/filebeat/module/zeek/stats/ingest/pipeline.json b/x-pack/filebeat/module/zeek/stats/ingest/pipeline.json deleted file mode 100644 index 6115bc6c1d2..00000000000 --- a/x-pack/filebeat/module/zeek/stats/ingest/pipeline.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "description": "Pipeline for normalizing Zeek stats.log", - "processors": [ - { - "set": { - "field": "event.created", - "value": "{{_ingest.timestamp}}" - } - }, - { - "date": { - "field": "zeek.stats.ts", - "formats": ["UNIX"] - } - }, - { - "remove": { - "field": "zeek.stats.ts" - } - } - ], - "on_failure" : [{ - "set" : { - "field" : "error.message", - "value" : "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/x-pack/filebeat/module/zeek/stats/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/stats/ingest/pipeline.yml new file mode 100644 index 00000000000..c0347161190 --- /dev/null +++ b/x-pack/filebeat/module/zeek/stats/ingest/pipeline.yml @@ -0,0 +1,18 @@ +description: Pipeline for normalizing Zeek stats.log +processors: +- set: + field: event.created + value: '{{_ingest.timestamp}}' +- date: + field: zeek.stats.ts + formats: + - UNIX +- remove: + field: zeek.stats.ts +- set: + field: event.kind + value: metric +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/x-pack/filebeat/module/zeek/stats/manifest.yml b/x-pack/filebeat/module/zeek/stats/manifest.yml index c4b122a19bf..f63ad40bf33 100644 --- a/x-pack/filebeat/module/zeek/stats/manifest.yml +++ b/x-pack/filebeat/module/zeek/stats/manifest.yml @@ -11,5 +11,5 @@ var: - name: tags default: [zeek.stats] -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/stats.yml diff --git a/x-pack/filebeat/module/zeek/stats/test/stats-json.log-expected.json b/x-pack/filebeat/module/zeek/stats/test/stats-json.log-expected.json index a2d8e3ab311..bcb5f24f2a2 100644 --- a/x-pack/filebeat/module/zeek/stats/test/stats-json.log-expected.json +++ b/x-pack/filebeat/module/zeek/stats/test/stats-json.log-expected.json @@ -2,6 +2,7 @@ { "@timestamp": "2016-10-16T08:17:58.714Z", "event.dataset": "zeek.stats", + "event.kind": "metric", "event.module": "zeek", "fileset.name": "stats", "input.type": "log", diff --git a/x-pack/filebeat/module/zeek/syslog/config/syslog.yml b/x-pack/filebeat/module/zeek/syslog/config/syslog.yml index b7accce096d..a8420237af0 100644 --- a/x-pack/filebeat/module/zeek/syslog/config/syslog.yml +++ b/x-pack/filebeat/module/zeek/syslog/config/syslog.yml @@ -41,10 +41,17 @@ processors: ignore_missing: true fail_on_error: false - + - convert: + fields: + - {from: "zeek.session_id", to: "event.id"} + - {from: "source.address", to: "source.ip", type: "ip"} + - {from: "destination.address", to: "destination.ip", type: "ip"} + - {from: "zeek.syslog.facility", to: "log.syslog.facility.name"} + - {from: "zeek.syslog.severity", to: "log.syslog.severity.name"} + - add_fields: + target: event + fields: + kind: event {{ if .community_id }} - community_id: - fields: - source_ip: source.address - destination_ip: destination.addresss {{ end }} diff --git a/x-pack/filebeat/module/zeek/syslog/ingest/pipeline.json b/x-pack/filebeat/module/zeek/syslog/ingest/pipeline.json deleted file mode 100644 index fcb98b1b91d..00000000000 --- a/x-pack/filebeat/module/zeek/syslog/ingest/pipeline.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "description": "Pipeline for normalizing Zeek syslog.log", - "processors": [ - { - "set": { - "field": "event.created", - "value": "{{_ingest.timestamp}}" - } - }, - { - "date": { - "field": "zeek.syslog.ts", - "formats": ["UNIX"] - } - }, - { - "remove": { - "field": "zeek.syslog.ts" - } - }, - { - "set": { - "field": "event.id", - "value": "{{zeek.session_id}}", - "if": "ctx.zeek.session_id != null" - } - }, - { - "set": { - "field": "source.ip", - "value": "{{source.address}}" - } - }, - { - "set": { - "field": "destination.ip", - "value": "{{destination.address}}" - } - } - ], - "on_failure" : [{ - "set" : { - "field" : "error.message", - "value" : "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/x-pack/filebeat/module/zeek/syslog/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/syslog/ingest/pipeline.yml new file mode 100644 index 00000000000..7fd848682b1 --- /dev/null +++ b/x-pack/filebeat/module/zeek/syslog/ingest/pipeline.yml @@ -0,0 +1,63 @@ +description: Pipeline for normalizing Zeek syslog.log +processors: +- set: + field: event.created + value: '{{_ingest.timestamp}}' +- date: + field: zeek.syslog.ts + formats: + - UNIX +- remove: + field: zeek.syslog.ts +- geoip: + field: destination.ip + target_field: destination.geo + ignore_missing: true +- geoip: + field: source.ip + target_field: source.geo + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: source.ip + target_field: source.as + properties: + - asn + - organization_name + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: destination.ip + target_field: destination.as + properties: + - asn + - organization_name + ignore_missing: true +- rename: + field: source.as.asn + target_field: source.as.number + ignore_missing: true +- rename: + field: source.as.organization_name + target_field: source.as.organization.name + ignore_missing: true +- rename: + field: destination.as.asn + target_field: destination.as.number + ignore_missing: true +- rename: + field: destination.as.organization_name + target_field: destination.as.organization.name + ignore_missing: true +- append: + field: related.ip + value: "{{source.ip}}" + if: "ctx?.source?.ip != null" +- append: + field: related.ip + value: "{{destination.ip}}" + if: "ctx?.destination?.ip != null" +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/x-pack/filebeat/module/zeek/syslog/manifest.yml b/x-pack/filebeat/module/zeek/syslog/manifest.yml index 2d75d440d2f..8db76ab5b36 100644 --- a/x-pack/filebeat/module/zeek/syslog/manifest.yml +++ b/x-pack/filebeat/module/zeek/syslog/manifest.yml @@ -13,5 +13,5 @@ var: - name: community_id default: true -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/syslog.yml diff --git a/x-pack/filebeat/module/zeek/traceroute/config/traceroute.yml b/x-pack/filebeat/module/zeek/traceroute/config/traceroute.yml index 1cd1a7031fd..8b4b40e0234 100644 --- a/x-pack/filebeat/module/zeek/traceroute/config/traceroute.yml +++ b/x-pack/filebeat/module/zeek/traceroute/config/traceroute.yml @@ -27,3 +27,17 @@ processors: ignore_missing: true fail_on_error: false + - convert: + fields: + - {from: "source.address", to: "source.ip", type: "ip"} + - {from: "destination.address", to: "destination.ip", type: "ip"} + ignore_missing: true + fail_on_error: false + - add_fields: + target: event + fields: + kind: event + category: + - network + type: + - info diff --git a/x-pack/filebeat/module/zeek/traceroute/ingest/pipeline.json b/x-pack/filebeat/module/zeek/traceroute/ingest/pipeline.json deleted file mode 100644 index 9a755fa3913..00000000000 --- a/x-pack/filebeat/module/zeek/traceroute/ingest/pipeline.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "description": "Pipeline for normalizing Zeek traceroute.log", - "processors": [ - { - "set": { - "field": "event.created", - "value": "{{_ingest.timestamp}}" - } - }, - { - "date": { - "field": "zeek.traceroute.ts", - "formats": ["UNIX"] - } - }, - { - "remove": { - "field": "zeek.traceroute.ts" - } - }, - { - "set": { - "field": "source.ip", - "value": "{{source.address}}" - } - }, - { - "set": { - "field": "destination.ip", - "value": "{{destination.address}}" - } - } - ], - "on_failure" : [{ - "set" : { - "field" : "error.message", - "value" : "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/x-pack/filebeat/module/zeek/traceroute/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/traceroute/ingest/pipeline.yml new file mode 100644 index 00000000000..6fa5a0bc993 --- /dev/null +++ b/x-pack/filebeat/module/zeek/traceroute/ingest/pipeline.yml @@ -0,0 +1,63 @@ +description: Pipeline for normalizing Zeek traceroute.log +processors: +- set: + field: event.created + value: '{{_ingest.timestamp}}' +- date: + field: zeek.traceroute.ts + formats: + - UNIX +- remove: + field: zeek.traceroute.ts +- geoip: + field: destination.ip + target_field: destination.geo + ignore_missing: true +- geoip: + field: source.ip + target_field: source.geo + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: source.ip + target_field: source.as + properties: + - asn + - organization_name + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: destination.ip + target_field: destination.as + properties: + - asn + - organization_name + ignore_missing: true +- rename: + field: source.as.asn + target_field: source.as.number + ignore_missing: true +- rename: + field: source.as.organization_name + target_field: source.as.organization.name + ignore_missing: true +- rename: + field: destination.as.asn + target_field: destination.as.number + ignore_missing: true +- rename: + field: destination.as.organization_name + target_field: destination.as.organization.name + ignore_missing: true +- append: + field: related.ip + value: "{{source.ip}}" + if: "ctx?.source?.ip != null" +- append: + field: related.ip + value: "{{destination.ip}}" + if: "ctx?.destination?.ip != null" +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/x-pack/filebeat/module/zeek/traceroute/manifest.yml b/x-pack/filebeat/module/zeek/traceroute/manifest.yml index c0dd44654df..0761e9b3bf4 100644 --- a/x-pack/filebeat/module/zeek/traceroute/manifest.yml +++ b/x-pack/filebeat/module/zeek/traceroute/manifest.yml @@ -11,5 +11,5 @@ var: - name: tags default: [zeek.traceroute] -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/traceroute.yml diff --git a/x-pack/filebeat/module/zeek/traceroute/test/traceroute-json.log-expected.json b/x-pack/filebeat/module/zeek/traceroute/test/traceroute-json.log-expected.json index 90bd0dd4eec..8fdfd983c94 100644 --- a/x-pack/filebeat/module/zeek/traceroute/test/traceroute-json.log-expected.json +++ b/x-pack/filebeat/module/zeek/traceroute/test/traceroute-json.log-expected.json @@ -2,13 +2,30 @@ { "@timestamp": "2013-02-26T22:02:38.650Z", "destination.address": "8.8.8.8", + "destination.as.number": 15169, + "destination.as.organization.name": "Google LLC", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "8.8.8.8", + "event.category": [ + "network" + ], "event.dataset": "zeek.traceroute", + "event.kind": "event", "event.module": "zeek", + "event.type": [ + "info" + ], "fileset.name": "traceroute", "input.type": "log", "log.offset": 0, "network.transport": "udp", + "related.ip": [ + "192.168.1.1", + "8.8.8.8" + ], "service.type": "zeek", "source.address": "192.168.1.1", "source.ip": "192.168.1.1", diff --git a/x-pack/filebeat/module/zeek/tunnel/config/tunnel.yml b/x-pack/filebeat/module/zeek/tunnel/config/tunnel.yml index 3fdd2c1faaa..ed9af2117ad 100644 --- a/x-pack/filebeat/module/zeek/tunnel/config/tunnel.yml +++ b/x-pack/filebeat/module/zeek/tunnel/config/tunnel.yml @@ -36,3 +36,19 @@ processors: ignore_missing: true fail_on_error: false + - convert: + fields: + - {from: "zeek.session_id", to: "event.id"} + - {from: "source.address", to: "source.ip", type: "ip"} + - {from: "destination.address", to: "destination.ip", type: "ip"} + - {from: "zeek.tunnel.action", to: "event.action"} + ignore_missing: true + fail_on_error: false + - add_fields: + target: event + fields: + kind: event + category: + - network + type: + - connection diff --git a/x-pack/filebeat/module/zeek/tunnel/ingest/pipeline.json b/x-pack/filebeat/module/zeek/tunnel/ingest/pipeline.json deleted file mode 100644 index bc9eacce8b0..00000000000 --- a/x-pack/filebeat/module/zeek/tunnel/ingest/pipeline.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "description": "Pipeline for normalizing Zeek tunnel.log", - "processors": [ - { - "set": { - "field": "event.created", - "value": "{{_ingest.timestamp}}" - } - }, - { - "date": { - "field": "zeek.tunnel.ts", - "formats": ["UNIX"] - } - }, - { - "remove": { - "field": "zeek.tunnel.ts" - } - }, - { - "set": { - "field": "event.id", - "value": "{{zeek.session_id}}", - "if": "ctx.zeek.session_id != null" - } - }, - { - "set": { - "field": "source.ip", - "value": "{{source.address}}" - } - }, - { - "set": { - "field": "destination.ip", - "value": "{{destination.address}}" - } - } - ], - "on_failure" : [{ - "set" : { - "field" : "error.message", - "value" : "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/x-pack/filebeat/module/zeek/tunnel/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/tunnel/ingest/pipeline.yml new file mode 100644 index 00000000000..402bce5fa5d --- /dev/null +++ b/x-pack/filebeat/module/zeek/tunnel/ingest/pipeline.yml @@ -0,0 +1,63 @@ +description: Pipeline for normalizing Zeek tunnel.log +processors: +- set: + field: event.created + value: '{{_ingest.timestamp}}' +- date: + field: zeek.tunnel.ts + formats: + - UNIX +- remove: + field: zeek.tunnel.ts +- geoip: + field: destination.ip + target_field: destination.geo + ignore_missing: true +- geoip: + field: source.ip + target_field: source.geo + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: source.ip + target_field: source.as + properties: + - asn + - organization_name + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: destination.ip + target_field: destination.as + properties: + - asn + - organization_name + ignore_missing: true +- rename: + field: source.as.asn + target_field: source.as.number + ignore_missing: true +- rename: + field: source.as.organization_name + target_field: source.as.organization.name + ignore_missing: true +- rename: + field: destination.as.asn + target_field: destination.as.number + ignore_missing: true +- rename: + field: destination.as.organization_name + target_field: destination.as.organization.name + ignore_missing: true +- append: + field: related.ip + value: "{{source.ip}}" + if: "ctx?.source?.ip != null" +- append: + field: related.ip + value: "{{destination.ip}}" + if: "ctx?.destination?.ip != null" +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/x-pack/filebeat/module/zeek/tunnel/manifest.yml b/x-pack/filebeat/module/zeek/tunnel/manifest.yml index ad3d712c33c..a0618a12b7e 100644 --- a/x-pack/filebeat/module/zeek/tunnel/manifest.yml +++ b/x-pack/filebeat/module/zeek/tunnel/manifest.yml @@ -11,5 +11,5 @@ var: - name: tags default: [zeek.tunnel] -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/tunnel.yml diff --git a/x-pack/filebeat/module/zeek/tunnel/test/tunnel-json.log-expected.json b/x-pack/filebeat/module/zeek/tunnel/test/tunnel-json.log-expected.json index 9504931de51..1e00e616e36 100644 --- a/x-pack/filebeat/module/zeek/tunnel/test/tunnel-json.log-expected.json +++ b/x-pack/filebeat/module/zeek/tunnel/test/tunnel-json.log-expected.json @@ -2,15 +2,39 @@ { "@timestamp": "2018-12-10T01:34:26.743Z", "destination.address": "132.16.110.133", + "destination.as.number": 427, + "destination.as.organization.name": "Air Force Systems Networking", + "destination.geo.continent_name": "North America", + "destination.geo.country_iso_code": "US", + "destination.geo.location.lat": 37.751, + "destination.geo.location.lon": -97.822, "destination.ip": "132.16.110.133", "destination.port": 8080, + "event.action": "Tunnel::DISCOVER", + "event.category": [ + "network" + ], "event.dataset": "zeek.tunnel", + "event.kind": "event", "event.module": "zeek", + "event.type": [ + "connection" + ], "fileset.name": "tunnel", "input.type": "log", "log.offset": 0, + "related.ip": [ + "132.16.146.79", + "132.16.110.133" + ], "service.type": "zeek", "source.address": "132.16.146.79", + "source.as.number": 427, + "source.as.organization.name": "Air Force Systems Networking", + "source.geo.continent_name": "North America", + "source.geo.country_iso_code": "US", + "source.geo.location.lat": 37.751, + "source.geo.location.lon": -97.822, "source.ip": "132.16.146.79", "source.port": 0, "tags": [ diff --git a/x-pack/filebeat/module/zeek/weird/config/weird.yml b/x-pack/filebeat/module/zeek/weird/config/weird.yml index 6f67c90ae4f..1256f96902b 100644 --- a/x-pack/filebeat/module/zeek/weird/config/weird.yml +++ b/x-pack/filebeat/module/zeek/weird/config/weird.yml @@ -36,3 +36,19 @@ processors: ignore_missing: true fail_on_error: false + - convert: + fields: + - {from: "zeek.session_id", to: "event.id"} + - {from: "source.address", to: "source.ip", type: "ip"} + - {from: "destination.address", to: "destination.ip", type: "ip"} + - {from: "zeek.weird.name", to: "rule.name"} + ignore_missing: true + fail_on_error: false + - add_fields: + target: event + fields: + kind: alert + category: + - network + type: + - info diff --git a/x-pack/filebeat/module/zeek/weird/ingest/pipeline.json b/x-pack/filebeat/module/zeek/weird/ingest/pipeline.json deleted file mode 100644 index a97cdeb22bb..00000000000 --- a/x-pack/filebeat/module/zeek/weird/ingest/pipeline.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "description": "Pipeline for normalizing Zeek weird.log", - "processors": [ - { - "set": { - "field": "event.created", - "value": "{{_ingest.timestamp}}" - } - }, - { - "date": { - "field": "zeek.weird.ts", - "formats": ["UNIX"] - } - }, - { - "remove": { - "field": "zeek.weird.ts" - } - }, - { - "set": { - "field": "event.id", - "value": "{{zeek.session_id}}", - "if": "ctx.zeek.session_id != null" - } - }, - { - "set": { - "field": "source.ip", - "value": "{{source.address}}", - "if": "ctx?.source?.address != null" - } - }, - { - "set": { - "field": "destination.ip", - "value": "{{destination.address}}", - "if": "ctx?.destination?.address != null" - } - } - ], - "on_failure" : [{ - "set" : { - "field" : "error.message", - "value" : "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/x-pack/filebeat/module/zeek/weird/ingest/pipeline.yml b/x-pack/filebeat/module/zeek/weird/ingest/pipeline.yml new file mode 100644 index 00000000000..e0325d9a1c5 --- /dev/null +++ b/x-pack/filebeat/module/zeek/weird/ingest/pipeline.yml @@ -0,0 +1,63 @@ +description: Pipeline for normalizing Zeek weird.log +processors: +- set: + field: event.created + value: '{{_ingest.timestamp}}' +- date: + field: zeek.weird.ts + formats: + - UNIX +- remove: + field: zeek.weird.ts +- geoip: + field: destination.ip + target_field: destination.geo + ignore_missing: true +- geoip: + field: source.ip + target_field: source.geo + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: source.ip + target_field: source.as + properties: + - asn + - organization_name + ignore_missing: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: destination.ip + target_field: destination.as + properties: + - asn + - organization_name + ignore_missing: true +- rename: + field: source.as.asn + target_field: source.as.number + ignore_missing: true +- rename: + field: source.as.organization_name + target_field: source.as.organization.name + ignore_missing: true +- rename: + field: destination.as.asn + target_field: destination.as.number + ignore_missing: true +- rename: + field: destination.as.organization_name + target_field: destination.as.organization.name + ignore_missing: true +- append: + field: related.ip + value: "{{source.ip}}" + if: "ctx?.source?.ip != null" +- append: + field: related.ip + value: "{{destination.ip}}" + if: "ctx?.destination?.ip != null" +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/x-pack/filebeat/module/zeek/weird/manifest.yml b/x-pack/filebeat/module/zeek/weird/manifest.yml index 63d48d32ee3..3e91c91c64a 100644 --- a/x-pack/filebeat/module/zeek/weird/manifest.yml +++ b/x-pack/filebeat/module/zeek/weird/manifest.yml @@ -11,5 +11,5 @@ var: - name: tags default: [zeek.weird] -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/weird.yml diff --git a/x-pack/filebeat/module/zeek/weird/test/weird-json.log-expected.json b/x-pack/filebeat/module/zeek/weird/test/weird-json.log-expected.json index f1fdb20678f..cc9f7f49508 100644 --- a/x-pack/filebeat/module/zeek/weird/test/weird-json.log-expected.json +++ b/x-pack/filebeat/module/zeek/weird/test/weird-json.log-expected.json @@ -4,12 +4,24 @@ "destination.address": "192.168.1.2", "destination.ip": "192.168.1.2", "destination.port": 53, + "event.category": [ + "network" + ], "event.dataset": "zeek.weird", "event.id": "C1ralPp062bkwWt4e", + "event.kind": "alert", "event.module": "zeek", + "event.type": [ + "info" + ], "fileset.name": "weird", "input.type": "log", "log.offset": 0, + "related.ip": [ + "192.168.1.1", + "192.168.1.2" + ], + "rule.name": "dns_unmatched_reply", "service.type": "zeek", "source.address": "192.168.1.1", "source.ip": "192.168.1.1", @@ -24,11 +36,19 @@ }, { "@timestamp": "2020-01-28T16:00:59.342Z", + "event.category": [ + "network" + ], "event.dataset": "zeek.weird", + "event.kind": "alert", "event.module": "zeek", + "event.type": [ + "info" + ], "fileset.name": "weird", "input.type": "log", "log.offset": 197, + "rule.name": "non_ip_packet_in_ethernet", "service.type": "zeek", "tags": [ "zeek.weird" diff --git a/x-pack/filebeat/module/zeek/x509/config/x509.yml b/x-pack/filebeat/module/zeek/x509/config/x509.yml index 3bebeab5697..49a670e46e5 100644 --- a/x-pack/filebeat/module/zeek/x509/config/x509.yml +++ b/x-pack/filebeat/module/zeek/x509/config/x509.yml @@ -57,3 +57,9 @@ processors: ignore_missing: true fail_on_error: false + - add_fields: + target: event + fields: + kind: event + type: + - info diff --git a/x-pack/filebeat/module/zeek/x509/test/x509-json.log-expected.json b/x-pack/filebeat/module/zeek/x509/test/x509-json.log-expected.json index 1cff57241ba..fff83c5969e 100644 --- a/x-pack/filebeat/module/zeek/x509/test/x509-json.log-expected.json +++ b/x-pack/filebeat/module/zeek/x509/test/x509-json.log-expected.json @@ -3,7 +3,11 @@ "@timestamp": "2018-12-03T20:00:00.143Z", "event.dataset": "zeek.x509", "event.id": "FxZ6gZ3YR6vFlIocq3", + "event.kind": "event", "event.module": "zeek", + "event.type": [ + "info" + ], "fileset.name": "x509", "input.type": "log", "log.offset": 0, From 754eac174cdb2a335ce578c39130b163b40b49d1 Mon Sep 17 00:00:00 2001 From: Andrew Cholakian Date: Tue, 28 Apr 2020 21:06:37 -0500 Subject: [PATCH 045/116] Fix icmp duration (#17920) At some point in the past we apparently stopped nesting [ICMP fields](https://www.elastic.co/guide/en/beats/heartbeat/master/exported-fields-icmp.html). This patch fixes that issue. --- heartbeat/monitors/active/icmp/icmp.go | 79 ++-- heartbeat/monitors/active/icmp/icmp_test.go | 90 +++++ heartbeat/monitors/active/icmp/loop.go | 370 +----------------- heartbeat/monitors/active/icmp/stdloop.go | 405 ++++++++++++++++++++ 4 files changed, 554 insertions(+), 390 deletions(-) create mode 100644 heartbeat/monitors/active/icmp/icmp_test.go create mode 100644 heartbeat/monitors/active/icmp/stdloop.go diff --git a/heartbeat/monitors/active/icmp/icmp.go b/heartbeat/monitors/active/icmp/icmp.go index 1cb19c90798..45fdf8a54b3 100644 --- a/heartbeat/monitors/active/icmp/icmp.go +++ b/heartbeat/monitors/active/icmp/icmp.go @@ -32,46 +32,71 @@ import ( "github.com/elastic/beats/v7/libbeat/logp" ) +var debugf = logp.MakeDebug("icmp") + func init() { monitors.RegisterActive("icmp", create) } -var debugf = logp.MakeDebug("icmp") - func create( name string, - cfg *common.Config, + commonConfig *common.Config, ) (jobs []jobs.Job, endpoints int, err error) { + loop, err := getStdLoop() + if err != nil { + logp.Warn("Failed to initialize ICMP loop %v", err) + return nil, 0, err + } + config := DefaultConfig - if err := cfg.Unpack(&config); err != nil { + if err := commonConfig.Unpack(&config); err != nil { return nil, 0, err } - ipVersion := config.Mode.Network() - if len(config.Hosts) > 0 && ipVersion == "" { - err := fmt.Errorf("pinging hosts requires ipv4 or ipv6 mode enabled") + jf, err := newJobFactory(config, monitors.NewStdResolver(), loop) + if err != nil { return nil, 0, err } + return jf.makeJobs() - var loopErr error - loopInit.Do(func() { - debugf("initializing ICMP loop") - loop, loopErr = newICMPLoop() - }) - if loopErr != nil { - logp.Warn("Failed to initialize ICMP loop %v", loopErr) - return nil, 0, loopErr +} + +type jobFactory struct { + config Config + resolver monitors.Resolver + loop ICMPLoop + ipVersion string +} + +func newJobFactory(config Config, resolver monitors.Resolver, loop ICMPLoop) (*jobFactory, error) { + jf := &jobFactory{config: config, resolver: resolver, loop: loop} + err := jf.checkConfig() + if err != nil { + return nil, err } - debugf("ICMP loop successfully initialized") - if err := loop.checkNetworkMode(ipVersion); err != nil { + return jf, nil +} + +func (jf *jobFactory) checkConfig() error { + jf.ipVersion = jf.config.Mode.Network() + if len(jf.config.Hosts) > 0 && jf.ipVersion == "" { + err := fmt.Errorf("pinging hosts requires ipv4 or ipv6 mode enabled") + return err + } + + return nil +} + +func (jf *jobFactory) makeJobs() (j []jobs.Job, endpoints int, err error) { + if err := jf.loop.checkNetworkMode(jf.ipVersion); err != nil { return nil, 0, err } - pingFactory := monitors.MakePingIPFactory(createPingIPFactory(&config)) + pingFactory := jf.pingIPFactory(&jf.config) - for _, host := range config.Hosts { - job, err := monitors.MakeByHostJob(host, config.Mode, monitors.NewStdResolver(), pingFactory) + for _, host := range jf.config.Hosts { + job, err := monitors.MakeByHostJob(host, jf.config.Mode, monitors.NewStdResolver(), pingFactory) if err != nil { return nil, 0, err @@ -82,15 +107,15 @@ func create( return nil, 0, err } - jobs = append(jobs, wrappers.WithURLField(u, job)) + j = append(j, wrappers.WithURLField(u, job)) } - return jobs, len(config.Hosts), nil + return j, len(jf.config.Hosts), nil } -func createPingIPFactory(config *Config) func(*beat.Event, *net.IPAddr) error { - return func(event *beat.Event, ip *net.IPAddr) error { - rtt, n, err := loop.ping(ip, config.Timeout, config.Wait) +func (jf *jobFactory) pingIPFactory(config *Config) func(*net.IPAddr) jobs.Job { + return monitors.MakePingIPFactory(func(event *beat.Event, ip *net.IPAddr) error { + rtt, n, err := jf.loop.ping(ip, config.Timeout, config.Wait) if err != nil { return err } @@ -98,9 +123,9 @@ func createPingIPFactory(config *Config) func(*beat.Event, *net.IPAddr) error { icmpFields := common.MapStr{"requests": n} if err == nil { icmpFields["rtt"] = look.RTT(rtt) - eventext.MergeEventFields(event, icmpFields) + eventext.MergeEventFields(event, common.MapStr{"icmp": icmpFields}) } return nil - } + }) } diff --git a/heartbeat/monitors/active/icmp/icmp_test.go b/heartbeat/monitors/active/icmp/icmp_test.go new file mode 100644 index 00000000000..11e7dae5380 --- /dev/null +++ b/heartbeat/monitors/active/icmp/icmp_test.go @@ -0,0 +1,90 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package icmp + +import ( + "net" + "net/url" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/elastic/beats/v7/heartbeat/hbtest" + "github.com/elastic/beats/v7/heartbeat/look" + "github.com/elastic/beats/v7/heartbeat/monitors" + "github.com/elastic/beats/v7/heartbeat/monitors/wrappers" + "github.com/elastic/beats/v7/heartbeat/scheduler/schedule" + "github.com/elastic/beats/v7/libbeat/beat" + "github.com/elastic/go-lookslike" + "github.com/elastic/go-lookslike/testslike" +) + +func TestICMPFields(t *testing.T) { + host := "localhost" + hostURL := &url.URL{Scheme: "icmp", Host: host} + ip := "127.0.0.1" + cfg := Config{ + Hosts: []string{host}, + Mode: monitors.IPSettings{IPv4: true, IPv6: false, Mode: monitors.PingAny}, + } + testMockLoop, e := execTestICMPCheck(t, cfg) + + validator := lookslike.Strict( + lookslike.Compose( + hbtest.BaseChecks(ip, "up", "icmp"), + hbtest.SummaryChecks(1, 0), + hbtest.URLChecks(t, hostURL), + hbtest.ResolveChecks(ip), + lookslike.MustCompile(map[string]interface{}{ + "icmp.requests": 1, + "icmp.rtt": look.RTT(testMockLoop.pingRtt), + }), + ), + ) + testslike.Test(t, validator, e.Fields) +} + +func execTestICMPCheck(t *testing.T, cfg Config) (mockLoop, *beat.Event) { + tl := mockLoop{pingRtt: time.Microsecond * 1000, pingRequests: 1} + jf, err := newJobFactory(cfg, monitors.NewStdResolver(), tl) + require.NoError(t, err) + j, endpoints, err := jf.makeJobs() + require.Len(t, j, 1) + require.Equal(t, 1, endpoints) + e := &beat.Event{} + sched, _ := schedule.Parse("@every 1s") + wrapped := wrappers.WrapCommon(j, "test", "", "icmp", sched, time.Duration(0)) + wrapped[0](e) + return tl, e +} + +type mockLoop struct { + pingRtt time.Duration + pingRequests int + pingErr error + checkNetworkModeErr error +} + +func (t mockLoop) checkNetworkMode(mode string) error { + return t.checkNetworkModeErr +} + +func (t mockLoop) ping(addr *net.IPAddr, timeout time.Duration, interval time.Duration) (time.Duration, int, error) { + return t.pingRtt, t.pingRequests, t.pingErr +} diff --git a/heartbeat/monitors/active/icmp/loop.go b/heartbeat/monitors/active/icmp/loop.go index 414686c1d31..de4d0ef4dfc 100644 --- a/heartbeat/monitors/active/icmp/loop.go +++ b/heartbeat/monitors/active/icmp/loop.go @@ -18,371 +18,15 @@ package icmp import ( - "bytes" - "encoding/binary" - "errors" - "fmt" - "math/rand" "net" - "os" - "runtime" - "sync" "time" - - "golang.org/x/net/icmp" - "golang.org/x/net/ipv4" - "golang.org/x/net/ipv6" -) - -type icmpLoop struct { - conn4, conn6 *icmp.PacketConn - recv chan packet - - mutex sync.Mutex - requests map[requestID]*requestContext -} - -type timeoutError struct { -} - -const ( - // iana types - protocolICMP = 1 - protocolIPv6ICMP = 58 -) - -type packet struct { - ts time.Time - addr net.Addr - - Type icmp.Type // type, either ipv4.ICMPType or ipv6.ICMPType - Code int // code - Checksum int // checksum - Echo icmp.Echo -} - -type requestID struct { - addr string - proto int - id int - seq int -} - -type requestContext struct { - l *icmpLoop - id requestID - ts time.Time - result chan requestResult -} - -type requestResult struct { - packet packet - err error -} - -var ( - loopInit sync.Once - loop *icmpLoop ) -func noPingCapabilityError(message string) error { - return fmt.Errorf(fmt.Sprintf("Insufficient privileges to perform ICMP ping. %s", message)) -} - -func newICMPLoop() (*icmpLoop, error) { - // Log errors at info level, as the loop is setup globally when ICMP module is loaded - // first (not yet configured). - // With multiple configurations using the icmp loop, we have to postpose - // IPv4/IPv6 checking - conn4 := createListener("IPv4", "ip4:icmp") - conn6 := createListener("IPv6", "ip6:ipv6-icmp") - unprivilegedPossible := false - l := &icmpLoop{ - conn4: conn4, - conn6: conn6, - recv: make(chan packet, 16), - requests: map[requestID]*requestContext{}, - } - - if l.conn4 == nil && l.conn6 == nil { - switch runtime.GOOS { - case "linux", "darwin": - unprivilegedPossible = true - //This is non-privileged ICMP, not udp - l.conn4 = createListener("Unprivileged IPv4", "udp4") - l.conn6 = createListener("Unprivileged IPv6", "udp6") - } - } - - if l.conn4 != nil { - go l.runICMPRecv(l.conn4, protocolICMP) - } - if l.conn6 != nil { - go l.runICMPRecv(l.conn6, protocolIPv6ICMP) - } - - if l.conn4 == nil && l.conn6 == nil { - if unprivilegedPossible { - var buffer bytes.Buffer - path, _ := os.Executable() - buffer.WriteString("You can run without root by setting cap_net_raw:\n sudo setcap cap_net_raw+eip ") - buffer.WriteString(path + " \n") - buffer.WriteString("Your system allows the use of unprivileged ping by setting net.ipv4.ping_group_range \n sysctl -w net.ipv4.ping_group_range=' ' ") - return nil, noPingCapabilityError(buffer.String()) - } - return nil, noPingCapabilityError("You must provide the appropriate permissions to this executable") - } - - return l, nil -} - -func (l *icmpLoop) checkNetworkMode(mode string) error { - ip4, ip6 := false, false - switch mode { - case "ip4": - ip4 = true - case "ip6": - ip6 = true - case "ip": - ip4, ip6 = true, true - default: - return fmt.Errorf("'%v' is not supported", mode) - } - - if ip4 && l.conn4 == nil { - return errors.New("failed to initiate IPv4 support. Check log details for permission configuration") - } - if ip6 && l.conn6 == nil { - return errors.New("failed to initiate IPv6 support. Check log details for permission configuration") - } - - return nil -} - -func (l *icmpLoop) runICMPRecv(conn *icmp.PacketConn, proto int) { - for { - bytes := make([]byte, 512) - conn.SetReadDeadline(time.Now().Add(1 * time.Second)) - _, addr, err := conn.ReadFrom(bytes) - if err != nil { - if neterr, ok := err.(*net.OpError); ok { - if neterr.Timeout() { - continue - } else { - // TODO: report error and quit loop? - return - } - } - } - - ts := time.Now() - m, err := icmp.ParseMessage(proto, bytes) - if err != nil { - continue - } - - // process echo reply only - if m.Type != ipv4.ICMPTypeEchoReply && m.Type != ipv6.ICMPTypeEchoReply { - continue - } - echo, ok := m.Body.(*icmp.Echo) - if !ok { - continue - } - - id := requestID{ - addr: addr.String(), - proto: proto, - id: echo.ID, - seq: echo.Seq, - } - - l.mutex.Lock() - ctx := l.requests[id] - if ctx != nil { - delete(l.requests, id) - } - l.mutex.Unlock() - - // no return context available for echo reply -> handle next message - if ctx == nil { - continue - } - - ctx.result <- requestResult{ - packet: packet{ - ts: ts, - addr: addr, - - Type: m.Type, - Code: m.Code, - Checksum: m.Checksum, - Echo: *echo, - }, - } - } -} - -func (l *icmpLoop) ping( - addr *net.IPAddr, - timeout time.Duration, - interval time.Duration, -) (time.Duration, int, error) { - - var err error - toTimer := time.NewTimer(timeout) - defer toTimer.Stop() - - ticker := time.NewTicker(interval) - defer ticker.Stop() - - done := false - doneSignal := make(chan struct{}) - - success := false - var rtt time.Duration - - // results accepts first response received only - results := make(chan time.Duration, 1) - requests := 0 - - awaitResponse := func(ctx *requestContext) { - select { - case <-doneSignal: - ctx.Stop() - - case r := <-ctx.result: - // ctx is removed from request tables automatically a response is - // received. No need to stop it. - - // try to push RTT. The first result available will be reported - select { - case results <- r.packet.ts.Sub(ctx.ts): - default: - } - } - } - - for !done { - var ctx *requestContext - ctx, err = l.sendEchoRequest(addr) - if err != nil { - close(doneSignal) - break - } - go awaitResponse(ctx) - requests++ - - select { - case <-toTimer.C: - // no response for any active request received. Finish loop - // and remove all requests from request table. - done = true - close(doneSignal) - - case <-ticker.C: - // No response yet. Send another request with every tick - - case rtt = <-results: - success = true - - done = true - close(doneSignal) - } - } - - if err != nil { - return 0, 0, err - } - - if !success { - return 0, requests, timeoutError{} - } - - return rtt, requests, nil -} - -func (l *icmpLoop) sendEchoRequest(addr *net.IPAddr) (*requestContext, error) { - var conn *icmp.PacketConn - var proto int - var typ icmp.Type - - if l == nil { - panic("icmp loop not initialized") - } - - if isIPv4(addr.IP) { - conn = l.conn4 - proto = protocolICMP - typ = ipv4.ICMPTypeEcho - } else if isIPv6(addr.IP) { - conn = l.conn6 - proto = protocolIPv6ICMP - typ = ipv6.ICMPTypeEchoRequest - } else { - return nil, fmt.Errorf("%v is unknown ip address", addr) - } - - id := requestID{ - addr: addr.String(), - proto: proto, - id: rand.Intn(0xffff), - seq: rand.Intn(0xffff), - } - - ctx := &requestContext{ - l: l, - id: id, - result: make(chan requestResult, 1), - } - - l.mutex.Lock() - l.requests[id] = ctx - l.mutex.Unlock() - - payloadBuf := make([]byte, 0, 8) - payload := bytes.NewBuffer(payloadBuf) - ts := time.Now() - binary.Write(payload, binary.BigEndian, ts.UnixNano()) - - msg := &icmp.Message{ - Type: typ, - Body: &icmp.Echo{ - ID: id.id, - Seq: id.seq, - Data: payload.Bytes(), - }, - } - encoded, _ := msg.Marshal(nil) - - _, err := conn.WriteTo(encoded, addr) - if err != nil { - return nil, err - } - - ctx.ts = ts - return ctx, nil -} - -func createListener(name, network string) *icmp.PacketConn { - conn, err := icmp.ListenPacket(network, "") - - // XXX: need to check for conn == nil, as 'err != nil' seems always to be - // true, even if error value itself is `nil`. Checking for conn suppresses - // misleading log message. - if conn == nil && err != nil { - return nil - } - return conn -} - -// timeoutError implements net.Error interface -func (timeoutError) Error() string { return "ping timeout" } -func (timeoutError) Timeout() bool { return true } -func (timeoutError) Temporary() bool { return true } - -func (r *requestContext) Stop() { - r.l.mutex.Lock() - delete(r.l.requests, r.id) - r.l.mutex.Unlock() +type ICMPLoop interface { + checkNetworkMode(mode string) error + ping( + addr *net.IPAddr, + timeout time.Duration, + interval time.Duration, + ) (time.Duration, int, error) } diff --git a/heartbeat/monitors/active/icmp/stdloop.go b/heartbeat/monitors/active/icmp/stdloop.go new file mode 100644 index 00000000000..932154c61ad --- /dev/null +++ b/heartbeat/monitors/active/icmp/stdloop.go @@ -0,0 +1,405 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package icmp + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "math/rand" + "net" + "os" + "runtime" + "sync" + "time" + + "golang.org/x/net/icmp" + "golang.org/x/net/ipv4" + "golang.org/x/net/ipv6" +) + +type stdICMPLoop struct { + conn4, conn6 *icmp.PacketConn + recv chan packet + + mutex sync.Mutex + requests map[requestID]*requestContext +} + +type timeoutError struct { +} + +const ( + // iana types + protocolICMP = 1 + protocolIPv6ICMP = 58 +) + +type packet struct { + ts time.Time + addr net.Addr + + Type icmp.Type // type, either ipv4.ICMPType or ipv6.ICMPType + Code int // code + Checksum int // checksum + Echo icmp.Echo +} + +type requestID struct { + addr string + proto int + id int + seq int +} + +type requestContext struct { + l *stdICMPLoop + id requestID + ts time.Time + result chan requestResult +} + +type requestResult struct { + packet packet + err error +} + +// stdLoop is a singleton for our main ICMP loop since it doesn't +// make sense to have multiples. While having a singleton is ugly +// is mandatory for the ICMP interface in go, where all monitors +// must share a single loop. +// These vars should not be used directly, but rather getStdLoop +// should be invoked to initialize and return stdLoop. +var ( + stdICMPLoopInit sync.Once + stdICMPLoopSingleton *stdICMPLoop +) + +func getStdLoop() (*stdICMPLoop, error) { + var loopErr error + stdICMPLoopInit.Do(func() { + debugf("initializing ICMP loop") + stdICMPLoopSingleton, loopErr = newICMPLoop() + if loopErr == nil { + debugf("ICMP loop successfully initialized") + } + }) + return stdICMPLoopSingleton, loopErr +} + +func noPingCapabilityError(message string) error { + return fmt.Errorf(fmt.Sprintf("Insufficient privileges to perform ICMP ping. %s", message)) +} + +func newICMPLoop() (*stdICMPLoop, error) { + // Log errors at info level, as the loop is setup globally when ICMP module is loaded + // first (not yet configured). + // With multiple configurations using the icmp loop, we have to postpose + // IPv4/IPv6 checking + conn4 := createListener("IPv4", "ip4:icmp") + conn6 := createListener("IPv6", "ip6:ipv6-icmp") + unprivilegedPossible := false + l := &stdICMPLoop{ + conn4: conn4, + conn6: conn6, + recv: make(chan packet, 16), + requests: map[requestID]*requestContext{}, + } + + if l.conn4 == nil && l.conn6 == nil { + switch runtime.GOOS { + case "linux", "darwin": + unprivilegedPossible = true + //This is non-privileged ICMP, not udp + l.conn4 = createListener("Unprivileged IPv4", "udp4") + l.conn6 = createListener("Unprivileged IPv6", "udp6") + } + } + + if l.conn4 != nil { + go l.runICMPRecv(l.conn4, protocolICMP) + } + if l.conn6 != nil { + go l.runICMPRecv(l.conn6, protocolIPv6ICMP) + } + + if l.conn4 == nil && l.conn6 == nil { + if unprivilegedPossible { + var buffer bytes.Buffer + path, _ := os.Executable() + buffer.WriteString("You can run without root by setting cap_net_raw:\n sudo setcap cap_net_raw+eip ") + buffer.WriteString(path + " \n") + buffer.WriteString("Your system allows the use of unprivileged ping by setting net.ipv4.ping_group_range \n sysctl -w net.ipv4.ping_group_range=' ' ") + return nil, noPingCapabilityError(buffer.String()) + } + return nil, noPingCapabilityError("You must provide the appropriate permissions to this executable") + } + + return l, nil +} + +func (l *stdICMPLoop) checkNetworkMode(mode string) error { + ip4, ip6 := false, false + switch mode { + case "ip4": + ip4 = true + case "ip6": + ip6 = true + case "ip": + ip4, ip6 = true, true + default: + return fmt.Errorf("'%v' is not supported", mode) + } + + if ip4 && l.conn4 == nil { + return errors.New("failed to initiate IPv4 support. Check log details for permission configuration") + } + if ip6 && l.conn6 == nil { + return errors.New("failed to initiate IPv6 support. Check log details for permission configuration") + } + + return nil +} + +func (l *stdICMPLoop) runICMPRecv(conn *icmp.PacketConn, proto int) { + for { + bytes := make([]byte, 512) + conn.SetReadDeadline(time.Now().Add(1 * time.Second)) + _, addr, err := conn.ReadFrom(bytes) + if err != nil { + if neterr, ok := err.(*net.OpError); ok { + if neterr.Timeout() { + continue + } else { + // TODO: report error and quit loop? + return + } + } + } + + ts := time.Now() + m, err := icmp.ParseMessage(proto, bytes) + if err != nil { + continue + } + + // process echo reply only + if m.Type != ipv4.ICMPTypeEchoReply && m.Type != ipv6.ICMPTypeEchoReply { + continue + } + echo, ok := m.Body.(*icmp.Echo) + if !ok { + continue + } + + id := requestID{ + addr: addr.String(), + proto: proto, + id: echo.ID, + seq: echo.Seq, + } + + l.mutex.Lock() + ctx := l.requests[id] + if ctx != nil { + delete(l.requests, id) + } + l.mutex.Unlock() + + // no return context available for echo reply -> handle next message + if ctx == nil { + continue + } + + ctx.result <- requestResult{ + packet: packet{ + ts: ts, + addr: addr, + + Type: m.Type, + Code: m.Code, + Checksum: m.Checksum, + Echo: *echo, + }, + } + } +} + +func (l *stdICMPLoop) ping( + addr *net.IPAddr, + timeout time.Duration, + interval time.Duration, +) (time.Duration, int, error) { + var err error + toTimer := time.NewTimer(timeout) + defer toTimer.Stop() + + ticker := time.NewTicker(interval) + defer ticker.Stop() + + done := false + doneSignal := make(chan struct{}) + + success := false + var rtt time.Duration + + // results accepts first response received only + results := make(chan time.Duration, 1) + requests := 0 + + awaitResponse := func(ctx *requestContext) { + select { + case <-doneSignal: + ctx.Stop() + + case r := <-ctx.result: + // ctx is removed from request tables automatically a response is + // received. No need to stop it. + + // try to push RTT. The first result available will be reported + select { + case results <- r.packet.ts.Sub(ctx.ts): + default: + } + } + } + + for !done { + var ctx *requestContext + ctx, err = l.sendEchoRequest(addr) + if err != nil { + close(doneSignal) + break + } + go awaitResponse(ctx) + requests++ + + select { + case <-toTimer.C: + // no response for any active request received. Finish loop + // and remove all pingRequests from request table. + done = true + close(doneSignal) + + case <-ticker.C: + // No response yet. Send another request with every tick + + case rtt = <-results: + success = true + + done = true + close(doneSignal) + } + } + + if err != nil { + return 0, 0, err + } + + if !success { + return 0, requests, timeoutError{} + } + + return rtt, requests, nil +} + +func (l *stdICMPLoop) sendEchoRequest(addr *net.IPAddr) (*requestContext, error) { + var conn *icmp.PacketConn + var proto int + var typ icmp.Type + + if l == nil { + panic("icmp loop not initialized") + } + + if isIPv4(addr.IP) { + conn = l.conn4 + proto = protocolICMP + typ = ipv4.ICMPTypeEcho + } else if isIPv6(addr.IP) { + conn = l.conn6 + proto = protocolIPv6ICMP + typ = ipv6.ICMPTypeEchoRequest + } else { + return nil, fmt.Errorf("%v is unknown ip address", addr) + } + + id := requestID{ + addr: addr.String(), + proto: proto, + id: rand.Intn(0xffff), + seq: rand.Intn(0xffff), + } + + ctx := &requestContext{ + l: l, + id: id, + result: make(chan requestResult, 1), + } + + l.mutex.Lock() + l.requests[id] = ctx + l.mutex.Unlock() + + payloadBuf := make([]byte, 0, 8) + payload := bytes.NewBuffer(payloadBuf) + ts := time.Now() + binary.Write(payload, binary.BigEndian, ts.UnixNano()) + + msg := &icmp.Message{ + Type: typ, + Body: &icmp.Echo{ + ID: id.id, + Seq: id.seq, + Data: payload.Bytes(), + }, + } + encoded, _ := msg.Marshal(nil) + + _, err := conn.WriteTo(encoded, addr) + if err != nil { + return nil, err + } + + ctx.ts = ts + return ctx, nil +} + +func createListener(name, network string) *icmp.PacketConn { + conn, err := icmp.ListenPacket(network, "") + + // XXX: need to check for conn == nil, as 'err != nil' seems always to be + // true, even if error value itself is `nil`. Checking for conn suppresses + // misleading log message. + if conn == nil && err != nil { + return nil + } + return conn +} + +// timeoutError implements net.Error interface +func (timeoutError) Error() string { return "ping timeout" } +func (timeoutError) Timeout() bool { return true } +func (timeoutError) Temporary() bool { return true } + +func (r *requestContext) Stop() { + r.l.mutex.Lock() + delete(r.l.requests, r.id) + r.l.mutex.Unlock() +} From f66b0797ccee458a52b5d578facdf1bd1e5e3f4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mi=20V=C3=A1nyi?= Date: Wed, 29 Apr 2020 09:09:45 +0200 Subject: [PATCH 046/116] Add Kerberos support to Elasticsearch output (#17927) ## What does this PR do? This PR adds support for Kerberos authentication to Elasticsearch output. ### Configuration Users can authenticate using either passwords or keytabs. The option `service_name` is not exposed as in case of ES it has be `HTTP`. Thus, the [SPN](https://web.mit.edu/kerberos/krb5-1.5/krb5-1.5.4/doc/krb5-user/What-is-a-Kerberos-Principal_003f.html) of the output is always `HTTP/{output.elasticsearch.host}@{output.elasticsearch.kerberos.realm}`. ```yaml # Authentication type to use with Kerberos. Available options: keytab, password. #kerberos.auth_type: password # Path to the keytab file. It is used when auth_type is set to keytab. #kerberos.keytab: /etc/elastic.keytab # Path to the Kerberos configuration. #kerberos.config_path: /etc/krb5.conf # Name of the Kerberos user. #kerberos.username: elastic # Password of the Kerberos user. It is used when auth_type is set to password. #kerberos.password: changeme # Kerberos realm. #kerberos.realm: ELASTIC ``` --- CHANGELOG.next.asciidoc | 1 + NOTICE.txt | 9 + auditbeat/auditbeat.reference.yml | 45 +++ filebeat/filebeat.reference.yml | 45 +++ filebeat/input/kafka/config.go | 2 +- go.mod | 2 +- heartbeat/heartbeat.reference.yml | 45 +++ journalbeat/journalbeat.reference.yml | 45 +++ libbeat/_meta/config.reference.yml.tmpl | 45 +++ libbeat/common/transport/kerberos/client.go | 65 ++++ libbeat/common/transport/kerberos/config.go | 37 +- libbeat/docs/shared-kerberos-config.asciidoc | 85 +++++ libbeat/esleg/eslegclient/config.go | 4 +- libbeat/esleg/eslegclient/connection.go | 47 ++- libbeat/outputs/elasticsearch/client.go | 4 +- libbeat/outputs/elasticsearch/config.go | 3 + .../elasticsearch/docs/elasticsearch.asciidoc | 8 + .../outputs/elasticsearch/elasticsearch.go | 1 + libbeat/outputs/kafka/config.go | 2 +- metricbeat/metricbeat.reference.yml | 45 +++ packetbeat/packetbeat.reference.yml | 45 +++ .../jcmturner/goidentity.v3/.gitignore | 14 + .../gopkg.in/jcmturner/goidentity.v3/LICENSE | 201 +++++++++++ .../jcmturner/goidentity.v3/README.md | 13 + .../jcmturner/goidentity.v3/authenticator.go | 6 + .../jcmturner/goidentity.v3/identity.go | 32 ++ .../gopkg.in/jcmturner/goidentity.v3/user.go | 154 ++++++++ .../jcmturner/gokrb5.v7/service/APExchange.go | 62 ++++ .../gokrb5.v7/service/authenticator.go | 118 ++++++ .../jcmturner/gokrb5.v7/service/cache.go | 148 ++++++++ .../jcmturner/gokrb5.v7/service/settings.go | 136 +++++++ .../jcmturner/gokrb5.v7/spnego/http.go | 293 +++++++++++++++ .../jcmturner/gokrb5.v7/spnego/krb5Token.go | 236 ++++++++++++ .../gokrb5.v7/spnego/negotiationToken.go | 338 ++++++++++++++++++ .../jcmturner/gokrb5.v7/spnego/spnego.go | 199 +++++++++++ vendor/modules.txt | 4 + winlogbeat/winlogbeat.reference.yml | 45 +++ x-pack/auditbeat/auditbeat.reference.yml | 45 +++ x-pack/filebeat/filebeat.reference.yml | 45 +++ .../functionbeat/functionbeat.reference.yml | 42 +++ x-pack/metricbeat/metricbeat.reference.yml | 45 +++ x-pack/winlogbeat/winlogbeat.reference.yml | 45 +++ 42 files changed, 2779 insertions(+), 27 deletions(-) create mode 100644 libbeat/common/transport/kerberos/client.go create mode 100644 libbeat/docs/shared-kerberos-config.asciidoc create mode 100644 vendor/gopkg.in/jcmturner/goidentity.v3/.gitignore create mode 100644 vendor/gopkg.in/jcmturner/goidentity.v3/LICENSE create mode 100644 vendor/gopkg.in/jcmturner/goidentity.v3/README.md create mode 100644 vendor/gopkg.in/jcmturner/goidentity.v3/authenticator.go create mode 100644 vendor/gopkg.in/jcmturner/goidentity.v3/identity.go create mode 100644 vendor/gopkg.in/jcmturner/goidentity.v3/user.go create mode 100644 vendor/gopkg.in/jcmturner/gokrb5.v7/service/APExchange.go create mode 100644 vendor/gopkg.in/jcmturner/gokrb5.v7/service/authenticator.go create mode 100644 vendor/gopkg.in/jcmturner/gokrb5.v7/service/cache.go create mode 100644 vendor/gopkg.in/jcmturner/gokrb5.v7/service/settings.go create mode 100644 vendor/gopkg.in/jcmturner/gokrb5.v7/spnego/http.go create mode 100644 vendor/gopkg.in/jcmturner/gokrb5.v7/spnego/krb5Token.go create mode 100644 vendor/gopkg.in/jcmturner/gokrb5.v7/spnego/negotiationToken.go create mode 100644 vendor/gopkg.in/jcmturner/gokrb5.v7/spnego/spnego.go diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index b63bff11ce4..b9de5ff716a 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -204,6 +204,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Add optional regex based cid extractor to `add_kubernetes_metadata` processor. {pull}17360[17360] - Add `urldecode` processor to for decoding URL-encoded fields. {pull}17505[17505] - Add support for AWS IAM `role_arn` in credentials config. {pull}17658[17658] {issue}12464[12464] +- Add Kerberos support to Elasticsearch output. {pull}17927[17927] *Auditbeat* diff --git a/NOTICE.txt b/NOTICE.txt index 58a4e3299bd..ffad1013734 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -7957,6 +7957,15 @@ License type (autodetected): Apache-2.0 Apache License 2.0 +-------------------------------------------------------------------- +Dependency: gopkg.in/jcmturner/goidentity.v3 +Version: v3.0.0 +License type (autodetected): Apache-2.0 +./vendor/gopkg.in/jcmturner/goidentity.v3/LICENSE: +-------------------------------------------------------------------- +Apache License 2.0 + + -------------------------------------------------------------------- Dependency: gopkg.in/jcmturner/gokrb5.v7 Version: v7.3.0 diff --git a/auditbeat/auditbeat.reference.yml b/auditbeat/auditbeat.reference.yml index 29cdec5dc9a..3a76502c73e 100644 --- a/auditbeat/auditbeat.reference.yml +++ b/auditbeat/auditbeat.reference.yml @@ -526,6 +526,27 @@ output.elasticsearch: # The pin is a base64 encoded string of the SHA-256 fingerprint. #ssl.ca_sha256: "" + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + + # Authentication type to use with Kerberos. Available options: keytab, password. + #kerberos.auth_type: password + + # Path to the keytab file. It is used when auth_type is set to keytab. + #kerberos.keytab: /etc/elastic.keytab + + # Path to the Kerberos configuration. + #kerberos.config_path: /etc/krb5.conf + + # Name of the Kerberos user. + #kerberos.username: elastic + + # Password of the Kerberos user. It is used when auth_type is set to password. + #kerberos.password: changeme + + # Kerberos realm. + #kerberos.realm: ELASTIC + #----------------------------- Logstash output --------------------------------- #output.logstash: # Boolean flag to enable or disable the output module. @@ -794,6 +815,9 @@ output.elasticsearch: # never, once, and freely. Default is never. #ssl.renegotiation: never + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + # Authentication type to use with Kerberos. Available options: keytab, password. #kerberos.auth_type: password @@ -1380,6 +1404,27 @@ logging.files: # never, once, and freely. Default is never. #ssl.renegotiation: never + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + + # Authentication type to use with Kerberos. Available options: keytab, password. + #kerberos.auth_type: password + + # Path to the keytab file. It is used when auth_type is set to keytab. + #kerberos.keytab: /etc/elastic.keytab + + # Path to the Kerberos configuration. + #kerberos.config_path: /etc/krb5.conf + + # Name of the Kerberos user. + #kerberos.username: elastic + + # Password of the Kerberos user. It is used when auth_type is set to password. + #kerberos.password: changeme + + # Kerberos realm. + #kerberos.realm: ELASTIC + #metrics.period: 10s #state.period: 1m diff --git a/filebeat/filebeat.reference.yml b/filebeat/filebeat.reference.yml index af2831f8848..dfa8f631360 100644 --- a/filebeat/filebeat.reference.yml +++ b/filebeat/filebeat.reference.yml @@ -1232,6 +1232,27 @@ output.elasticsearch: # The pin is a base64 encoded string of the SHA-256 fingerprint. #ssl.ca_sha256: "" + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + + # Authentication type to use with Kerberos. Available options: keytab, password. + #kerberos.auth_type: password + + # Path to the keytab file. It is used when auth_type is set to keytab. + #kerberos.keytab: /etc/elastic.keytab + + # Path to the Kerberos configuration. + #kerberos.config_path: /etc/krb5.conf + + # Name of the Kerberos user. + #kerberos.username: elastic + + # Password of the Kerberos user. It is used when auth_type is set to password. + #kerberos.password: changeme + + # Kerberos realm. + #kerberos.realm: ELASTIC + #----------------------------- Logstash output --------------------------------- #output.logstash: # Boolean flag to enable or disable the output module. @@ -1500,6 +1521,9 @@ output.elasticsearch: # never, once, and freely. Default is never. #ssl.renegotiation: never + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + # Authentication type to use with Kerberos. Available options: keytab, password. #kerberos.auth_type: password @@ -2086,6 +2110,27 @@ logging.files: # never, once, and freely. Default is never. #ssl.renegotiation: never + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + + # Authentication type to use with Kerberos. Available options: keytab, password. + #kerberos.auth_type: password + + # Path to the keytab file. It is used when auth_type is set to keytab. + #kerberos.keytab: /etc/elastic.keytab + + # Path to the Kerberos configuration. + #kerberos.config_path: /etc/krb5.conf + + # Name of the Kerberos user. + #kerberos.username: elastic + + # Password of the Kerberos user. It is used when auth_type is set to password. + #kerberos.password: changeme + + # Kerberos realm. + #kerberos.realm: ELASTIC + #metrics.period: 10s #state.period: 1m diff --git a/filebeat/input/kafka/config.go b/filebeat/input/kafka/config.go index b132a843055..0e4888b90c3 100644 --- a/filebeat/input/kafka/config.go +++ b/filebeat/input/kafka/config.go @@ -180,7 +180,7 @@ func newSaramaConfig(config kafkaInputConfig) (*sarama.Config, error) { k.Net.TLS.Config = tls.BuildModuleConfig("") } - if config.Kerberos != nil { + if config.Kerberos.IsEnabled() { cfgwarn.Beta("Kerberos authentication for Kafka is beta.") k.Net.SASL.Enable = true diff --git a/go.mod b/go.mod index 8c593491feb..6dc10d0b15f 100644 --- a/go.mod +++ b/go.mod @@ -158,7 +158,7 @@ require ( google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb google.golang.org/grpc v1.27.1 gopkg.in/inf.v0 v0.9.0 - gopkg.in/jcmturner/gokrb5.v7 v7.3.0 // indirect + gopkg.in/jcmturner/gokrb5.v7 v7.3.0 gopkg.in/mgo.v2 v2.0.0-20160818020120-3f83fa500528 gopkg.in/yaml.v2 v2.2.8 howett.net/plist v0.0.0-20181124034731-591f970eefbb diff --git a/heartbeat/heartbeat.reference.yml b/heartbeat/heartbeat.reference.yml index 89baae61518..306569f0a2c 100644 --- a/heartbeat/heartbeat.reference.yml +++ b/heartbeat/heartbeat.reference.yml @@ -677,6 +677,27 @@ output.elasticsearch: # The pin is a base64 encoded string of the SHA-256 fingerprint. #ssl.ca_sha256: "" + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + + # Authentication type to use with Kerberos. Available options: keytab, password. + #kerberos.auth_type: password + + # Path to the keytab file. It is used when auth_type is set to keytab. + #kerberos.keytab: /etc/elastic.keytab + + # Path to the Kerberos configuration. + #kerberos.config_path: /etc/krb5.conf + + # Name of the Kerberos user. + #kerberos.username: elastic + + # Password of the Kerberos user. It is used when auth_type is set to password. + #kerberos.password: changeme + + # Kerberos realm. + #kerberos.realm: ELASTIC + #----------------------------- Logstash output --------------------------------- #output.logstash: # Boolean flag to enable or disable the output module. @@ -945,6 +966,9 @@ output.elasticsearch: # never, once, and freely. Default is never. #ssl.renegotiation: never + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + # Authentication type to use with Kerberos. Available options: keytab, password. #kerberos.auth_type: password @@ -1531,6 +1555,27 @@ logging.files: # never, once, and freely. Default is never. #ssl.renegotiation: never + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + + # Authentication type to use with Kerberos. Available options: keytab, password. + #kerberos.auth_type: password + + # Path to the keytab file. It is used when auth_type is set to keytab. + #kerberos.keytab: /etc/elastic.keytab + + # Path to the Kerberos configuration. + #kerberos.config_path: /etc/krb5.conf + + # Name of the Kerberos user. + #kerberos.username: elastic + + # Password of the Kerberos user. It is used when auth_type is set to password. + #kerberos.password: changeme + + # Kerberos realm. + #kerberos.realm: ELASTIC + #metrics.period: 10s #state.period: 1m diff --git a/journalbeat/journalbeat.reference.yml b/journalbeat/journalbeat.reference.yml index b5b8fd9c11d..0d995735fba 100644 --- a/journalbeat/journalbeat.reference.yml +++ b/journalbeat/journalbeat.reference.yml @@ -464,6 +464,27 @@ output.elasticsearch: # The pin is a base64 encoded string of the SHA-256 fingerprint. #ssl.ca_sha256: "" + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + + # Authentication type to use with Kerberos. Available options: keytab, password. + #kerberos.auth_type: password + + # Path to the keytab file. It is used when auth_type is set to keytab. + #kerberos.keytab: /etc/elastic.keytab + + # Path to the Kerberos configuration. + #kerberos.config_path: /etc/krb5.conf + + # Name of the Kerberos user. + #kerberos.username: elastic + + # Password of the Kerberos user. It is used when auth_type is set to password. + #kerberos.password: changeme + + # Kerberos realm. + #kerberos.realm: ELASTIC + #----------------------------- Logstash output --------------------------------- #output.logstash: # Boolean flag to enable or disable the output module. @@ -732,6 +753,9 @@ output.elasticsearch: # never, once, and freely. Default is never. #ssl.renegotiation: never + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + # Authentication type to use with Kerberos. Available options: keytab, password. #kerberos.auth_type: password @@ -1318,6 +1342,27 @@ logging.files: # never, once, and freely. Default is never. #ssl.renegotiation: never + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + + # Authentication type to use with Kerberos. Available options: keytab, password. + #kerberos.auth_type: password + + # Path to the keytab file. It is used when auth_type is set to keytab. + #kerberos.keytab: /etc/elastic.keytab + + # Path to the Kerberos configuration. + #kerberos.config_path: /etc/krb5.conf + + # Name of the Kerberos user. + #kerberos.username: elastic + + # Password of the Kerberos user. It is used when auth_type is set to password. + #kerberos.password: changeme + + # Kerberos realm. + #kerberos.realm: ELASTIC + #metrics.period: 10s #state.period: 1m diff --git a/libbeat/_meta/config.reference.yml.tmpl b/libbeat/_meta/config.reference.yml.tmpl index 9a9d2fdeb71..a72aa80ba42 100644 --- a/libbeat/_meta/config.reference.yml.tmpl +++ b/libbeat/_meta/config.reference.yml.tmpl @@ -406,6 +406,27 @@ output.elasticsearch: # # The pin is a base64 encoded string of the SHA-256 fingerprint. #ssl.ca_sha256: "" + + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + + # Authentication type to use with Kerberos. Available options: keytab, password. + #kerberos.auth_type: password + + # Path to the keytab file. It is used when auth_type is set to keytab. + #kerberos.keytab: /etc/elastic.keytab + + # Path to the Kerberos configuration. + #kerberos.config_path: /etc/krb5.conf + + # Name of the Kerberos user. + #kerberos.username: elastic + + # Password of the Kerberos user. It is used when auth_type is set to password. + #kerberos.password: changeme + + # Kerberos realm. + #kerberos.realm: ELASTIC {{if not .ExcludeLogstash}} #----------------------------- Logstash output --------------------------------- #output.logstash: @@ -675,6 +696,9 @@ output.elasticsearch: # never, once, and freely. Default is never. #ssl.renegotiation: never + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + # Authentication type to use with Kerberos. Available options: keytab, password. #kerberos.auth_type: password @@ -1261,6 +1285,27 @@ logging.files: # never, once, and freely. Default is never. #ssl.renegotiation: never + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + + # Authentication type to use with Kerberos. Available options: keytab, password. + #kerberos.auth_type: password + + # Path to the keytab file. It is used when auth_type is set to keytab. + #kerberos.keytab: /etc/elastic.keytab + + # Path to the Kerberos configuration. + #kerberos.config_path: /etc/krb5.conf + + # Name of the Kerberos user. + #kerberos.username: elastic + + # Password of the Kerberos user. It is used when auth_type is set to password. + #kerberos.password: changeme + + # Kerberos realm. + #kerberos.realm: ELASTIC + #metrics.period: 10s #state.period: 1m diff --git a/libbeat/common/transport/kerberos/client.go b/libbeat/common/transport/kerberos/client.go new file mode 100644 index 00000000000..1cbfb0a4338 --- /dev/null +++ b/libbeat/common/transport/kerberos/client.go @@ -0,0 +1,65 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package kerberos + +import ( + "fmt" + "net/http" + + krbclient "gopkg.in/jcmturner/gokrb5.v7/client" + krbconfig "gopkg.in/jcmturner/gokrb5.v7/config" + "gopkg.in/jcmturner/gokrb5.v7/keytab" + "gopkg.in/jcmturner/gokrb5.v7/spnego" +) + +type Client struct { + spClient *spnego.Client +} + +func NewClient(config *Config, httpClient *http.Client, esurl string) (*Client, error) { + var krbClient *krbclient.Client + krbConf, err := krbconfig.Load(config.ConfigPath) + if err != nil { + return nil, fmt.Errorf("error creating Kerberos client: %+v", err) + } + + switch config.AuthType { + case authKeytab: + kTab, err := keytab.Load(config.KeyTabPath) + if err != nil { + return nil, fmt.Errorf("cannot load keytab file %s: %+v", config.KeyTabPath, err) + } + krbClient = krbclient.NewClientWithKeytab(config.Username, config.Realm, kTab, krbConf) + case authPassword: + krbClient = krbclient.NewClientWithPassword(config.Username, config.Realm, config.Password, krbConf) + default: + return nil, InvalidAuthType + } + + return &Client{ + spClient: spnego.NewClient(krbClient, httpClient, ""), + }, nil +} + +func (c *Client) Do(req *http.Request) (*http.Response, error) { + return c.spClient.Do(req) +} + +func (c *Client) CloseIdleConnections() { + c.spClient.CloseIdleConnections() +} diff --git a/libbeat/common/transport/kerberos/config.go b/libbeat/common/transport/kerberos/config.go index fe2450fa639..42b779485fe 100644 --- a/libbeat/common/transport/kerberos/config.go +++ b/libbeat/common/transport/kerberos/config.go @@ -17,33 +17,44 @@ package kerberos -import "fmt" +import ( + "errors" + "fmt" +) type AuthType uint const ( - AUTH_PASSWORD = 1 - AUTH_KEYTAB = 2 + authPassword = 1 + authKeytab = 2 - authPassword = "password" - authKeytabStr = "keytab" + authPasswordStr = "password" + authKeytabStr = "keytab" ) var ( + InvalidAuthType = errors.New("invalid authentication type") + authTypes = map[string]AuthType{ - authPassword: AUTH_PASSWORD, - authKeytabStr: AUTH_KEYTAB, + authPasswordStr: authPassword, + authKeytabStr: authKeytab, } ) type Config struct { + Enabled *bool `config:"enabled" yaml:"enabled,omitempty"` AuthType AuthType `config:"auth_type" validate:"required"` KeyTabPath string `config:"keytab"` - ConfigPath string `config:"config_path"` + ConfigPath string `config:"config_path" validate:"required"` ServiceName string `config:"service_name"` Username string `config:"username"` Password string `config:"password"` - Realm string `config:"realm"` + Realm string `config:"realm" validate:"required"` +} + +// IsEnabled returns true if the `enable` field is set to true in the yaml. +func (c *Config) IsEnabled() bool { + return c != nil && (c.Enabled == nil || *c.Enabled) } // Unpack validates and unpack "auth_type" config option @@ -59,19 +70,21 @@ func (t *AuthType) Unpack(value string) error { } func (c *Config) Validate() error { - if c.AuthType == AUTH_PASSWORD { + switch c.AuthType { + case authPassword: if c.Username == "" { return fmt.Errorf("password authentication is selected for Kerberos, but username is not configured") } if c.Password == "" { return fmt.Errorf("password authentication is selected for Kerberos, but password is not configured") } - } - if c.AuthType == AUTH_KEYTAB { + case authKeytab: if c.KeyTabPath == "" { return fmt.Errorf("keytab authentication is selected for Kerberos, but path to keytab is not configured") } + default: + return InvalidAuthType } return nil diff --git a/libbeat/docs/shared-kerberos-config.asciidoc b/libbeat/docs/shared-kerberos-config.asciidoc new file mode 100644 index 00000000000..7accd6f7df9 --- /dev/null +++ b/libbeat/docs/shared-kerberos-config.asciidoc @@ -0,0 +1,85 @@ +[[configuration-kerberos]] +== Configure Kerberos + +You can specify Kerberos options with any output or input that supports Kerberos, like {es} and Kafka. + +The following encryption types are supported: + +* aes128-cts-hmac-sha1-96 +* aes128-cts-hmac-sha256-128 +* aes256-cts-hmac-sha1-96 +* aes256-cts-hmac-sha384-192 +* des3-cbc-sha1-kd +* rc4-hmac + +Example output config with Kerberos password based authentication: + +[source,yaml] +---- +output.elasticsearch.hosts: ["http://my-elasticsearch.elastic.co:9200"] +output.elasticsearch.kerberos.auth_type: password +output.elasticsearch.kerberos.username: "elastic" +output.elasticsearch.kerberos.password: "changeme" +output.elasticsearch.kerberos.config_path: "/etc/krb5.conf" +output.elasticsearch.kerberos.realm: "ELASTIC.CO" +---- + +The service principal name for the Elasticsearch instance is contructed from these options. Based on this configuration +it is going to be `HTTP/my-elasticsearch.elastic.co@ELASTIC.CO`. + +[float] +=== Configuration options + +You can specify the following options in the `kerberos` section of the +{beatname_lc}.yml+ config file: + +[float] +==== `enabled` + +The `enabled` setting can be used to enable the kerberos configuration by setting +it to `false`. The default value is `true`. + +NOTE: Kerberos settings are disabled if either `enabled` is set to `false` or the +`kerberos` section is missing. + +[float] +==== `auth_type` + +There are two options to authenticate with Kerberos KDC: `password` and `keytab`. + +`password` expects the principal name and its password. When choosing `keytab`, you +have to specify a princial name and a path to a keytab. The keytab must contain +the keys of the selected principal. Otherwise, authentication will fail. + +[float] +==== `config_path` + +You need to set the path to the `krb5.conf`, so +{beatname_lc} can find the Kerberos KDC to +retrieve a ticket. + +[float] +==== `username` + +Name of the principal used to connect to the output. + +[float] +==== `password` + +If you configured `password` for `auth_type`, you have to provide a password +for the selected principal. + +[float] +==== `keytab` + +If you configured `keytab` for `auth_type`, you have to provide the path to the +keytab of the selected principal. + +[float] +==== `service_name` + +This option can only be configured for Kafka. It is the name of the Kafka service, usually `kafka`. + +[float] +==== `realm` + +Name of the realm where the output resides. + diff --git a/libbeat/esleg/eslegclient/config.go b/libbeat/esleg/eslegclient/config.go index 5c171a4eb2b..d9a299d68c7 100644 --- a/libbeat/esleg/eslegclient/config.go +++ b/libbeat/esleg/eslegclient/config.go @@ -22,6 +22,7 @@ import ( "time" "github.com/elastic/beats/v7/libbeat/common" + "github.com/elastic/beats/v7/libbeat/common/transport/kerberos" "github.com/elastic/beats/v7/libbeat/common/transport/tlscommon" ) @@ -32,7 +33,8 @@ type config struct { Params map[string]string `config:"parameters"` Headers map[string]string `config:"headers"` - TLS *tlscommon.Config `config:"ssl"` + TLS *tlscommon.Config `config:"ssl"` + Kerberos *kerberos.Config `config:"kerberos"` ProxyURL string `config:"proxy_url"` ProxyDisable bool `config:"proxy_disable"` diff --git a/libbeat/esleg/eslegclient/connection.go b/libbeat/esleg/eslegclient/connection.go index e1c20f795bd..7001d2e453d 100644 --- a/libbeat/esleg/eslegclient/connection.go +++ b/libbeat/esleg/eslegclient/connection.go @@ -28,17 +28,23 @@ import ( "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/libbeat/common/transport" + "github.com/elastic/beats/v7/libbeat/common/transport/kerberos" "github.com/elastic/beats/v7/libbeat/common/transport/tlscommon" "github.com/elastic/beats/v7/libbeat/logp" "github.com/elastic/beats/v7/libbeat/testing" ) +type esHTTPClient interface { + Do(req *http.Request) (resp *http.Response, err error) + CloseIdleConnections() +} + // Connection manages the connection for a given client. type Connection struct { ConnectionSettings Encoder BodyEncoder - HTTP *http.Client + HTTP esHTTPClient version common.Version log *logp.Logger @@ -55,7 +61,8 @@ type ConnectionSettings struct { APIKey string Headers map[string]string - TLS *tlscommon.TLSConfig + TLS *tlscommon.TLSConfig + Kerberos *kerberos.Config OnConnectCallback func() error Observer transport.IOStatser @@ -120,20 +127,39 @@ func NewConnection(s ConnectionSettings) (*Connection, error) { } } - return &Connection{ - ConnectionSettings: s, - HTTP: &http.Client{ + var httpClient esHTTPClient + httpClient = &http.Client{ + Transport: &http.Transport{ + Dial: dialer.Dial, + DialTLS: tlsDialer.Dial, + TLSClientConfig: s.TLS.ToConfig(), + Proxy: proxy, + IdleConnTimeout: s.IdleConnTimeout, + }, + Timeout: s.Timeout, + } + + if s.Kerberos.IsEnabled() { + c := &http.Client{ Transport: &http.Transport{ Dial: dialer.Dial, - DialTLS: tlsDialer.Dial, - TLSClientConfig: s.TLS.ToConfig(), Proxy: proxy, IdleConnTimeout: s.IdleConnTimeout, }, Timeout: s.Timeout, - }, - Encoder: encoder, - log: logp.NewLogger("esclientleg"), + } + httpClient, err = kerberos.NewClient(s.Kerberos, c, s.URL) + if err != nil { + return nil, err + } + logp.Info("kerberos client created") + } + + return &Connection{ + ConnectionSettings: s, + HTTP: httpClient, + Encoder: encoder, + log: logp.NewLogger("esclientleg"), }, nil } @@ -190,6 +216,7 @@ func NewClients(cfg *common.Config) ([]Connection, error) { Proxy: proxyURL, ProxyDisable: config.ProxyDisable, TLS: tlsConfig, + Kerberos: config.Kerberos, Username: config.Username, Password: config.Password, APIKey: config.APIKey, diff --git a/libbeat/outputs/elasticsearch/client.go b/libbeat/outputs/elasticsearch/client.go index 2969c0f057b..bee2769cb9e 100644 --- a/libbeat/outputs/elasticsearch/client.go +++ b/libbeat/outputs/elasticsearch/client.go @@ -84,6 +84,7 @@ func NewClient( APIKey: base64.StdEncoding.EncodeToString([]byte(s.APIKey)), Headers: s.Headers, TLS: s.TLS, + Kerberos: s.Kerberos, Proxy: s.Proxy, ProxyDisable: s.ProxyDisable, Parameters: s.Parameters, @@ -150,12 +151,13 @@ func (client *Client) Clone() *Client { // empty. ProxyDisable: client.conn.Proxy == nil, TLS: client.conn.TLS, + Kerberos: client.conn.Kerberos, Username: client.conn.Username, Password: client.conn.Password, APIKey: client.conn.APIKey, Parameters: nil, // XXX: do not pass params? Headers: client.conn.Headers, - Timeout: client.conn.HTTP.Timeout, + Timeout: client.conn.Timeout, CompressionLevel: client.conn.CompressionLevel, OnConnectCallback: nil, Observer: nil, diff --git a/libbeat/outputs/elasticsearch/config.go b/libbeat/outputs/elasticsearch/config.go index 499bba2eeff..d094f005df5 100644 --- a/libbeat/outputs/elasticsearch/config.go +++ b/libbeat/outputs/elasticsearch/config.go @@ -22,6 +22,7 @@ import ( "time" "github.com/elastic/beats/v7/libbeat/common" + "github.com/elastic/beats/v7/libbeat/common/transport/kerberos" "github.com/elastic/beats/v7/libbeat/common/transport/tlscommon" ) @@ -39,6 +40,7 @@ type elasticsearchConfig struct { CompressionLevel int `config:"compression_level" validate:"min=0, max=9"` EscapeHTML bool `config:"escape_html"` TLS *tlscommon.Config `config:"ssl"` + Kerberos *kerberos.Config `config:"kerberos"` BulkMaxSize int `config:"bulk_max_size"` MaxRetries int `config:"max_retries"` Timeout time.Duration `config:"timeout"` @@ -69,6 +71,7 @@ var ( CompressionLevel: 0, EscapeHTML: false, TLS: nil, + Kerberos: nil, LoadBalance: true, Backoff: Backoff{ Init: 1 * time.Second, diff --git a/libbeat/outputs/elasticsearch/docs/elasticsearch.asciidoc b/libbeat/outputs/elasticsearch/docs/elasticsearch.asciidoc index c36e6b24163..254349f39bc 100644 --- a/libbeat/outputs/elasticsearch/docs/elasticsearch.asciidoc +++ b/libbeat/outputs/elasticsearch/docs/elasticsearch.asciidoc @@ -676,3 +676,11 @@ for HTTPS-based connections. If the `ssl` section is missing, the host CAs are u Elasticsearch. See <> for more information. + +===== `kebreros` + +Configuration options for Kerberos authentication. + +See <> for more information. + +include::{libbeat-dir}/shared-kerberos-config.asciidoc[] diff --git a/libbeat/outputs/elasticsearch/elasticsearch.go b/libbeat/outputs/elasticsearch/elasticsearch.go index b6c3bd797a9..512b74895ea 100644 --- a/libbeat/outputs/elasticsearch/elasticsearch.go +++ b/libbeat/outputs/elasticsearch/elasticsearch.go @@ -97,6 +97,7 @@ func makeES( Proxy: proxyURL, ProxyDisable: config.ProxyDisable, TLS: tlsConfig, + Kerberos: config.Kerberos, Username: config.Username, Password: config.Password, APIKey: config.APIKey, diff --git a/libbeat/outputs/kafka/config.go b/libbeat/outputs/kafka/config.go index d2b645f075d..3174646da4a 100644 --- a/libbeat/outputs/kafka/config.go +++ b/libbeat/outputs/kafka/config.go @@ -216,7 +216,7 @@ func newSaramaConfig(log *logp.Logger, config *kafkaConfig) (*sarama.Config, err k.Net.TLS.Config = tls.BuildModuleConfig("") } - if config.Kerberos != nil { + if config.Kerberos.IsEnabled() { cfgwarn.Beta("Kerberos authentication for Kafka is beta.") k.Net.SASL.Enable = true diff --git a/metricbeat/metricbeat.reference.yml b/metricbeat/metricbeat.reference.yml index bdbb3ae87ba..9dcfe20071b 100644 --- a/metricbeat/metricbeat.reference.yml +++ b/metricbeat/metricbeat.reference.yml @@ -1279,6 +1279,27 @@ output.elasticsearch: # The pin is a base64 encoded string of the SHA-256 fingerprint. #ssl.ca_sha256: "" + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + + # Authentication type to use with Kerberos. Available options: keytab, password. + #kerberos.auth_type: password + + # Path to the keytab file. It is used when auth_type is set to keytab. + #kerberos.keytab: /etc/elastic.keytab + + # Path to the Kerberos configuration. + #kerberos.config_path: /etc/krb5.conf + + # Name of the Kerberos user. + #kerberos.username: elastic + + # Password of the Kerberos user. It is used when auth_type is set to password. + #kerberos.password: changeme + + # Kerberos realm. + #kerberos.realm: ELASTIC + #----------------------------- Logstash output --------------------------------- #output.logstash: # Boolean flag to enable or disable the output module. @@ -1547,6 +1568,9 @@ output.elasticsearch: # never, once, and freely. Default is never. #ssl.renegotiation: never + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + # Authentication type to use with Kerberos. Available options: keytab, password. #kerberos.auth_type: password @@ -2133,6 +2157,27 @@ logging.files: # never, once, and freely. Default is never. #ssl.renegotiation: never + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + + # Authentication type to use with Kerberos. Available options: keytab, password. + #kerberos.auth_type: password + + # Path to the keytab file. It is used when auth_type is set to keytab. + #kerberos.keytab: /etc/elastic.keytab + + # Path to the Kerberos configuration. + #kerberos.config_path: /etc/krb5.conf + + # Name of the Kerberos user. + #kerberos.username: elastic + + # Password of the Kerberos user. It is used when auth_type is set to password. + #kerberos.password: changeme + + # Kerberos realm. + #kerberos.realm: ELASTIC + #metrics.period: 10s #state.period: 1m diff --git a/packetbeat/packetbeat.reference.yml b/packetbeat/packetbeat.reference.yml index 993ddb4b06c..30621ffbde4 100644 --- a/packetbeat/packetbeat.reference.yml +++ b/packetbeat/packetbeat.reference.yml @@ -953,6 +953,27 @@ output.elasticsearch: # The pin is a base64 encoded string of the SHA-256 fingerprint. #ssl.ca_sha256: "" + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + + # Authentication type to use with Kerberos. Available options: keytab, password. + #kerberos.auth_type: password + + # Path to the keytab file. It is used when auth_type is set to keytab. + #kerberos.keytab: /etc/elastic.keytab + + # Path to the Kerberos configuration. + #kerberos.config_path: /etc/krb5.conf + + # Name of the Kerberos user. + #kerberos.username: elastic + + # Password of the Kerberos user. It is used when auth_type is set to password. + #kerberos.password: changeme + + # Kerberos realm. + #kerberos.realm: ELASTIC + #----------------------------- Logstash output --------------------------------- #output.logstash: # Boolean flag to enable or disable the output module. @@ -1221,6 +1242,9 @@ output.elasticsearch: # never, once, and freely. Default is never. #ssl.renegotiation: never + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + # Authentication type to use with Kerberos. Available options: keytab, password. #kerberos.auth_type: password @@ -1807,6 +1831,27 @@ logging.files: # never, once, and freely. Default is never. #ssl.renegotiation: never + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + + # Authentication type to use with Kerberos. Available options: keytab, password. + #kerberos.auth_type: password + + # Path to the keytab file. It is used when auth_type is set to keytab. + #kerberos.keytab: /etc/elastic.keytab + + # Path to the Kerberos configuration. + #kerberos.config_path: /etc/krb5.conf + + # Name of the Kerberos user. + #kerberos.username: elastic + + # Password of the Kerberos user. It is used when auth_type is set to password. + #kerberos.password: changeme + + # Kerberos realm. + #kerberos.realm: ELASTIC + #metrics.period: 10s #state.period: 1m diff --git a/vendor/gopkg.in/jcmturner/goidentity.v3/.gitignore b/vendor/gopkg.in/jcmturner/goidentity.v3/.gitignore new file mode 100644 index 00000000000..a1338d68517 --- /dev/null +++ b/vendor/gopkg.in/jcmturner/goidentity.v3/.gitignore @@ -0,0 +1,14 @@ +# Binaries for programs and plugins +*.exe +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 +.glide/ diff --git a/vendor/gopkg.in/jcmturner/goidentity.v3/LICENSE b/vendor/gopkg.in/jcmturner/goidentity.v3/LICENSE new file mode 100644 index 00000000000..8dada3edaf5 --- /dev/null +++ b/vendor/gopkg.in/jcmturner/goidentity.v3/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/gopkg.in/jcmturner/goidentity.v3/README.md b/vendor/gopkg.in/jcmturner/goidentity.v3/README.md new file mode 100644 index 00000000000..89b33ebb70b --- /dev/null +++ b/vendor/gopkg.in/jcmturner/goidentity.v3/README.md @@ -0,0 +1,13 @@ +# goidentity + +Standard interface to holding authenticated identities and their attributes. + +To get the package, execute: +``` +go get gopkg.in/jcmturner/goidentity.v3 +``` +To import this package, add the following line to your code: +```go +import "gopkg.in/jcmturner/goidentity.v3" + +``` \ No newline at end of file diff --git a/vendor/gopkg.in/jcmturner/goidentity.v3/authenticator.go b/vendor/gopkg.in/jcmturner/goidentity.v3/authenticator.go new file mode 100644 index 00000000000..42ec79b0617 --- /dev/null +++ b/vendor/gopkg.in/jcmturner/goidentity.v3/authenticator.go @@ -0,0 +1,6 @@ +package goidentity + +type Authenticator interface { + Authenticate() (Identity, bool, error) + Mechanism() string // gives the name of the type of authentication mechanism +} diff --git a/vendor/gopkg.in/jcmturner/goidentity.v3/identity.go b/vendor/gopkg.in/jcmturner/goidentity.v3/identity.go new file mode 100644 index 00000000000..d36c23fe050 --- /dev/null +++ b/vendor/gopkg.in/jcmturner/goidentity.v3/identity.go @@ -0,0 +1,32 @@ +package goidentity + +import "time" + +const ( + CTXKey = "jcmturner/goidentity" +) + +type Identity interface { + UserName() string + SetUserName(s string) + Domain() string + SetDomain(s string) + DisplayName() string + SetDisplayName(s string) + Human() bool + SetHuman(b bool) + AuthTime() time.Time + SetAuthTime(t time.Time) + AuthzAttributes() []string + AddAuthzAttribute(a string) + RemoveAuthzAttribute(a string) + Authenticated() bool + SetAuthenticated(b bool) + Authorized(a string) bool + SessionID() string + Expired() bool + Attributes() map[string]interface{} + SetAttribute(k string, v interface{}) + SetAttributes(map[string]interface{}) + RemoveAttribute(k string) +} diff --git a/vendor/gopkg.in/jcmturner/goidentity.v3/user.go b/vendor/gopkg.in/jcmturner/goidentity.v3/user.go new file mode 100644 index 00000000000..d79f140c9f4 --- /dev/null +++ b/vendor/gopkg.in/jcmturner/goidentity.v3/user.go @@ -0,0 +1,154 @@ +package goidentity + +import ( + "github.com/hashicorp/go-uuid" + "time" +) + +type User struct { + authenticated bool + domain string + userName string + displayName string + email string + human bool + groupMembership map[string]bool + authTime time.Time + sessionID string + expiry time.Time + attributes map[string]interface{} +} + +func NewUser(username string) User { + uuid, err := uuid.GenerateUUID() + if err != nil { + uuid = "00unique-sess-ions-uuid-unavailable0" + } + return User{ + userName: username, + groupMembership: make(map[string]bool), + sessionID: uuid, + } +} + +func (u *User) UserName() string { + return u.userName +} + +func (u *User) SetUserName(s string) { + u.userName = s +} + +func (u *User) Domain() string { + return u.domain +} + +func (u *User) SetDomain(s string) { + u.domain = s +} + +func (u *User) DisplayName() string { + if u.displayName == "" { + return u.userName + } + return u.displayName +} + +func (u *User) SetDisplayName(s string) { + u.displayName = s +} + +func (u *User) Human() bool { + return u.human +} + +func (u *User) SetHuman(b bool) { + u.human = b +} + +func (u *User) AuthTime() time.Time { + return u.authTime +} + +func (u *User) SetAuthTime(t time.Time) { + u.authTime = t +} + +func (u *User) AuthzAttributes() []string { + s := make([]string, len(u.groupMembership)) + i := 0 + for a := range u.groupMembership { + s[i] = a + i++ + } + return s +} + +func (u *User) Authenticated() bool { + return u.authenticated +} + +func (u *User) SetAuthenticated(b bool) { + u.authenticated = b +} + +func (u *User) AddAuthzAttribute(a string) { + u.groupMembership[a] = true +} + +func (u *User) RemoveAuthzAttribute(a string) { + if _, ok := u.groupMembership[a]; !ok { + return + } + delete(u.groupMembership, a) +} + +func (u *User) EnableAuthzAttribute(a string) { + if enabled, ok := u.groupMembership[a]; ok && !enabled { + u.groupMembership[a] = true + } +} + +func (u *User) DisableAuthzAttribute(a string) { + if enabled, ok := u.groupMembership[a]; ok && enabled { + u.groupMembership[a] = false + } +} + +func (u *User) Authorized(a string) bool { + if enabled, ok := u.groupMembership[a]; ok && enabled { + return true + } + return false +} + +func (u *User) SessionID() string { + return u.sessionID +} + +func (u *User) SetExpiry(t time.Time) { + u.expiry = t +} + +func (u *User) Expired() bool { + if !u.expiry.IsZero() && time.Now().UTC().After(u.expiry) { + return true + } + return false +} + +func (u *User) Attributes() map[string]interface{} { + return u.attributes +} + +func (u *User) SetAttribute(k string, v interface{}) { + u.attributes[k] = v +} + +func (u *User) SetAttributes(a map[string]interface{}) { + u.attributes = a +} + +func (u *User) RemoveAttribute(k string) { + delete(u.attributes, k) +} diff --git a/vendor/gopkg.in/jcmturner/gokrb5.v7/service/APExchange.go b/vendor/gopkg.in/jcmturner/gokrb5.v7/service/APExchange.go new file mode 100644 index 00000000000..4126cfa1582 --- /dev/null +++ b/vendor/gopkg.in/jcmturner/gokrb5.v7/service/APExchange.go @@ -0,0 +1,62 @@ +package service + +import ( + "time" + + "gopkg.in/jcmturner/gokrb5.v7/credentials" + "gopkg.in/jcmturner/gokrb5.v7/iana/errorcode" + "gopkg.in/jcmturner/gokrb5.v7/messages" +) + +// VerifyAPREQ verifies an AP_REQ sent to the service. Returns a boolean for if the AP_REQ is valid and the client's principal name and realm. +func VerifyAPREQ(APReq messages.APReq, s *Settings) (bool, *credentials.Credentials, error) { + var creds *credentials.Credentials + + ok, err := APReq.Verify(s.Keytab, s.MaxClockSkew(), s.ClientAddress()) + if err != nil || !ok { + return false, creds, err + } + + if s.RequireHostAddr() && len(APReq.Ticket.DecryptedEncPart.CAddr) < 1 { + return false, creds, + messages.NewKRBError(APReq.Ticket.SName, APReq.Ticket.Realm, errorcode.KRB_AP_ERR_BADADDR, "ticket does not contain HostAddress values required") + } + + // Check for replay + rc := GetReplayCache(s.MaxClockSkew()) + if rc.IsReplay(APReq.Ticket.SName, APReq.Authenticator) { + return false, creds, + messages.NewKRBError(APReq.Ticket.SName, APReq.Ticket.Realm, errorcode.KRB_AP_ERR_REPEAT, "replay detected") + } + + c := credentials.NewFromPrincipalName(APReq.Authenticator.CName, APReq.Authenticator.CRealm) + creds = c + creds.SetAuthTime(time.Now().UTC()) + creds.SetAuthenticated(true) + creds.SetValidUntil(APReq.Ticket.DecryptedEncPart.EndTime) + + //PAC decoding + if !s.disablePACDecoding { + isPAC, pac, err := APReq.Ticket.GetPACType(s.Keytab, s.KeytabPrincipal(), s.Logger()) + if isPAC && err != nil { + return false, creds, err + } + if isPAC { + // There is a valid PAC. Adding attributes to creds + creds.SetADCredentials(credentials.ADCredentials{ + GroupMembershipSIDs: pac.KerbValidationInfo.GetGroupMembershipSIDs(), + LogOnTime: pac.KerbValidationInfo.LogOnTime.Time(), + LogOffTime: pac.KerbValidationInfo.LogOffTime.Time(), + PasswordLastSet: pac.KerbValidationInfo.PasswordLastSet.Time(), + EffectiveName: pac.KerbValidationInfo.EffectiveName.Value, + FullName: pac.KerbValidationInfo.FullName.Value, + UserID: int(pac.KerbValidationInfo.UserID), + PrimaryGroupID: int(pac.KerbValidationInfo.PrimaryGroupID), + LogonServer: pac.KerbValidationInfo.LogonServer.Value, + LogonDomainName: pac.KerbValidationInfo.LogonDomainName.Value, + LogonDomainID: pac.KerbValidationInfo.LogonDomainID.String(), + }) + } + } + return true, creds, nil +} diff --git a/vendor/gopkg.in/jcmturner/gokrb5.v7/service/authenticator.go b/vendor/gopkg.in/jcmturner/gokrb5.v7/service/authenticator.go new file mode 100644 index 00000000000..d60d259ad36 --- /dev/null +++ b/vendor/gopkg.in/jcmturner/gokrb5.v7/service/authenticator.go @@ -0,0 +1,118 @@ +package service + +import ( + "encoding/base64" + "fmt" + "strings" + "time" + + goidentity "gopkg.in/jcmturner/goidentity.v3" + "gopkg.in/jcmturner/gokrb5.v7/client" + "gopkg.in/jcmturner/gokrb5.v7/config" + "gopkg.in/jcmturner/gokrb5.v7/credentials" +) + +// NewKRB5BasicAuthenticator creates a new NewKRB5BasicAuthenticator +func NewKRB5BasicAuthenticator(headerVal string, krb5conf *config.Config, serviceSettings *Settings, clientSettings *client.Settings) KRB5BasicAuthenticator { + return KRB5BasicAuthenticator{ + BasicHeaderValue: headerVal, + clientConfig: krb5conf, + serviceSettings: serviceSettings, + clientSettings: clientSettings, + } +} + +// KRB5BasicAuthenticator implements gopkg.in/jcmturner/goidentity.v3.Authenticator interface. +// It takes username and password so can be used for basic authentication. +type KRB5BasicAuthenticator struct { + BasicHeaderValue string + serviceSettings *Settings + clientSettings *client.Settings + clientConfig *config.Config + realm string + username string + password string +} + +// Authenticate and return the identity. The boolean indicates if the authentication was successful. +func (a KRB5BasicAuthenticator) Authenticate() (i goidentity.Identity, ok bool, err error) { + a.realm, a.username, a.password, err = parseBasicHeaderValue(a.BasicHeaderValue) + if err != nil { + err = fmt.Errorf("could not parse basic authentication header: %v", err) + return + } + cl := client.NewClientWithPassword(a.username, a.realm, a.password, a.clientConfig) + err = cl.Login() + if err != nil { + // Username and/or password could be wrong + err = fmt.Errorf("error with user credentials during login: %v", err) + return + } + tkt, _, err := cl.GetServiceTicket(a.serviceSettings.SName()) + if err != nil { + err = fmt.Errorf("could not get service ticket: %v", err) + return + } + err = tkt.DecryptEncPart(a.serviceSettings.Keytab, a.serviceSettings.KeytabPrincipal()) + if err != nil { + err = fmt.Errorf("could not decrypt service ticket: %v", err) + return + } + cl.Credentials.SetAuthTime(time.Now().UTC()) + cl.Credentials.SetAuthenticated(true) + isPAC, pac, err := tkt.GetPACType(a.serviceSettings.Keytab, a.serviceSettings.KeytabPrincipal(), a.serviceSettings.Logger()) + if isPAC && err != nil { + err = fmt.Errorf("error processing PAC: %v", err) + return + } + if isPAC { + // There is a valid PAC. Adding attributes to creds + cl.Credentials.SetADCredentials(credentials.ADCredentials{ + GroupMembershipSIDs: pac.KerbValidationInfo.GetGroupMembershipSIDs(), + LogOnTime: pac.KerbValidationInfo.LogOnTime.Time(), + LogOffTime: pac.KerbValidationInfo.LogOffTime.Time(), + PasswordLastSet: pac.KerbValidationInfo.PasswordLastSet.Time(), + EffectiveName: pac.KerbValidationInfo.EffectiveName.Value, + FullName: pac.KerbValidationInfo.FullName.Value, + UserID: int(pac.KerbValidationInfo.UserID), + PrimaryGroupID: int(pac.KerbValidationInfo.PrimaryGroupID), + LogonServer: pac.KerbValidationInfo.LogonServer.Value, + LogonDomainName: pac.KerbValidationInfo.LogonDomainName.Value, + LogonDomainID: pac.KerbValidationInfo.LogonDomainID.String(), + }) + } + ok = true + i = cl.Credentials + return +} + +// Mechanism returns the authentication mechanism. +func (a KRB5BasicAuthenticator) Mechanism() string { + return "Kerberos Basic" +} + +func parseBasicHeaderValue(s string) (domain, username, password string, err error) { + b, err := base64.StdEncoding.DecodeString(s) + if err != nil { + return + } + v := string(b) + vc := strings.SplitN(v, ":", 2) + password = vc[1] + // Domain and username can be specified in 2 formats: + // - no domain specified + // \ + // @ + if strings.Contains(vc[0], `\`) { + u := strings.SplitN(vc[0], `\`, 2) + domain = u[0] + username = u[1] + } else if strings.Contains(vc[0], `@`) { + u := strings.SplitN(vc[0], `@`, 2) + domain = u[1] + username = u[0] + } else { + username = vc[0] + } + return +} diff --git a/vendor/gopkg.in/jcmturner/gokrb5.v7/service/cache.go b/vendor/gopkg.in/jcmturner/gokrb5.v7/service/cache.go new file mode 100644 index 00000000000..c844749362d --- /dev/null +++ b/vendor/gopkg.in/jcmturner/gokrb5.v7/service/cache.go @@ -0,0 +1,148 @@ +// Package service provides server side integrations for Kerberos authentication. +package service + +import ( + "gopkg.in/jcmturner/gokrb5.v7/types" + "sync" + "time" +) + +/*The server MUST utilize a replay cache to remember any authenticator +presented within the allowable clock skew. +The replay cache will store at least the server name, along with the +client name, time, and microsecond fields from the recently-seen +authenticators, and if a matching tuple is found, the +KRB_AP_ERR_REPEAT error is returned. Note that the rejection here is +restricted to authenticators from the same principal to the same +server. Other client principals communicating with the same server +principal should not have their authenticators rejected if the time +and microsecond fields happen to match some other client's +authenticator. + +If a server loses track of authenticators presented within the +allowable clock skew, it MUST reject all requests until the clock +skew interval has passed, providing assurance that any lost or +replayed authenticators will fall outside the allowable clock skew +and can no longer be successfully replayed. If this were not done, +an attacker could subvert the authentication by recording the ticket +and authenticator sent over the network to a server and replaying +them following an event that caused the server to lose track of +recently seen authenticators.*/ + +// Cache for tickets received from clients keyed by fully qualified client name. Used to track replay of tickets. +type Cache struct { + entries map[string]clientEntries + mux sync.RWMutex +} + +// clientEntries holds entries of client details sent to the service. +type clientEntries struct { + replayMap map[time.Time]replayCacheEntry + seqNumber int64 + subKey types.EncryptionKey +} + +// Cache entry tracking client time values of tickets sent to the service. +type replayCacheEntry struct { + presentedTime time.Time + sName types.PrincipalName + cTime time.Time // This combines the ticket's CTime and Cusec +} + +func (c *Cache) getClientEntries(cname types.PrincipalName) (clientEntries, bool) { + c.mux.RLock() + defer c.mux.RUnlock() + ce, ok := c.entries[cname.PrincipalNameString()] + return ce, ok +} + +func (c *Cache) getClientEntry(cname types.PrincipalName, t time.Time) (replayCacheEntry, bool) { + if ce, ok := c.getClientEntries(cname); ok { + c.mux.RLock() + defer c.mux.RUnlock() + if e, ok := ce.replayMap[t]; ok { + return e, true + } + } + return replayCacheEntry{}, false +} + +// Instance of the ServiceCache. This needs to be a singleton. +var replayCache Cache +var once sync.Once + +// GetReplayCache returns a pointer to the Cache singleton. +func GetReplayCache(d time.Duration) *Cache { + // Create a singleton of the ReplayCache and start a background thread to regularly clean out old entries + once.Do(func() { + replayCache = Cache{ + entries: make(map[string]clientEntries), + } + go func() { + for { + // TODO consider using a context here. + time.Sleep(d) + replayCache.ClearOldEntries(d) + } + }() + }) + return &replayCache +} + +// AddEntry adds an entry to the Cache. +func (c *Cache) AddEntry(sname types.PrincipalName, a types.Authenticator) { + ct := a.CTime.Add(time.Duration(a.Cusec) * time.Microsecond) + if ce, ok := c.getClientEntries(a.CName); ok { + c.mux.Lock() + defer c.mux.Unlock() + ce.replayMap[ct] = replayCacheEntry{ + presentedTime: time.Now().UTC(), + sName: sname, + cTime: ct, + } + ce.seqNumber = a.SeqNumber + ce.subKey = a.SubKey + } else { + c.mux.Lock() + defer c.mux.Unlock() + c.entries[a.CName.PrincipalNameString()] = clientEntries{ + replayMap: map[time.Time]replayCacheEntry{ + ct: { + presentedTime: time.Now().UTC(), + sName: sname, + cTime: ct, + }, + }, + seqNumber: a.SeqNumber, + subKey: a.SubKey, + } + } +} + +// ClearOldEntries clears entries from the Cache that are older than the duration provided. +func (c *Cache) ClearOldEntries(d time.Duration) { + c.mux.Lock() + defer c.mux.Unlock() + for ke, ce := range c.entries { + for k, e := range ce.replayMap { + if time.Now().UTC().Sub(e.presentedTime) > d { + delete(ce.replayMap, k) + } + } + if len(ce.replayMap) == 0 { + delete(c.entries, ke) + } + } +} + +// IsReplay tests if the Authenticator provided is a replay within the duration defined. If this is not a replay add the entry to the cache for tracking. +func (c *Cache) IsReplay(sname types.PrincipalName, a types.Authenticator) bool { + ct := a.CTime.Add(time.Duration(a.Cusec) * time.Microsecond) + if e, ok := c.getClientEntry(a.CName, ct); ok { + if e.sName.Equal(sname) { + return true + } + } + c.AddEntry(sname, a) + return false +} diff --git a/vendor/gopkg.in/jcmturner/gokrb5.v7/service/settings.go b/vendor/gopkg.in/jcmturner/gokrb5.v7/service/settings.go new file mode 100644 index 00000000000..6e373ced6ba --- /dev/null +++ b/vendor/gopkg.in/jcmturner/gokrb5.v7/service/settings.go @@ -0,0 +1,136 @@ +package service + +import ( + "log" + "time" + + "gopkg.in/jcmturner/gokrb5.v7/keytab" + "gopkg.in/jcmturner/gokrb5.v7/types" +) + +// Settings defines service side configuration settings. +type Settings struct { + Keytab *keytab.Keytab + ktprinc *types.PrincipalName + sname string + requireHostAddr bool + disablePACDecoding bool + cAddr types.HostAddress + maxClockSkew time.Duration + logger *log.Logger +} + +// NewSettings creates a new service Settings. +func NewSettings(kt *keytab.Keytab, settings ...func(*Settings)) *Settings { + s := new(Settings) + s.Keytab = kt + for _, set := range settings { + set(s) + } + return s +} + +// RequireHostAddr used to configure service side to required host addresses to be specified in Kerberos tickets. +// +// s := NewSettings(kt, RequireHostAddr(true)) +func RequireHostAddr(b bool) func(*Settings) { + return func(s *Settings) { + s.requireHostAddr = b + } +} + +// RequireHostAddr indicates if the service should require the host address to be included in the ticket. +func (s *Settings) RequireHostAddr() bool { + return s.requireHostAddr +} + +// DecodePAC used to configure service side to enable/disable PAC decoding if the PAC is present. +// Defaults to enabled if not specified. +// +// s := NewSettings(kt, DecodePAC(false)) +func DecodePAC(b bool) func(*Settings) { + return func(s *Settings) { + s.disablePACDecoding = !b + } +} + +// DecodePAC indicates whether the service should decode any PAC information present in the ticket. +func (s *Settings) DecodePAC() bool { + return !s.disablePACDecoding +} + +// ClientAddress used to configure service side with the clients host address to be used during validation. +// +// s := NewSettings(kt, ClientAddress(h)) +func ClientAddress(h types.HostAddress) func(*Settings) { + return func(s *Settings) { + s.cAddr = h + } +} + +// ClientAddress returns the client host address which has been provided to the service. +func (s *Settings) ClientAddress() types.HostAddress { + return s.cAddr +} + +// Logger used to configure service side with a logger. +// +// s := NewSettings(kt, Logger(l)) +func Logger(l *log.Logger) func(*Settings) { + return func(s *Settings) { + s.logger = l + } +} + +// Logger returns the logger instances configured for the service. If none is configured nill will be returned. +func (s *Settings) Logger() *log.Logger { + return s.logger +} + +// KeytabPrincipal used to override the principal name used to find the key in the keytab. +// +// s := NewSettings(kt, KeytabPrincipal("someaccount")) +func KeytabPrincipal(p string) func(*Settings) { + return func(s *Settings) { + pn, _ := types.ParseSPNString(p) + s.ktprinc = &pn + } +} + +// KeytabPrincipal returns the principal name used to find the key in the keytab if it has been overridden. +func (s *Settings) KeytabPrincipal() *types.PrincipalName { + return s.ktprinc +} + +// MaxClockSkew used to configure service side with the maximum acceptable clock skew +// between the service and the issue time of kerberos tickets +// +// s := NewSettings(kt, MaxClockSkew(d)) +func MaxClockSkew(d time.Duration) func(*Settings) { + return func(s *Settings) { + s.maxClockSkew = d + } +} + +// MaxClockSkew returns the maximum acceptable clock skew between the service and the issue time of kerberos tickets. +// If none is defined a duration of 5 minutes is returned. +func (s *Settings) MaxClockSkew() time.Duration { + if s.maxClockSkew.Nanoseconds() == 0 { + return time.Duration(5) * time.Minute + } + return s.maxClockSkew +} + +// SName used provide a specific service name to the service settings. +// +// s := NewSettings(kt, SName("HTTP/some.service.com")) +func SName(sname string) func(*Settings) { + return func(s *Settings) { + s.sname = sname + } +} + +// SName returns the specific service name to the service. +func (s *Settings) SName() string { + return s.sname +} diff --git a/vendor/gopkg.in/jcmturner/gokrb5.v7/spnego/http.go b/vendor/gopkg.in/jcmturner/gokrb5.v7/spnego/http.go new file mode 100644 index 00000000000..0cb28449c5d --- /dev/null +++ b/vendor/gopkg.in/jcmturner/gokrb5.v7/spnego/http.go @@ -0,0 +1,293 @@ +package spnego + +import ( + "bytes" + "context" + "encoding/base64" + "errors" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "net/http/cookiejar" + "net/url" + "strings" + + "gopkg.in/jcmturner/goidentity.v3" + "gopkg.in/jcmturner/gokrb5.v7/client" + "gopkg.in/jcmturner/gokrb5.v7/gssapi" + "gopkg.in/jcmturner/gokrb5.v7/keytab" + "gopkg.in/jcmturner/gokrb5.v7/krberror" + "gopkg.in/jcmturner/gokrb5.v7/service" + "gopkg.in/jcmturner/gokrb5.v7/types" +) + +// Client side functionality // + +// Client will negotiate authentication with a server using SPNEGO. +type Client struct { + *http.Client + krb5Client *client.Client + spn string + reqs []*http.Request +} + +type redirectErr struct { + reqTarget *http.Request +} + +func (e redirectErr) Error() string { + return fmt.Sprintf("redirect to %v", e.reqTarget.URL) +} + +type teeReadCloser struct { + io.Reader + io.Closer +} + +// NewClient returns an SPNEGO enabled HTTP client. +func NewClient(krb5Cl *client.Client, httpCl *http.Client, spn string) *Client { + if httpCl == nil { + httpCl = http.DefaultClient + } + // Add a cookie jar if there isn't one + if httpCl.Jar == nil { + httpCl.Jar, _ = cookiejar.New(nil) + } + // Add a CheckRedirect function that will execute any functional already defined and then error with a redirectErr + f := httpCl.CheckRedirect + httpCl.CheckRedirect = func(req *http.Request, via []*http.Request) error { + if f != nil { + err := f(req, via) + if err != nil { + return err + } + } + return redirectErr{reqTarget: req} + } + return &Client{ + Client: httpCl, + krb5Client: krb5Cl, + spn: spn, + } +} + +// Do is the SPNEGO enabled HTTP client's equivalent of the http.Client's Do method. +func (c *Client) Do(req *http.Request) (resp *http.Response, err error) { + var body bytes.Buffer + if req.Body != nil { + // Use a tee reader to capture any body sent in case we have to replay it again + teeR := io.TeeReader(req.Body, &body) + teeRC := teeReadCloser{teeR, req.Body} + req.Body = teeRC + } + resp, err = c.Client.Do(req) + if err != nil { + if ue, ok := err.(*url.Error); ok { + if e, ok := ue.Err.(redirectErr); ok { + // Picked up a redirect + e.reqTarget.Header.Del(HTTPHeaderAuthRequest) + c.reqs = append(c.reqs, e.reqTarget) + if len(c.reqs) >= 10 { + return resp, errors.New("stopped after 10 redirects") + } + if req.Body != nil { + // Refresh the body reader so the body can be sent again + e.reqTarget.Body = ioutil.NopCloser(&body) + } + return c.Do(e.reqTarget) + } + } + return resp, err + } + if respUnauthorizedNegotiate(resp) { + err := SetSPNEGOHeader(c.krb5Client, req, c.spn) + if err != nil { + return resp, err + } + if req.Body != nil { + // Refresh the body reader so the body can be sent again + req.Body = ioutil.NopCloser(&body) + } + return c.Do(req) + } + return resp, err +} + +// Get is the SPNEGO enabled HTTP client's equivalent of the http.Client's Get method. +func (c *Client) Get(url string) (resp *http.Response, err error) { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + return c.Do(req) +} + +// Post is the SPNEGO enabled HTTP client's equivalent of the http.Client's Post method. +func (c *Client) Post(url, contentType string, body io.Reader) (resp *http.Response, err error) { + req, err := http.NewRequest("POST", url, body) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", contentType) + return c.Do(req) +} + +// PostForm is the SPNEGO enabled HTTP client's equivalent of the http.Client's PostForm method. +func (c *Client) PostForm(url string, data url.Values) (resp *http.Response, err error) { + return c.Post(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode())) +} + +// Head is the SPNEGO enabled HTTP client's equivalent of the http.Client's Head method. +func (c *Client) Head(url string) (resp *http.Response, err error) { + req, err := http.NewRequest("HEAD", url, nil) + if err != nil { + return nil, err + } + return c.Do(req) +} + +func respUnauthorizedNegotiate(resp *http.Response) bool { + if resp.StatusCode == http.StatusUnauthorized { + if resp.Header.Get(HTTPHeaderAuthResponse) == HTTPHeaderAuthResponseValueKey { + return true + } + } + return false +} + +// SetSPNEGOHeader gets the service ticket and sets it as the SPNEGO authorization header on HTTP request object. +// To auto generate the SPN from the request object pass a null string "". +func SetSPNEGOHeader(cl *client.Client, r *http.Request, spn string) error { + if spn == "" { + h := strings.TrimSuffix(strings.SplitN(r.URL.Host, ":", 2)[0], ".") + name, err := net.LookupCNAME(h) + if err == nil { + // Underlyng canonical name should be used for SPN + h = strings.TrimSuffix(name, ".") + } + spn = "HTTP/" + h + r.Host = h + } + cl.Log("using SPN %s", spn) + s := SPNEGOClient(cl, spn) + err := s.AcquireCred() + if err != nil { + return fmt.Errorf("could not acquire client credential: %v", err) + } + st, err := s.InitSecContext() + if err != nil { + return fmt.Errorf("could not initialize context: %v", err) + } + nb, err := st.Marshal() + if err != nil { + return krberror.Errorf(err, krberror.EncodingError, "could not marshal SPNEGO") + } + hs := "Negotiate " + base64.StdEncoding.EncodeToString(nb) + r.Header.Set(HTTPHeaderAuthRequest, hs) + return nil +} + +// Service side functionality // + +type ctxKey string + +const ( + // spnegoNegTokenRespKRBAcceptCompleted - The response on successful authentication always has this header. Capturing as const so we don't have marshaling and encoding overhead. + spnegoNegTokenRespKRBAcceptCompleted = "Negotiate oRQwEqADCgEAoQsGCSqGSIb3EgECAg==" + // spnegoNegTokenRespReject - The response on a failed authentication always has this rejection header. Capturing as const so we don't have marshaling and encoding overhead. + spnegoNegTokenRespReject = "Negotiate oQcwBaADCgEC" + // spnegoNegTokenRespIncompleteKRB5 - Response token specifying incomplete context and KRB5 as the supported mechtype. + spnegoNegTokenRespIncompleteKRB5 = "Negotiate oRQwEqADCgEBoQsGCSqGSIb3EgECAg==" + // CTXKeyAuthenticated is the request context key holding a boolean indicating if the request has been authenticated. + CTXKeyAuthenticated ctxKey = "github.com/jcmturner/gokrb5/CTXKeyAuthenticated" + // CTXKeyCredentials is the request context key holding the credentials gopkg.in/jcmturner/goidentity.v2/Identity object. + CTXKeyCredentials ctxKey = "github.com/jcmturner/gokrb5/CTXKeyCredentials" + // HTTPHeaderAuthRequest is the header that will hold authn/z information. + HTTPHeaderAuthRequest = "Authorization" + // HTTPHeaderAuthResponse is the header that will hold SPNEGO data from the server. + HTTPHeaderAuthResponse = "WWW-Authenticate" + // HTTPHeaderAuthResponseValueKey is the key in the auth header for SPNEGO. + HTTPHeaderAuthResponseValueKey = "Negotiate" + // UnauthorizedMsg is the message returned in the body when authentication fails. + UnauthorizedMsg = "Unauthorised.\n" +) + +// SPNEGOKRB5Authenticate is a Kerberos SPNEGO authentication HTTP handler wrapper. +func SPNEGOKRB5Authenticate(inner http.Handler, kt *keytab.Keytab, settings ...func(*service.Settings)) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Get the auth header + s := strings.SplitN(r.Header.Get(HTTPHeaderAuthRequest), " ", 2) + if len(s) != 2 || s[0] != HTTPHeaderAuthResponseValueKey { + // No Authorization header set so return 401 with WWW-Authenticate Negotiate header + w.Header().Set(HTTPHeaderAuthResponse, HTTPHeaderAuthResponseValueKey) + http.Error(w, UnauthorizedMsg, http.StatusUnauthorized) + return + } + + // Set up the SPNEGO GSS-API mechanism + var spnego *SPNEGO + h, err := types.GetHostAddress(r.RemoteAddr) + if err == nil { + // put in this order so that if the user provides a ClientAddress it will override the one here. + o := append([]func(*service.Settings){service.ClientAddress(h)}, settings...) + spnego = SPNEGOService(kt, o...) + } else { + spnego = SPNEGOService(kt, settings...) + spnego.Log("%s - SPNEGO could not parse client address: %v", r.RemoteAddr, err) + } + + // Decode the header into an SPNEGO context token + b, err := base64.StdEncoding.DecodeString(s[1]) + if err != nil { + spnegoNegotiateKRB5MechType(spnego, w, "%s - SPNEGO error in base64 decoding negotiation header: %v", r.RemoteAddr, err) + return + } + var st SPNEGOToken + err = st.Unmarshal(b) + if err != nil { + spnegoNegotiateKRB5MechType(spnego, w, "%s - SPNEGO error in unmarshaling SPNEGO token: %v", r.RemoteAddr, err) + return + } + + // Validate the context token + authed, ctx, status := spnego.AcceptSecContext(&st) + if status.Code != gssapi.StatusComplete && status.Code != gssapi.StatusContinueNeeded { + spnegoResponseReject(spnego, w, "%s - SPNEGO validation error: %v", r.RemoteAddr, status) + return + } + if status.Code == gssapi.StatusContinueNeeded { + spnegoNegotiateKRB5MechType(spnego, w, "%s - SPNEGO GSS-API continue needed", r.RemoteAddr) + return + } + if authed { + id := ctx.Value(CTXKeyCredentials).(goidentity.Identity) + requestCtx := r.Context() + requestCtx = context.WithValue(requestCtx, CTXKeyCredentials, id) + requestCtx = context.WithValue(requestCtx, CTXKeyAuthenticated, ctx.Value(CTXKeyAuthenticated)) + spnegoResponseAcceptCompleted(spnego, w, "%s %s@%s - SPNEGO authentication succeeded", r.RemoteAddr, id.UserName(), id.Domain()) + inner.ServeHTTP(w, r.WithContext(requestCtx)) + } else { + spnegoResponseReject(spnego, w, "%s - SPNEGO Kerberos authentication failed", r.RemoteAddr) + return + } + }) +} + +func spnegoNegotiateKRB5MechType(s *SPNEGO, w http.ResponseWriter, format string, v ...interface{}) { + s.Log(format, v...) + w.Header().Set(HTTPHeaderAuthResponse, spnegoNegTokenRespIncompleteKRB5) + http.Error(w, UnauthorizedMsg, http.StatusUnauthorized) +} + +func spnegoResponseReject(s *SPNEGO, w http.ResponseWriter, format string, v ...interface{}) { + s.Log(format, v...) + w.Header().Set(HTTPHeaderAuthResponse, spnegoNegTokenRespReject) + http.Error(w, UnauthorizedMsg, http.StatusUnauthorized) +} + +func spnegoResponseAcceptCompleted(s *SPNEGO, w http.ResponseWriter, format string, v ...interface{}) { + s.Log(format, v...) + w.Header().Set(HTTPHeaderAuthResponse, spnegoNegTokenRespKRBAcceptCompleted) +} diff --git a/vendor/gopkg.in/jcmturner/gokrb5.v7/spnego/krb5Token.go b/vendor/gopkg.in/jcmturner/gokrb5.v7/spnego/krb5Token.go new file mode 100644 index 00000000000..8d82df2af39 --- /dev/null +++ b/vendor/gopkg.in/jcmturner/gokrb5.v7/spnego/krb5Token.go @@ -0,0 +1,236 @@ +package spnego + +import ( + "context" + "encoding/binary" + "encoding/hex" + "errors" + "fmt" + + "github.com/jcmturner/gofork/encoding/asn1" + "gopkg.in/jcmturner/gokrb5.v7/asn1tools" + "gopkg.in/jcmturner/gokrb5.v7/client" + "gopkg.in/jcmturner/gokrb5.v7/credentials" + "gopkg.in/jcmturner/gokrb5.v7/gssapi" + "gopkg.in/jcmturner/gokrb5.v7/iana/chksumtype" + "gopkg.in/jcmturner/gokrb5.v7/iana/msgtype" + "gopkg.in/jcmturner/gokrb5.v7/krberror" + "gopkg.in/jcmturner/gokrb5.v7/messages" + "gopkg.in/jcmturner/gokrb5.v7/service" + "gopkg.in/jcmturner/gokrb5.v7/types" +) + +// GSSAPI KRB5 MechToken IDs. +const ( + TOK_ID_KRB_AP_REQ = "0100" + TOK_ID_KRB_AP_REP = "0200" + TOK_ID_KRB_ERROR = "0300" +) + +// KRB5Token context token implementation for GSSAPI. +type KRB5Token struct { + OID asn1.ObjectIdentifier + tokID []byte + APReq messages.APReq + APRep messages.APRep + KRBError messages.KRBError + settings *service.Settings + context context.Context +} + +// Marshal a KRB5Token into a slice of bytes. +func (m *KRB5Token) Marshal() ([]byte, error) { + // Create the header + b, _ := asn1.Marshal(m.OID) + b = append(b, m.tokID...) + var tb []byte + var err error + switch hex.EncodeToString(m.tokID) { + case TOK_ID_KRB_AP_REQ: + tb, err = m.APReq.Marshal() + if err != nil { + return []byte{}, fmt.Errorf("error marshalling AP_REQ for MechToken: %v", err) + } + case TOK_ID_KRB_AP_REP: + return []byte{}, errors.New("marshal of AP_REP GSSAPI MechToken not supported by gokrb5") + case TOK_ID_KRB_ERROR: + return []byte{}, errors.New("marshal of KRB_ERROR GSSAPI MechToken not supported by gokrb5") + } + if err != nil { + return []byte{}, fmt.Errorf("error mashalling kerberos message within mech token: %v", err) + } + b = append(b, tb...) + return asn1tools.AddASNAppTag(b, 0), nil +} + +// Unmarshal a KRB5Token. +func (m *KRB5Token) Unmarshal(b []byte) error { + var oid asn1.ObjectIdentifier + r, err := asn1.UnmarshalWithParams(b, &oid, fmt.Sprintf("application,explicit,tag:%v", 0)) + if err != nil { + return fmt.Errorf("error unmarshalling KRB5Token OID: %v", err) + } + m.OID = oid + m.tokID = r[0:2] + switch hex.EncodeToString(m.tokID) { + case TOK_ID_KRB_AP_REQ: + var a messages.APReq + err = a.Unmarshal(r[2:]) + if err != nil { + return fmt.Errorf("error unmarshalling KRB5Token AP_REQ: %v", err) + } + m.APReq = a + case TOK_ID_KRB_AP_REP: + var a messages.APRep + err = a.Unmarshal(r[2:]) + if err != nil { + return fmt.Errorf("error unmarshalling KRB5Token AP_REP: %v", err) + } + m.APRep = a + case TOK_ID_KRB_ERROR: + var a messages.KRBError + err = a.Unmarshal(r[2:]) + if err != nil { + return fmt.Errorf("error unmarshalling KRB5Token KRBError: %v", err) + } + m.KRBError = a + } + return nil +} + +// Verify a KRB5Token. +func (m *KRB5Token) Verify() (bool, gssapi.Status) { + switch hex.EncodeToString(m.tokID) { + case TOK_ID_KRB_AP_REQ: + ok, creds, err := service.VerifyAPREQ(m.APReq, m.settings) + if err != nil { + return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: err.Error()} + } + if !ok { + return false, gssapi.Status{Code: gssapi.StatusDefectiveCredential, Message: "KRB5_AP_REQ token not valid"} + } + m.context = context.Background() + m.context = context.WithValue(m.context, CTXKeyCredentials, creds) + m.context = context.WithValue(m.context, CTXKeyAuthenticated, ok) + return true, gssapi.Status{Code: gssapi.StatusComplete} + case TOK_ID_KRB_AP_REP: + // Client side + // TODO how to verify the AP_REP - not yet implemented + return false, gssapi.Status{Code: gssapi.StatusFailure, Message: "verifying an AP_REP is not currently supported by gokrb5"} + case TOK_ID_KRB_ERROR: + if m.KRBError.MsgType != msgtype.KRB_ERROR { + return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "KRB5_Error token not valid"} + } + return true, gssapi.Status{Code: gssapi.StatusUnavailable} + } + return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "unknown TOK_ID in KRB5 token"} +} + +// IsAPReq tests if the MechToken contains an AP_REQ. +func (m *KRB5Token) IsAPReq() bool { + if hex.EncodeToString(m.tokID) == TOK_ID_KRB_AP_REQ { + return true + } + return false +} + +// IsAPRep tests if the MechToken contains an AP_REP. +func (m *KRB5Token) IsAPRep() bool { + if hex.EncodeToString(m.tokID) == TOK_ID_KRB_AP_REP { + return true + } + return false +} + +// IsKRBError tests if the MechToken contains an KRB_ERROR. +func (m *KRB5Token) IsKRBError() bool { + if hex.EncodeToString(m.tokID) == TOK_ID_KRB_ERROR { + return true + } + return false +} + +// Context returns the KRB5 token's context which will contain any verify user identity information. +func (m *KRB5Token) Context() context.Context { + return m.context +} + +// NewKRB5TokenAPREQ creates a new KRB5 token with AP_REQ +func NewKRB5TokenAPREQ(cl *client.Client, tkt messages.Ticket, sessionKey types.EncryptionKey, GSSAPIFlags []int, APOptions []int) (KRB5Token, error) { + // TODO consider providing the SPN rather than the specific tkt and key and get these from the krb client. + var m KRB5Token + m.OID = gssapi.OID(gssapi.OIDKRB5) + tb, _ := hex.DecodeString(TOK_ID_KRB_AP_REQ) + m.tokID = tb + + auth, err := krb5TokenAuthenticator(cl.Credentials, GSSAPIFlags) + if err != nil { + return m, err + } + APReq, err := messages.NewAPReq( + tkt, + sessionKey, + auth, + ) + if err != nil { + return m, err + } + for _, o := range APOptions { + types.SetFlag(&APReq.APOptions, o) + } + m.APReq = APReq + return m, nil +} + +// krb5TokenAuthenticator creates a new kerberos authenticator for kerberos MechToken +func krb5TokenAuthenticator(creds *credentials.Credentials, flags []int) (types.Authenticator, error) { + //RFC 4121 Section 4.1.1 + auth, err := types.NewAuthenticator(creds.Domain(), creds.CName()) + if err != nil { + return auth, krberror.Errorf(err, krberror.KRBMsgError, "error generating new authenticator") + } + auth.Cksum = types.Checksum{ + CksumType: chksumtype.GSSAPI, + Checksum: newAuthenticatorChksum(flags), + } + return auth, nil +} + +// Create new authenticator checksum for kerberos MechToken +func newAuthenticatorChksum(flags []int) []byte { + a := make([]byte, 24) + binary.LittleEndian.PutUint32(a[:4], 16) + for _, i := range flags { + if i == gssapi.ContextFlagDeleg { + x := make([]byte, 28-len(a)) + a = append(a, x...) + } + f := binary.LittleEndian.Uint32(a[20:24]) + f |= uint32(i) + binary.LittleEndian.PutUint32(a[20:24], f) + } + return a +} + +/* +The authenticator checksum field SHALL have the following format: + +Octet Name Description +----------------------------------------------------------------- +0..3 Lgth Number of octets in Bnd field; Represented + in little-endian order; Currently contains + hex value 10 00 00 00 (16). +4..19 Bnd Channel binding information, as described in + section 4.1.1.2. +20..23 Flags Four-octet context-establishment flags in + little-endian order as described in section + 4.1.1.1. +24..25 DlgOpt The delegation option identifier (=1) in + little-endian order [optional]. This field + and the next two fields are present if and + only if GSS_C_DELEG_FLAG is set as described + in section 4.1.1.1. +26..27 Dlgth The length of the Deleg field in little-endian order [optional]. +28..(n-1) Deleg A KRB_CRED message (n = Dlgth + 28) [optional]. +n..last Exts Extensions [optional]. +*/ diff --git a/vendor/gopkg.in/jcmturner/gokrb5.v7/spnego/negotiationToken.go b/vendor/gopkg.in/jcmturner/gokrb5.v7/spnego/negotiationToken.go new file mode 100644 index 00000000000..4a80f3595e0 --- /dev/null +++ b/vendor/gopkg.in/jcmturner/gokrb5.v7/spnego/negotiationToken.go @@ -0,0 +1,338 @@ +package spnego + +import ( + "context" + "errors" + "fmt" + + "github.com/jcmturner/gofork/encoding/asn1" + "gopkg.in/jcmturner/gokrb5.v7/client" + "gopkg.in/jcmturner/gokrb5.v7/gssapi" + "gopkg.in/jcmturner/gokrb5.v7/messages" + "gopkg.in/jcmturner/gokrb5.v7/service" + "gopkg.in/jcmturner/gokrb5.v7/types" +) + +/* +https://msdn.microsoft.com/en-us/library/ms995330.aspx + +NegotiationToken ::= CHOICE { + negTokenInit [0] NegTokenInit, This is the Negotiation token sent from the client to the server. + negTokenResp [1] NegTokenResp +} + +NegTokenInit ::= SEQUENCE { + mechTypes [0] MechTypeList, + reqFlags [1] ContextFlags OPTIONAL, + -- inherited from RFC 2478 for backward compatibility, + -- RECOMMENDED to be left out + mechToken [2] OCTET STRING OPTIONAL, + mechListMIC [3] OCTET STRING OPTIONAL, + ... +} + +NegTokenResp ::= SEQUENCE { + negState [0] ENUMERATED { + accept-completed (0), + accept-incomplete (1), + reject (2), + request-mic (3) + } OPTIONAL, + -- REQUIRED in the first reply from the target + supportedMech [1] MechType OPTIONAL, + -- present only in the first reply from the target + responseToken [2] OCTET STRING OPTIONAL, + mechListMIC [3] OCTET STRING OPTIONAL, + ... +} +*/ + +// Negotiation state values. +const ( + NegStateAcceptCompleted NegState = 0 + NegStateAcceptIncomplete NegState = 1 + NegStateReject NegState = 2 + NegStateRequestMIC NegState = 3 +) + +// NegState is a type to indicate the SPNEGO negotiation state. +type NegState int + +// NegTokenInit implements Negotiation Token of type Init. +type NegTokenInit struct { + MechTypes []asn1.ObjectIdentifier + ReqFlags gssapi.ContextFlags + MechTokenBytes []byte + MechListMIC []byte + mechToken gssapi.ContextToken + settings *service.Settings +} + +type marshalNegTokenInit struct { + MechTypes []asn1.ObjectIdentifier `asn1:"explicit,tag:0"` + ReqFlags gssapi.ContextFlags `asn1:"explicit,optional,tag:1"` + MechTokenBytes []byte `asn1:"explicit,optional,omitempty,tag:2"` + MechListMIC []byte `asn1:"explicit,optional,omitempty,tag:3"` // This field is not used when negotiating Kerberos tokens +} + +// NegTokenResp implements Negotiation Token of type Resp/Targ +type NegTokenResp struct { + NegState asn1.Enumerated + SupportedMech asn1.ObjectIdentifier + ResponseToken []byte + MechListMIC []byte + mechToken gssapi.ContextToken + settings *service.Settings +} + +type marshalNegTokenResp struct { + NegState asn1.Enumerated `asn1:"explicit,tag:0"` + SupportedMech asn1.ObjectIdentifier `asn1:"explicit,optional,tag:1"` + ResponseToken []byte `asn1:"explicit,optional,omitempty,tag:2"` + MechListMIC []byte `asn1:"explicit,optional,omitempty,tag:3"` // This field is not used when negotiating Kerberos tokens +} + +// NegTokenTarg implements Negotiation Token of type Resp/Targ +type NegTokenTarg NegTokenResp + +// Marshal an Init negotiation token +func (n *NegTokenInit) Marshal() ([]byte, error) { + m := marshalNegTokenInit{ + MechTypes: n.MechTypes, + ReqFlags: n.ReqFlags, + MechTokenBytes: n.MechTokenBytes, + MechListMIC: n.MechListMIC, + } + b, err := asn1.Marshal(m) + if err != nil { + return nil, err + } + nt := asn1.RawValue{ + Tag: 0, + Class: 2, + IsCompound: true, + Bytes: b, + } + nb, err := asn1.Marshal(nt) + if err != nil { + return nil, err + } + return nb, nil +} + +// Unmarshal an Init negotiation token +func (n *NegTokenInit) Unmarshal(b []byte) error { + init, nt, err := UnmarshalNegToken(b) + if err != nil { + return err + } + if !init { + return errors.New("bytes were not that of a NegTokenInit") + } + nInit := nt.(NegTokenInit) + n.MechTokenBytes = nInit.MechTokenBytes + n.MechListMIC = nInit.MechListMIC + n.MechTypes = nInit.MechTypes + n.ReqFlags = nInit.ReqFlags + return nil +} + +// Verify an Init negotiation token +func (n *NegTokenInit) Verify() (bool, gssapi.Status) { + // Check if supported mechanisms are in the MechTypeList + var mtSupported bool + for _, m := range n.MechTypes { + if m.Equal(gssapi.OID(gssapi.OIDKRB5)) || m.Equal(gssapi.OID(gssapi.OIDMSLegacyKRB5)) { + if n.mechToken == nil && n.MechTokenBytes == nil { + return false, gssapi.Status{Code: gssapi.StatusContinueNeeded} + } + mtSupported = true + break + } + } + if !mtSupported { + return false, gssapi.Status{Code: gssapi.StatusBadMech, Message: "no supported mechanism specified in negotiation"} + } + // There should be some mechtoken bytes for a KRB5Token (other mech types are not supported) + mt := new(KRB5Token) + mt.settings = n.settings + if n.mechToken == nil { + err := mt.Unmarshal(n.MechTokenBytes) + if err != nil { + return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: err.Error()} + } + n.mechToken = mt + } else { + var ok bool + mt, ok = n.mechToken.(*KRB5Token) + if !ok { + return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "MechToken is not a KRB5 token as expected"} + } + } + // RFC4178 states that the initial negotiation message can optionally contain the initial mechanism token for the preferred mechanism of the client. + if !mt.OID.Equal(n.MechTypes[0]) { + return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "OID of MechToken does not match the first in the MechTypeList"} + } + // Verify the mechtoken + return n.mechToken.Verify() +} + +// Context returns the SPNEGO context which will contain any verify user identity information. +func (n *NegTokenInit) Context() context.Context { + if n.mechToken != nil { + mt, ok := n.mechToken.(*KRB5Token) + if !ok { + return nil + } + return mt.Context() + } + return nil +} + +// Marshal a Resp/Targ negotiation token +func (n *NegTokenResp) Marshal() ([]byte, error) { + m := marshalNegTokenResp{ + NegState: n.NegState, + SupportedMech: n.SupportedMech, + ResponseToken: n.ResponseToken, + MechListMIC: n.MechListMIC, + } + b, err := asn1.Marshal(m) + if err != nil { + return nil, err + } + nt := asn1.RawValue{ + Tag: 1, + Class: 2, + IsCompound: true, + Bytes: b, + } + nb, err := asn1.Marshal(nt) + if err != nil { + return nil, err + } + return nb, nil +} + +// Unmarshal a Resp/Targ negotiation token +func (n *NegTokenResp) Unmarshal(b []byte) error { + init, nt, err := UnmarshalNegToken(b) + if err != nil { + return err + } + if init { + return errors.New("bytes were not that of a NegTokenResp") + } + nResp := nt.(NegTokenResp) + n.MechListMIC = nResp.MechListMIC + n.NegState = nResp.NegState + n.ResponseToken = nResp.ResponseToken + n.SupportedMech = nResp.SupportedMech + return nil +} + +// Verify a Resp/Targ negotiation token +func (n *NegTokenResp) Verify() (bool, gssapi.Status) { + if n.SupportedMech.Equal(gssapi.OID(gssapi.OIDKRB5)) || n.SupportedMech.Equal(gssapi.OID(gssapi.OIDMSLegacyKRB5)) { + if n.mechToken == nil && n.ResponseToken == nil { + return false, gssapi.Status{Code: gssapi.StatusContinueNeeded} + } + mt := new(KRB5Token) + mt.settings = n.settings + if n.mechToken == nil { + err := mt.Unmarshal(n.ResponseToken) + if err != nil { + return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: err.Error()} + } + n.mechToken = mt + } else { + var ok bool + mt, ok = n.mechToken.(*KRB5Token) + if !ok { + return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "MechToken is not a KRB5 token as expected"} + } + } + if mt == nil { + return false, gssapi.Status{Code: gssapi.StatusContinueNeeded} + } + // Verify the mechtoken + return mt.Verify() + } + return false, gssapi.Status{Code: gssapi.StatusBadMech, Message: "no supported mechanism specified in negotiation"} +} + +// State returns the negotiation state of the negotiation response. +func (n *NegTokenResp) State() NegState { + return NegState(n.NegState) +} + +// Context returns the SPNEGO context which will contain any verify user identity information. +func (n *NegTokenResp) Context() context.Context { + if n.mechToken != nil { + mt, ok := n.mechToken.(*KRB5Token) + if !ok { + return nil + } + return mt.Context() + } + return nil +} + +// UnmarshalNegToken umarshals and returns either a NegTokenInit or a NegTokenResp. +// +// The boolean indicates if the response is a NegTokenInit. +// If error is nil and the boolean is false the response is a NegTokenResp. +func UnmarshalNegToken(b []byte) (bool, interface{}, error) { + var a asn1.RawValue + _, err := asn1.Unmarshal(b, &a) + if err != nil { + return false, nil, fmt.Errorf("error unmarshalling NegotiationToken: %v", err) + } + switch a.Tag { + case 0: + var n marshalNegTokenInit + _, err = asn1.Unmarshal(a.Bytes, &n) + if err != nil { + return false, nil, fmt.Errorf("error unmarshalling NegotiationToken type %d (Init): %v", a.Tag, err) + } + nt := NegTokenInit{ + MechTypes: n.MechTypes, + ReqFlags: n.ReqFlags, + MechTokenBytes: n.MechTokenBytes, + MechListMIC: n.MechListMIC, + } + return true, nt, nil + case 1: + var n marshalNegTokenResp + _, err = asn1.Unmarshal(a.Bytes, &n) + if err != nil { + return false, nil, fmt.Errorf("error unmarshalling NegotiationToken type %d (Resp/Targ): %v", a.Tag, err) + } + nt := NegTokenResp{ + NegState: n.NegState, + SupportedMech: n.SupportedMech, + ResponseToken: n.ResponseToken, + MechListMIC: n.MechListMIC, + } + return false, nt, nil + default: + return false, nil, errors.New("unknown choice type for NegotiationToken") + } + +} + +// NewNegTokenInitKRB5 creates new Init negotiation token for Kerberos 5 +func NewNegTokenInitKRB5(cl *client.Client, tkt messages.Ticket, sessionKey types.EncryptionKey) (NegTokenInit, error) { + mt, err := NewKRB5TokenAPREQ(cl, tkt, sessionKey, []int{gssapi.ContextFlagInteg, gssapi.ContextFlagConf}, []int{}) + if err != nil { + return NegTokenInit{}, fmt.Errorf("error getting KRB5 token; %v", err) + } + mtb, err := mt.Marshal() + if err != nil { + return NegTokenInit{}, fmt.Errorf("error marshalling KRB5 token; %v", err) + } + return NegTokenInit{ + MechTypes: []asn1.ObjectIdentifier{gssapi.OID(gssapi.OIDKRB5)}, + MechTokenBytes: mtb, + }, nil +} diff --git a/vendor/gopkg.in/jcmturner/gokrb5.v7/spnego/spnego.go b/vendor/gopkg.in/jcmturner/gokrb5.v7/spnego/spnego.go new file mode 100644 index 00000000000..f82947c7e13 --- /dev/null +++ b/vendor/gopkg.in/jcmturner/gokrb5.v7/spnego/spnego.go @@ -0,0 +1,199 @@ +// Package spnego implements the Simple and Protected GSSAPI Negotiation Mechanism for Kerberos authentication. +package spnego + +import ( + "context" + "errors" + "fmt" + + "github.com/jcmturner/gofork/encoding/asn1" + "gopkg.in/jcmturner/gokrb5.v7/asn1tools" + "gopkg.in/jcmturner/gokrb5.v7/client" + "gopkg.in/jcmturner/gokrb5.v7/gssapi" + "gopkg.in/jcmturner/gokrb5.v7/keytab" + "gopkg.in/jcmturner/gokrb5.v7/service" +) + +// SPNEGO implements the GSS-API mechanism for RFC 4178 +type SPNEGO struct { + serviceSettings *service.Settings + client *client.Client + spn string +} + +// SPNEGOClient configures the SPNEGO mechanism suitable for client side use. +func SPNEGOClient(cl *client.Client, spn string) *SPNEGO { + s := new(SPNEGO) + s.client = cl + s.spn = spn + s.serviceSettings = service.NewSettings(nil, service.SName(spn)) + return s +} + +// SPNEGOService configures the SPNEGO mechanism suitable for service side use. +func SPNEGOService(kt *keytab.Keytab, options ...func(*service.Settings)) *SPNEGO { + s := new(SPNEGO) + s.serviceSettings = service.NewSettings(kt, options...) + return s +} + +// OID returns the GSS-API assigned OID for SPNEGO. +func (s *SPNEGO) OID() asn1.ObjectIdentifier { + return gssapi.OID(gssapi.OIDSPNEGO) +} + +// AcquireCred is the GSS-API method to acquire a client credential via Kerberos for SPNEGO. +func (s *SPNEGO) AcquireCred() error { + return s.client.Login() +} + +// InitSecContext is the GSS-API method for the client to a generate a context token to the service via Kerberos. +func (s *SPNEGO) InitSecContext() (gssapi.ContextToken, error) { + tkt, key, err := s.client.GetServiceTicket(s.spn) + if err != nil { + return &SPNEGOToken{}, err + } + negTokenInit, err := NewNegTokenInitKRB5(s.client, tkt, key) + if err != nil { + return &SPNEGOToken{}, fmt.Errorf("could not create NegTokenInit: %v", err) + } + return &SPNEGOToken{ + Init: true, + NegTokenInit: negTokenInit, + settings: s.serviceSettings, + }, nil +} + +// AcceptSecContext is the GSS-API method for the service to verify the context token provided by the client and +// establish a context. +func (s *SPNEGO) AcceptSecContext(ct gssapi.ContextToken) (bool, context.Context, gssapi.Status) { + var ctx context.Context + t, ok := ct.(*SPNEGOToken) + if !ok { + return false, ctx, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "context token provided was not an SPNEGO token"} + } + t.settings = s.serviceSettings + var oid asn1.ObjectIdentifier + if t.Init { + oid = t.NegTokenInit.MechTypes[0] + } + if t.Resp { + oid = t.NegTokenResp.SupportedMech + } + if !(oid.Equal(gssapi.OID(gssapi.OIDKRB5)) || oid.Equal(gssapi.OID(gssapi.OIDMSLegacyKRB5))) { + return false, ctx, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "SPNEGO OID of MechToken is not of type KRB5"} + } + // Flags in the NegInit must be used t.NegTokenInit.ReqFlags + ok, status := t.Verify() + ctx = t.Context() + return ok, ctx, status +} + +// Log will write to the service's logger if it is configured. +func (s *SPNEGO) Log(format string, v ...interface{}) { + if s.serviceSettings.Logger() != nil { + s.serviceSettings.Logger().Printf(format, v...) + } +} + +// SPNEGOToken is a GSS-API context token +type SPNEGOToken struct { + Init bool + Resp bool + NegTokenInit NegTokenInit + NegTokenResp NegTokenResp + settings *service.Settings + context context.Context +} + +// Marshal SPNEGO context token +func (s *SPNEGOToken) Marshal() ([]byte, error) { + var b []byte + if s.Init { + hb, _ := asn1.Marshal(gssapi.OID(gssapi.OIDSPNEGO)) + tb, err := s.NegTokenInit.Marshal() + if err != nil { + return b, fmt.Errorf("could not marshal NegTokenInit: %v", err) + } + b = append(hb, tb...) + return asn1tools.AddASNAppTag(b, 0), nil + } + if s.Resp { + b, err := s.NegTokenResp.Marshal() + if err != nil { + return b, fmt.Errorf("could not marshal NegTokenResp: %v", err) + } + return b, nil + } + return b, errors.New("SPNEGO cannot be marshalled. It contains neither a NegTokenInit or NegTokenResp") +} + +// Unmarshal SPNEGO context token +func (s *SPNEGOToken) Unmarshal(b []byte) error { + var r []byte + var err error + if b[0] != byte(161) { + // Not a NegTokenResp/Targ could be a NegTokenInit + var oid asn1.ObjectIdentifier + r, err = asn1.UnmarshalWithParams(b, &oid, fmt.Sprintf("application,explicit,tag:%v", 0)) + if err != nil { + return fmt.Errorf("not a valid SPNEGO token: %v", err) + } + // Check the OID is the SPNEGO OID value + SPNEGOOID := gssapi.OID(gssapi.OIDSPNEGO) + if !oid.Equal(SPNEGOOID) { + return fmt.Errorf("OID %s does not match SPNEGO OID %s", oid.String(), SPNEGOOID.String()) + } + } else { + // Could be a NegTokenResp/Targ + r = b + } + + _, nt, err := UnmarshalNegToken(r) + if err != nil { + return err + } + switch v := nt.(type) { + case NegTokenInit: + s.Init = true + s.NegTokenInit = v + s.NegTokenInit.settings = s.settings + case NegTokenResp: + s.Resp = true + s.NegTokenResp = v + s.NegTokenResp.settings = s.settings + default: + return errors.New("unknown choice type for NegotiationToken") + } + return nil +} + +// Verify the SPNEGOToken +func (s *SPNEGOToken) Verify() (bool, gssapi.Status) { + if (!s.Init && !s.Resp) || (s.Init && s.Resp) { + return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "invalid SPNEGO token, unclear if NegTokenInit or NegTokenResp"} + } + if s.Init { + s.NegTokenInit.settings = s.settings + ok, status := s.NegTokenInit.Verify() + if ok { + s.context = s.NegTokenInit.Context() + } + return ok, status + } + if s.Resp { + s.NegTokenResp.settings = s.settings + ok, status := s.NegTokenResp.Verify() + if ok { + s.context = s.NegTokenResp.Context() + } + return ok, status + } + // should not be possible to get here + return false, gssapi.Status{Code: gssapi.StatusFailure, Message: "unable to verify SPNEGO token"} +} + +// Context returns the SPNEGO context which will contain any verify user identity information. +func (s *SPNEGOToken) Context() context.Context { + return s.context +} diff --git a/vendor/modules.txt b/vendor/modules.txt index f98dccb3804..f1901f98bca 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1052,6 +1052,8 @@ gopkg.in/inf.v0 gopkg.in/jcmturner/aescts.v1 # gopkg.in/jcmturner/dnsutils.v1 v1.0.1 gopkg.in/jcmturner/dnsutils.v1 +# gopkg.in/jcmturner/goidentity.v3 v3.0.0 +gopkg.in/jcmturner/goidentity.v3 # gopkg.in/jcmturner/gokrb5.v7 v7.3.0 gopkg.in/jcmturner/gokrb5.v7/asn1tools gopkg.in/jcmturner/gokrb5.v7/client @@ -1082,6 +1084,8 @@ gopkg.in/jcmturner/gokrb5.v7/keytab gopkg.in/jcmturner/gokrb5.v7/krberror gopkg.in/jcmturner/gokrb5.v7/messages gopkg.in/jcmturner/gokrb5.v7/pac +gopkg.in/jcmturner/gokrb5.v7/service +gopkg.in/jcmturner/gokrb5.v7/spnego gopkg.in/jcmturner/gokrb5.v7/types # gopkg.in/jcmturner/rpc.v1 v1.1.0 gopkg.in/jcmturner/rpc.v1/mstypes diff --git a/winlogbeat/winlogbeat.reference.yml b/winlogbeat/winlogbeat.reference.yml index d0e11d083e8..471b6c4e7fc 100644 --- a/winlogbeat/winlogbeat.reference.yml +++ b/winlogbeat/winlogbeat.reference.yml @@ -449,6 +449,27 @@ output.elasticsearch: # The pin is a base64 encoded string of the SHA-256 fingerprint. #ssl.ca_sha256: "" + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + + # Authentication type to use with Kerberos. Available options: keytab, password. + #kerberos.auth_type: password + + # Path to the keytab file. It is used when auth_type is set to keytab. + #kerberos.keytab: /etc/elastic.keytab + + # Path to the Kerberos configuration. + #kerberos.config_path: /etc/krb5.conf + + # Name of the Kerberos user. + #kerberos.username: elastic + + # Password of the Kerberos user. It is used when auth_type is set to password. + #kerberos.password: changeme + + # Kerberos realm. + #kerberos.realm: ELASTIC + #----------------------------- Logstash output --------------------------------- #output.logstash: # Boolean flag to enable or disable the output module. @@ -717,6 +738,9 @@ output.elasticsearch: # never, once, and freely. Default is never. #ssl.renegotiation: never + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + # Authentication type to use with Kerberos. Available options: keytab, password. #kerberos.auth_type: password @@ -1303,6 +1327,27 @@ logging.files: # never, once, and freely. Default is never. #ssl.renegotiation: never + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + + # Authentication type to use with Kerberos. Available options: keytab, password. + #kerberos.auth_type: password + + # Path to the keytab file. It is used when auth_type is set to keytab. + #kerberos.keytab: /etc/elastic.keytab + + # Path to the Kerberos configuration. + #kerberos.config_path: /etc/krb5.conf + + # Name of the Kerberos user. + #kerberos.username: elastic + + # Password of the Kerberos user. It is used when auth_type is set to password. + #kerberos.password: changeme + + # Kerberos realm. + #kerberos.realm: ELASTIC + #metrics.period: 10s #state.period: 1m diff --git a/x-pack/auditbeat/auditbeat.reference.yml b/x-pack/auditbeat/auditbeat.reference.yml index a164895d2d0..5e98dab34de 100644 --- a/x-pack/auditbeat/auditbeat.reference.yml +++ b/x-pack/auditbeat/auditbeat.reference.yml @@ -582,6 +582,27 @@ output.elasticsearch: # The pin is a base64 encoded string of the SHA-256 fingerprint. #ssl.ca_sha256: "" + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + + # Authentication type to use with Kerberos. Available options: keytab, password. + #kerberos.auth_type: password + + # Path to the keytab file. It is used when auth_type is set to keytab. + #kerberos.keytab: /etc/elastic.keytab + + # Path to the Kerberos configuration. + #kerberos.config_path: /etc/krb5.conf + + # Name of the Kerberos user. + #kerberos.username: elastic + + # Password of the Kerberos user. It is used when auth_type is set to password. + #kerberos.password: changeme + + # Kerberos realm. + #kerberos.realm: ELASTIC + #----------------------------- Logstash output --------------------------------- #output.logstash: # Boolean flag to enable or disable the output module. @@ -850,6 +871,9 @@ output.elasticsearch: # never, once, and freely. Default is never. #ssl.renegotiation: never + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + # Authentication type to use with Kerberos. Available options: keytab, password. #kerberos.auth_type: password @@ -1436,6 +1460,27 @@ logging.files: # never, once, and freely. Default is never. #ssl.renegotiation: never + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + + # Authentication type to use with Kerberos. Available options: keytab, password. + #kerberos.auth_type: password + + # Path to the keytab file. It is used when auth_type is set to keytab. + #kerberos.keytab: /etc/elastic.keytab + + # Path to the Kerberos configuration. + #kerberos.config_path: /etc/krb5.conf + + # Name of the Kerberos user. + #kerberos.username: elastic + + # Password of the Kerberos user. It is used when auth_type is set to password. + #kerberos.password: changeme + + # Kerberos realm. + #kerberos.realm: ELASTIC + #metrics.period: 10s #state.period: 1m diff --git a/x-pack/filebeat/filebeat.reference.yml b/x-pack/filebeat/filebeat.reference.yml index 3f88fa42976..9ac62c5e34f 100644 --- a/x-pack/filebeat/filebeat.reference.yml +++ b/x-pack/filebeat/filebeat.reference.yml @@ -1970,6 +1970,27 @@ output.elasticsearch: # The pin is a base64 encoded string of the SHA-256 fingerprint. #ssl.ca_sha256: "" + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + + # Authentication type to use with Kerberos. Available options: keytab, password. + #kerberos.auth_type: password + + # Path to the keytab file. It is used when auth_type is set to keytab. + #kerberos.keytab: /etc/elastic.keytab + + # Path to the Kerberos configuration. + #kerberos.config_path: /etc/krb5.conf + + # Name of the Kerberos user. + #kerberos.username: elastic + + # Password of the Kerberos user. It is used when auth_type is set to password. + #kerberos.password: changeme + + # Kerberos realm. + #kerberos.realm: ELASTIC + #----------------------------- Logstash output --------------------------------- #output.logstash: # Boolean flag to enable or disable the output module. @@ -2238,6 +2259,9 @@ output.elasticsearch: # never, once, and freely. Default is never. #ssl.renegotiation: never + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + # Authentication type to use with Kerberos. Available options: keytab, password. #kerberos.auth_type: password @@ -2824,6 +2848,27 @@ logging.files: # never, once, and freely. Default is never. #ssl.renegotiation: never + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + + # Authentication type to use with Kerberos. Available options: keytab, password. + #kerberos.auth_type: password + + # Path to the keytab file. It is used when auth_type is set to keytab. + #kerberos.keytab: /etc/elastic.keytab + + # Path to the Kerberos configuration. + #kerberos.config_path: /etc/krb5.conf + + # Name of the Kerberos user. + #kerberos.username: elastic + + # Password of the Kerberos user. It is used when auth_type is set to password. + #kerberos.password: changeme + + # Kerberos realm. + #kerberos.realm: ELASTIC + #metrics.period: 10s #state.period: 1m diff --git a/x-pack/functionbeat/functionbeat.reference.yml b/x-pack/functionbeat/functionbeat.reference.yml index 0e945a8dea2..9cf9d78c613 100644 --- a/x-pack/functionbeat/functionbeat.reference.yml +++ b/x-pack/functionbeat/functionbeat.reference.yml @@ -792,6 +792,27 @@ output.elasticsearch: # The pin is a base64 encoded string of the SHA-256 fingerprint. #ssl.ca_sha256: "" + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + + # Authentication type to use with Kerberos. Available options: keytab, password. + #kerberos.auth_type: password + + # Path to the keytab file. It is used when auth_type is set to keytab. + #kerberos.keytab: /etc/elastic.keytab + + # Path to the Kerberos configuration. + #kerberos.config_path: /etc/krb5.conf + + # Name of the Kerberos user. + #kerberos.username: elastic + + # Password of the Kerberos user. It is used when auth_type is set to password. + #kerberos.password: changeme + + # Kerberos realm. + #kerberos.realm: ELASTIC + #----------------------------- Logstash output --------------------------------- #output.logstash: # Boolean flag to enable or disable the output module. @@ -1305,6 +1326,27 @@ logging.files: # never, once, and freely. Default is never. #ssl.renegotiation: never + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + + # Authentication type to use with Kerberos. Available options: keytab, password. + #kerberos.auth_type: password + + # Path to the keytab file. It is used when auth_type is set to keytab. + #kerberos.keytab: /etc/elastic.keytab + + # Path to the Kerberos configuration. + #kerberos.config_path: /etc/krb5.conf + + # Name of the Kerberos user. + #kerberos.username: elastic + + # Password of the Kerberos user. It is used when auth_type is set to password. + #kerberos.password: changeme + + # Kerberos realm. + #kerberos.realm: ELASTIC + #metrics.period: 10s #state.period: 1m diff --git a/x-pack/metricbeat/metricbeat.reference.yml b/x-pack/metricbeat/metricbeat.reference.yml index d736b844393..be35d457ced 100644 --- a/x-pack/metricbeat/metricbeat.reference.yml +++ b/x-pack/metricbeat/metricbeat.reference.yml @@ -1704,6 +1704,27 @@ output.elasticsearch: # The pin is a base64 encoded string of the SHA-256 fingerprint. #ssl.ca_sha256: "" + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + + # Authentication type to use with Kerberos. Available options: keytab, password. + #kerberos.auth_type: password + + # Path to the keytab file. It is used when auth_type is set to keytab. + #kerberos.keytab: /etc/elastic.keytab + + # Path to the Kerberos configuration. + #kerberos.config_path: /etc/krb5.conf + + # Name of the Kerberos user. + #kerberos.username: elastic + + # Password of the Kerberos user. It is used when auth_type is set to password. + #kerberos.password: changeme + + # Kerberos realm. + #kerberos.realm: ELASTIC + #----------------------------- Logstash output --------------------------------- #output.logstash: # Boolean flag to enable or disable the output module. @@ -1972,6 +1993,9 @@ output.elasticsearch: # never, once, and freely. Default is never. #ssl.renegotiation: never + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + # Authentication type to use with Kerberos. Available options: keytab, password. #kerberos.auth_type: password @@ -2558,6 +2582,27 @@ logging.files: # never, once, and freely. Default is never. #ssl.renegotiation: never + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + + # Authentication type to use with Kerberos. Available options: keytab, password. + #kerberos.auth_type: password + + # Path to the keytab file. It is used when auth_type is set to keytab. + #kerberos.keytab: /etc/elastic.keytab + + # Path to the Kerberos configuration. + #kerberos.config_path: /etc/krb5.conf + + # Name of the Kerberos user. + #kerberos.username: elastic + + # Password of the Kerberos user. It is used when auth_type is set to password. + #kerberos.password: changeme + + # Kerberos realm. + #kerberos.realm: ELASTIC + #metrics.period: 10s #state.period: 1m diff --git a/x-pack/winlogbeat/winlogbeat.reference.yml b/x-pack/winlogbeat/winlogbeat.reference.yml index a7a7b562ca8..c8643d904ab 100644 --- a/x-pack/winlogbeat/winlogbeat.reference.yml +++ b/x-pack/winlogbeat/winlogbeat.reference.yml @@ -452,6 +452,27 @@ output.elasticsearch: # The pin is a base64 encoded string of the SHA-256 fingerprint. #ssl.ca_sha256: "" + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + + # Authentication type to use with Kerberos. Available options: keytab, password. + #kerberos.auth_type: password + + # Path to the keytab file. It is used when auth_type is set to keytab. + #kerberos.keytab: /etc/elastic.keytab + + # Path to the Kerberos configuration. + #kerberos.config_path: /etc/krb5.conf + + # Name of the Kerberos user. + #kerberos.username: elastic + + # Password of the Kerberos user. It is used when auth_type is set to password. + #kerberos.password: changeme + + # Kerberos realm. + #kerberos.realm: ELASTIC + #----------------------------- Logstash output --------------------------------- #output.logstash: # Boolean flag to enable or disable the output module. @@ -720,6 +741,9 @@ output.elasticsearch: # never, once, and freely. Default is never. #ssl.renegotiation: never + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + # Authentication type to use with Kerberos. Available options: keytab, password. #kerberos.auth_type: password @@ -1306,6 +1330,27 @@ logging.files: # never, once, and freely. Default is never. #ssl.renegotiation: never + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + + # Authentication type to use with Kerberos. Available options: keytab, password. + #kerberos.auth_type: password + + # Path to the keytab file. It is used when auth_type is set to keytab. + #kerberos.keytab: /etc/elastic.keytab + + # Path to the Kerberos configuration. + #kerberos.config_path: /etc/krb5.conf + + # Name of the Kerberos user. + #kerberos.username: elastic + + # Password of the Kerberos user. It is used when auth_type is set to password. + #kerberos.password: changeme + + # Kerberos realm. + #kerberos.realm: ELASTIC + #metrics.period: 10s #state.period: 1m From c1160e3ca099c2479ee289f169e433a8c1cd8d00 Mon Sep 17 00:00:00 2001 From: Chris Mark Date: Wed, 29 Apr 2020 12:31:50 +0300 Subject: [PATCH 047/116] Enable keystore for autodiscover static configuration (#16306) --- CHANGELOG.next.asciidoc | 1 + filebeat/autodiscover/builder/hints/logs.go | 4 +- filebeat/beater/filebeat.go | 1 + .../autodiscover/builder/hints/monitors.go | 4 +- heartbeat/beater/heartbeat.go | 13 ++- .../autodiscover/appenders/config/config.go | 2 +- libbeat/autodiscover/autodiscover.go | 13 +-- libbeat/autodiscover/autodiscover_test.go | 19 ++-- libbeat/autodiscover/provider.go | 7 +- .../autodiscover/providers/docker/docker.go | 7 +- .../docker/docker_integration_test.go | 9 +- .../autodiscover/providers/jolokia/jolokia.go | 7 +- .../providers/kubernetes/kubernetes.go | 7 +- .../providers/kubernetes/node_test.go | 5 + .../providers/kubernetes/pod_test.go | 6 + .../providers/kubernetes/service_test.go | 5 + libbeat/autodiscover/template/config.go | 18 ++- libbeat/autodiscover/template/config_test.go | 87 +++++++++++++++ libbeat/beat/beat.go | 3 + libbeat/cmd/instance/beat.go | 1 + libbeat/docs/shared-autodiscover.asciidoc | 6 + .../autodiscover/builder/hints/metrics.go | 5 +- .../builder/hints/metrics_test.go | 104 ++++++++++++++++++ metricbeat/beater/metricbeat.go | 7 +- .../docs/autodiscover-docker-config.asciidoc | 21 ++++ metricbeat/docs/autodiscover-hints.asciidoc | 4 +- .../autodiscover-kubernetes-config.asciidoc | 21 ++++ .../providers/aws/ec2/provider.go | 9 +- .../providers/aws/ec2/provider_test.go | 4 +- .../providers/aws/elb/provider.go | 9 +- .../providers/aws/elb/provider_test.go | 4 +- 31 files changed, 367 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index b9de5ff716a..bcb358e8ef1 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -204,6 +204,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Add optional regex based cid extractor to `add_kubernetes_metadata` processor. {pull}17360[17360] - Add `urldecode` processor to for decoding URL-encoded fields. {pull}17505[17505] - Add support for AWS IAM `role_arn` in credentials config. {pull}17658[17658] {issue}12464[12464] +- Add keystore support for autodiscover static configurations. {pull]16306[16306] - Add Kerberos support to Elasticsearch output. {pull}17927[17927] *Auditbeat* diff --git a/filebeat/autodiscover/builder/hints/logs.go b/filebeat/autodiscover/builder/hints/logs.go index 05ec4ac7b8f..e2f37caee74 100644 --- a/filebeat/autodiscover/builder/hints/logs.go +++ b/filebeat/autodiscover/builder/hints/logs.go @@ -109,7 +109,7 @@ func (l *logHints) CreateConfig(event bus.Event) []*common.Config { } logp.Debug("hints.builder", "generated config %+v", configs) // Apply information in event to the template to generate the final config - return template.ApplyConfigTemplate(event, configs) + return template.ApplyConfigTemplate(event, configs, false) } tempCfg := common.MapStr{} @@ -163,7 +163,7 @@ func (l *logHints) CreateConfig(event bus.Event) []*common.Config { logp.Debug("hints.builder", "generated config %+v", config) // Apply information in event to the template to generate the final config - return template.ApplyConfigTemplate(event, []*common.Config{config}) + return template.ApplyConfigTemplate(event, []*common.Config{config}, false) } func (l *logHints) getMultiline(hints common.MapStr) common.MapStr { diff --git a/filebeat/beater/filebeat.go b/filebeat/beater/filebeat.go index c339c0a6cc0..b16dad08895 100644 --- a/filebeat/beater/filebeat.go +++ b/filebeat/beater/filebeat.go @@ -317,6 +317,7 @@ func (fb *Filebeat) Run(b *beat.Beat) error { ), autodiscover.QueryConfig(), config.Autodiscover, + b.Keystore, ) if err != nil { return err diff --git a/heartbeat/autodiscover/builder/hints/monitors.go b/heartbeat/autodiscover/builder/hints/monitors.go index ba89d81b456..836b5a9326c 100644 --- a/heartbeat/autodiscover/builder/hints/monitors.go +++ b/heartbeat/autodiscover/builder/hints/monitors.go @@ -91,7 +91,7 @@ func (hb *heartbeatHints) CreateConfig(event bus.Event) []*common.Config { } hb.logger.Debugf("generated config %+v", configs) // Apply information in event to the template to generate the final config - return template.ApplyConfigTemplate(event, configs) + return template.ApplyConfigTemplate(event, configs, false) } tempCfg := common.MapStr{} @@ -121,7 +121,7 @@ func (hb *heartbeatHints) CreateConfig(event bus.Event) []*common.Config { } // Apply information in event to the template to generate the final config - return template.ApplyConfigTemplate(event, configs) + return template.ApplyConfigTemplate(event, configs, false) } func (hb *heartbeatHints) getType(hints common.MapStr) common.MapStr { diff --git a/heartbeat/beater/heartbeat.go b/heartbeat/beater/heartbeat.go index dbdfe1dd740..1ad682fc496 100644 --- a/heartbeat/beater/heartbeat.go +++ b/heartbeat/beater/heartbeat.go @@ -156,7 +156,18 @@ func (bt *Heartbeat) RunReloadableMonitors(b *beat.Beat) (err error) { // makeAutodiscover creates an autodiscover object ready to be started. func (bt *Heartbeat) makeAutodiscover(b *beat.Beat) (*autodiscover.Autodiscover, error) { - return autodiscover.NewAutodiscover("heartbeat", b.Publisher, bt.dynamicFactory, autodiscover.QueryConfig(), bt.config.Autodiscover) + autodiscover, err := autodiscover.NewAutodiscover( + "heartbeat", + b.Publisher, + bt.dynamicFactory, + autodiscover.QueryConfig(), + bt.config.Autodiscover, + b.Keystore, + ) + if err != nil { + return nil, err + } + return autodiscover, nil } // Stop stops the beat. diff --git a/libbeat/autodiscover/appenders/config/config.go b/libbeat/autodiscover/appenders/config/config.go index 018ee1b587d..60f8a543f4a 100644 --- a/libbeat/autodiscover/appenders/config/config.go +++ b/libbeat/autodiscover/appenders/config/config.go @@ -104,7 +104,7 @@ func (c *configAppender) Append(event bus.Event) { } // Apply the template - template.ApplyConfigTemplate(event, cfgs) + template.ApplyConfigTemplate(event, cfgs, false) } // Replace old config with newly appended configs diff --git a/libbeat/autodiscover/autodiscover.go b/libbeat/autodiscover/autodiscover.go index c4941c4b176..668a350b865 100644 --- a/libbeat/autodiscover/autodiscover.go +++ b/libbeat/autodiscover/autodiscover.go @@ -29,6 +29,7 @@ import ( "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/libbeat/common/bus" "github.com/elastic/beats/v7/libbeat/common/reload" + "github.com/elastic/beats/v7/libbeat/keystore" "github.com/elastic/beats/v7/libbeat/logp" ) @@ -72,6 +73,7 @@ func NewAutodiscover( factory cfgfile.RunnerFactory, configurer EventConfigurer, config *Config, + keystore keystore.Keystore, ) (*Autodiscover, error) { logger := logp.NewLogger("autodiscover") @@ -81,7 +83,7 @@ func NewAutodiscover( // Init providers var providers []Provider for _, providerCfg := range config.Providers { - provider, err := Registry.BuildProvider(bus, providerCfg) + provider, err := Registry.BuildProvider(bus, providerCfg, keystore) if err != nil { return nil, errors.Wrap(err, "error in autodiscover provider settings") } @@ -191,10 +193,7 @@ func (a *Autodiscover) handleStart(event bus.Event) bool { if a.logger.IsDebug() { for _, c := range configs { - rc := map[string]interface{}{} - c.Unpack(&rc) - - a.logger.Debugf("Generated config: %+v", rc) + a.logger.Debugf("Generated config: %+v", common.DebugString(c, true)) } } @@ -202,7 +201,7 @@ func (a *Autodiscover) handleStart(event bus.Event) bool { for _, config := range configs { hash, err := cfgfile.HashConfig(config) if err != nil { - a.logger.Debugf("Could not hash config %v: %v", config, err) + a.logger.Debugf("Could not hash config %v: %v", common.DebugString(config, true), err) continue } @@ -216,7 +215,7 @@ func (a *Autodiscover) handleStart(event bus.Event) bool { dynFields := a.meta.Store(hash, meta) if a.configs[eventID][hash] != nil { - a.logger.Debugf("Config %v is already running", config) + a.logger.Debugf("Config %v is already running", common.DebugString(config, true)) continue } diff --git a/libbeat/autodiscover/autodiscover_test.go b/libbeat/autodiscover/autodiscover_test.go index 182cd99f3cb..23a3fe14da3 100644 --- a/libbeat/autodiscover/autodiscover_test.go +++ b/libbeat/autodiscover/autodiscover_test.go @@ -30,6 +30,7 @@ import ( "github.com/elastic/beats/v7/libbeat/cfgfile" "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/libbeat/common/bus" + "github.com/elastic/beats/v7/libbeat/keystore" "github.com/elastic/beats/v7/libbeat/tests/resources" ) @@ -142,7 +143,7 @@ func TestAutodiscover(t *testing.T) { // Register mock autodiscover provider busChan := make(chan bus.Bus, 1) Registry = NewRegistry() - Registry.AddProvider("mock", func(b bus.Bus, uuid uuid.UUID, c *common.Config) (Provider, error) { + Registry.AddProvider("mock", func(b bus.Bus, uuid uuid.UUID, c *common.Config, k keystore.Keystore) (Provider, error) { // intercept bus to mock events busChan <- b @@ -164,9 +165,9 @@ func TestAutodiscover(t *testing.T) { config := Config{ Providers: []*common.Config{providerConfig}, } - + k, _ := keystore.NewFileKeystore("test") // Create autodiscover manager - autodiscover, err := NewAutodiscover("test", nil, &adapter, &adapter, &config) + autodiscover, err := NewAutodiscover("test", nil, &adapter, &adapter, &config, k) if err != nil { t.Fatal(err) } @@ -266,7 +267,7 @@ func TestAutodiscoverHash(t *testing.T) { busChan := make(chan bus.Bus, 1) Registry = NewRegistry() - Registry.AddProvider("mock", func(b bus.Bus, uuid uuid.UUID, c *common.Config) (Provider, error) { + Registry.AddProvider("mock", func(b bus.Bus, uuid uuid.UUID, c *common.Config, k keystore.Keystore) (Provider, error) { // intercept bus to mock events busChan <- b @@ -291,9 +292,9 @@ func TestAutodiscoverHash(t *testing.T) { config := Config{ Providers: []*common.Config{providerConfig}, } - + k, _ := keystore.NewFileKeystore("test") // Create autodiscover manager - autodiscover, err := NewAutodiscover("test", nil, &adapter, &adapter, &config) + autodiscover, err := NewAutodiscover("test", nil, &adapter, &adapter, &config, k) if err != nil { t.Fatal(err) } @@ -332,7 +333,7 @@ func TestAutodiscoverWithConfigCheckFailures(t *testing.T) { // Register mock autodiscover provider busChan := make(chan bus.Bus, 1) Registry = NewRegistry() - Registry.AddProvider("mock", func(b bus.Bus, uuid uuid.UUID, c *common.Config) (Provider, error) { + Registry.AddProvider("mock", func(b bus.Bus, uuid uuid.UUID, c *common.Config, k keystore.Keystore) (Provider, error) { // intercept bus to mock events busChan <- b @@ -357,9 +358,9 @@ func TestAutodiscoverWithConfigCheckFailures(t *testing.T) { config := Config{ Providers: []*common.Config{providerConfig}, } - + k, _ := keystore.NewFileKeystore("test") // Create autodiscover manager - autodiscover, err := NewAutodiscover("test", nil, &adapter, &adapter, &config) + autodiscover, err := NewAutodiscover("test", nil, &adapter, &adapter, &config, k) if err != nil { t.Fatal(err) } diff --git a/libbeat/autodiscover/provider.go b/libbeat/autodiscover/provider.go index f7bfefc69d0..510e09ab4bf 100644 --- a/libbeat/autodiscover/provider.go +++ b/libbeat/autodiscover/provider.go @@ -26,6 +26,7 @@ import ( "github.com/elastic/beats/v7/libbeat/cfgfile" "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/libbeat/common/bus" + "github.com/elastic/beats/v7/libbeat/keystore" ) // Provider for autodiscover @@ -34,7 +35,7 @@ type Provider interface { } // ProviderBuilder creates a new provider based on the given config and returns it -type ProviderBuilder func(bus.Bus, uuid.UUID, *common.Config) (Provider, error) +type ProviderBuilder func(bus.Bus, uuid.UUID, *common.Config, keystore.Keystore) (Provider, error) // AddProvider registers a new ProviderBuilder func (r *registry) AddProvider(name string, provider ProviderBuilder) error { @@ -69,7 +70,7 @@ func (r *registry) GetProvider(name string) ProviderBuilder { } // BuildProvider reads provider configuration and instantiate one -func (r *registry) BuildProvider(bus bus.Bus, c *common.Config) (Provider, error) { +func (r *registry) BuildProvider(bus bus.Bus, c *common.Config, keystore keystore.Keystore) (Provider, error) { var config ProviderConfig err := c.Unpack(&config) if err != nil { @@ -86,5 +87,5 @@ func (r *registry) BuildProvider(bus bus.Bus, c *common.Config) (Provider, error return nil, err } - return builder(bus, uuid, c) + return builder(bus, uuid, c, keystore) } diff --git a/libbeat/autodiscover/providers/docker/docker.go b/libbeat/autodiscover/providers/docker/docker.go index 42a551ebf72..9bfa13000b1 100644 --- a/libbeat/autodiscover/providers/docker/docker.go +++ b/libbeat/autodiscover/providers/docker/docker.go @@ -33,6 +33,7 @@ import ( "github.com/elastic/beats/v7/libbeat/common/bus" "github.com/elastic/beats/v7/libbeat/common/docker" "github.com/elastic/beats/v7/libbeat/common/safemapstr" + "github.com/elastic/beats/v7/libbeat/keystore" "github.com/elastic/beats/v7/libbeat/logp" ) @@ -55,10 +56,11 @@ type Provider struct { stoppers map[string]*time.Timer stopTrigger chan *dockerContainerMetadata logger *logp.Logger + keystore keystore.Keystore } // AutodiscoverBuilder builds and returns an autodiscover provider -func AutodiscoverBuilder(bus bus.Bus, uuid uuid.UUID, c *common.Config) (autodiscover.Provider, error) { +func AutodiscoverBuilder(bus bus.Bus, uuid uuid.UUID, c *common.Config, keystore keystore.Keystore) (autodiscover.Provider, error) { logger := logp.NewLogger("docker") errWrap := func(err error) error { @@ -115,6 +117,7 @@ func AutodiscoverBuilder(bus bus.Bus, uuid uuid.UUID, c *common.Config) (autodis stoppers: make(map[string]*time.Timer), stopTrigger: make(chan *dockerContainerMetadata), logger: logger, + keystore: keystore, }, nil } @@ -303,6 +306,8 @@ func (d *Provider) emitContainer(container *docker.Container, meta *dockerMetada } func (d *Provider) publish(event bus.Event) { + // attach keystore to the event to be consumed by the static configs + event["keystore"] = d.keystore // Try to match a config if config := d.templates.GetConfig(event); config != nil { event["config"] = config diff --git a/libbeat/autodiscover/providers/docker/docker_integration_test.go b/libbeat/autodiscover/providers/docker/docker_integration_test.go index b8afbafbb62..0e10af438ff 100644 --- a/libbeat/autodiscover/providers/docker/docker_integration_test.go +++ b/libbeat/autodiscover/providers/docker/docker_integration_test.go @@ -23,14 +23,14 @@ import ( "testing" "time" - "github.com/elastic/beats/v7/libbeat/autodiscover/template" - "github.com/elastic/beats/v7/libbeat/logp" - "github.com/gofrs/uuid" "github.com/stretchr/testify/assert" + "github.com/elastic/beats/v7/libbeat/autodiscover/template" "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/libbeat/common/bus" + "github.com/elastic/beats/v7/libbeat/keystore" + "github.com/elastic/beats/v7/libbeat/logp" dk "github.com/elastic/beats/v7/libbeat/tests/docker" ) @@ -53,7 +53,8 @@ func TestDockerStart(t *testing.T) { s := &template.MapperSettings{nil, nil} config.Templates = *s - provider, err := AutodiscoverBuilder(bus, UUID, common.MustNewConfigFrom(config)) + k, _ := keystore.NewFileKeystore("test") + provider, err := AutodiscoverBuilder(bus, UUID, common.MustNewConfigFrom(config), k) if err != nil { t.Fatal(err) } diff --git a/libbeat/autodiscover/providers/jolokia/jolokia.go b/libbeat/autodiscover/providers/jolokia/jolokia.go index b370d747b89..4a18ffffec9 100644 --- a/libbeat/autodiscover/providers/jolokia/jolokia.go +++ b/libbeat/autodiscover/providers/jolokia/jolokia.go @@ -27,6 +27,7 @@ import ( "github.com/elastic/beats/v7/libbeat/autodiscover/template" "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/libbeat/common/bus" + "github.com/elastic/beats/v7/libbeat/keystore" ) func init() { @@ -48,11 +49,12 @@ type Provider struct { appenders autodiscover.Appenders templates template.Mapper discovery DiscoveryProber + keystore keystore.Keystore } // AutodiscoverBuilder builds a Jolokia Discovery autodiscover provider, it fails if // there is some problem with the configuration -func AutodiscoverBuilder(bus bus.Bus, uuid uuid.UUID, c *common.Config) (autodiscover.Provider, error) { +func AutodiscoverBuilder(bus bus.Bus, uuid uuid.UUID, c *common.Config, keystore keystore.Keystore) (autodiscover.Provider, error) { errWrap := func(err error) error { return errors.Wrap(err, "error setting up jolokia autodiscover provider") } @@ -92,6 +94,7 @@ func AutodiscoverBuilder(bus bus.Bus, uuid uuid.UUID, c *common.Config) (autodis builders: builders, appenders: appenders, discovery: discovery, + keystore: keystore, }, nil } @@ -106,6 +109,8 @@ func (p *Provider) Start() { } func (p *Provider) publish(event bus.Event) { + // attach keystore to the event to be consumed by the static configs + event["keystore"] = p.keystore if config := p.templates.GetConfig(event); config != nil { event["config"] = config } else if config := p.builders.GetConfig(event); config != nil { diff --git a/libbeat/autodiscover/providers/kubernetes/kubernetes.go b/libbeat/autodiscover/providers/kubernetes/kubernetes.go index ec3480fa00b..4a4a4566f8e 100644 --- a/libbeat/autodiscover/providers/kubernetes/kubernetes.go +++ b/libbeat/autodiscover/providers/kubernetes/kubernetes.go @@ -30,6 +30,7 @@ import ( "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/libbeat/common/bus" "github.com/elastic/beats/v7/libbeat/common/kubernetes" + "github.com/elastic/beats/v7/libbeat/keystore" "github.com/elastic/beats/v7/libbeat/logp" ) @@ -54,10 +55,11 @@ type Provider struct { appenders autodiscover.Appenders logger *logp.Logger eventer Eventer + keystore keystore.Keystore } // AutodiscoverBuilder builds and returns an autodiscover provider -func AutodiscoverBuilder(bus bus.Bus, uuid uuid.UUID, c *common.Config) (autodiscover.Provider, error) { +func AutodiscoverBuilder(bus bus.Bus, uuid uuid.UUID, c *common.Config, keystore keystore.Keystore) (autodiscover.Provider, error) { logger := logp.NewLogger("autodiscover") errWrap := func(err error) error { @@ -97,6 +99,7 @@ func AutodiscoverBuilder(bus bus.Bus, uuid uuid.UUID, c *common.Config) (autodis builders: builders, appenders: appenders, logger: logger, + keystore: keystore, } switch config.Resource { @@ -135,6 +138,8 @@ func (p *Provider) String() string { } func (p *Provider) publish(event bus.Event) { + // attach keystore to the event to be consumed by the static configs + event["keystore"] = p.keystore // Try to match a config if config := p.templates.GetConfig(event); config != nil { event["config"] = config diff --git a/libbeat/autodiscover/providers/kubernetes/node_test.go b/libbeat/autodiscover/providers/kubernetes/node_test.go index 0685adfe1bd..f2fbe78dba6 100644 --- a/libbeat/autodiscover/providers/kubernetes/node_test.go +++ b/libbeat/autodiscover/providers/kubernetes/node_test.go @@ -33,6 +33,7 @@ import ( "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/libbeat/common/bus" "github.com/elastic/beats/v7/libbeat/common/kubernetes" + "github.com/elastic/beats/v7/libbeat/keystore" "github.com/elastic/beats/v7/libbeat/logp" ) @@ -112,6 +113,7 @@ func TestGenerateHints_Node(t *testing.T) { } func TestEmitEvent_Node(t *testing.T) { + k, _ := keystore.NewFileKeystore("test") name := "metricbeat" nodeIP := "192.168.0.1" uid := "005f3b90-4b9d-12f8-acf0-31020a840133" @@ -160,6 +162,7 @@ func TestEmitEvent_Node(t *testing.T) { "host": "192.168.0.1", "id": uid, "provider": UUID, + "keystore": k, "kubernetes": common.MapStr{ "node": common.MapStr{ "name": "metricbeat", @@ -219,6 +222,7 @@ func TestEmitEvent_Node(t *testing.T) { "host": "", "id": uid, "provider": UUID, + "keystore": k, "kubernetes": common.MapStr{ "node": common.MapStr{ "name": "metricbeat", @@ -252,6 +256,7 @@ func TestEmitEvent_Node(t *testing.T) { bus: bus.New(logp.NewLogger("bus"), "test"), templates: mapper, logger: logp.NewLogger("kubernetes"), + keystore: k, } no := &node{ diff --git a/libbeat/autodiscover/providers/kubernetes/pod_test.go b/libbeat/autodiscover/providers/kubernetes/pod_test.go index f63dbdf1e73..05b50987b2e 100644 --- a/libbeat/autodiscover/providers/kubernetes/pod_test.go +++ b/libbeat/autodiscover/providers/kubernetes/pod_test.go @@ -33,6 +33,7 @@ import ( "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/libbeat/common/bus" "github.com/elastic/beats/v7/libbeat/common/kubernetes" + "github.com/elastic/beats/v7/libbeat/keystore" "github.com/elastic/beats/v7/libbeat/logp" ) @@ -332,6 +333,7 @@ func TestGenerateHints(t *testing.T) { } func TestEmitEvent(t *testing.T) { + k, _ := keystore.NewFileKeystore("test") name := "filebeat" namespace := "default" podIP := "127.0.0.1" @@ -395,6 +397,7 @@ func TestEmitEvent(t *testing.T) { "host": "127.0.0.1", "id": cid, "provider": UUID, + "keystore": k, "kubernetes": common.MapStr{ "container": common.MapStr{ "id": "foobar", @@ -527,6 +530,7 @@ func TestEmitEvent(t *testing.T) { "host": "", "id": cid, "provider": UUID, + "keystore": k, "kubernetes": common.MapStr{ "container": common.MapStr{ "id": "", @@ -596,6 +600,7 @@ func TestEmitEvent(t *testing.T) { "host": "127.0.0.1", "id": cid, "provider": UUID, + "keystore": k, "kubernetes": common.MapStr{ "container": common.MapStr{ "id": "", @@ -645,6 +650,7 @@ func TestEmitEvent(t *testing.T) { bus: bus.New(logp.NewLogger("bus"), "test"), templates: mapper, logger: logp.NewLogger("kubernetes"), + keystore: k, } pod := &pod{ diff --git a/libbeat/autodiscover/providers/kubernetes/service_test.go b/libbeat/autodiscover/providers/kubernetes/service_test.go index 6d6582b3ff2..0e3c8ddb0a8 100644 --- a/libbeat/autodiscover/providers/kubernetes/service_test.go +++ b/libbeat/autodiscover/providers/kubernetes/service_test.go @@ -33,6 +33,7 @@ import ( "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/libbeat/common/bus" "github.com/elastic/beats/v7/libbeat/common/kubernetes" + "github.com/elastic/beats/v7/libbeat/keystore" "github.com/elastic/beats/v7/libbeat/logp" ) @@ -233,6 +234,7 @@ func TestGenerateHints_Service(t *testing.T) { } func TestEmitEvent_Service(t *testing.T) { + k, _ := keystore.NewFileKeystore("test") name := "metricbeat" namespace := "default" clusterIP := "192.168.0.1" @@ -280,6 +282,7 @@ func TestEmitEvent_Service(t *testing.T) { "host": "192.168.0.1", "id": uid, "provider": UUID, + "keystore": k, "port": 8080, "kubernetes": common.MapStr{ "service": common.MapStr{ @@ -369,6 +372,7 @@ func TestEmitEvent_Service(t *testing.T) { "id": uid, "port": 8080, "provider": UUID, + "keystore": k, "kubernetes": common.MapStr{ "service": common.MapStr{ "name": "metricbeat", @@ -405,6 +409,7 @@ func TestEmitEvent_Service(t *testing.T) { bus: bus.New(logp.NewLogger("bus"), "test"), templates: mapper, logger: logp.NewLogger("kubernetes"), + keystore: k, } service := &service{ diff --git a/libbeat/autodiscover/template/config.go b/libbeat/autodiscover/template/config.go index 151f76dde0f..0ce05526ecb 100644 --- a/libbeat/autodiscover/template/config.go +++ b/libbeat/autodiscover/template/config.go @@ -21,6 +21,7 @@ import ( "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/libbeat/common/bus" "github.com/elastic/beats/v7/libbeat/conditions" + "github.com/elastic/beats/v7/libbeat/keystore" "github.com/elastic/beats/v7/libbeat/logp" "github.com/elastic/go-ucfg" ) @@ -81,7 +82,7 @@ func (c Mapper) GetConfig(event bus.Event) []*common.Config { continue } - configs := ApplyConfigTemplate(event, mapping.Configs) + configs := ApplyConfigTemplate(event, mapping.Configs, true) if configs != nil { result = append(result, configs...) } @@ -90,7 +91,7 @@ func (c Mapper) GetConfig(event bus.Event) []*common.Config { } // ApplyConfigTemplate takes a set of templated configs and applys information in an event map -func ApplyConfigTemplate(event bus.Event, configs []*common.Config) []*common.Config { +func ApplyConfigTemplate(event bus.Event, configs []*common.Config, keystoreEnabled bool) []*common.Config { var result []*common.Config // unpack input vars, err := ucfg.NewFrom(map[string]interface{}{ @@ -105,6 +106,19 @@ func ApplyConfigTemplate(event bus.Event, configs []*common.Config) []*common.Co ucfg.ResolveEnv, ucfg.VarExp, } + + if keystoreEnabled { + if val, ok := event["keystore"]; ok { + eventKeystore := val.(keystore.Keystore) + opts = append(opts, ucfg.Resolve(keystore.ResolverWrap(eventKeystore))) + delete(event, "keystore") + } + } else { + if _, ok := event["keystore"]; ok { + delete(event, "keystore") + } + } + for _, config := range configs { c, err := ucfg.NewFrom(config, opts...) if err != nil { diff --git a/libbeat/autodiscover/template/config_test.go b/libbeat/autodiscover/template/config_test.go index 570de15a840..ccb27a7127a 100644 --- a/libbeat/autodiscover/template/config_test.go +++ b/libbeat/autodiscover/template/config_test.go @@ -18,12 +18,16 @@ package template import ( + "os" + "path/filepath" "testing" + "github.com/docker/docker/pkg/ioutils" "github.com/stretchr/testify/assert" "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/libbeat/common/bus" + "github.com/elastic/beats/v7/libbeat/keystore" ) func TestConfigsMapping(t *testing.T) { @@ -93,6 +97,61 @@ func TestConfigsMapping(t *testing.T) { } } +func TestConfigsMappingKeystore(t *testing.T) { + secret := "mapping_secret" + //expected config + config, _ := common.NewConfigFrom(map[string]interface{}{ + "correct": "config", + "password": secret, + }) + + path := getTemporaryKeystoreFile() + defer os.Remove(path) + // store the secret + keystore := createAnExistingKeystore(path, secret) + + tests := []struct { + mapping string + event bus.Event + expected []*common.Config + }{ + // Match config + { + mapping: ` +- condition.equals: + foo: 3 + config: + - correct: config + password: "${PASSWORD}"`, + event: bus.Event{ + "foo": 3, + "keystore": keystore, + }, + expected: []*common.Config{config}, + }, + } + + for _, test := range tests { + var mappings MapperSettings + config, err := common.NewConfigWithYAML([]byte(test.mapping), "") + if err != nil { + t.Fatal(err) + } + + if err := config.Unpack(&mappings); err != nil { + t.Fatal(err) + } + + mapper, err := NewConfigMapper(mappings) + if err != nil { + t.Fatal(err) + } + + res := mapper.GetConfig(test.event) + assert.Equal(t, test.expected, res) + } +} + func TestNilConditionConfig(t *testing.T) { var mappings MapperSettings data := ` @@ -111,3 +170,31 @@ func TestNilConditionConfig(t *testing.T) { assert.NoError(t, err) assert.Nil(t, mappings[0].ConditionConfig) } + +// create a keystore with an existing key +/// `PASSWORD` with the value of `secret` variable. +func createAnExistingKeystore(path string, secret string) keystore.Keystore { + keyStore, err := keystore.NewFileKeystore(path) + // Fail fast in the test suite + if err != nil { + panic(err) + } + + writableKeystore, err := keystore.AsWritableKeystore(keyStore) + if err != nil { + panic(err) + } + + writableKeystore.Store("PASSWORD", []byte(secret)) + writableKeystore.Save() + return keyStore +} + +// create a temporary file on disk to save the keystore. +func getTemporaryKeystoreFile() string { + path, err := ioutils.TempDir("", "testing") + if err != nil { + panic(err) + } + return filepath.Join(path, "keystore") +} diff --git a/libbeat/beat/beat.go b/libbeat/beat/beat.go index 3aaed4c6641..75585ba8992 100644 --- a/libbeat/beat/beat.go +++ b/libbeat/beat/beat.go @@ -19,6 +19,7 @@ package beat import ( "github.com/elastic/beats/v7/libbeat/common" + "github.com/elastic/beats/v7/libbeat/keystore" "github.com/elastic/beats/v7/libbeat/management" ) @@ -66,6 +67,8 @@ type Beat struct { Fields []byte // Data from fields.yml ConfigManager management.ConfigManager // config manager + + Keystore keystore.Keystore } // BeatConfig struct contains the basic configuration of every beat diff --git a/libbeat/cmd/instance/beat.go b/libbeat/cmd/instance/beat.go index a9e59173633..e2b4d7d6630 100644 --- a/libbeat/cmd/instance/beat.go +++ b/libbeat/cmd/instance/beat.go @@ -584,6 +584,7 @@ func (b *Beat) configure(settings Settings) error { } b.keystore = store + b.Beat.Keystore = store err = cloudid.OverwriteSettings(cfg) if err != nil { return err diff --git a/libbeat/docs/shared-autodiscover.asciidoc b/libbeat/docs/shared-autodiscover.asciidoc index a3911a65490..22a72a9b52e 100644 --- a/libbeat/docs/shared-autodiscover.asciidoc +++ b/libbeat/docs/shared-autodiscover.asciidoc @@ -18,6 +18,7 @@ to set conditions that, when met, launch specific configurations. On start, {beatname_uc} will scan existing containers and launch the proper configs for them. Then it will watch for new start/stop events. This ensures you don't need to worry about state, but only define your desired configs. +ifdef::autodiscoverDocker[] [float] ===== Docker @@ -124,6 +125,10 @@ running configuration for a container, 60s by default. ======================================= endif::[] +endif::autodiscoverDocker[] + + +ifdef::autodiscoverKubernetes[] [float] ===== Kubernetes @@ -243,6 +248,7 @@ running configuration for a container, 60s by default. include::../../{beatname_lc}/docs/autodiscover-kubernetes-config.asciidoc[] +endif::autodiscoverKubernetes[] [float] ===== Manually Defining Ports with Kubernetes diff --git a/metricbeat/autodiscover/builder/hints/metrics.go b/metricbeat/autodiscover/builder/hints/metrics.go index 3e859b23d11..1647fb9fbc7 100644 --- a/metricbeat/autodiscover/builder/hints/metrics.go +++ b/metricbeat/autodiscover/builder/hints/metrics.go @@ -84,6 +84,7 @@ func (m *metricHints) CreateConfig(event bus.Event) []*common.Config { } modulesConfig := m.getModules(hints) + // here we handle raw configs if provided if modulesConfig != nil { configs := []*common.Config{} for _, cfg := range modulesConfig { @@ -93,7 +94,7 @@ func (m *metricHints) CreateConfig(event bus.Event) []*common.Config { } logp.Debug("hints.builder", "generated config %+v", configs) // Apply information in event to the template to generate the final config - return template.ApplyConfigTemplate(event, configs) + return template.ApplyConfigTemplate(event, configs, false) } @@ -154,7 +155,7 @@ func (m *metricHints) CreateConfig(event bus.Event) []*common.Config { // Apply information in event to the template to generate the final config // This especially helps in a scenario where endpoints are configured as: // co.elastic.metrics/hosts= "${data.host}:9090" - return template.ApplyConfigTemplate(event, config) + return template.ApplyConfigTemplate(event, config, false) } func (m *metricHints) getModule(hints common.MapStr) string { diff --git a/metricbeat/autodiscover/builder/hints/metrics_test.go b/metricbeat/autodiscover/builder/hints/metrics_test.go index f3f9d5a5200..4b3f7e0430b 100644 --- a/metricbeat/autodiscover/builder/hints/metrics_test.go +++ b/metricbeat/autodiscover/builder/hints/metrics_test.go @@ -18,13 +18,17 @@ package hints import ( + "os" + "path/filepath" "sort" "testing" + "github.com/docker/docker/pkg/ioutils" "github.com/stretchr/testify/assert" "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/libbeat/common/bus" + "github.com/elastic/beats/v7/libbeat/keystore" "github.com/elastic/beats/v7/metricbeat/mb" ) @@ -324,6 +328,78 @@ func TestGenerateHints(t *testing.T) { } } +func TestGenerateHintsDoesNotAccessKeystore(t *testing.T) { + path := getTemporaryKeystoreFile() + defer os.Remove(path) + // store the secret + keystore := createAnExistingKeystore(path, "stored_secret") + os.Setenv("PASSWORD", "env_secret") + + tests := []struct { + message string + event bus.Event + len int + result common.MapStr + }{ + { + message: "Module, namespace, host hint should return valid config", + event: bus.Event{ + "host": "1.2.3.4", + "port": 9090, + "hints": common.MapStr{ + "metrics": common.MapStr{ + "module": "mockmoduledefaults", + "hosts": "${data.host}:9090", + "password": "${PASSWORD}", + }, + }, + "keystore": keystore, + }, + len: 1, + result: common.MapStr{ + "module": "mockmoduledefaults", + "metricsets": []string{"default"}, + "hosts": []interface{}{"1.2.3.4:9090"}, + "timeout": "3s", + "period": "1m", + "enabled": true, + "password": "env_secret", + }, + }, + } + for _, test := range tests { + mockRegister := mb.NewRegister() + mockRegister.MustAddMetricSet("mockmoduledefaults", "default", NewMockMetricSet, mb.DefaultMetricSet()) + + m := metricHints{ + Key: defaultConfig().Key, + Registry: mockRegister, + } + cfgs := m.CreateConfig(test.event) + assert.Equal(t, len(cfgs), test.len) + if len(cfgs) != 0 { + config := common.MapStr{} + err := cfgs[0].Unpack(&config) + assert.Nil(t, err, test.message) + + // metricsets order is random, order it for tests + if v, err := config.GetValue("metricsets"); err == nil { + if msets, ok := v.([]interface{}); ok { + metricsets := make([]string, len(msets)) + for i, v := range msets { + metricsets[i] = v.(string) + } + sort.Strings(metricsets) + config["metricsets"] = metricsets + } + } + + assert.Equal(t, test.result, config, test.message) + } + + } +} + type MockMetricSet struct { mb.BaseMetricSet } @@ -335,3 +411,31 @@ func NewMockMetricSet(base mb.BaseMetricSet) (mb.MetricSet, error) { func (ms *MockMetricSet) Fetch(report mb.Reporter) { } + +// create a keystore with an existing key +/// `PASSWORD` with the value of `secret` variable. +func createAnExistingKeystore(path string, secret string) keystore.Keystore { + keyStore, err := keystore.NewFileKeystore(path) + // Fail fast in the test suite + if err != nil { + panic(err) + } + + writableKeystore, err := keystore.AsWritableKeystore(keyStore) + if err != nil { + panic(err) + } + + writableKeystore.Store("PASSWORD", []byte(secret)) + writableKeystore.Save() + return keyStore +} + +// create a temporary file on disk to save the keystore. +func getTemporaryKeystoreFile() string { + path, err := ioutils.TempDir("", "testing") + if err != nil { + panic(err) + } + return filepath.Join(path, "keystore") +} diff --git a/metricbeat/beater/metricbeat.go b/metricbeat/beater/metricbeat.go index 050e7f265c1..fbf9d23110f 100644 --- a/metricbeat/beater/metricbeat.go +++ b/metricbeat/beater/metricbeat.go @@ -181,7 +181,12 @@ func newMetricbeat(b *beat.Beat, c *common.Config, options ...Option) (*Metricbe if config.Autodiscover != nil { var err error metricbeat.autodiscover, err = autodiscover.NewAutodiscover( - "metricbeat", b.Publisher, factory, autodiscover.QueryConfig(), config.Autodiscover) + "metricbeat", + b.Publisher, + factory, autodiscover.QueryConfig(), + config.Autodiscover, + b.Keystore, + ) if err != nil { return nil, err } diff --git a/metricbeat/docs/autodiscover-docker-config.asciidoc b/metricbeat/docs/autodiscover-docker-config.asciidoc index 79dce08e7a6..bd026ec0c69 100644 --- a/metricbeat/docs/autodiscover-docker-config.asciidoc +++ b/metricbeat/docs/autodiscover-docker-config.asciidoc @@ -19,3 +19,24 @@ metricbeat.autodiscover: This configuration launches a `redis` module for all containers running an image with `redis` in the name. `labels.dedot` defaults to be `true` for docker autodiscover, which means dots in docker labels are replaced with '_' by default. +Also Metricbeat autodiscover supports leveraging <> in order to retrieve sensitive data like passwords. +Here is an example of how a configuration using keystore would look like: + +["source","yaml",subs="attributes"] +------------------------------------------------------------------------------------- +metricbeat.autodiscover: + providers: + - type: docker + labels.dedot: true + templates: + - condition: + contains: + docker.container.image: redis + config: + - module: redis + metricsets: ["info", "keyspace"] + hosts: "${data.host}:6379" + password: "${REDIS_PASSWORD}" +------------------------------------------------------------------------------------- + +where `REDIS_PASSWORD` is a key stored in local keystore of Metricbeat. diff --git a/metricbeat/docs/autodiscover-hints.asciidoc b/metricbeat/docs/autodiscover-hints.asciidoc index 1bc56d8f54d..a34b623bd36 100644 --- a/metricbeat/docs/autodiscover-hints.asciidoc +++ b/metricbeat/docs/autodiscover-hints.asciidoc @@ -43,7 +43,9 @@ The username to use for authentication [float] ===== `co.elastic.metrics/password` -The password to use for authentication. It is recommended to retrieve this sensitive information from an ENV variable or a keystore and avoid placing passwords in plain text +The password to use for authentication. It is recommended to retrieve this sensitive information from an ENV variable +and avoid placing passwords in plain text. Unlike static autodiscover configuration, hints based autodiscover has +no access to the keystore of Metricbeat since it could be a potential security issue. [float] ===== `co.elastic.metrics/ssl.*` diff --git a/metricbeat/docs/autodiscover-kubernetes-config.asciidoc b/metricbeat/docs/autodiscover-kubernetes-config.asciidoc index 81e4ad15949..f3e6a74cdfb 100644 --- a/metricbeat/docs/autodiscover-kubernetes-config.asciidoc +++ b/metricbeat/docs/autodiscover-kubernetes-config.asciidoc @@ -17,3 +17,24 @@ metricbeat.autodiscover: ------------------------------------------------------------------------------------- This configuration launches a `prometheus` module for all containers of pods annotated `prometheus.io/scrape=true`. + +Also Metricbeat autodiscover supports leveraging <> in order to retrieve sensitive data like passwords. +Here is an example of how a configuration using keystore would look like: + +["source","yaml",subs="attributes"] +------------------------------------------------------------------------------------- +metricbeat.autodiscover: + providers: + - type: kubernetes + templates: + - condition: + contains: + kubernetes.labels.app: "redis" + config: + - module: redis + metricsets: ["info", "keyspace"] + hosts: "${data.host}:6379" + password: "${REDIS_PASSWORD}" +------------------------------------------------------------------------------------- + +where `REDIS_PASSWORD` is a key stored in local keystore of Metricbeat. diff --git a/x-pack/libbeat/autodiscover/providers/aws/ec2/provider.go b/x-pack/libbeat/autodiscover/providers/aws/ec2/provider.go index 15d8015e52c..4d457c46a8c 100644 --- a/x-pack/libbeat/autodiscover/providers/aws/ec2/provider.go +++ b/x-pack/libbeat/autodiscover/providers/aws/ec2/provider.go @@ -15,6 +15,7 @@ import ( "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/libbeat/common/bus" "github.com/elastic/beats/v7/libbeat/common/cfgwarn" + "github.com/elastic/beats/v7/libbeat/keystore" "github.com/elastic/beats/v7/libbeat/logp" awsauto "github.com/elastic/beats/v7/x-pack/libbeat/autodiscover/providers/aws" awscommon "github.com/elastic/beats/v7/x-pack/libbeat/common/aws" @@ -33,10 +34,11 @@ type Provider struct { stopListener bus.Listener watcher *watcher uuid uuid.UUID + keystore keystore.Keystore } // AutodiscoverBuilder is the main builder for this provider. -func AutodiscoverBuilder(bus bus.Bus, uuid uuid.UUID, c *common.Config) (autodiscover.Provider, error) { +func AutodiscoverBuilder(bus bus.Bus, uuid uuid.UUID, c *common.Config, keystore keystore.Keystore) (autodiscover.Provider, error) { cfgwarn.Experimental("aws_ec2 autodiscover is experimental") config := awsauto.DefaultConfig() @@ -78,12 +80,12 @@ func AutodiscoverBuilder(bus bus.Bus, uuid uuid.UUID, c *common.Config) (autodis config.AWSConfig.Endpoint, "ec2", region, awsCfg))) } - return internalBuilder(uuid, bus, config, newAPIFetcher(clients)) + return internalBuilder(uuid, bus, config, newAPIFetcher(clients), keystore) } // internalBuilder is mainly intended for testing via mocks and stubs. // it can be configured to use a fetcher that doesn't actually hit the AWS API. -func internalBuilder(uuid uuid.UUID, bus bus.Bus, config *awsauto.Config, fetcher fetcher) (*Provider, error) { +func internalBuilder(uuid uuid.UUID, bus bus.Bus, config *awsauto.Config, fetcher fetcher, keystore keystore.Keystore) (*Provider, error) { mapper, err := template.NewConfigMapper(config.Templates) if err != nil { return nil, err @@ -94,6 +96,7 @@ func internalBuilder(uuid uuid.UUID, bus bus.Bus, config *awsauto.Config, fetche bus: bus, templates: &mapper, uuid: uuid, + keystore: keystore, } p.watcher = newWatcher( diff --git a/x-pack/libbeat/autodiscover/providers/aws/ec2/provider_test.go b/x-pack/libbeat/autodiscover/providers/aws/ec2/provider_test.go index 255ae2e141b..b22321eeb23 100644 --- a/x-pack/libbeat/autodiscover/providers/aws/ec2/provider_test.go +++ b/x-pack/libbeat/autodiscover/providers/aws/ec2/provider_test.go @@ -15,6 +15,7 @@ import ( "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/libbeat/common/bus" + "github.com/elastic/beats/v7/libbeat/keystore" "github.com/elastic/beats/v7/libbeat/logp" awsauto "github.com/elastic/beats/v7/x-pack/libbeat/autodiscover/providers/aws" "github.com/elastic/beats/v7/x-pack/libbeat/autodiscover/providers/aws/test" @@ -33,7 +34,8 @@ func Test_internalBuilder(t *testing.T) { } uuid, _ := uuid.NewV4() - provider, err := internalBuilder(uuid, pBus, cfg, fetcher) + k, _ := keystore.NewFileKeystore("test") + provider, err := internalBuilder(uuid, pBus, cfg, fetcher, k) require.NoError(t, err) startListener := pBus.Subscribe("start") diff --git a/x-pack/libbeat/autodiscover/providers/aws/elb/provider.go b/x-pack/libbeat/autodiscover/providers/aws/elb/provider.go index 8513e0f9d2a..522b5ba9a4f 100644 --- a/x-pack/libbeat/autodiscover/providers/aws/elb/provider.go +++ b/x-pack/libbeat/autodiscover/providers/aws/elb/provider.go @@ -15,6 +15,7 @@ import ( "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/libbeat/common/bus" "github.com/elastic/beats/v7/libbeat/common/cfgwarn" + "github.com/elastic/beats/v7/libbeat/keystore" "github.com/elastic/beats/v7/libbeat/logp" awsauto "github.com/elastic/beats/v7/x-pack/libbeat/autodiscover/providers/aws" awscommon "github.com/elastic/beats/v7/x-pack/libbeat/common/aws" @@ -35,10 +36,11 @@ type Provider struct { stopListener bus.Listener watcher *watcher uuid uuid.UUID + keystore keystore.Keystore } // AutodiscoverBuilder is the main builder for this provider. -func AutodiscoverBuilder(bus bus.Bus, uuid uuid.UUID, c *common.Config) (autodiscover.Provider, error) { +func AutodiscoverBuilder(bus bus.Bus, uuid uuid.UUID, c *common.Config, keystore keystore.Keystore) (autodiscover.Provider, error) { cfgwarn.Experimental("aws_elb autodiscover is experimental") config := awsauto.DefaultConfig() @@ -85,12 +87,12 @@ func AutodiscoverBuilder(bus bus.Bus, uuid uuid.UUID, c *common.Config) (autodis config.AWSConfig.Endpoint, "elasticloadbalancing", region, awsCfg))) } - return internalBuilder(uuid, bus, config, newAPIFetcher(clients)) + return internalBuilder(uuid, bus, config, newAPIFetcher(clients), keystore) } // internalBuilder is mainly intended for testing via mocks and stubs. // it can be configured to use a fetcher that doesn't actually hit the AWS API. -func internalBuilder(uuid uuid.UUID, bus bus.Bus, config *awsauto.Config, fetcher fetcher) (*Provider, error) { +func internalBuilder(uuid uuid.UUID, bus bus.Bus, config *awsauto.Config, fetcher fetcher, keystore keystore.Keystore) (*Provider, error) { mapper, err := template.NewConfigMapper(config.Templates) if err != nil { return nil, err @@ -101,6 +103,7 @@ func internalBuilder(uuid uuid.UUID, bus bus.Bus, config *awsauto.Config, fetche bus: bus, templates: &mapper, uuid: uuid, + keystore: keystore, } p.watcher = newWatcher( diff --git a/x-pack/libbeat/autodiscover/providers/aws/elb/provider_test.go b/x-pack/libbeat/autodiscover/providers/aws/elb/provider_test.go index e6012c39d33..d6f9a918377 100644 --- a/x-pack/libbeat/autodiscover/providers/aws/elb/provider_test.go +++ b/x-pack/libbeat/autodiscover/providers/aws/elb/provider_test.go @@ -16,6 +16,7 @@ import ( "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/libbeat/common/bus" + "github.com/elastic/beats/v7/libbeat/keystore" "github.com/elastic/beats/v7/libbeat/logp" awsauto "github.com/elastic/beats/v7/x-pack/libbeat/autodiscover/providers/aws" ) @@ -74,7 +75,8 @@ func Test_internalBuilder(t *testing.T) { } uuid, _ := uuid.NewV4() - provider, err := internalBuilder(uuid, pBus, cfg, fetcher) + k, _ := keystore.NewFileKeystore("test") + provider, err := internalBuilder(uuid, pBus, cfg, fetcher, k) require.NoError(t, err) startListener := pBus.Subscribe("start") From d272f7f160991951c9cb06dcbcba80596c5aed36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mi=20V=C3=A1nyi?= Date: Wed, 29 Apr 2020 11:39:25 +0200 Subject: [PATCH 048/116] Add missing $INSTALL_FLAG to go run in make update (#18046) --- libbeat/scripts/Makefile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/libbeat/scripts/Makefile b/libbeat/scripts/Makefile index f4420581f52..99509ecf7e1 100755 --- a/libbeat/scripts/Makefile +++ b/libbeat/scripts/Makefile @@ -204,7 +204,8 @@ prepare-tests: .PHONY: unit-tests unit-tests: ## @testing Runs the unit tests with coverage. Race is not enabled for unit tests because tests run much slower. unit-tests: prepare-tests - $(COVERAGE_TOOL) $(RACE) -coverprofile=${COVERAGE_DIR}/unit.cov ${GOPACKAGES} + GOFLAGS="${INSTALL_FLAG}" \ + $(COVERAGE_TOOL) $(RACE) -coverprofile=${COVERAGE_DIR}/unit.cov ${GOPACKAGES} .PHONY: unit unit: ## @testing Runs the unit tests without coverage reports. @@ -214,7 +215,8 @@ unit: ## @testing Runs the unit tests without coverage reports. integration-tests: ## @testing Run integration tests. Unit tests are run as part of the integration tests. integration-tests: prepare-tests mage rm -f docker-compose.yml.lock - $(COVERAGE_TOOL) -tags=integration $(RACE) -coverprofile=${COVERAGE_DIR}/integration.cov ${GOPACKAGES} + GOFLAGS="${INSTALL_FLAG}" \ + $(COVERAGE_TOOL) -tags=integration $(RACE) -coverprofile=${COVERAGE_DIR}/integration.cov ${GOPACKAGES} .PHONY: integration-tests-environment integration-tests-environment: ## @testing Runs the integration inside a virtual environment. This can be run on any docker-machine (local, remote) @@ -359,7 +361,7 @@ update: python-env fields collect config ## @build Update expects the most recen ifneq ($(shell [[ $(BEAT_NAME) == libbeat || $(BEAT_NAME) == metricbeat ]] && echo true ),true) mkdir -p include - go run ${ES_BEATS}/dev-tools/cmd/asset/asset.go -license $(LICENSE) -pkg include -in fields.yml -out include/fields.go $(BEAT_NAME) + go run ${INSTALL_FLAG} ${ES_BEATS}/dev-tools/cmd/asset/asset.go -license $(LICENSE) -pkg include -in fields.yml -out include/fields.go $(BEAT_NAME) endif ifneq ($(shell [[ $(BEAT_NAME) == libbeat || $(BEAT_NAME) == metricbeat ]] && echo true ),true) From 5cd6b522e3208aba6a52f760027e6bda6f9b2ad0 Mon Sep 17 00:00:00 2001 From: Stijn Holzhauer Date: Wed, 29 Apr 2020 15:18:46 +0200 Subject: [PATCH 049/116] Changing field type to keyword (#17978) * Changing field type to keyword * Adding changelog --- CHANGELOG.next.asciidoc | 1 + filebeat/docs/fields.asciidoc | 6 +++--- filebeat/module/nginx/fields.go | 2 +- filebeat/module/nginx/ingress_controller/_meta/fields.yml | 6 +++--- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index bcb358e8ef1..5183073cd50 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -115,6 +115,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Remove migrationVersion map 7.7.0 reference from Kibana dashboard file to fix backward compatibility issues. {pull}17425[17425] - Fix issue 17734 to retry on rate-limit error in the Filebeat httpjson input. {issue}17734[17734] {pull}17735[17735] - Fixed `cloudfoundry.access` to have the correct `cloudfoundry.app.id` contents. {pull}17847[17847] +- Fixing `ingress_controller.` fields to be of type keyword instead of text. ${issue}17834[17834] - Fixed typo in log message. {pull}17897[17897] - Fix Cisco ASA ASA 3020** and 106023 messages {pull}17964[17964] diff --git a/filebeat/docs/fields.asciidoc b/filebeat/docs/fields.asciidoc index bc2db15417d..488cab967ff 100644 --- a/filebeat/docs/fields.asciidoc +++ b/filebeat/docs/fields.asciidoc @@ -26458,7 +26458,7 @@ format: duration The name of the upstream. -type: text +type: keyword -- @@ -26468,7 +26468,7 @@ type: text The name of the alternative upstream. -type: text +type: keyword -- @@ -26512,7 +26512,7 @@ type: long The randomly generated ID of the request -type: text +type: keyword -- diff --git a/filebeat/module/nginx/fields.go b/filebeat/module/nginx/fields.go index df2afc9669e..2f9e50ceb60 100644 --- a/filebeat/module/nginx/fields.go +++ b/filebeat/module/nginx/fields.go @@ -32,5 +32,5 @@ func init() { // AssetNginx returns asset data. // This is the base64 encoded gzipped contents of module/nginx. func AssetNginx() string { - return "eJzsWM2O4zYMvs9TENtLC8z4AXIoUGyxwBxaFEUPvWUVi3aIlUWXlDObty9kOz/jkRM7md32YJ9m7PD7PooUJfIJvuB+Bb4k//UBIFBwuIIPv8f/PzwAWNRcqA7EfgU/PwAA/Ma2cQgFC9RGlHwJYYvQmoDjEgpyqNkDgG5ZwjpnX1C5giANPgAUhM7qqoV6Am8qPNHHJ+xrXEEp3NT9m4SG+HxqgaAQrsYExOec75zT5DmqHl+niC+Qx+cj+2DIa0/RrshJSIcf9RylpOScSxKsOOCa6rUjDa9+cpBnRMx+8OWCxPj84jsr4KJngOc/wFgrqIqawXMAUjAQSWGDuWkUgdqXOVcVewgM5HPXWHyEDSpZ1NbT3BH6oVA4g398RdXFaovGoig4+oLw+e+nTywvRiza+Nfn7A3an2gcKDeSt8JJQVADC9qo63P3JaP6zDS5uhu2+7WiD9lmH1DTy+vIDL/UJmxXsA2hzgS1Zq+YRawkTEWlmC4Sfb6/FdIoyjr+OVNCtMsSdlM4Kwxbtrf5/E+DGrIkwiR3xc11VFzGQiV5MzSdQhhlr3coSuxv8ThtOoX5kB/rnO3c6L5OMA0mNJrCmaajQBGUe+I9gjGF3pRvq8KU5F63hnNDP77HxnUMqzyMVOZzSIs7yofRuOxa0r0OJ7WRL3k5VDNiPlPLnSI4RTJTAmtWNM6lCuI8KWMI8/XcKyWdv7doGUeatRtKZBqm++0bIWcfyKMP96x4f2yXyNlVvKkLn3Pjg+zXpJyqnDdJu4o4VZzjvP3Z/aIuIE0VI1gS+3eK32WwycGjsH+vhLoANXOF3i+VrgOOSTtIQhGWb9ewtPCz+pWcvcc8tG6l75aOfTmvW/l4xASy6AMVhHLlZu9wh3PvmI7LLGU35X5Tj3g7zlYLx24we2s5hS/czBe2gsZmN7FWqGrKudfYtNW11CZfxhZxHY8CYefwm+T5c8fS5/uJa+nV/y+9+g+HTHg6i47WmFNBeR+P0Z7v2Lo49GXYTi1IBUtlwgpS7fyVcP21RehJoSOFH7vFJ1+evpDHx34pH8F4e/yyYbv/6bo/gUZmBZabjRt+OvhjG0ldE665RBUCOlMrWlDyObbZU5BEvXGJ4AUlum3safaWSK5jJ1ZrEDRV6lbdeRHw6zAvJ6x7hIvbJNIfOS4rMC6geBNoh+Pzl/vVnNFMVXbs+79r6vYp28s+aADexOqJZ+E96ARF2Q1GAuO+fNe03SJEvlgsfAD2IJgj7Q7T6aNzYz6BUVDM2VuFFwpbqMg56t5Ea3bNG1Xjro+Pb269F0UHO1SIqO8UtFdlZuSWcduGEOMtV24PJXoUE9DC868n1S3j5cUc6VjfvJ4g5nSwDStGvzgZPBeguEOJ51f7qq9z8RwyeZRvGzmv6v0Vi3z5GBFJzs5OIwiKten83uzbs9nolSJQs6QvErdlS4Qbc/fyfXqZlC+T8mVSvkzKl0n5Mim/rGeZlC+T8mVSvkzK/+NJ+b8BAAD//1+3mU8=" + return "eJzsWM9u48YPvucpiP1dfgUSPYAPBYotFsihRVH00Jt3rKFkIqOhSo6c+u2LkeQ/kUe25GS3PUinRDK/7+OQwxnyCV5wvwJfkv/7ASBQcLiCT7/G/z89AFjUXKgOxH4FPz4AAPzCtnEIBQvURpR8CWGL0JqA4xIKcqjZA4BuWcI6Z19QuYIgDT4AFITO6qqFegJvKjzRxyfsa1xBKdzU/ZuEhvh8aYGgEK7GBMTnnO+c0+Q5qh5fp4ivkMfnM/tgyGtP0a7ISUiHH/UcpaTknEsSrDjgmuq1Iw1vfnKQZ0TMfvDlisT4/OQ7K+CiZ4Dn38BYK6iKmsFzAFIwEElhg7lpFIHalzlXFXsIDORz11h8hA0qWdTW09wR+qFQOIN/fEPVxWqLxqIoOHpB+Prn0xeWVyMWbfzra3aB9jsaB8qN5K1wUhDUwII26vrafcmoPjNNru6G7X6t6EO22QfU9PI6MsMvtQnbFWxDqDNBrdkrZhErCVNRKaaLRJ/vl0IaRVnHP2dKiHZZwm4KZ4Vhy/Y+n/9qUEOWRJjkrri5jorLWKgkb4amUwij7PUORYn9PR6nTacwH/JjnbOdG923CabBhEZTONN0FCiC8p54j2BMoTflZVWYktzr1nBu6Mf32LiOYZWHkcp8DmlxR/kwGtddS7rX4aQ28jUvh2pGzGdqeacITpHMlMCaFY1zqYI4T8oYwnw975WSzt97tIwjzdoNJTIN0/3+jZCzD+TRh/eseH9sl8jZTbypC59z44Ps16Scqpx3SbuJOFWc47z92ftFXUGaKkawJPYfFL/rYJODR2H/UQl1BWrmCn1cKt0GHJN2kIQiLN+uYWnhZ/UrOXuPeWjdSt8tHftyXrfy+YgJZNEHKgjlxs3e4Q7n3jEdl1nKbsr9ph7xdpytFo7dYHZpOYUv3M0XtoLGZnexVqhqyrnX2LTVrdQmX8YWcR2PAmHn8Jvk+XPH0uf7iWvp1f8rvfr/DpnwdBYdrTGngvI+HqM937F1cejLsJ1akAqWyoQVpNr5G+H6Y4vQk0JHCv/vFp98efpCHh/7pXwE4+3xy4bt/ofb/gQamRVYbjZu+Ongj20kdU245RJVCOhMrWhByefYZk9BEvXGJYJXlOi2safZWyK5jp1YrUHQVKlbdefFC+5fWYYlasLSR8S4U6KCI811EcYFFG8C7XB8BPMhgs6Ypoo7dv/fNYH7xO1lHzQAb2INxbMgH3SCouwGg4FxX75r8m4RIl8sGT4AexDMkXaHGfXRuTGfwCgo5uytwiuFLVTkHHVvojW75kLVuOvjQ5x7b0fRwQ4VIuoHBe1NsRm5a9y9J8R4y5XbQ4kexQS08PzzSXhLen09R1rXi9cTxJxOuGHd6Ncng+cCFHco8SBrX/UFLx5IJo/ybSPn5b2/a5EvHyMiydkhagRBsTad35t9e0gbvVEHapb0jeK+hIlwY+5ev1gvI/NlZL6MzJeR+TIyX0bm1/UsI/NlZL6MzJeR+b88Mv8nAAD///5cnQ8=" } diff --git a/filebeat/module/nginx/ingress_controller/_meta/fields.yml b/filebeat/module/nginx/ingress_controller/_meta/fields.yml index 0c9ca13de32..2c467e3856a 100644 --- a/filebeat/module/nginx/ingress_controller/_meta/fields.yml +++ b/filebeat/module/nginx/ingress_controller/_meta/fields.yml @@ -22,11 +22,11 @@ description: > Time elapsed since the first bytes were read from the client - name: upstream.name - type: text + type: keyword description: > The name of the upstream. - name: upstream.alternative_name - type: text + type: keyword description: > The name of the alternative upstream. - name: upstream.response.length @@ -44,7 +44,7 @@ description: > The status code of the response obtained from the upstream server - name: http.request.id - type: text + type: keyword description: > The randomly generated ID of the request - name: upstream.ip From ac5cbf4646eb6ed62f7ece6a64673c308e6e2e0a Mon Sep 17 00:00:00 2001 From: Pier-Hugues Pellerin Date: Wed, 29 Apr 2020 09:21:08 -0400 Subject: [PATCH 050/116] [Elatic Agent] Enable debug level logging for Metricbeat and Filebeat for Alpha (#17935) * Enable debug level logging for Metricbeat and Filebeat for Alpha * Changelog --- x-pack/elastic-agent/CHANGELOG.asciidoc | 1 + x-pack/elastic-agent/pkg/agent/program/supported.go | 2 +- x-pack/elastic-agent/spec/filebeat.yml | 2 +- x-pack/elastic-agent/spec/metricbeat.yml | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/x-pack/elastic-agent/CHANGELOG.asciidoc b/x-pack/elastic-agent/CHANGELOG.asciidoc index ed928e1d688..f062fe182da 100644 --- a/x-pack/elastic-agent/CHANGELOG.asciidoc +++ b/x-pack/elastic-agent/CHANGELOG.asciidoc @@ -40,3 +40,4 @@ - Pack ECS metadata to request payload send to fleet {pull}17894[17894] - Allow CLI overrides of paths {pull}17781[17781] - Enable Filebeat input: S3, Azureeventhub, cloudfoundry, httpjson, netflow, o365audit. {pull}17909[17909] +- Enable debug log level for Metricbeat and Filebeat when run under the Elastic Agent. {pull}17935[17935] diff --git a/x-pack/elastic-agent/pkg/agent/program/supported.go b/x-pack/elastic-agent/pkg/agent/program/supported.go index b8b5ddd3a40..ca70abd8771 100644 --- a/x-pack/elastic-agent/pkg/agent/program/supported.go +++ b/x-pack/elastic-agent/pkg/agent/program/supported.go @@ -19,7 +19,7 @@ func init() { // Packed Files // spec/filebeat.yml // spec/metricbeat.yml - unpacked := packer.MustUnpack("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") + unpacked := packer.MustUnpack("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") SupportedMap = make(map[string]bool) for f, v := range unpacked { diff --git a/x-pack/elastic-agent/spec/filebeat.yml b/x-pack/elastic-agent/spec/filebeat.yml index 3c1adf7d7a9..0ed7bd422d4 100644 --- a/x-pack/elastic-agent/spec/filebeat.yml +++ b/x-pack/elastic-agent/spec/filebeat.yml @@ -1,6 +1,6 @@ name: Filebeat cmd: filebeat -args: ["-E", "setup.ilm.enabled=false", "-E", "setup.template.enabled=false", "-E", "management.mode=x-pack-fleet", "-E", "management.enabled=true"] +args: ["-E", "setup.ilm.enabled=false", "-E", "setup.template.enabled=false", "-E", "management.mode=x-pack-fleet", "-E", "management.enabled=true", "-E", "logging.level=debug"] configurable: grpc rules: - inject_index: diff --git a/x-pack/elastic-agent/spec/metricbeat.yml b/x-pack/elastic-agent/spec/metricbeat.yml index b9085c8fbb6..3dc7f6507d5 100644 --- a/x-pack/elastic-agent/spec/metricbeat.yml +++ b/x-pack/elastic-agent/spec/metricbeat.yml @@ -1,6 +1,6 @@ name: Metricbeat cmd: metricbeat -args: ["-E", "setup.ilm.enabled=false", "-E", "setup.template.enabled=false", "-E", "management.mode=x-pack-fleet", "-E", "management.enabled=true"] +args: ["-E", "setup.ilm.enabled=false", "-E", "setup.template.enabled=false", "-E", "management.mode=x-pack-fleet", "-E", "management.enabled=true", "-E", "logging.level=debug"] configurable: grpc post_install: - move_file: From d4829e1990c3b115575cfc090c04d1237540e23a Mon Sep 17 00:00:00 2001 From: Pier-Hugues Pellerin Date: Wed, 29 Apr 2020 09:27:18 -0400 Subject: [PATCH 051/116] Update configuration yaml for the Elastic Agent (#18051) --- x-pack/elastic-agent/_meta/elastic-agent.fleet.yml | 5 ----- .../pkg/agent/application/configuration_embed.go | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/x-pack/elastic-agent/_meta/elastic-agent.fleet.yml b/x-pack/elastic-agent/_meta/elastic-agent.fleet.yml index 20143509c1b..8d817c8212e 100644 --- a/x-pack/elastic-agent/_meta/elastic-agent.fleet.yml +++ b/x-pack/elastic-agent/_meta/elastic-agent.fleet.yml @@ -5,11 +5,6 @@ management: mode: "fleet" - # Check in frequency configure the time between calls to fleet to retrieve the new configuration. - # - # Default is 30s - #checkin_frequency: 30s - # Add variance between API calls to better distribute the calls. #jitter: 5s diff --git a/x-pack/elastic-agent/pkg/agent/application/configuration_embed.go b/x-pack/elastic-agent/pkg/agent/application/configuration_embed.go index a34b1b7a2dd..db61023400e 100644 --- a/x-pack/elastic-agent/pkg/agent/application/configuration_embed.go +++ b/x-pack/elastic-agent/pkg/agent/application/configuration_embed.go @@ -15,7 +15,7 @@ var DefaultAgentFleetConfig []byte func init() { // Packed File // _meta/elastic-agent.fleet.yml - unpacked := packer.MustUnpack("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") + unpacked := packer.MustUnpack("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") raw, ok := unpacked["_meta/elastic-agent.fleet.yml"] if !ok { // ensure we have something loaded. From 2b8fd054b76f006c297cd0184f3aa07cd453a26e Mon Sep 17 00:00:00 2001 From: Michal Pristas Date: Wed, 29 Apr 2020 16:21:47 +0200 Subject: [PATCH 052/116] [Elastic-Agent] Use default output by default (#18091) [Elastic-Agent] Use default output by default (#18091) --- x-pack/elastic-agent/CHANGELOG.asciidoc | 1 + .../agent/application/monitoring_decorator.go | 25 ++++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/x-pack/elastic-agent/CHANGELOG.asciidoc b/x-pack/elastic-agent/CHANGELOG.asciidoc index f062fe182da..ecd9603fca8 100644 --- a/x-pack/elastic-agent/CHANGELOG.asciidoc +++ b/x-pack/elastic-agent/CHANGELOG.asciidoc @@ -26,6 +26,7 @@ - Make sure that the Elastic Agent connect over TLS in cloud. {pull}17843[17843] - Moved stream.* fields to top of event {pull}17858[17858] - Fix an issue where the checkin_frequency, jitter, and backoff options where not configurable. {pull}17843[17843] +- Use default output by default {pull}18091[18091] ==== New features 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 bd0b240c65d..89328dd05b2 100644 --- a/x-pack/elastic-agent/pkg/agent/application/monitoring_decorator.go +++ b/x-pack/elastic-agent/pkg/agent/application/monitoring_decorator.go @@ -19,10 +19,11 @@ const ( monitoringOutputFormatKey = "outputs.%s" outputKey = "output" - enabledKey = "settings.monitoring.enabled" - outputsKey = "outputs" - elasticsearchKey = "elasticsearch" - typeKey = "type" + enabledKey = "settings.monitoring.enabled" + outputsKey = "outputs" + elasticsearchKey = "elasticsearch" + typeKey = "type" + defaultOutputName = "default" ) func injectMonitoring(outputGroup string, rootAst *transpiler.AST, programsToRun []program.Program) ([]program.Program, error) { @@ -40,17 +41,17 @@ func injectMonitoring(outputGroup string, rootAst *transpiler.AST, programsToRun config[enabledKey] = false } else { // get monitoring output name to be used + monitoringOutputName := defaultOutputName useOutputNode, found := transpiler.Lookup(rootAst, monitoringUseOutputKey) - if !found { - return programsToRun, nil - } + if found { - monitoringOutputNameKey, ok := useOutputNode.Value().(*transpiler.StrVal) - if !ok { - return programsToRun, nil - } + monitoringOutputNameKey, ok := useOutputNode.Value().(*transpiler.StrVal) + if !ok { + return programsToRun, nil + } - monitoringOutputName := monitoringOutputNameKey.String() + monitoringOutputName = monitoringOutputNameKey.String() + } ast := rootAst.Clone() if err := getMonitoringRule(monitoringOutputName).Apply(ast); err != nil { From 185f2022c00780993bac7ebfccd34512b3ebd963 Mon Sep 17 00:00:00 2001 From: Michal Pristas Date: Wed, 29 Apr 2020 17:06:28 +0200 Subject: [PATCH 053/116] [Elastic-Agent] Use data subfolder as default for process logs (#17960) [Elastic-Agent] Use data subfolder as default for process logs (#17960) --- x-pack/elastic-agent/CHANGELOG.asciidoc | 1 + .../pkg/agent/application/global_config.go | 64 +------------------ .../pkg/agent/application/paths/paths.go | 45 +++++++++++++ x-pack/elastic-agent/pkg/agent/cmd/common.go | 6 +- .../plugin/app/monitoring/beats/drop_test.go | 32 +++++----- .../plugin/app/monitoring/beats/monitoring.go | 18 +++--- .../pkg/core/plugin/app/start.go | 8 +-- 7 files changed, 80 insertions(+), 94 deletions(-) create mode 100644 x-pack/elastic-agent/pkg/agent/application/paths/paths.go diff --git a/x-pack/elastic-agent/CHANGELOG.asciidoc b/x-pack/elastic-agent/CHANGELOG.asciidoc index ecd9603fca8..bb57c61f03e 100644 --- a/x-pack/elastic-agent/CHANGELOG.asciidoc +++ b/x-pack/elastic-agent/CHANGELOG.asciidoc @@ -41,4 +41,5 @@ - Pack ECS metadata to request payload send to fleet {pull}17894[17894] - Allow CLI overrides of paths {pull}17781[17781] - Enable Filebeat input: S3, Azureeventhub, cloudfoundry, httpjson, netflow, o365audit. {pull}17909[17909] +- Use data subfolder as default for process logs {pull}17960[17960] - Enable debug log level for Metricbeat and Filebeat when run under the Elastic Agent. {pull}17935[17935] diff --git a/x-pack/elastic-agent/pkg/agent/application/global_config.go b/x-pack/elastic-agent/pkg/agent/application/global_config.go index a08a59b0912..16d5f21639e 100644 --- a/x-pack/elastic-agent/pkg/agent/application/global_config.go +++ b/x-pack/elastic-agent/pkg/agent/application/global_config.go @@ -5,46 +5,12 @@ package application import ( - "os" - "path/filepath" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application/paths" - "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/errors" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/config" ) -var ( - homePath string - dataPath string - overwrites *common.Config -) - -func init() { - homePath = retrieveExecutablePath() - dataPath = retrieveDataPath() - overwrites = common.NewConfig() - common.ConfigOverwriteFlag(nil, overwrites, "path.home", "path.home", "", "Agent root path") - common.ConfigOverwriteFlag(nil, overwrites, "path.data", "path.data", "", "Data path contains Agent managed binaries") -} - -// HomePath returns home path where. -func HomePath() string { - if val, err := overwrites.String("path.home", -1); err == nil { - return val - } - - return homePath -} - -// DataPath returns data path where. -func DataPath() string { - if val, err := overwrites.String("path.data", -1); err == nil { - return val - } - - return dataPath -} - // InjectAgentConfig injects config to a provided configuration. func InjectAgentConfig(c *config.Config) error { globalConfig := agentGlobalConfig() @@ -52,14 +18,6 @@ func InjectAgentConfig(c *config.Config) error { return errors.New("failed to inject agent global config", err, errors.TypeConfig) } - return injectOverwrites(c) -} - -func injectOverwrites(c *config.Config) error { - if err := c.Merge(overwrites); err != nil { - return errors.New("failed to inject agent overwrites", err, errors.TypeConfig) - } - return nil } @@ -68,24 +26,8 @@ func injectOverwrites(c *config.Config) error { func agentGlobalConfig() map[string]interface{} { return map[string]interface{}{ "path": map[string]interface{}{ - "data": dataPath, - "home": homePath, + "data": paths.Data(), + "home": paths.Home(), }, } } - -// retrieveExecutablePath returns a directory where binary lives -// Executable is not supported on nacl. -func retrieveExecutablePath() string { - execPath, err := os.Executable() - if err != nil { - panic(err) - } - - return filepath.Dir(execPath) -} - -// retrieveHomePath returns a home directory of current user -func retrieveDataPath() string { - return filepath.Join(retrieveExecutablePath(), "data") -} diff --git a/x-pack/elastic-agent/pkg/agent/application/paths/paths.go b/x-pack/elastic-agent/pkg/agent/application/paths/paths.go new file mode 100644 index 00000000000..a45000b40ae --- /dev/null +++ b/x-pack/elastic-agent/pkg/agent/application/paths/paths.go @@ -0,0 +1,45 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package paths + +import ( + "flag" + "os" + "path/filepath" +) + +var ( + homePath string + dataPath string +) + +func init() { + exePath := retrieveExecutablePath() + + fs := flag.CommandLine + fs.StringVar(&homePath, "path.home", exePath, "Agent root path") + fs.StringVar(&dataPath, "path.data", filepath.Join(exePath, "data"), "Data path contains Agent managed binaries") +} + +// Home returns a directory where binary lives +// Executable is not supported on nacl. +func Home() string { + return homePath +} + +// Data returns a home directory of current user +func Data() string { + return dataPath +} + +func retrieveExecutablePath() string { + + execPath, err := os.Executable() + if err != nil { + panic(err) + } + + return filepath.Dir(execPath) +} diff --git a/x-pack/elastic-agent/pkg/agent/cmd/common.go b/x-pack/elastic-agent/pkg/agent/cmd/common.go index 956b7324d3c..5fe9947e34f 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/common.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/common.go @@ -12,7 +12,7 @@ import ( "github.com/spf13/cobra" - "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application/paths" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/basecmd" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/cli" ) @@ -27,8 +27,8 @@ type globalFlags struct { // Config returns path which identifies configuration file. func (f *globalFlags) Config() string { - if len(f.PathConfigFile) == 0 { - return filepath.Join(application.HomePath(), defaultConfig) + if len(f.PathConfigFile) == 0 || f.PathConfigFile == defaultConfig { + return filepath.Join(paths.Home(), defaultConfig) } return f.PathConfigFile } diff --git a/x-pack/elastic-agent/pkg/core/plugin/app/monitoring/beats/drop_test.go b/x-pack/elastic-agent/pkg/core/plugin/app/monitoring/beats/drop_test.go index a4d06169ca8..5c2f6be7f19 100644 --- a/x-pack/elastic-agent/pkg/core/plugin/app/monitoring/beats/drop_test.go +++ b/x-pack/elastic-agent/pkg/core/plugin/app/monitoring/beats/drop_test.go @@ -15,22 +15,22 @@ type testCase struct { func TestMonitoringDrops(t *testing.T) { cases := []testCase{ - testCase{`/var/lib/drop/abc.sock`, "/var/lib/drop"}, - testCase{`npipe://drop`, ""}, - testCase{`http+npipe://drop`, ""}, - testCase{`\\.\pipe\drop`, ""}, - testCase{`unix:///var/lib/drop/abc.sock`, "/var/lib/drop"}, - testCase{`http+unix:///var/lib/drop/abc.sock`, "/var/lib/drop"}, - testCase{`file:///var/lib/drop/abc.sock`, "/var/lib/drop"}, - testCase{`http://localhost/stats`, ""}, - testCase{`localhost/stats`, ""}, - testCase{`http://localhost:8080/stats`, ""}, - testCase{`localhost:8080/stats`, ""}, - testCase{`http://1.2.3.4/stats`, ""}, - testCase{`http://1.2.3.4:5678/stats`, ""}, - testCase{`1.2.3.4:5678/stats`, ""}, - testCase{`http://hithere.com:5678/stats`, ""}, - testCase{`hithere.com:5678/stats`, ""}, + {`/var/lib/drop/abc.sock`, "/var/lib/drop"}, + {`npipe://drop`, ""}, + {`http+npipe://drop`, ""}, + {`\\.\pipe\drop`, ""}, + {`unix:///var/lib/drop/abc.sock`, "/var/lib/drop"}, + {`http+unix:///var/lib/drop/abc.sock`, "/var/lib/drop"}, + {`file:///var/lib/drop/abc.sock`, "/var/lib/drop"}, + {`http://localhost/stats`, ""}, + {`localhost/stats`, ""}, + {`http://localhost:8080/stats`, ""}, + {`localhost:8080/stats`, ""}, + {`http://1.2.3.4/stats`, ""}, + {`http://1.2.3.4:5678/stats`, ""}, + {`1.2.3.4:5678/stats`, ""}, + {`http://hithere.com:5678/stats`, ""}, + {`hithere.com:5678/stats`, ""}, } for _, c := range cases { diff --git a/x-pack/elastic-agent/pkg/core/plugin/app/monitoring/beats/monitoring.go b/x-pack/elastic-agent/pkg/core/plugin/app/monitoring/beats/monitoring.go index c551f5ef18c..7e6b820611c 100644 --- a/x-pack/elastic-agent/pkg/core/plugin/app/monitoring/beats/monitoring.go +++ b/x-pack/elastic-agent/pkg/core/plugin/app/monitoring/beats/monitoring.go @@ -6,16 +6,18 @@ package beats import ( "fmt" + + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application/paths" ) const ( - // args: pipeline name, application name - logFileFormat = "/var/log/elastic-agent/%s/%s" - // args: install path, pipeline name, application name - logFileFormatWin = "%s\\logs\\elastic-agent\\%s\\%s" + // args: data path, pipeline name, application name + logFileFormat = "%s/logs/%s/%s" + // args: data path, install path, pipeline name, application name + logFileFormatWin = "%s\\logs\\%s\\%s" // args: pipeline name, application name - mbEndpointFileFormat = "unix:///var/run/elastic-agent/%s/%s/%s.sock" + mbEndpointFileFormat = "unix://%s/run/%s/%s/%s.sock" // args: pipeline name, application name mbEndpointFileFormatWin = `npipe:///%s-%s` ) @@ -25,13 +27,13 @@ func getMonitoringEndpoint(program, operatingSystem, pipelineID string) string { return fmt.Sprintf(mbEndpointFileFormatWin, pipelineID, program) } - return fmt.Sprintf(mbEndpointFileFormat, pipelineID, program, program) + return fmt.Sprintf(mbEndpointFileFormat, paths.Data(), pipelineID, program, program) } func getLoggingFile(program, operatingSystem, installPath, pipelineID string) string { if operatingSystem == "windows" { - return fmt.Sprintf(logFileFormatWin, installPath, pipelineID, program) + return fmt.Sprintf(logFileFormatWin, paths.Data(), pipelineID, program) } - return fmt.Sprintf(logFileFormat, pipelineID, program) + return fmt.Sprintf(logFileFormat, paths.Data(), pipelineID, program) } diff --git a/x-pack/elastic-agent/pkg/core/plugin/app/start.go b/x-pack/elastic-agent/pkg/core/plugin/app/start.go index 00684753a0d..9bfc40781e2 100644 --- a/x-pack/elastic-agent/pkg/core/plugin/app/start.go +++ b/x-pack/elastic-agent/pkg/core/plugin/app/start.go @@ -15,6 +15,7 @@ import ( "gopkg.in/yaml.v2" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application/paths" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/errors" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/core/plugin/authority" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/core/plugin/process" @@ -210,12 +211,7 @@ func (a *Application) checkGrpcHTTP(ctx context.Context, address string, ca *aut } func injectDataPath(args []string, pipelineID, id string) []string { - wd := "" - if w, err := os.Getwd(); err == nil { - wd = w - } - - dataPath := filepath.Join(wd, "data", pipelineID, id) + dataPath := filepath.Join(paths.Data(), pipelineID, id) return append(args, "-E", "path.data="+dataPath) } From 1fbe1676e19f555d92d9b188812bd508ee45c469 Mon Sep 17 00:00:00 2001 From: kaiyan-sheng Date: Wed, 29 Apr 2020 11:25:27 -0600 Subject: [PATCH 054/116] [Metricbeat] Add static mapping for metricsets in aws module (#17650) * Add static mapping for metricsets in aws module --- metricbeat/docs/fields.asciidoc | 1188 ++++++++++++++++- .../module/aws/billing/_meta/fields.yml | 6 + .../module/aws/ebs/_meta/fields.yml | 36 + .../module/aws/elb/_meta/docs.asciidoc | 16 +- .../module/aws/elb/_meta/fields.yml | 180 +++ x-pack/metricbeat/module/aws/elb/manifest.yml | 6 +- x-pack/metricbeat/module/aws/fields.go | 2 +- .../module/aws/lambda/_meta/docs.asciidoc | 1 + .../module/aws/lambda/_meta/fields.yml | 42 + .../metricbeat/module/aws/lambda/manifest.yml | 2 +- .../module/aws/natgateway/_meta/fields.yml | 45 + .../module/aws/sns/_meta/fields.yml | 36 + .../aws/transitgateway/_meta/fields.yml | 21 + .../module/aws/usage/_meta/fields.yml | 9 + .../module/aws/vpn/_meta/fields.yml | 12 + 15 files changed, 1529 insertions(+), 73 deletions(-) diff --git a/metricbeat/docs/fields.asciidoc b/metricbeat/docs/fields.asciidoc index 0aaafa9488b..0b3ca4edff7 100644 --- a/metricbeat/docs/fields.asciidoc +++ b/metricbeat/docs/fields.asciidoc @@ -1577,6 +1577,17 @@ type: object `billing` contains the estimated charges for your AWS account in Cloudwatch. + + +*`aws.billing.metrics.EstimatedCharges.max`*:: ++ +-- +Maximum estimated charges for AWS acccount. + +type: long + +-- + [float] === cloudwatch @@ -1883,6 +1894,107 @@ type: double `ebs` contains the metrics that were scraped from AWS CloudWatch which contains monitoring metrics sent by AWS EBS. + + +*`aws.ebs.metrics.VolumeReadBytes.avg`*:: ++ +-- +Average size of each read operation during the period, except on volumes attached to a Nitro-based instance, where the average represents the average over the specified period. + +type: double + +-- + +*`aws.ebs.metrics.VolumeWriteBytes.avg`*:: ++ +-- +Average size of each write operation during the period, except on volumes attached to a Nitro-based instance, where the average represents the average over the specified period. + +type: double + +-- + +*`aws.ebs.metrics.VolumeReadOps.avg`*:: ++ +-- +The total number of read operations in a specified period of time. + +type: double + +-- + +*`aws.ebs.metrics.VolumeWriteOps.avg`*:: ++ +-- +The total number of write operations in a specified period of time. + +type: double + +-- + +*`aws.ebs.metrics.VolumeQueueLength.avg`*:: ++ +-- +The number of read and write operation requests waiting to be completed in a specified period of time. + +type: double + +-- + +*`aws.ebs.metrics.VolumeThroughputPercentage.avg`*:: ++ +-- +The percentage of I/O operations per second (IOPS) delivered of the total IOPS provisioned for an Amazon EBS volume. Used with Provisioned IOPS SSD volumes only. + +type: double + +-- + +*`aws.ebs.metrics.VolumeConsumedReadWriteOps.avg`*:: ++ +-- +The total amount of read and write operations (normalized to 256K capacity units) consumed in a specified period of time. Used with Provisioned IOPS SSD volumes only. + +type: double + +-- + +*`aws.ebs.metrics.BurstBalance.avg`*:: ++ +-- +Used with General Purpose SSD (gp2), Throughput Optimized HDD (st1), and Cold HDD (sc1) volumes only. Provides information about the percentage of I/O credits (for gp2) or throughput credits (for st1 and sc1) remaining in the burst bucket. + +type: double + +-- + +*`aws.ebs.metrics.VolumeTotalReadTime.sum`*:: ++ +-- +The total number of seconds spent by all read operations that completed in a specified period of time. + +type: double + +-- + +*`aws.ebs.metrics.VolumeTotalWriteTime.sum`*:: ++ +-- +The total number of seconds spent by all write operations that completed in a specified period of time. + +type: double + +-- + +*`aws.ebs.metrics.VolumeIdleTime.sum`*:: ++ +-- +The total number of seconds in a specified period of time when no read or write operations were submitted. + +type: double + +-- + [float] === ec2 @@ -2088,173 +2200,915 @@ type: long -- -*`aws.ec2.diskio.write.ops`*:: +*`aws.ec2.diskio.write.ops`*:: ++ +-- +Completed write operations to all instance store volumes available to the instance in a specified period of time. + + +type: long + +-- + +*`aws.ec2.diskio.write.ops_per_sec`*:: ++ +-- +Completed write operations per second to all instance store volumes available to the instance in a specified period of time. + + +type: long + +-- + +*`aws.ec2.status.check_failed`*:: ++ +-- +Reports whether the instance has passed both the instance status check and the system status check in the last minute. + + +type: long + +-- + +*`aws.ec2.status.check_failed_system`*:: ++ +-- +Reports whether the instance has passed the system status check in the last minute. + + +type: long + +-- + +*`aws.ec2.status.check_failed_instance`*:: ++ +-- +Reports whether the instance has passed the instance status check in the last minute. + + +type: long + +-- + +*`aws.ec2.instance.core.count`*:: ++ +-- +The number of CPU cores for the instance. + + +type: integer + +-- + +*`aws.ec2.instance.image.id`*:: ++ +-- +The ID of the image used to launch the instance. + + +type: keyword + +-- + +*`aws.ec2.instance.monitoring.state`*:: ++ +-- +Indicates whether detailed monitoring is enabled. + + +type: keyword + +-- + +*`aws.ec2.instance.private.dns_name`*:: ++ +-- +The private DNS name of the network interface. + + +type: keyword + +-- + +*`aws.ec2.instance.private.ip`*:: ++ +-- +The private IPv4 address associated with the network interface. + + +type: ip + +-- + +*`aws.ec2.instance.public.dns_name`*:: ++ +-- +The public DNS name of the instance. + + +type: keyword + +-- + +*`aws.ec2.instance.public.ip`*:: ++ +-- +The address of the Elastic IP address (IPv4) bound to the network interface. + + +type: ip + +-- + +*`aws.ec2.instance.state.code`*:: ++ +-- +The state of the instance, as a 16-bit unsigned integer. + + +type: integer + +-- + +*`aws.ec2.instance.state.name`*:: ++ +-- +The state of the instance (pending | running | shutting-down | terminated | stopping | stopped). + + +type: keyword + +-- + +*`aws.ec2.instance.threads_per_core`*:: ++ +-- +The number of threads per CPU core. + + +type: integer + +-- + +[float] +=== elb + +`elb` contains the metrics that were scraped from AWS CloudWatch which contains monitoring metrics sent by AWS ELB. + + + + +*`aws.elb.metrics.BackendConnectionErrors.sum`*:: ++ +-- +The number of connections that were not successfully established between the load balancer and the registered instances. + +type: long + +-- + +*`aws.elb.metrics.HTTPCode_Backend_2XX.sum`*:: ++ +-- +The number of HTTP 2XX response code generated by registered instances. + +type: long + +-- + +*`aws.elb.metrics.HTTPCode_Backend_3XX.sum`*:: ++ +-- +The number of HTTP 3XX response code generated by registered instances. + +type: long + +-- + +*`aws.elb.metrics.HTTPCode_Backend_4XX.sum`*:: ++ +-- +The number of HTTP 4XX response code generated by registered instances. + +type: long + +-- + +*`aws.elb.metrics.HTTPCode_Backend_5XX.sum`*:: ++ +-- +The number of HTTP 5XX response code generated by registered instances. + +type: long + +-- + +*`aws.elb.metrics.HTTPCode_ELB_4XX.sum`*:: ++ +-- +The number of HTTP 4XX client error codes generated by the load balancer. + +type: long + +-- + +*`aws.elb.metrics.HTTPCode_ELB_5XX.sum`*:: ++ +-- +The number of HTTP 5XX server error codes generated by the load balancer. + +type: long + +-- + +*`aws.elb.metrics.RequestCount.sum`*:: ++ +-- +The number of requests completed or connections made during the specified interval. + +type: long + +-- + +*`aws.elb.metrics.SpilloverCount.sum`*:: ++ +-- +The total number of requests that were rejected because the surge queue is full. + +type: long + +-- + +*`aws.elb.metrics.HealthyHostCount.max`*:: ++ +-- +The number of healthy instances registered with your load balancer. + +type: long + +-- + +*`aws.elb.metrics.SurgeQueueLength.max`*:: ++ +-- +The total number of requests (HTTP listener) or connections (TCP listener) that are pending routing to a healthy instance. + +type: long + +-- + +*`aws.elb.metrics.UnHealthyHostCount.max`*:: ++ +-- +The number of unhealthy instances registered with your load balancer. + +type: long + +-- + +*`aws.elb.metrics.Latency.avg`*:: ++ +-- +The total time elapsed, in seconds, from the time the load balancer sent the request to a registered instance until the instance started to send the response headers. + +type: double + +-- + +*`aws.elb.metrics.EstimatedALBActiveConnectionCount.avg`*:: ++ +-- +The estimated number of concurrent TCP connections active from clients to the load balancer and from the load balancer to targets. + +type: double + +-- + +*`aws.elb.metrics.EstimatedALBConsumedLCUs.avg`*:: ++ +-- +The estimated number of load balancer capacity units (LCU) used by an Application Load Balancer. + +type: double + +-- + +*`aws.elb.metrics.EstimatedALBNewConnectionCount.avg`*:: ++ +-- +The estimated number of new TCP connections established from clients to the load balancer and from the load balancer to targets. + +type: double + +-- + +*`aws.elb.metrics.EstimatedProcessedBytes.avg`*:: ++ +-- +The estimated number of bytes processed by an Application Load Balancer. + +type: double + +-- + +[float] +=== applicationelb + +`applicationelb` contains the metrics that were scraped from AWS CloudWatch which contains monitoring metrics sent by AWS ApplicationELB. + + + + +*`aws.applicationelb.metrics.ActiveConnectionCount.sum`*:: ++ +-- +The total number of concurrent TCP connections active from clients to the load balancer and from the load balancer to targets. + +type: long + +-- + +*`aws.applicationelb.metrics.ClientTLSNegotiationErrorCount.sum`*:: ++ +-- +The number of TLS connections initiated by the client that did not establish a session with the load balancer due to a TLS error. + +type: long + +-- + +*`aws.applicationelb.metrics.HTTP_Fixed_Response_Count.sum`*:: ++ +-- +The number of fixed-response actions that were successful. + +type: long + +-- + +*`aws.applicationelb.metrics.HTTP_Redirect_Count.sum`*:: ++ +-- +The number of redirect actions that were successful. + +type: long + +-- + +*`aws.applicationelb.metrics.HTTP_Redirect_Url_Limit_Exceeded_Count.sum`*:: ++ +-- +The number of redirect actions that couldn't be completed because the URL in the response location header is larger than 8K. + +type: long + +-- + +*`aws.applicationelb.metrics.HTTPCode_ELB_3XX_Count.sum`*:: ++ +-- +The number of HTTP 3XX redirection codes that originate from the load balancer. + +type: long + +-- + +*`aws.applicationelb.metrics.HTTPCode_ELB_4XX_Count.sum`*:: ++ +-- +The number of HTTP 4XX client error codes that originate from the load balancer. + +type: long + +-- + +*`aws.applicationelb.metrics.HTTPCode_ELB_5XX_Count.sum`*:: ++ +-- +The number of HTTP 5XX server error codes that originate from the load balancer. + +type: long + +-- + +*`aws.applicationelb.metrics.HTTPCode_ELB_500_Count.sum`*:: ++ +-- +The number of HTTP 500 error codes that originate from the load balancer. + +type: long + +-- + +*`aws.applicationelb.metrics.HTTPCode_ELB_502_Count.sum`*:: ++ +-- +The number of HTTP 502 error codes that originate from the load balancer. + +type: long + +-- + +*`aws.applicationelb.metrics.HTTPCode_ELB_503_Count.sum`*:: ++ +-- +The number of HTTP 503 error codes that originate from the load balancer. + +type: long + +-- + +*`aws.applicationelb.metrics.HTTPCode_ELB_504_Count.sum`*:: ++ +-- +The number of HTTP 504 error codes that originate from the load balancer. + +type: long + +-- + +*`aws.applicationelb.metrics.IPv6ProcessedBytes.sum`*:: ++ +-- +The total number of bytes processed by the load balancer over IPv6. + +type: long + +-- + +*`aws.applicationelb.metrics.IPv6RequestCount.sum`*:: ++ +-- +The number of IPv6 requests received by the load balancer. + +type: long + +-- + +*`aws.applicationelb.metrics.NewConnectionCount.sum`*:: ++ +-- +The total number of new TCP connections established from clients to the load balancer and from the load balancer to targets. + +type: long + +-- + +*`aws.applicationelb.metrics.ProcessedBytes.sum`*:: ++ +-- +The total number of bytes processed by the load balancer over IPv4 and IPv6. + +type: long + +-- + +*`aws.applicationelb.metrics.RejectedConnectionCount.sum`*:: ++ +-- +The number of connections that were rejected because the load balancer had reached its maximum number of connections. + +type: long + +-- + +*`aws.applicationelb.metrics.RequestCount.sum`*:: ++ +-- +The number of requests processed over IPv4 and IPv6. + +type: long + +-- + +*`aws.applicationelb.metrics.RuleEvaluations.sum`*:: ++ +-- +The number of rules processed by the load balancer given a request rate averaged over an hour. + +type: long + +-- + +*`aws.applicationelb.metrics.ConsumedLCUs.avg`*:: ++ +-- +The number of load balancer capacity units (LCU) used by your load balancer. + +type: double + +-- + +[float] +=== networkelb + +`networkelb` contains the metrics that were scraped from AWS CloudWatch which contains monitoring metrics sent by AWS NetworkELB. + + + + +*`aws.networkelb.metrics.ActiveFlowCount.avg`*:: ++ +-- +The total number of concurrent flows (or connections) from clients to targets. + +type: double + +-- + +*`aws.networkelb.metrics.ActiveFlowCount_TCP.avg`*:: ++ +-- +The total number of concurrent TCP flows (or connections) from clients to targets. + +type: double + +-- + +*`aws.networkelb.metrics.ActiveFlowCount_TLS.avg`*:: ++ +-- +The total number of concurrent TLS flows (or connections) from clients to targets. + +type: double + +-- + +*`aws.networkelb.metrics.ActiveFlowCount_UDP.avg`*:: ++ +-- +The total number of concurrent UDP flows (or connections) from clients to targets. + +type: double + +-- + +*`aws.networkelb.metrics.ConsumedLCUs.avg`*:: ++ +-- +The number of load balancer capacity units (LCU) used by your load balancer. + +type: double + +-- + +*`aws.networkelb.metrics.ClientTLSNegotiationErrorCount.sum`*:: ++ +-- +The total number of TLS handshakes that failed during negotiation between a client and a TLS listener. + +type: long + +-- + +*`aws.networkelb.metrics.NewFlowCount.sum`*:: ++ +-- +The total number of new flows (or connections) established from clients to targets in the time period. + +type: long + +-- + +*`aws.networkelb.metrics.NewFlowCount_TLS.sum`*:: ++ +-- +The total number of new TLS flows (or connections) established from clients to targets in the time period. + +type: long + +-- + +*`aws.networkelb.metrics.ProcessedBytes.sum`*:: ++ +-- +The total number of bytes processed by the load balancer, including TCP/IP headers. + +type: long + +-- + +*`aws.networkelb.metrics.ProcessedBytes_TLS.sum`*:: ++ +-- +The total number of bytes processed by TLS listeners. + +type: long + +-- + +*`aws.networkelb.metrics.TargetTLSNegotiationErrorCount.sum`*:: ++ +-- +The total number of TLS handshakes that failed during negotiation between a TLS listener and a target. + +type: long + +-- + +*`aws.networkelb.metrics.TCP_Client_Reset_Count.sum`*:: ++ +-- +The total number of reset (RST) packets sent from a client to a target. + +type: long + +-- + +*`aws.networkelb.metrics.TCP_ELB_Reset_Count.sum`*:: ++ +-- +The total number of reset (RST) packets generated by the load balancer. + +type: long + +-- + +*`aws.networkelb.metrics.TCP_Target_Reset_Count.sum`*:: ++ +-- +The total number of reset (RST) packets sent from a target to a client. + +type: long + +-- + +*`aws.networkelb.metrics.HealthyHostCount.max`*:: ++ +-- +The number of targets that are considered healthy. + +type: long + +-- + +*`aws.networkelb.metrics.UnHealthyHostCount.max`*:: ++ +-- +The number of targets that are considered unhealthy. + +type: long + +-- + +[float] +=== lambda + +`lambda` contains the metrics that were scraped from AWS CloudWatch which contains monitoring metrics sent by AWS Lambda. + + + + +*`aws.lambda.metrics.Invocations.avg`*:: ++ +-- +The number of times your function code is executed, including successful executions and executions that result in a function error. + +type: double + +-- + +*`aws.lambda.metrics.Errors.avg`*:: ++ +-- +The number of invocations that result in a function error. + +type: double + +-- + +*`aws.lambda.metrics.DeadLetterErrors.avg`*:: ++ +-- +For asynchronous invocation, the number of times Lambda attempts to send an event to a dead-letter queue but fails. + +type: double + +-- + +*`aws.lambda.metrics.DestinationDeliveryFailures.avg`*:: ++ +-- +For asynchronous invocation, the number of times Lambda attempts to send an event to a destination but fails. + +type: double + +-- + +*`aws.lambda.metrics.Duration.avg`*:: ++ +-- +The amount of time that your function code spends processing an event. + +type: double + +-- + +*`aws.lambda.metrics.Throttles.avg`*:: ++ +-- +The number of invocation requests that are throttled. + +type: double + +-- + +*`aws.lambda.metrics.IteratorAge.avg`*:: ++ +-- +For event source mappings that read from streams, the age of the last record in the event. + +type: double + +-- + +*`aws.lambda.metrics.ConcurrentExecutions.avg`*:: ++ +-- +The number of function instances that are processing events. + +type: double + +-- + +*`aws.lambda.metrics.UnreservedConcurrentExecutions.avg`*:: ++ +-- +For an AWS Region, the number of events that are being processed by functions that don't have reserved concurrency. + +type: double + +-- + +*`aws.lambda.metrics.ProvisionedConcurrentExecutions.max`*:: + -- -Completed write operations to all instance store volumes available to the instance in a specified period of time. - +The number of function instances that are processing events on provisioned concurrency. type: long -- -*`aws.ec2.diskio.write.ops_per_sec`*:: +*`aws.lambda.metrics.ProvisionedConcurrencyUtilization.max`*:: + -- -Completed write operations per second to all instance store volumes available to the instance in a specified period of time. - +For a version or alias, the value of ProvisionedConcurrentExecutions divided by the total amount of provisioned concurrency allocated. type: long -- -*`aws.ec2.status.check_failed`*:: +*`aws.lambda.metrics.ProvisionedConcurrencyInvocations.sum`*:: + -- -Reports whether the instance has passed both the instance status check and the system status check in the last minute. - +The number of times your function code is executed on provisioned concurrency. type: long -- -*`aws.ec2.status.check_failed_system`*:: +*`aws.lambda.metrics.ProvisionedConcurrencySpilloverInvocations.sum`*:: + -- -Reports whether the instance has passed the system status check in the last minute. - +The number of times your function code is executed on standard concurrency when all provisioned concurrency is in use. type: long -- -*`aws.ec2.status.check_failed_instance`*:: +[float] +=== natgateway + +`natgateway` contains the metrics from Cloudwatch to track usage of NAT gateway related resources. + + + + +*`aws.natgateway.metrics.BytesInFromDestination.sum`*:: + -- -Reports whether the instance has passed the instance status check in the last minute. - +The number of bytes received by the NAT gateway from the destination. type: long -- -*`aws.ec2.instance.core.count`*:: +*`aws.natgateway.metrics.BytesInFromSource.sum`*:: + -- -The number of CPU cores for the instance. - +The number of bytes received by the NAT gateway from clients in your VPC. -type: integer +type: long -- -*`aws.ec2.instance.image.id`*:: +*`aws.natgateway.metrics.BytesOutToDestination.sum`*:: + -- -The ID of the image used to launch the instance. +The number of bytes sent out through the NAT gateway to the destination. - -type: keyword +type: long -- -*`aws.ec2.instance.monitoring.state`*:: +*`aws.natgateway.metrics.BytesOutToSource.sum`*:: + -- -Indicates whether detailed monitoring is enabled. - +The number of bytes sent through the NAT gateway to the clients in your VPC. -type: keyword +type: long -- -*`aws.ec2.instance.private.dns_name`*:: +*`aws.natgateway.metrics.ConnectionAttemptCount.sum`*:: + -- -The private DNS name of the network interface. +The number of connection attempts made through the NAT gateway. - -type: keyword +type: long -- -*`aws.ec2.instance.private.ip`*:: +*`aws.natgateway.metrics.ConnectionEstablishedCount.sum`*:: + -- -The private IPv4 address associated with the network interface. - +The number of connections established through the NAT gateway. -type: ip +type: long -- -*`aws.ec2.instance.public.dns_name`*:: +*`aws.natgateway.metrics.ErrorPortAllocation.sum`*:: + -- -The public DNS name of the instance. +The number of times the NAT gateway could not allocate a source port. - -type: keyword +type: long -- -*`aws.ec2.instance.public.ip`*:: +*`aws.natgateway.metrics.IdleTimeoutCount.sum`*:: + -- -The address of the Elastic IP address (IPv4) bound to the network interface. - +The number of connections that transitioned from the active state to the idle state. -type: ip +type: long -- -*`aws.ec2.instance.state.code`*:: +*`aws.natgateway.metrics.PacketsDropCount.sum`*:: + -- -The state of the instance, as a 16-bit unsigned integer. +The number of packets dropped by the NAT gateway. - -type: integer +type: long -- -*`aws.ec2.instance.state.name`*:: +*`aws.natgateway.metrics.PacketsInFromDestination.sum`*:: + -- -The state of the instance (pending | running | shutting-down | terminated | stopping | stopped). - +The number of packets received by the NAT gateway from the destination. -type: keyword +type: long -- -*`aws.ec2.instance.threads_per_core`*:: +*`aws.natgateway.metrics.PacketsInFromSource.sum`*:: + -- -The number of threads per CPU core. +The number of packets received by the NAT gateway from clients in your VPC. - -type: integer +type: long -- -[float] -=== elb +*`aws.natgateway.metrics.PacketsOutToDestination.sum`*:: ++ +-- +The number of packets sent out through the NAT gateway to the destination. -`elb` contains the metrics that were scraped from AWS CloudWatch which contains monitoring metrics sent by AWS ELB. +type: long +-- -[float] -=== lambda +*`aws.natgateway.metrics.PacketsOutToSource.sum`*:: ++ +-- +The number of packets sent through the NAT gateway to the clients in your VPC. -`lambda` contains the metrics that were scraped from AWS CloudWatch which contains monitoring metrics sent by AWS Lambda. +type: long +-- -[float] -=== natgateway +*`aws.natgateway.metrics.ActiveConnectionCount.max`*:: ++ +-- +The total number of concurrent active TCP connections through the NAT gateway. -`natgateway` contains the metrics from Cloudwatch to track usage of NAT gateway related resources. +type: long +-- [float] === rds @@ -3255,6 +4109,107 @@ format: duration `sns` contains the metrics that were scraped from AWS CloudWatch which contains monitoring metrics sent by AWS SNS. + + +*`aws.sns.metrics.PublishSize.avg`*:: ++ +-- +The size of messages published. + +type: double + +-- + +*`aws.sns.metrics.SMSSuccessRate.avg`*:: ++ +-- +The rate of successful SMS message deliveries. + +type: double + +-- + +*`aws.sns.metrics.NumberOfMessagesPublished.sum`*:: ++ +-- +The number of messages published to your Amazon SNS topics. + +type: long + +-- + +*`aws.sns.metrics.NumberOfNotificationsDelivered.sum`*:: ++ +-- +The number of messages successfully delivered from your Amazon SNS topics to subscribing endpoints. + +type: long + +-- + +*`aws.sns.metrics.NumberOfNotificationsFailed.sum`*:: ++ +-- +The number of messages that Amazon SNS failed to deliver. + +type: long + +-- + +*`aws.sns.metrics.NumberOfNotificationsFilteredOut.sum`*:: ++ +-- +The number of messages that were rejected by subscription filter policies. + +type: long + +-- + +*`aws.sns.metrics.NumberOfNotificationsFilteredOut-InvalidAttributes.sum`*:: ++ +-- +The number of messages that were rejected by subscription filter policies because the messages' attributes are invalid – for example, because the attribute JSON is incorrectly formatted. + +type: long + +-- + +*`aws.sns.metrics.NumberOfNotificationsFilteredOut-NoMessageAttributes.sum`*:: ++ +-- +The number of messages that were rejected by subscription filter policies because the messages have no attributes. + +type: long + +-- + +*`aws.sns.metrics.NumberOfNotificationsRedrivenToDlq.sum`*:: ++ +-- +The number of messages that have been moved to a dead-letter queue. + +type: long + +-- + +*`aws.sns.metrics.NumberOfNotificationsFailedToRedriveToDlq.sum`*:: ++ +-- +The number of messages that couldn't be moved to a dead-letter queue. + +type: long + +-- + +*`aws.sns.metrics.SMSMonthToDateSpentUSD.sum`*:: ++ +-- +The charges you have accrued since the start of the current calendar month for sending SMS messages. + +type: long + +-- + [float] === sqs @@ -3372,18 +4327,123 @@ type: keyword `transitgateway` contains the metrics from Cloudwatch to track usage of transit gateway related resources. + + +*`aws.transitgateway.metrics.BytesIn.sum`*:: ++ +-- +The number of bytes received by the transit gateway. + +type: long + +-- + +*`aws.transitgateway.metrics.BytesOut.sum`*:: ++ +-- +The number of bytes sent from the transit gateway. + +type: long + +-- + +*`aws.transitgateway.metrics.PacketsIn.sum`*:: ++ +-- +The number of packets received by the transit gateway. + +type: long + +-- + +*`aws.transitgateway.metrics.PacketsOut.sum`*:: ++ +-- +The number of packets sent by the transit gateway. + +type: long + +-- + +*`aws.transitgateway.metrics.PacketDropCountBlackhole.sum`*:: ++ +-- +The number of packets dropped because they matched a blackhole route. + +type: long + +-- + +*`aws.transitgateway.metrics.PacketDropCountNoRoute.sum`*:: ++ +-- +The number of packets dropped because they did not match a route. + +type: long + +-- + [float] === usage `usage` contains the metrics from Cloudwatch to track usage of some AWS resources. + + +*`aws.usage.metrics.CallCount.sum`*:: ++ +-- +The number of specified API operations performed in your account. + +type: long + +-- + +*`aws.usage.metrics.ResourceCount.sum`*:: ++ +-- +The number of the specified resources running in your account. The resources are defined by the dimensions associated with the metric. + +type: long + +-- + [float] === vpn `vpn` contains the metrics from Cloudwatch to track usage of VPN related resources. + + +*`aws.vpn.metrics.TunnelState.avg`*:: ++ +-- +The state of the tunnel. For static VPNs, 0 indicates DOWN and 1 indicates UP. For BGP VPNs, 1 indicates ESTABLISHED and 0 is used for all other states. + +type: double + +-- + +*`aws.vpn.metrics.TunnelDataIn.sum`*:: ++ +-- +The bytes received through the VPN tunnel. + +type: double + +-- + +*`aws.vpn.metrics.TunnelDataOut.sum`*:: ++ +-- +The bytes sent through the VPN tunnel. + +type: double + +-- + [[exported-fields-azure]] == azure fields diff --git a/x-pack/metricbeat/module/aws/billing/_meta/fields.yml b/x-pack/metricbeat/module/aws/billing/_meta/fields.yml index 94b06a2f151..2b246415653 100644 --- a/x-pack/metricbeat/module/aws/billing/_meta/fields.yml +++ b/x-pack/metricbeat/module/aws/billing/_meta/fields.yml @@ -4,3 +4,9 @@ `billing` contains the estimated charges for your AWS account in Cloudwatch. release: beta fields: + - name: metrics + type: group + fields: + - name: EstimatedCharges.max + type: long + description: Maximum estimated charges for AWS acccount. diff --git a/x-pack/metricbeat/module/aws/ebs/_meta/fields.yml b/x-pack/metricbeat/module/aws/ebs/_meta/fields.yml index cd6f0ada0e3..eac4fd48d70 100644 --- a/x-pack/metricbeat/module/aws/ebs/_meta/fields.yml +++ b/x-pack/metricbeat/module/aws/ebs/_meta/fields.yml @@ -4,3 +4,39 @@ `ebs` contains the metrics that were scraped from AWS CloudWatch which contains monitoring metrics sent by AWS EBS. release: ga fields: + - name: metrics + type: group + fields: + - name: VolumeReadBytes.avg + type: double + description: Average size of each read operation during the period, except on volumes attached to a Nitro-based instance, where the average represents the average over the specified period. + - name: VolumeWriteBytes.avg + type: double + description: Average size of each write operation during the period, except on volumes attached to a Nitro-based instance, where the average represents the average over the specified period. + - name: VolumeReadOps.avg + type: double + description: The total number of read operations in a specified period of time. + - name: VolumeWriteOps.avg + type: double + description: The total number of write operations in a specified period of time. + - name: VolumeQueueLength.avg + type: double + description: The number of read and write operation requests waiting to be completed in a specified period of time. + - name: VolumeThroughputPercentage.avg + type: double + description: The percentage of I/O operations per second (IOPS) delivered of the total IOPS provisioned for an Amazon EBS volume. Used with Provisioned IOPS SSD volumes only. + - name: VolumeConsumedReadWriteOps.avg + type: double + description: The total amount of read and write operations (normalized to 256K capacity units) consumed in a specified period of time. Used with Provisioned IOPS SSD volumes only. + - name: BurstBalance.avg + type: double + description: Used with General Purpose SSD (gp2), Throughput Optimized HDD (st1), and Cold HDD (sc1) volumes only. Provides information about the percentage of I/O credits (for gp2) or throughput credits (for st1 and sc1) remaining in the burst bucket. + - name: VolumeTotalReadTime.sum + type: double + description: The total number of seconds spent by all read operations that completed in a specified period of time. + - name: VolumeTotalWriteTime.sum + type: double + description: The total number of seconds spent by all write operations that completed in a specified period of time. + - name: VolumeIdleTime.sum + type: double + description: The total number of seconds in a specified period of time when no read or write operations were submitted. diff --git a/x-pack/metricbeat/module/aws/elb/_meta/docs.asciidoc b/x-pack/metricbeat/module/aws/elb/_meta/docs.asciidoc index 3975a252e88..8f242e8867a 100644 --- a/x-pack/metricbeat/module/aws/elb/_meta/docs.asciidoc +++ b/x-pack/metricbeat/module/aws/elb/_meta/docs.asciidoc @@ -98,17 +98,25 @@ https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-cl |Metric Name|Statistic Method |ActiveFlowCount | Average |ActiveFlowCount_TLS | Average -|ClientTLSNegotiationErrorCount | Sum +|ActiveFlowCount_TCP | Average +|ActiveFlowCount_UDP | Average |ConsumedLCUs | Average -|HealthyHostCount | Maximum +|ConsumedLCUs_TCP | Average +|ConsumedLCUs_TLS | Average +|ConsumedLCUs_UDP | Average +|ClientTLSNegotiationErrorCount | Sum |NewFlowCount | Sum |NewFlowCount_TLS | Sum +|NewFlowCount_TCP | Sum +|NewFlowCount_UDP | Sum |ProcessedBytes | Sum +|ProcessedBytes_TCP | Sum |ProcessedBytes_TLS | Sum +|ProcessedBytes_UDP| Sum |TargetTLSNegotiationErrorCount | Sum |TCP_Client_Reset_Count | Sum |TCP_ELB_Reset_Count | Sum |TCP_Target_Reset_Count | Sum -|UnHealthyHostCount | Average -|EstimatedALBConsumedLCUs | Maximum +|UnHealthyHostCount | Maximum +|HealthyHostCount | Maximum |=== diff --git a/x-pack/metricbeat/module/aws/elb/_meta/fields.yml b/x-pack/metricbeat/module/aws/elb/_meta/fields.yml index 634bc04774d..c49b5a7caeb 100644 --- a/x-pack/metricbeat/module/aws/elb/_meta/fields.yml +++ b/x-pack/metricbeat/module/aws/elb/_meta/fields.yml @@ -4,3 +4,183 @@ `elb` contains the metrics that were scraped from AWS CloudWatch which contains monitoring metrics sent by AWS ELB. release: ga fields: + - name: metrics + type: group + fields: + - name: BackendConnectionErrors.sum + type: long + description: The number of connections that were not successfully established between the load balancer and the registered instances. + - name: HTTPCode_Backend_2XX.sum + type: long + description: The number of HTTP 2XX response code generated by registered instances. + - name: HTTPCode_Backend_3XX.sum + type: long + description: The number of HTTP 3XX response code generated by registered instances. + - name: HTTPCode_Backend_4XX.sum + type: long + description: The number of HTTP 4XX response code generated by registered instances. + - name: HTTPCode_Backend_5XX.sum + type: long + description: The number of HTTP 5XX response code generated by registered instances. + - name: HTTPCode_ELB_4XX.sum + type: long + description: The number of HTTP 4XX client error codes generated by the load balancer. + - name: HTTPCode_ELB_5XX.sum + type: long + description: The number of HTTP 5XX server error codes generated by the load balancer. + - name: RequestCount.sum + type: long + description: The number of requests completed or connections made during the specified interval. + - name: SpilloverCount.sum + type: long + description: The total number of requests that were rejected because the surge queue is full. + - name: HealthyHostCount.max + type: long + description: The number of healthy instances registered with your load balancer. + - name: SurgeQueueLength.max + type: long + description: The total number of requests (HTTP listener) or connections (TCP listener) that are pending routing to a healthy instance. + - name: UnHealthyHostCount.max + type: long + description: The number of unhealthy instances registered with your load balancer. + - name: Latency.avg + type: double + description: The total time elapsed, in seconds, from the time the load balancer sent the request to a registered instance until the instance started to send the response headers. + - name: EstimatedALBActiveConnectionCount.avg + type: double + description: The estimated number of concurrent TCP connections active from clients to the load balancer and from the load balancer to targets. + - name: EstimatedALBConsumedLCUs.avg + type: double + description: The estimated number of load balancer capacity units (LCU) used by an Application Load Balancer. + - name: EstimatedALBNewConnectionCount.avg + type: double + description: The estimated number of new TCP connections established from clients to the load balancer and from the load balancer to targets. + - name: EstimatedProcessedBytes.avg + type: double + description: The estimated number of bytes processed by an Application Load Balancer. +- name: applicationelb + type: group + description: > + `applicationelb` contains the metrics that were scraped from AWS CloudWatch which contains monitoring metrics sent by AWS ApplicationELB. + release: ga + fields: + - name: metrics + type: group + fields: + - name: ActiveConnectionCount.sum + type: long + description: The total number of concurrent TCP connections active from clients to the load balancer and from the load balancer to targets. + - name: ClientTLSNegotiationErrorCount.sum + type: long + description: The number of TLS connections initiated by the client that did not establish a session with the load balancer due to a TLS error. + - name: HTTP_Fixed_Response_Count.sum + type: long + description: The number of fixed-response actions that were successful. + - name: HTTP_Redirect_Count.sum + type: long + description: The number of redirect actions that were successful. + - name: HTTP_Redirect_Url_Limit_Exceeded_Count.sum + type: long + description: The number of redirect actions that couldn't be completed because the URL in the response location header is larger than 8K. + - name: HTTPCode_ELB_3XX_Count.sum + type: long + description: The number of HTTP 3XX redirection codes that originate from the load balancer. + - name: HTTPCode_ELB_4XX_Count.sum + type: long + description: The number of HTTP 4XX client error codes that originate from the load balancer. + - name: HTTPCode_ELB_5XX_Count.sum + type: long + description: The number of HTTP 5XX server error codes that originate from the load balancer. + - name: HTTPCode_ELB_500_Count.sum + type: long + description: The number of HTTP 500 error codes that originate from the load balancer. + - name: HTTPCode_ELB_502_Count.sum + type: long + description: The number of HTTP 502 error codes that originate from the load balancer. + - name: HTTPCode_ELB_503_Count.sum + type: long + description: The number of HTTP 503 error codes that originate from the load balancer. + - name: HTTPCode_ELB_504_Count.sum + type: long + description: The number of HTTP 504 error codes that originate from the load balancer. + - name: IPv6ProcessedBytes.sum + type: long + description: The total number of bytes processed by the load balancer over IPv6. + - name: IPv6RequestCount.sum + type: long + description: The number of IPv6 requests received by the load balancer. + - name: NewConnectionCount.sum + type: long + description: The total number of new TCP connections established from clients to the load balancer and from the load balancer to targets. + - name: ProcessedBytes.sum + type: long + description: The total number of bytes processed by the load balancer over IPv4 and IPv6. + - name: RejectedConnectionCount.sum + type: long + description: The number of connections that were rejected because the load balancer had reached its maximum number of connections. + - name: RequestCount.sum + type: long + description: The number of requests processed over IPv4 and IPv6. + - name: RuleEvaluations.sum + type: long + description: The number of rules processed by the load balancer given a request rate averaged over an hour. + - name: ConsumedLCUs.avg + type: double + description: The number of load balancer capacity units (LCU) used by your load balancer. +- name: networkelb + type: group + description: > + `networkelb` contains the metrics that were scraped from AWS CloudWatch which contains monitoring metrics sent by AWS NetworkELB. + release: ga + fields: + - name: metrics + type: group + fields: + - name: ActiveFlowCount.avg + type: double + description: The total number of concurrent flows (or connections) from clients to targets. + - name: ActiveFlowCount_TCP.avg + type: double + description: The total number of concurrent TCP flows (or connections) from clients to targets. + - name: ActiveFlowCount_TLS.avg + type: double + description: The total number of concurrent TLS flows (or connections) from clients to targets. + - name: ActiveFlowCount_UDP.avg + type: double + description: The total number of concurrent UDP flows (or connections) from clients to targets. + - name: ConsumedLCUs.avg + type: double + description: The number of load balancer capacity units (LCU) used by your load balancer. + - name: ClientTLSNegotiationErrorCount.sum + type: long + description: The total number of TLS handshakes that failed during negotiation between a client and a TLS listener. + - name: NewFlowCount.sum + type: long + description: The total number of new flows (or connections) established from clients to targets in the time period. + - name: NewFlowCount_TLS.sum + type: long + description: The total number of new TLS flows (or connections) established from clients to targets in the time period. + - name: ProcessedBytes.sum + type: long + description: The total number of bytes processed by the load balancer, including TCP/IP headers. + - name: ProcessedBytes_TLS.sum + type: long + description: The total number of bytes processed by TLS listeners. + - name: TargetTLSNegotiationErrorCount.sum + type: long + description: The total number of TLS handshakes that failed during negotiation between a TLS listener and a target. + - name: TCP_Client_Reset_Count.sum + type: long + description: The total number of reset (RST) packets sent from a client to a target. + - name: TCP_ELB_Reset_Count.sum + type: long + description: The total number of reset (RST) packets generated by the load balancer. + - name: TCP_Target_Reset_Count.sum + type: long + description: The total number of reset (RST) packets sent from a target to a client. + - name: HealthyHostCount.max + type: long + description: The number of targets that are considered healthy. + - name: UnHealthyHostCount.max + type: long + description: The number of targets that are considered unhealthy. diff --git a/x-pack/metricbeat/module/aws/elb/manifest.yml b/x-pack/metricbeat/module/aws/elb/manifest.yml index 211a42c63bd..daa6a34fffd 100644 --- a/x-pack/metricbeat/module/aws/elb/manifest.yml +++ b/x-pack/metricbeat/module/aws/elb/manifest.yml @@ -32,12 +32,12 @@ input: tags.resource_type_filter: elasticloadbalancing - namespace: AWS/NetworkELB statistic: ["Average"] - name: ["ActiveFlowCount", "ActiveFlowCount_TLS", "ConsumedLCUs"] + name: ["ActiveFlowCount", "ActiveFlowCount_TCP", "ActiveFlowCount_TLS", "ActiveFlowCount_UDP", "ConsumedLCUs", "ConsumedLCUs_TCP", "ConsumedLCUs_TLS", "ConsumedLCUs_UDP"] tags.resource_type_filter: elasticloadbalancing - namespace: AWS/NetworkELB statistic: ["Sum"] - name: ["ClientTLSNegotiationErrorCount", "NewFlowCount", "NewFlowCount_TLS", - "ProcessedBytes", "ProcessedBytes_TLS", "TargetTLSNegotiationErrorCount", + name: ["ClientTLSNegotiationErrorCount", "NewFlowCount", "NewFlowCount_TLS", "NewFlowCount_TCP", "NewFlowCount_UDP", + "ProcessedBytes", "ProcessedBytes_TCP", "ProcessedBytes_TLS", "ProcessedBytes_UDP", "TargetTLSNegotiationErrorCount", "TCP_Client_Reset_Count", "TCP_ELB_Reset_Count", "TCP_Target_Reset_Count"] tags.resource_type_filter: elasticloadbalancing - namespace: AWS/NetworkELB diff --git a/x-pack/metricbeat/module/aws/fields.go b/x-pack/metricbeat/module/aws/fields.go index d50507f86c0..f3bcf9a8eb6 100644 --- a/x-pack/metricbeat/module/aws/fields.go +++ b/x-pack/metricbeat/module/aws/fields.go @@ -19,5 +19,5 @@ func init() { // AssetAws returns asset data. // This is the base64 encoded gzipped contents of module/aws. func AssetAws() string { - return "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" + return "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" } diff --git a/x-pack/metricbeat/module/aws/lambda/_meta/docs.asciidoc b/x-pack/metricbeat/module/aws/lambda/_meta/docs.asciidoc index 1d1f8e22a7e..70f1263823c 100644 --- a/x-pack/metricbeat/module/aws/lambda/_meta/docs.asciidoc +++ b/x-pack/metricbeat/module/aws/lambda/_meta/docs.asciidoc @@ -43,6 +43,7 @@ https://docs.aws.amazon.com/lambda/latest/dg/monitoring-functions-metrics.html[l |Invocations | Average |Errors | Average |DeadLetterErrors | Average +|DestinationDeliveryFailures | Average |Duration | Average |Throttles | Average |IteratorAge | Average diff --git a/x-pack/metricbeat/module/aws/lambda/_meta/fields.yml b/x-pack/metricbeat/module/aws/lambda/_meta/fields.yml index 1cebeff318f..91becec6fef 100644 --- a/x-pack/metricbeat/module/aws/lambda/_meta/fields.yml +++ b/x-pack/metricbeat/module/aws/lambda/_meta/fields.yml @@ -4,3 +4,45 @@ `lambda` contains the metrics that were scraped from AWS CloudWatch which contains monitoring metrics sent by AWS Lambda. release: beta fields: + - name: metrics + type: group + fields: + - name: Invocations.avg + type: double + description: The number of times your function code is executed, including successful executions and executions that result in a function error. + - name: Errors.avg + type: double + description: The number of invocations that result in a function error. + - name: DeadLetterErrors.avg + type: double + description: For asynchronous invocation, the number of times Lambda attempts to send an event to a dead-letter queue but fails. + - name: DestinationDeliveryFailures.avg + type: double + description: For asynchronous invocation, the number of times Lambda attempts to send an event to a destination but fails. + - name: Duration.avg + type: double + description: The amount of time that your function code spends processing an event. + - name: Throttles.avg + type: double + description: The number of invocation requests that are throttled. + - name: IteratorAge.avg + type: double + description: For event source mappings that read from streams, the age of the last record in the event. + - name: ConcurrentExecutions.avg + type: double + description: The number of function instances that are processing events. + - name: UnreservedConcurrentExecutions.avg + type: double + description: For an AWS Region, the number of events that are being processed by functions that don't have reserved concurrency. + - name: ProvisionedConcurrentExecutions.max + type: long + description: The number of function instances that are processing events on provisioned concurrency. + - name: ProvisionedConcurrencyUtilization.max + type: long + description: For a version or alias, the value of ProvisionedConcurrentExecutions divided by the total amount of provisioned concurrency allocated. + - name: ProvisionedConcurrencyInvocations.sum + type: long + description: The number of times your function code is executed on provisioned concurrency. + - name: ProvisionedConcurrencySpilloverInvocations.sum + type: long + description: The number of times your function code is executed on standard concurrency when all provisioned concurrency is in use. diff --git a/x-pack/metricbeat/module/aws/lambda/manifest.yml b/x-pack/metricbeat/module/aws/lambda/manifest.yml index 71fd0b7ef1c..f1f18fd8783 100644 --- a/x-pack/metricbeat/module/aws/lambda/manifest.yml +++ b/x-pack/metricbeat/module/aws/lambda/manifest.yml @@ -6,7 +6,7 @@ input: metrics: - namespace: AWS/Lambda statistic: ["Average"] - name: ["Invocations", "Errors", "DeadLetterErrors", "Duration", + name: ["Invocations", "Errors", "DeadLetterErrors", "DestinationDeliveryFailures", "Duration", "Throttles", "IteratorAge", "ConcurrentExecutions", "UnreservedConcurrentExecutions"] tags.resource_type_filter: lambda diff --git a/x-pack/metricbeat/module/aws/natgateway/_meta/fields.yml b/x-pack/metricbeat/module/aws/natgateway/_meta/fields.yml index d6694c4c656..8ff5a3a2edd 100644 --- a/x-pack/metricbeat/module/aws/natgateway/_meta/fields.yml +++ b/x-pack/metricbeat/module/aws/natgateway/_meta/fields.yml @@ -4,3 +4,48 @@ `natgateway` contains the metrics from Cloudwatch to track usage of NAT gateway related resources. release: beta fields: + - name: metrics + type: group + fields: + - name: BytesInFromDestination.sum + type: long + description: The number of bytes received by the NAT gateway from the destination. + - name: BytesInFromSource.sum + type: long + description: The number of bytes received by the NAT gateway from clients in your VPC. + - name: BytesOutToDestination.sum + type: long + description: The number of bytes sent out through the NAT gateway to the destination. + - name: BytesOutToSource.sum + type: long + description: The number of bytes sent through the NAT gateway to the clients in your VPC. + - name: ConnectionAttemptCount.sum + type: long + description: The number of connection attempts made through the NAT gateway. + - name: ConnectionEstablishedCount.sum + type: long + description: The number of connections established through the NAT gateway. + - name: ErrorPortAllocation.sum + type: long + description: The number of times the NAT gateway could not allocate a source port. + - name: IdleTimeoutCount.sum + type: long + description: The number of connections that transitioned from the active state to the idle state. + - name: PacketsDropCount.sum + type: long + description: The number of packets dropped by the NAT gateway. + - name: PacketsInFromDestination.sum + type: long + description: The number of packets received by the NAT gateway from the destination. + - name: PacketsInFromSource.sum + type: long + description: The number of packets received by the NAT gateway from clients in your VPC. + - name: PacketsOutToDestination.sum + type: long + description: The number of packets sent out through the NAT gateway to the destination. + - name: PacketsOutToSource.sum + type: long + description: The number of packets sent through the NAT gateway to the clients in your VPC. + - name: ActiveConnectionCount.max + type: long + description: The total number of concurrent active TCP connections through the NAT gateway. diff --git a/x-pack/metricbeat/module/aws/sns/_meta/fields.yml b/x-pack/metricbeat/module/aws/sns/_meta/fields.yml index a70ffa1568c..5c34d717373 100644 --- a/x-pack/metricbeat/module/aws/sns/_meta/fields.yml +++ b/x-pack/metricbeat/module/aws/sns/_meta/fields.yml @@ -4,3 +4,39 @@ `sns` contains the metrics that were scraped from AWS CloudWatch which contains monitoring metrics sent by AWS SNS. release: beta fields: + - name: metrics + type: group + fields: + - name: PublishSize.avg + type: double + description: The size of messages published. + - name: SMSSuccessRate.avg + type: double + description: The rate of successful SMS message deliveries. + - name: NumberOfMessagesPublished.sum + type: long + description: The number of messages published to your Amazon SNS topics. + - name: NumberOfNotificationsDelivered.sum + type: long + description: The number of messages successfully delivered from your Amazon SNS topics to subscribing endpoints. + - name: NumberOfNotificationsFailed.sum + type: long + description: The number of messages that Amazon SNS failed to deliver. + - name: NumberOfNotificationsFilteredOut.sum + type: long + description: The number of messages that were rejected by subscription filter policies. + - name: NumberOfNotificationsFilteredOut-InvalidAttributes.sum + type: long + description: The number of messages that were rejected by subscription filter policies because the messages' attributes are invalid – for example, because the attribute JSON is incorrectly formatted. + - name: NumberOfNotificationsFilteredOut-NoMessageAttributes.sum + type: long + description: The number of messages that were rejected by subscription filter policies because the messages have no attributes. + - name: NumberOfNotificationsRedrivenToDlq.sum + type: long + description: The number of messages that have been moved to a dead-letter queue. + - name: NumberOfNotificationsFailedToRedriveToDlq.sum + type: long + description: The number of messages that couldn't be moved to a dead-letter queue. + - name: SMSMonthToDateSpentUSD.sum + type: long + description: The charges you have accrued since the start of the current calendar month for sending SMS messages. diff --git a/x-pack/metricbeat/module/aws/transitgateway/_meta/fields.yml b/x-pack/metricbeat/module/aws/transitgateway/_meta/fields.yml index ed376cd296d..e687ae973d0 100644 --- a/x-pack/metricbeat/module/aws/transitgateway/_meta/fields.yml +++ b/x-pack/metricbeat/module/aws/transitgateway/_meta/fields.yml @@ -4,3 +4,24 @@ `transitgateway` contains the metrics from Cloudwatch to track usage of transit gateway related resources. release: beta fields: + - name: metrics + type: group + fields: + - name: BytesIn.sum + type: long + description: The number of bytes received by the transit gateway. + - name: BytesOut.sum + type: long + description: The number of bytes sent from the transit gateway. + - name: PacketsIn.sum + type: long + description: The number of packets received by the transit gateway. + - name: PacketsOut.sum + type: long + description: The number of packets sent by the transit gateway. + - name: PacketDropCountBlackhole.sum + type: long + description: The number of packets dropped because they matched a blackhole route. + - name: PacketDropCountNoRoute.sum + type: long + description: The number of packets dropped because they did not match a route. diff --git a/x-pack/metricbeat/module/aws/usage/_meta/fields.yml b/x-pack/metricbeat/module/aws/usage/_meta/fields.yml index aa138f70eec..6b69118adc6 100644 --- a/x-pack/metricbeat/module/aws/usage/_meta/fields.yml +++ b/x-pack/metricbeat/module/aws/usage/_meta/fields.yml @@ -4,3 +4,12 @@ `usage` contains the metrics from Cloudwatch to track usage of some AWS resources. release: beta fields: + - name: metrics + type: group + fields: + - name: CallCount.sum + type: long + description: The number of specified API operations performed in your account. + - name: ResourceCount.sum + type: long + description: The number of the specified resources running in your account. The resources are defined by the dimensions associated with the metric. diff --git a/x-pack/metricbeat/module/aws/vpn/_meta/fields.yml b/x-pack/metricbeat/module/aws/vpn/_meta/fields.yml index 9d1ae32eebb..4569fcc1a57 100644 --- a/x-pack/metricbeat/module/aws/vpn/_meta/fields.yml +++ b/x-pack/metricbeat/module/aws/vpn/_meta/fields.yml @@ -4,3 +4,15 @@ `vpn` contains the metrics from Cloudwatch to track usage of VPN related resources. release: beta fields: + - name: metrics + type: group + fields: + - name: TunnelState.avg + type: double + description: The state of the tunnel. For static VPNs, 0 indicates DOWN and 1 indicates UP. For BGP VPNs, 1 indicates ESTABLISHED and 0 is used for all other states. + - name: TunnelDataIn.sum + type: double + description: The bytes received through the VPN tunnel. + - name: TunnelDataOut.sum + type: double + description: The bytes sent through the VPN tunnel. From d00ccfa2ef615af9243608631dcc7ea9195ea9ee Mon Sep 17 00:00:00 2001 From: DeDe Morton Date: Wed, 29 Apr 2020 11:05:55 -0700 Subject: [PATCH 055/116] [docs] Refactor heartbeat monitor docs (#18075) --- heartbeat/docs/configuring-howto.asciidoc | 3 + heartbeat/docs/heartbeat-options.asciidoc | 663 +----------------- heartbeat/docs/heartbeat-scheduler.asciidoc | 41 ++ .../monitors/monitor-common-options.asciidoc | 143 ++++ heartbeat/docs/monitors/monitor-http.asciidoc | 246 +++++++ heartbeat/docs/monitors/monitor-icmp.asciidoc | 39 ++ heartbeat/docs/monitors/monitor-tcp.asciidoc | 134 ++++ 7 files changed, 620 insertions(+), 649 deletions(-) create mode 100644 heartbeat/docs/heartbeat-scheduler.asciidoc create mode 100644 heartbeat/docs/monitors/monitor-common-options.asciidoc create mode 100644 heartbeat/docs/monitors/monitor-http.asciidoc create mode 100644 heartbeat/docs/monitors/monitor-icmp.asciidoc create mode 100644 heartbeat/docs/monitors/monitor-tcp.asciidoc diff --git a/heartbeat/docs/configuring-howto.asciidoc b/heartbeat/docs/configuring-howto.asciidoc index 7f2b6547d7e..525fc15baf3 100644 --- a/heartbeat/docs/configuring-howto.asciidoc +++ b/heartbeat/docs/configuring-howto.asciidoc @@ -26,6 +26,7 @@ _Beats Platform Reference_ for more about the structure of the config file. The following topics describe how to configure Heartbeat: * <> +* <> * <> * <> * <> @@ -44,6 +45,8 @@ The following topics describe how to configure Heartbeat: include::./heartbeat-options.asciidoc[] +include::./heartbeat-scheduler.asciidoc[] + include::./heartbeat-general-options.asciidoc[] include::{libbeat-dir}/shared-path-config.asciidoc[] diff --git a/heartbeat/docs/heartbeat-options.asciidoc b/heartbeat/docs/heartbeat-options.asciidoc index 4e166610a53..23f33c26a3d 100644 --- a/heartbeat/docs/heartbeat-options.asciidoc +++ b/heartbeat/docs/heartbeat-options.asciidoc @@ -81,661 +81,26 @@ monitor definitions only, e.g. what is normally under the `heartbeat.monitors` s ---------------------------------------------------------------------- [float] -[[monitor-options]] -=== Monitor options +[[monitor-types]] +=== Monitor types -You can specify the following options when defining a {beatname_uc} monitor in any location. -These options are the same for all monitors. Each monitor type has additional configuration -options that are specific to that monitor type. +You can configure {beatname_uc} to use the following monitor types: -[float] -[[monitor-type]] -==== `type` - -The type of monitor to run. One of: - -* `icmp`: Uses an ICMP (v4 and v6) Echo Request to ping the configured hosts. -Requires special permissions or root access. See <>. -* `tcp`: Connects via TCP and optionally verifies the endpoint by sending and/or -receiving a custom payload. See <>. -* `http`: Connects via HTTP and optionally verifies that the host returns the -expected response. See <>. Will use `Elastic-Heartbeat` as the user agent product. +*<>*:: Uses an ICMP (v4 and v6) Echo Request to ping the configured hosts. +Requires special permissions or root access. +*<>*:: Connects via TCP and optionally verifies the endpoint by sending and/or +receiving a custom payload. +*<>*:: Connects via HTTP and optionally verifies that the host returns the +expected response. Will use `Elastic-Heartbeat` as +the user agent product. The `tcp` and `http` monitor types both support SSL/TLS and some proxy settings. -[float] -[[monitor-id]] -==== `id` - -A unique identifier for this configuration. This should not change with edits to the monitor configuration -regardless of changes to any config fields. Examples: `uploader-service`, `http://example.net`, `us-west-loadbalancer`. Note that this uniqueness is only within a given beat instance. If you want to monitor the same endpoint from multiple locations it is recommended that those heartbeat instances use the same IDs so that their results can be correlated. You can use the `host.geo.name` property to disambiguate them. - -When querying against indexed monitor data this is the field you will be aggregating with. Appears in the -<> as `monitor.id`. - -If you do not set this explicitly the monitor's config will be hashed and a generated value used. This value will -change with any options change to this monitor making aggregations over time between changes impossible. For this reason -it is recommended that you set this manually. - -[float] -[[monitor-name]] -==== `name` - -Optional human readable name for this monitor. This value appears in the <> -as `monitor.name`. - -[float] -[[monitor-enabled]] -==== `enabled` - -A Boolean value that specifies whether the module is enabled. If the `enabled` -option is missing from the configuration block, the module is enabled by -default. - -[float] -[[monitor-schedule]] -==== `schedule` - -A cron-like expression that specifies the task schedule. For example: - -* `*/5 * * * * * *` runs the task every 5 seconds (for example, at 10:00:00, -10:00:05, and so on). -* `@every 5s` runs the task every 5 seconds from the time when {beatname_uc} was -started. - -The `schedule` option uses a cron-like syntax based on https://github.com/gorhill/cronexpr#implementation[this `cronexpr` implementation], -but adds the `@every` keyword. - -For stats on the execution of scheduled tasks you can enable the HTTP stats server with `http.enabled: true` in heartbeat.yml, then run `curl http://localhost:5066/stats | jq .heartbeat.scheduler` to view the scheduler's stats. Stats are provided for both jobs and tasks. Each time a monitor is scheduled is considered to be a single job, while portions of the work a job does, like DNS lookups and executing network requests are defined as tasks. The stats provided are: - -* **jobs.active:** The number of actively running jobs/monitors. -* **jobs.missed_deadline:** The number of jobs that executed after their scheduled time. This can be caused either by overlong long timeouts from the previous job or high load preventing heartbeat from keeping up with work. -* **tasks.active:** The number of tasks currently running. -* **tasks.waiting:** If the global `schedule.limit` option is set, this number will reflect the number of tasks that are ready to execute, but have not been started in order to prevent exceeding `schedule.limit`. - -[float] -[[monitor-ipv4]] -==== `ipv4` - -A Boolean value that specifies whether to ping using the ipv4 protocol if -hostnames are configured. The default is `true`. - -[float] -[[monitor-ipv6]] -==== `ipv6` - -A Boolean value that specifies whether to ping using the ipv6 protocol -if hostnames are configured. The default is `true`. - -[float] -[[monitor-mode]] -==== `mode` - -If `mode` is `any`, the monitor pings only one IP address for a hostname. If -`mode` is `all`, the monitor pings all resolvable IPs for a hostname. The -`mode: all` setting is useful if you are using a DNS-load balancer and want to -ping every IP address for the specified hostname. The default is `any`. - -[float] -[[monitor-timeout]] -==== `timeout` - -The total running time for each ping test. This is the total time allowed for -testing the connection and exchanging data. The default is 16 seconds (16s). - -If the timeout is exceeded, {beatname_uc} publishes a `service-down` event. If the -value specified for `timeout` is greater than `schedule`, intermediate checks -will not be executed by the scheduler. - -[float] -[[monitor-fields]] -==== `fields` - -Optional fields that you can specify to add additional information to the -output. For example, you might add fields that you can use for filtering log -data. Fields can be scalar values, arrays, dictionaries, or any nested -combination of these. By default, the fields that you specify here will be -grouped under a `fields` sub-dictionary in the output document. To store the -custom fields as top-level fields, set the `fields_under_root` option to true. -If a duplicate field is declared in the general configuration, then its value -will be overwritten by the value declared here. - -[float] -[[monitor-fields-under-root]] -==== `fields_under_root` - -If this option is set to true, the custom <> -are stored as top-level fields in the output document instead of being grouped -under a `fields` sub-dictionary. If the custom field names conflict with other -field names added by {beatname_uc}, then the custom fields overwrite the other -fields. - -[float] -[[monitor-tags]] -==== `tags` - -A list of tags that will be sent with the monitor event. This setting is optional. - -[float] -[[monitor-processors]] -==== `processors` - -A list of processors to apply to the data generated by the monitor. - -See <> for information about specifying -processors in your config. - -[float] -[[monitor-keep-null]] -==== `keep_null` - -If this option is set to true, fields with `null` values will be published in -the output document. By default, `keep_null` is set to `false`. - -[float] -[[monitor-icmp-options]] -=== ICMP options - -These options configure {beatname_uc} to use ICMP (v4 and v6) Echo Requests to check -the configured hosts. These options are valid when the <> is -`icmp`. Please note that on most platforms you must execute Heartbeat with elevated permissions -to perform ICMP pings. - -On Linux, regular users may perform pings if the right file capabilities are set. Run -`sudo setcap cap_net_raw+eip /path/to/heartbeat` to grant {beatname_uc} ping capabilities on Linux. -Alternatively, one may grant ping permissions to the user {beatname_uc} runs as. To grant ping permissions -in this way, run `sudo sysctl -w net.ipv4.ping_group_range='myuserid myuserid'`. - -Other platforms may require {beatname_uc} to run as root or administrator to execute pings. - -[float] -[[monitor-icmp-hosts]] -==== `hosts` - -A list of hosts to ping. - -[float] -[[monitor-icmp-wait]] -==== `wait` - -The duration to wait before emitting another ICMP Echo Request. The default is 1 -second (1s). - -[float] -[[monitor-tcp-options]] -=== TCP options - -These options configure {beatname_uc} to connect via TCP and optionally verify the -endpoint by sending and/or receiving a custom payload. These options are valid when -the <> is `tcp`. - -[float] -[[monitor-tcp-hosts]] -==== `hosts` - -A list of hosts to ping. The entries in the list can be: - -* A plain host name, such as `localhost`, or an IP address. If you specify this -option, you must also specify a value for <>. If the -monitor is <>, {beatname_uc} establishes an -SSL/TLS-based connection. Otherwise, it establishes a plain TCP connection. -* A hostname and port, such as `localhost:12345`. {beatname_uc} connects -to the port on the specified host. If the monitor is -<>, {beatname_uc} establishes an -SSL/TLS-based connection. Otherwise, it establishes a TCP connection. -* A full URL using the syntax `scheme://:[port]`, where: -** `scheme` is one of `tcp`, `plain`, `ssl` or `tls`. If `tcp` or `plain` is -specified, {beatname_uc} establishes a TCP connection even if the monitor is -configured to use SSL. If `tls` or `ssl` is specified, {beatname_uc} establishes -an SSL connection. However, if the monitor is not configured to use SSL, the -system defaults are used (currently not supported on Windows). -** `host` is the hostname. -** `port` is the port number. If `port` is missing in the URL, the -<> setting is required. - -[float] -[[monitor-tcp-ports]] -==== `ports` - -A list of ports to ping if the host specified in <> -does not contain a port number. It is generally preferable to use a single value here, -since each port will be monitored using a separate `id`, with the given `id` value, -used as a prefix in the Heartbeat data, and the configured `name` shared across events -sent via this check. - -Example configuration: - -[source,yaml] -------------------------------------------------------------------------------- -- type: tcp - id: my-host-services - name: My Host Services - hosts: ["myhost"] - ports: [80, 9200, 5044] - schedule: '@every 5s' -------------------------------------------------------------------------------- - -[float] -[[monitor-tcp-check]] -==== `check` - -An optional payload string to send to the remote host and the expected answer. -If no payload is specified, the endpoint is assumed to be available if the -connection attempt was successful. If `send` is specified without `receive`, -any response is accepted as OK. If `receive` is specified without `send`, no -payload is sent, but the client expects to receive a payload in the form of a -"hello message" or "banner" on connect. - -Example configuration: - -[source,yaml] -------------------------------------------------------------------------------- -- type: tcp - id: echo-service - name: Echo Service - hosts: ["myhost"] - ports: [7] - check.send: 'Hello World' - check.receive: 'Hello World' - schedule: '@every 5s' -------------------------------------------------------------------------------- - - -[float] -[[monitor-tcp-proxy-url]] -==== `proxy_url` - -The URL of the SOCKS5 proxy to use when connecting to the server. The value -must be a URL with a scheme of socks5://. - -If the SOCKS5 proxy server requires client authentication, then a username and -password can be embedded in the URL as shown in the example. - -[source,yaml] -------------------------------------------------------------------------------- - proxy_url: socks5://user:password@socks5-proxy:2233 -------------------------------------------------------------------------------- - -When using a proxy, hostnames are resolved on the proxy server instead of on -the client. You can change this behavior by setting the -`proxy_use_local_resolver` option. - -[float] -[[monitor-tcp-proxy-use-local-resolver]] -==== `proxy_use_local_resolver` - -A Boolean value that determines whether hostnames are resolved locally instead -of being resolved on the proxy server. The default value is false, which means -that name resolution occurs on the proxy server. - -[float] -[[monitor-tcp-tls-ssl]] -==== `ssl` - -The TLS/SSL connection settings. If the monitor is -<>, it will attempt an SSL -handshake. If `check` is not configured, the monitor will only check to see if -it can establish an SSL/TLS connection. This check can fail either at TCP level -or during certificate validation. - -Example configuration: - -[source,yaml] -------------------------------------------------------------------------------- -- type: tcp - id: tls-mail - name: TLS Mail - hosts: ["mail.example.net"] - ports: [465] - schedule: '@every 5s' - ssl: - certificate_authorities: ['/etc/ca.crt'] - supported_protocols: ["TLSv1.0", "TLSv1.1", "TLSv1.2"] -------------------------------------------------------------------------------- - - -Also see <> for a full description of the `ssl` options. - -[float] -[[monitor-http-options]] -=== HTTP options - -These options configure {beatname_uc} to connect via HTTP and optionally verify that -the host returns the expected response. These options are valid when the -<> is `http`. - -[float] -[[monitor-http-urls]] -==== `hosts` - -A list of URLs to ping. - -Example configuration: - -[source,yaml] -------------------------------------------------------------------------------- -- type: http - id: myhost - name: My HTTP Host - schedule: '@every 5s' - hosts: ["http://myhost:80"] -------------------------------------------------------------------------------- - -[float] -[[monitor-http-max-redirects]] -==== `max_redirects` - -The total number of redirections Heartbeat will follow. Defaults to 0, meaning heartbeat will not follow redirects, -but will report the status of the redirect. If set to a number greater than 0 heartbeat will follow that number of redirects. - -When this option is set to a value greater than zero the `monitor.ip` field will no longer be reported, as multiple -DNS requests across multiple IPs may return multiple IPs. Fine grained network timing data will also not be recorded, as with redirects -that data will span multiple requests. Specifically the fields `http.rtt.content.us`, `http.rtt.response_header.us`, -`http.rtt.total.us`, `http.rtt.validate.us`, `http.rtt.write_request.us` and `dns.rtt.us` will be omitted. - -[float] -[[monitor-http-proxy-url]] -==== `proxy_url` - -The HTTP proxy URL. This setting is optional. Example `http://proxy.mydomain.com:3128` - -[float] -[[monitor-http-username]] -==== `username` - -The username for authenticating with the server. The credentials are passed -with the request. This setting is optional. - -You need to specify credentials when your `check.response` settings require it. -For example, you can check for a 403 response (`check.response.status: [403]`) -without setting credentials. - -[float] -[[monitor-http-password]] -==== `password` - -The password for authenticating with the server. This setting is optional. - -[float] -[[monitor-http-tls-ssl]] -==== `ssl` - -The TLS/SSL connection settings for use with the HTTPS endpoint. If you don't -specify settings, the system defaults are used. - - -Example configuration: - -[source,yaml] -------------------------------------------------------------------------------- -- type: http - id: my-http-service - name: My HTTP Service - hosts: ["https://myhost:443"] - schedule: '@every 5s' - ssl: - certificate_authorities: ['/etc/ca.crt'] - supported_protocols: ["TLSv1.0", "TLSv1.1", "TLSv1.2"] -------------------------------------------------------------------------------- - -Also see <> for a full description of the `ssl` options. - -[float] -[[monitor-http-response]] -=== `response` - -Controls the indexing of the HTTP response body contents to the `http.response.body.contents` field. - -Set `response.include_body` to one of the options listed below. - -*`on_error`*:: Include the body if an error is encountered during the check. This is the default. -*`never`*:: Never include the body. -*`always`*:: Always include the body with checks. - -Set `response.include_body_max_bytes` to control the maximum size of the stored body contents. Defaults to 1024 bytes. - -[float] -[[monitor-http-check]] -==== `check` - -An optional `request` to send to the remote host and the expected `response`. - -Example configuration: - -[source,yaml] -------------------------------------------------------------------------------- -- type: http - id: my-http-host - name: My HTTP Service - hosts: ["http://myhost:80"] - check.request.method: HEAD - check.response.status: [200] - schedule: '@every 5s' -------------------------------------------------------------------------------- - - -Under `check.request`, specify these options: - -*`method`*:: The HTTP method to use. Valid values are `"HEAD"`, `"GET"` and -`"POST"`. -*`headers`*:: A dictionary of additional HTTP headers to send. By default heartbeat -will set the 'User-Agent' header to identify itself. -*`body`*:: Optional request body content. - -Example configuration: -This monitor POSTs an `x-www-form-urlencoded` string -to the endpoint `/demo/add` - -[source,yaml] -------------------------------------------------------------------------------- -- type: http - id: demo-service - name: Demo Service - schedule: '@every 5s' - urls: ["http://localhost:8080/demo/add"] - check.request: - method: POST - headers: - 'Content-Type': 'application/x-www-form-urlencoded' - # urlencode the body: - body: "name=first&email=someemail%40someemailprovider.com" - check.response: - status: [200] - body: - - Saved - - saved -------------------------------------------------------------------------------- - -Under `check.response`, specify these options: - -*`status`*:: A list of expected status codes. 4xx and 5xx codes are considered `down` by default. Other codes are considered `up`. -*`headers`*:: The required response headers. -*`body`*:: A list of regular expressions to match the the body output. Only a single expression needs to match. HTTP response -bodies of up to 100MiB are supported. - -Example configuration: -This monitor examines the -response body for the strings `saved` or `Saved` and expects 200 or 201 status codes - -[source,yaml] -------------------------------------------------------------------------------- -- type: http - id: demo-service - name: Demo Service - schedule: '@every 5s' - urls: ["http://localhost:8080/demo/add"] - check.request: - method: POST - headers: - 'Content-Type': 'application/x-www-form-urlencoded' - # urlencode the body: - body: "name=first&email=someemail%40someemailprovider.com" - check.response: - status: [200, 201] - body: - - Saved - - saved -------------------------------------------------------------------------------- - -*`json`*:: A list of <> expressions executed against the body when parsed as JSON. Body sizes -must be less than or equal to 100 MiB. - -The following configuration shows how to check the response when the body -contains JSON: - -[source,yaml] -------------------------------------------------------------------------------- -- type: http - id: demo-service - name: Demo Service - schedule: '@every 5s' - hosts: ["https://myhost:80"] - check.request: - method: GET - headers: - 'X-API-Key': '12345-mykey-67890' - check.response: - status: [200] - json: - - description: check status - condition: - equals: - status: ok -------------------------------------------------------------------------------- - -The following configuration shows how to check the response for multiple regex -patterns: - -[source,yaml] -------------------------------------------------------------------------------- -- type: http - id: demo-service - name: Demo Service - schedule: '@every 5s' - hosts: ["https://myhost:80"] - check.request: - method: GET - headers: - 'X-API-Key': '12345-mykey-67890' - check.response: - status: [200] - body: - - hello - - world -------------------------------------------------------------------------------- - -The following configuration shows how to check the response with a multiline -regex: - -[source,yaml] -------------------------------------------------------------------------------- -- type: http - id: demo-service - name: Demo Service - schedule: '@every 5s' - hosts: ["https://myhost:80"] - check.request: - method: GET - headers: - 'X-API-Key': '12345-mykey-67890' - check.response: - status: [200] - body: '(?s)first.*second.*third' -------------------------------------------------------------------------------- - - -[float] -[[monitors-scheduler]] -=== Scheduler options - -You specify options under `heartbeat.scheduler` to control the behavior of the task -scheduler. - -Example configuration: - -[source,yaml] -------------------------------------------------------------------------------- -heartbeat.scheduler: - limit: 10 - location: 'UTC-08:00' -------------------------------------------------------------------------------- - -In the example, setting `limit` to 10 guarantees that only 10 concurrent -I/O tasks will be active. An I/O task can be the actual check or resolving an -address via DNS. - -[float] -[[heartbeat-scheduler-limit]] -==== `limit` - -The number of concurrent I/O tasks that {beatname_uc} is allowed to execute. If set -to 0, there is no limit. The default is 0. - -Most operating systems set a file descriptor limit of 1024. For {beatname_uc} to -operate correctly and not accidentally block libbeat output, the value that you -specify for `limit` should be below the configured ulimit. - - -[float] -[[heartbeat-scheduler-location]] -==== `location` - -The timezone for the scheduler. By default the scheduler uses localtime. - -[float] -[[monitor-watch-poll-file]] -==== `watch.poll_file` - -deprecated:[6.5.0,Replaced by using dynamic reloading via the `heartbeat.config.monitors` option.] - -The JSON file to watch for additional monitor configurations. The JSON file can -contain multiple objects, each of which specifies a different monitor config. -{beatname_uc} checks this file periodically and starts a new monitor instance for -each new JSON object added to the file. For example, imagine that you add -10 new entries to the JSON file, each for a different hostname. When {beatname_uc} -picks up the changes in the file, it merges the original config -(`heartbeat.yml`) plus the JSON objects, and starts a monitor for each new host -that you've configured. If you delete an object from the JSON file and it -doesn't exist in the main config, {beatname_uc} stops the monitor instance running -for that object. - -Each monitor has a unique ID that's based on parameters like protocol, host, -and port. If two monitors have the same ID, {beatname_uc} uses the settings that -are defined in the last JSON object of the merged config. This means that -you can specify settings in the JSON file that overwrite the settings in -the main config. In this way, the configuration that you specify for the -monitor in the main {beatname_uc} config file acts like a default config that you -can live-reconfigure by specifying additional configurations in the external -JSON file. - -Example configuration: - -[source, yaml] -------------------------------------------------------------------------------- -heartbeat.monitors: -- type: tcp - id: demo-service - name: Demo Service - schedule: '*/5 * * * * * *' - hosts: ["myhost"] - watch.poll_file: - path: {path.config}/monitors/dynamic.json - interval: 5s -------------------------------------------------------------------------------- +include::monitors/monitor-common-options.asciidoc[] -*`path`*:: Specifies the path to the JSON file to check for updates. -*`interval`*:: Specifies how often {beatname_uc} checks the file for changes. +include::monitors/monitor-icmp.asciidoc[] -To reconfigure the settings specified in the example config, you could define -the following JSON objects in `dynamic.json`: +include::monitors/monitor-tcp.asciidoc[] -[source, json] -------------------------------------------------------------------------------- -{"hosts": ["myhost:1234"], "schedule": "*/15 * * * * * *"} <1> -{"hosts": ["tls://otherhost:479"], "ssl.certificate_authorities": ["path/to/ca/file.pem"]} <2> -------------------------------------------------------------------------------- -<1> Upon detecting the changes, {beatname_uc} stops the old monitor and then -restarts it with a schedule of 15 seconds between checks. -<2> {beatname_uc} starts a new monitor that uses a TLS-based connection with a -custom CA certificate. +include::monitors/monitor-http.asciidoc[] diff --git a/heartbeat/docs/heartbeat-scheduler.asciidoc b/heartbeat/docs/heartbeat-scheduler.asciidoc new file mode 100644 index 00000000000..ac2f647f6ee --- /dev/null +++ b/heartbeat/docs/heartbeat-scheduler.asciidoc @@ -0,0 +1,41 @@ +[[monitors-scheduler]] +== Configure the task scheduler + +++++ +Task scheduler +++++ + +You specify options under `heartbeat.scheduler` to control the behavior of the task +scheduler. + +Example configuration: + +[source,yaml] +------------------------------------------------------------------------------- +heartbeat.scheduler: + limit: 10 + location: 'UTC-08:00' +------------------------------------------------------------------------------- + +In the example, setting `limit` to 10 guarantees that only 10 concurrent +I/O tasks will be active. An I/O task can be the actual check or resolving an +address via DNS. + +[float] +[[heartbeat-scheduler-limit]] +==== `limit` + +The number of concurrent I/O tasks that {beatname_uc} is allowed to execute. If set +to 0, there is no limit. The default is 0. + +Most operating systems set a file descriptor limit of 1024. For {beatname_uc} to +operate correctly and not accidentally block libbeat output, the value that you +specify for `limit` should be below the configured ulimit. + + +[float] +[[heartbeat-scheduler-location]] +==== `location` + +The timezone for the scheduler. By default the scheduler uses localtime. + diff --git a/heartbeat/docs/monitors/monitor-common-options.asciidoc b/heartbeat/docs/monitors/monitor-common-options.asciidoc new file mode 100644 index 00000000000..68194b28119 --- /dev/null +++ b/heartbeat/docs/monitors/monitor-common-options.asciidoc @@ -0,0 +1,143 @@ +[[monitor-options]] +=== Common monitor options + +You can specify the following options when defining a {beatname_uc} monitor in any location. +These options are the same for all monitors. Each monitor type has additional configuration +options that are specific to that monitor type. + +[float] +[[monitor-type]] +==== `type` + +The type of monitor to run. See <>. + +[float] +[[monitor-id]] +==== `id` + +A unique identifier for this configuration. This should not change with edits to the monitor configuration +regardless of changes to any config fields. Examples: `uploader-service`, `http://example.net`, `us-west-loadbalancer`. Note that this uniqueness is only within a given beat instance. If you want to monitor the same endpoint from multiple locations it is recommended that those heartbeat instances use the same IDs so that their results can be correlated. You can use the `host.geo.name` property to disambiguate them. + +When querying against indexed monitor data this is the field you will be aggregating with. Appears in the +<> as `monitor.id`. + +If you do not set this explicitly the monitor's config will be hashed and a generated value used. This value will +change with any options change to this monitor making aggregations over time between changes impossible. For this reason +it is recommended that you set this manually. + +[float] +[[monitor-name]] +==== `name` + +Optional human readable name for this monitor. This value appears in the <> +as `monitor.name`. + +[float] +[[monitor-enabled]] +==== `enabled` + +A Boolean value that specifies whether the module is enabled. If the `enabled` +option is missing from the configuration block, the module is enabled by +default. + +[float] +[[monitor-schedule]] +==== `schedule` + +A cron-like expression that specifies the task schedule. For example: + +* `*/5 * * * * * *` runs the task every 5 seconds (for example, at 10:00:00, +10:00:05, and so on). +* `@every 5s` runs the task every 5 seconds from the time when {beatname_uc} was +started. + +The `schedule` option uses a cron-like syntax based on https://github.com/gorhill/cronexpr#implementation[this `cronexpr` implementation], +but adds the `@every` keyword. + +For stats on the execution of scheduled tasks you can enable the HTTP stats server with `http.enabled: true` in heartbeat.yml, then run `curl http://localhost:5066/stats | jq .heartbeat.scheduler` to view the scheduler's stats. Stats are provided for both jobs and tasks. Each time a monitor is scheduled is considered to be a single job, while portions of the work a job does, like DNS lookups and executing network requests are defined as tasks. The stats provided are: + +* **jobs.active:** The number of actively running jobs/monitors. +* **jobs.missed_deadline:** The number of jobs that executed after their scheduled time. This can be caused either by overlong long timeouts from the previous job or high load preventing heartbeat from keeping up with work. +* **tasks.active:** The number of tasks currently running. +* **tasks.waiting:** If the global `schedule.limit` option is set, this number will reflect the number of tasks that are ready to execute, but have not been started in order to prevent exceeding `schedule.limit`. + +Also see the <> settings. + +[float] +[[monitor-ipv4]] +==== `ipv4` + +A Boolean value that specifies whether to ping using the ipv4 protocol if +hostnames are configured. The default is `true`. + +[float] +[[monitor-ipv6]] +==== `ipv6` + +A Boolean value that specifies whether to ping using the ipv6 protocol +if hostnames are configured. The default is `true`. + +[float] +[[monitor-mode]] +==== `mode` + +If `mode` is `any`, the monitor pings only one IP address for a hostname. If +`mode` is `all`, the monitor pings all resolvable IPs for a hostname. The +`mode: all` setting is useful if you are using a DNS-load balancer and want to +ping every IP address for the specified hostname. The default is `any`. + +[float] +[[monitor-timeout]] +==== `timeout` + +The total running time for each ping test. This is the total time allowed for +testing the connection and exchanging data. The default is 16 seconds (16s). + +If the timeout is exceeded, {beatname_uc} publishes a `service-down` event. If the +value specified for `timeout` is greater than `schedule`, intermediate checks +will not be executed by the scheduler. + +[float] +[[monitor-fields]] +==== `fields` + +Optional fields that you can specify to add additional information to the +output. For example, you might add fields that you can use for filtering log +data. Fields can be scalar values, arrays, dictionaries, or any nested +combination of these. By default, the fields that you specify here will be +grouped under a `fields` sub-dictionary in the output document. To store the +custom fields as top-level fields, set the `fields_under_root` option to true. +If a duplicate field is declared in the general configuration, then its value +will be overwritten by the value declared here. + +[float] +[[monitor-fields-under-root]] +==== `fields_under_root` + +If this option is set to true, the custom <> +are stored as top-level fields in the output document instead of being grouped +under a `fields` sub-dictionary. If the custom field names conflict with other +field names added by {beatname_uc}, then the custom fields overwrite the other +fields. + +[float] +[[monitor-tags]] +==== `tags` + +A list of tags that will be sent with the monitor event. This setting is optional. + +[float] +[[monitor-processors]] +==== `processors` + +A list of processors to apply to the data generated by the monitor. + +See <> for information about specifying +processors in your config. + +[float] +[[monitor-keep-null]] +==== `keep_null` + +If this option is set to true, fields with `null` values will be published in +the output document. By default, `keep_null` is set to `false`. diff --git a/heartbeat/docs/monitors/monitor-http.asciidoc b/heartbeat/docs/monitors/monitor-http.asciidoc new file mode 100644 index 00000000000..1cea32f662f --- /dev/null +++ b/heartbeat/docs/monitors/monitor-http.asciidoc @@ -0,0 +1,246 @@ +[[monitor-http-options]] +=== HTTP options + +Also see <>. + +The options described here configure {beatname_uc} to connect via HTTP and +optionally verify that the host returns the expected response. + +Example configuration: + +[source,yaml] +---- +- type: http + id: myhost + name: My HTTP Host + schedule: '@every 5s' + hosts: ["http://myhost:80"] +---- + +[float] +[[monitor-http-urls]] +==== `hosts` + +A list of URLs to ping. + +[float] +[[monitor-http-max-redirects]] +==== `max_redirects` + +The total number of redirections Heartbeat will follow. Defaults to 0, meaning heartbeat will not follow redirects, +but will report the status of the redirect. If set to a number greater than 0 heartbeat will follow that number of redirects. + +When this option is set to a value greater than zero the `monitor.ip` field will no longer be reported, as multiple +DNS requests across multiple IPs may return multiple IPs. Fine grained network timing data will also not be recorded, as with redirects +that data will span multiple requests. Specifically the fields `http.rtt.content.us`, `http.rtt.response_header.us`, +`http.rtt.total.us`, `http.rtt.validate.us`, `http.rtt.write_request.us` and `dns.rtt.us` will be omitted. + +[float] +[[monitor-http-proxy-url]] +==== `proxy_url` + +The HTTP proxy URL. This setting is optional. Example `http://proxy.mydomain.com:3128` + +[float] +[[monitor-http-username]] +==== `username` + +The username for authenticating with the server. The credentials are passed +with the request. This setting is optional. + +You need to specify credentials when your `check.response` settings require it. +For example, you can check for a 403 response (`check.response.status: [403]`) +without setting credentials. + +[float] +[[monitor-http-password]] +==== `password` + +The password for authenticating with the server. This setting is optional. + +[float] +[[monitor-http-tls-ssl]] +==== `ssl` + +The TLS/SSL connection settings for use with the HTTPS endpoint. If you don't +specify settings, the system defaults are used. + + +Example configuration: + +[source,yaml] +------------------------------------------------------------------------------- +- type: http + id: my-http-service + name: My HTTP Service + hosts: ["https://myhost:443"] + schedule: '@every 5s' + ssl: + certificate_authorities: ['/etc/ca.crt'] + supported_protocols: ["TLSv1.0", "TLSv1.1", "TLSv1.2"] +------------------------------------------------------------------------------- + +Also see <> for a full description of the `ssl` options. + +[float] +[[monitor-http-response]] +=== `response` + +Controls the indexing of the HTTP response body contents to the `http.response.body.contents` field. + +Set `response.include_body` to one of the options listed below. + +*`on_error`*:: Include the body if an error is encountered during the check. This is the default. +*`never`*:: Never include the body. +*`always`*:: Always include the body with checks. + +Set `response.include_body_max_bytes` to control the maximum size of the stored body contents. Defaults to 1024 bytes. + +[float] +[[monitor-http-check]] +==== `check` + +An optional `request` to send to the remote host and the expected `response`. + +Example configuration: + +[source,yaml] +------------------------------------------------------------------------------- +- type: http + id: my-http-host + name: My HTTP Service + hosts: ["http://myhost:80"] + check.request.method: HEAD + check.response.status: [200] + schedule: '@every 5s' +------------------------------------------------------------------------------- + + +Under `check.request`, specify these options: + +*`method`*:: The HTTP method to use. Valid values are `"HEAD"`, `"GET"` and +`"POST"`. +*`headers`*:: A dictionary of additional HTTP headers to send. By default heartbeat +will set the 'User-Agent' header to identify itself. +*`body`*:: Optional request body content. + +Example configuration: +This monitor POSTs an `x-www-form-urlencoded` string +to the endpoint `/demo/add` + +[source,yaml] +------------------------------------------------------------------------------- +- type: http + id: demo-service + name: Demo Service + schedule: '@every 5s' + urls: ["http://localhost:8080/demo/add"] + check.request: + method: POST + headers: + 'Content-Type': 'application/x-www-form-urlencoded' + # urlencode the body: + body: "name=first&email=someemail%40someemailprovider.com" + check.response: + status: [200] + body: + - Saved + - saved +------------------------------------------------------------------------------- + +Under `check.response`, specify these options: + +*`status`*:: A list of expected status codes. 4xx and 5xx codes are considered `down` by default. Other codes are considered `up`. +*`headers`*:: The required response headers. +*`body`*:: A list of regular expressions to match the the body output. Only a single expression needs to match. HTTP response +bodies of up to 100MiB are supported. + +Example configuration: +This monitor examines the +response body for the strings `saved` or `Saved` and expects 200 or 201 status codes + +[source,yaml] +------------------------------------------------------------------------------- +- type: http + id: demo-service + name: Demo Service + schedule: '@every 5s' + urls: ["http://localhost:8080/demo/add"] + check.request: + method: POST + headers: + 'Content-Type': 'application/x-www-form-urlencoded' + # urlencode the body: + body: "name=first&email=someemail%40someemailprovider.com" + check.response: + status: [200, 201] + body: + - Saved + - saved +------------------------------------------------------------------------------- + +*`json`*:: A list of <> expressions executed against the body when parsed as JSON. Body sizes +must be less than or equal to 100 MiB. + +The following configuration shows how to check the response when the body +contains JSON: + +[source,yaml] +------------------------------------------------------------------------------- +- type: http + id: demo-service + name: Demo Service + schedule: '@every 5s' + hosts: ["https://myhost:80"] + check.request: + method: GET + headers: + 'X-API-Key': '12345-mykey-67890' + check.response: + status: [200] + json: + - description: check status + condition: + equals: + status: ok +------------------------------------------------------------------------------- + +The following configuration shows how to check the response for multiple regex +patterns: + +[source,yaml] +------------------------------------------------------------------------------- +- type: http + id: demo-service + name: Demo Service + schedule: '@every 5s' + hosts: ["https://myhost:80"] + check.request: + method: GET + headers: + 'X-API-Key': '12345-mykey-67890' + check.response: + status: [200] + body: + - hello + - world +------------------------------------------------------------------------------- + +The following configuration shows how to check the response with a multiline +regex: + +[source,yaml] +------------------------------------------------------------------------------- +- type: http + id: demo-service + name: Demo Service + schedule: '@every 5s' + hosts: ["https://myhost:80"] + check.request: + method: GET + headers: + 'X-API-Key': '12345-mykey-67890' + check.response: + status: [200] + body: '(?s)first.*second.*third' +------------------------------------------------------------------------------- diff --git a/heartbeat/docs/monitors/monitor-icmp.asciidoc b/heartbeat/docs/monitors/monitor-icmp.asciidoc new file mode 100644 index 00000000000..ccd0ba5f397 --- /dev/null +++ b/heartbeat/docs/monitors/monitor-icmp.asciidoc @@ -0,0 +1,39 @@ +[[monitor-icmp-options]] +=== ICMP options + +Also see <>. + +The options described here configure {beatname_uc} to use ICMP (v4 and v6) Echo +Requests to check the configured hosts. Please note that on most platforms you +must execute Heartbeat with elevated permissions to perform ICMP pings. + +On Linux, regular users may perform pings if the right file capabilities are set. Run +`sudo setcap cap_net_raw+eip /path/to/heartbeat` to grant {beatname_uc} ping capabilities on Linux. +Alternatively, one may grant ping permissions to the user {beatname_uc} runs as. To grant ping permissions +in this way, run `sudo sysctl -w net.ipv4.ping_group_range='myuserid myuserid'`. + +Other platforms may require {beatname_uc} to run as root or administrator to execute pings. + +Example configuration: + +[source,yaml] +---- +- type: icmp + id: ping-myhost + name: My Host Ping + hosts: ["myhost"] + schedule: '*/5 * * * * * *' +---- + +[float] +[[monitor-icmp-hosts]] +==== `hosts` + +A list of hosts to ping. + +[float] +[[monitor-icmp-wait]] +==== `wait` + +The duration to wait before emitting another ICMP Echo Request. The default is 1 +second (1s). diff --git a/heartbeat/docs/monitors/monitor-tcp.asciidoc b/heartbeat/docs/monitors/monitor-tcp.asciidoc new file mode 100644 index 00000000000..10d39b06302 --- /dev/null +++ b/heartbeat/docs/monitors/monitor-tcp.asciidoc @@ -0,0 +1,134 @@ +[[monitor-tcp-options]] +=== TCP options + +Also see <>. + +The options described here configure {beatname_uc} to connect via TCP and +optionally verify the endpoint by sending and/or receiving a custom payload. + +Example configuration: + +[source,yaml] +---- +- type: tcp + id: my-host-services + name: My Host Services + hosts: ["myhost"] + ports: [80, 9200, 5044] + schedule: '@every 5s' +---- + +[float] +[[monitor-tcp-hosts]] +==== `hosts` + +A list of hosts to ping. The entries in the list can be: + +* A plain host name, such as `localhost`, or an IP address. If you specify this +option, you must also specify a value for <>. If the +monitor is <>, {beatname_uc} establishes an +SSL/TLS-based connection. Otherwise, it establishes a plain TCP connection. +* A hostname and port, such as `localhost:12345`. {beatname_uc} connects +to the port on the specified host. If the monitor is +<>, {beatname_uc} establishes an +SSL/TLS-based connection. Otherwise, it establishes a TCP connection. +* A full URL using the syntax `scheme://:[port]`, where: +** `scheme` is one of `tcp`, `plain`, `ssl` or `tls`. If `tcp` or `plain` is +specified, {beatname_uc} establishes a TCP connection even if the monitor is +configured to use SSL. If `tls` or `ssl` is specified, {beatname_uc} establishes +an SSL connection. However, if the monitor is not configured to use SSL, the +system defaults are used (currently not supported on Windows). +** `host` is the hostname. +** `port` is the port number. If `port` is missing in the URL, the +<> setting is required. + +[float] +[[monitor-tcp-ports]] +==== `ports` + +A list of ports to ping if the host specified in <> +does not contain a port number. It is generally preferable to use a single value here, +since each port will be monitored using a separate `id`, with the given `id` value, +used as a prefix in the Heartbeat data, and the configured `name` shared across events +sent via this check. + +[float] +[[monitor-tcp-check]] +==== `check` + +An optional payload string to send to the remote host and the expected answer. +If no payload is specified, the endpoint is assumed to be available if the +connection attempt was successful. If `send` is specified without `receive`, +any response is accepted as OK. If `receive` is specified without `send`, no +payload is sent, but the client expects to receive a payload in the form of a +"hello message" or "banner" on connect. + +Example configuration: + +[source,yaml] +------------------------------------------------------------------------------- +- type: tcp + id: echo-service + name: Echo Service + hosts: ["myhost"] + ports: [7] + check.send: 'Hello World' + check.receive: 'Hello World' + schedule: '@every 5s' +------------------------------------------------------------------------------- + + +[float] +[[monitor-tcp-proxy-url]] +==== `proxy_url` + +The URL of the SOCKS5 proxy to use when connecting to the server. The value +must be a URL with a scheme of socks5://. + +If the SOCKS5 proxy server requires client authentication, then a username and +password can be embedded in the URL as shown in the example. + +[source,yaml] +------------------------------------------------------------------------------- + proxy_url: socks5://user:password@socks5-proxy:2233 +------------------------------------------------------------------------------- + +When using a proxy, hostnames are resolved on the proxy server instead of on +the client. You can change this behavior by setting the +`proxy_use_local_resolver` option. + +[float] +[[monitor-tcp-proxy-use-local-resolver]] +==== `proxy_use_local_resolver` + +A Boolean value that determines whether hostnames are resolved locally instead +of being resolved on the proxy server. The default value is false, which means +that name resolution occurs on the proxy server. + +[float] +[[monitor-tcp-tls-ssl]] +==== `ssl` + +The TLS/SSL connection settings. If the monitor is +<>, it will attempt an SSL +handshake. If `check` is not configured, the monitor will only check to see if +it can establish an SSL/TLS connection. This check can fail either at TCP level +or during certificate validation. + +Example configuration: + +[source,yaml] +------------------------------------------------------------------------------- +- type: tcp + id: tls-mail + name: TLS Mail + hosts: ["mail.example.net"] + ports: [465] + schedule: '@every 5s' + ssl: + certificate_authorities: ['/etc/ca.crt'] + supported_protocols: ["TLSv1.0", "TLSv1.1", "TLSv1.2"] +------------------------------------------------------------------------------- + + +Also see <> for a full description of the `ssl` options. From ced5032a5051ef822875c2528a0f5218351af065 Mon Sep 17 00:00:00 2001 From: DeDe Morton Date: Wed, 29 Apr 2020 11:13:41 -0700 Subject: [PATCH 056/116] [docs] Remove incorrect tag (#17989) --- filebeat/docs/modules/activemq.asciidoc | 2 -- x-pack/filebeat/module/activemq/_meta/docs.asciidoc | 2 -- 2 files changed, 4 deletions(-) diff --git a/filebeat/docs/modules/activemq.asciidoc b/filebeat/docs/modules/activemq.asciidoc index c276cd63952..225090f80ef 100644 --- a/filebeat/docs/modules/activemq.asciidoc +++ b/filebeat/docs/modules/activemq.asciidoc @@ -10,8 +10,6 @@ This file is generated! See scripts/docs_collector.py == ActiveMQ module -ga[] - This module parses Apache ActiveMQ logs. It supports application and audit logs. include::../include/what-happens.asciidoc[] diff --git a/x-pack/filebeat/module/activemq/_meta/docs.asciidoc b/x-pack/filebeat/module/activemq/_meta/docs.asciidoc index 6e7d6d74551..f632747c8a4 100644 --- a/x-pack/filebeat/module/activemq/_meta/docs.asciidoc +++ b/x-pack/filebeat/module/activemq/_meta/docs.asciidoc @@ -5,8 +5,6 @@ == ActiveMQ module -ga[] - This module parses Apache ActiveMQ logs. It supports application and audit logs. include::../include/what-happens.asciidoc[] From 1d85b30e0bd4361e8a8bc578cd824071841f2dc6 Mon Sep 17 00:00:00 2001 From: kaiyan-sheng Date: Wed, 29 Apr 2020 13:41:03 -0600 Subject: [PATCH 057/116] add changelog for PR #17650 and PR #17614 (#18102) --- CHANGELOG.next.asciidoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 5183073cd50..56a49e52ecd 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -115,7 +115,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Remove migrationVersion map 7.7.0 reference from Kibana dashboard file to fix backward compatibility issues. {pull}17425[17425] - Fix issue 17734 to retry on rate-limit error in the Filebeat httpjson input. {issue}17734[17734] {pull}17735[17735] - Fixed `cloudfoundry.access` to have the correct `cloudfoundry.app.id` contents. {pull}17847[17847] -- Fixing `ingress_controller.` fields to be of type keyword instead of text. ${issue}17834[17834] +- Fixing `ingress_controller.` fields to be of type keyword instead of text. {issue}17834[17834] - Fixed typo in log message. {pull}17897[17897] - Fix Cisco ASA ASA 3020** and 106023 messages {pull}17964[17964] @@ -357,6 +357,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Allow partial region and zone name in googlecloud module config. {pull}17913[17913] - Add aggregation aligner as a config parameter for googlecloud stackdriver metricset. {issue}17141[[17141] {pull}17719[17719] - Move the perfmon metricset to GA. {issue}16608[16608] {pull}17879[17879] +- Add static mapping for metricsets under aws module. {pull}17614[17614] {pull}17650[17650] *Packetbeat* From db298745a37bbff62811890ca8a478cd824eea25 Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Wed, 29 Apr 2020 16:53:48 -0400 Subject: [PATCH 058/116] Run kubernetes integration tests inside of a pod and use kind to setup a kubernetes cluster (#17656) * Use kind to bring up a local kubernetes cluster and then run the integration tests against the kind cluster. * Run mage update. * Add more tests. * Fix more tests, install kind in prepare-tests. * Switch to running kubernetes integration tests inside of Kubernetes. * Use golang 1.13.9. * Fix for other beats. * Run mage fmt. * Don't run kubernetes integration tests if not inside kubernetes environment. * Fix metricbeat to use mage and the makefile shim for mage. * Improve the error message when kind or kubectl is not available. * Refactor the integration tests into a more module system. * Fix go vet. * Setup travis to use kind. * Run kubernetes integration tests in Jenkins. * Fix filebeat magefile. * Fix travis and Jenkins. * Check requirements of the test runner before actually running the tests. * Add return on parsebool error. * Don't return err on missing requirements for tester. * Run make update. * Move the kubernetes items to its own module, import that module only by metricbeat. --- .ci/scripts/install-kind.sh | 13 + .ci/scripts/install-kubectl.sh | 14 + .ci/scripts/kind-setup.sh | 13 - .travis.yml | 8 +- Jenkinsfile | 14 +- NOTICE.txt | 9 + dev-tools/mage/common.go | 14 +- dev-tools/mage/gotest.go | 53 +- dev-tools/mage/integtest.go | 514 ++--- dev-tools/mage/integtest_docker.go | 233 +++ dev-tools/mage/integtest_mage.go | 58 + dev-tools/mage/kubectl.go | 129 ++ dev-tools/mage/kuberemote.go | 612 ++++++ dev-tools/mage/kubernetes/kind.go | 143 ++ dev-tools/mage/kubernetes/kuberemote.go | 612 ++++++ dev-tools/mage/kubernetes/kubernetes.go | 165 ++ dev-tools/mage/target/integtest/integtest.go | 18 +- filebeat/magefile.go | 16 +- go.sum | 5 + libbeat/scripts/Makefile | 2 +- metricbeat/docs/fields.asciidoc | 10 + metricbeat/magefile.go | 11 +- metricbeat/module/kubernetes/_meta/Dockerfile | 8 - .../kubernetes/_meta/Dockerfile.kube-state | 8 - metricbeat/module/kubernetes/_meta/README.md | 23 + metricbeat/module/kubernetes/_meta/kubeconfig | 18 - .../apiserver/apiserver_integration_test.go | 39 + .../controllermanager_integration_test.go | 39 + .../module/kubernetes/docker-compose.yml | 24 - .../module/kubernetes/event/_meta/fields.yml | 4 + metricbeat/module/kubernetes/fields.go | 2 +- metricbeat/module/kubernetes/kubernetes.yml | 135 ++ .../state_container_integration_test.go | 39 + .../state_cronjob_integration_test.go | 39 + .../state_deployment_integration_test.go | 39 + .../state_node/state_node_integration_test.go | 39 + ...state_persistentvolume_integration_test.go | 39 + ..._persistentvolumeclaim_integration_test.go | 39 + .../state_pod/state_pod_integration_test.go | 39 + .../state_replicaset_integration_test.go | 39 + .../state_resourcequota_integration_test.go | 39 + .../state_service_integration_test.go | 39 + .../state_statefulset_integration_test.go | 39 + .../state_storageclass_integration_test.go | 39 + .../module/kubernetes/test/integration.go | 50 + .../module/kubernetes/test_kubernetes.py | 88 - .../docker/spdystream/CONTRIBUTING.md | 13 + vendor/github.com/docker/spdystream/LICENSE | 191 ++ .../github.com/docker/spdystream/LICENSE.docs | 425 ++++ .../github.com/docker/spdystream/MAINTAINERS | 28 + vendor/github.com/docker/spdystream/README.md | 77 + .../docker/spdystream/connection.go | 958 +++++++++ .../github.com/docker/spdystream/handlers.go | 38 + .../github.com/docker/spdystream/priority.go | 98 + .../docker/spdystream/spdy/dictionary.go | 187 ++ .../github.com/docker/spdystream/spdy/read.go | 348 ++++ .../docker/spdystream/spdy/types.go | 275 +++ .../docker/spdystream/spdy/write.go | 318 +++ vendor/github.com/docker/spdystream/stream.go | 327 +++ vendor/github.com/docker/spdystream/utils.go | 16 + vendor/golang.org/x/crypto/blowfish/block.go | 159 ++ vendor/golang.org/x/crypto/blowfish/cipher.go | 99 + vendor/golang.org/x/crypto/blowfish/const.go | 199 ++ .../x/crypto/chacha20/chacha_arm64.go | 17 + .../x/crypto/chacha20/chacha_arm64.s | 308 +++ .../x/crypto/chacha20/chacha_generic.go | 364 ++++ .../x/crypto/chacha20/chacha_noasm.go | 13 + .../x/crypto/chacha20/chacha_ppc64le.go | 16 + .../x/crypto/chacha20/chacha_ppc64le.s | 449 +++++ .../x/crypto/chacha20/chacha_s390x.go | 26 + .../x/crypto/chacha20/chacha_s390x.s | 224 ++ vendor/golang.org/x/crypto/chacha20/xor.go | 41 + .../x/crypto/curve25519/curve25519.go | 95 + .../x/crypto/curve25519/curve25519_amd64.go | 240 +++ .../x/crypto/curve25519/curve25519_amd64.s | 1793 +++++++++++++++++ .../x/crypto/curve25519/curve25519_generic.go | 828 ++++++++ .../x/crypto/curve25519/curve25519_noasm.go | 11 + .../x/crypto/internal/subtle/aliasing.go | 32 + .../internal/subtle/aliasing_appengine.go | 35 + .../x/crypto/poly1305/bits_compat.go | 39 + .../x/crypto/poly1305/bits_go1.13.go | 21 + .../golang.org/x/crypto/poly1305/mac_noasm.go | 11 + .../golang.org/x/crypto/poly1305/poly1305.go | 89 + .../golang.org/x/crypto/poly1305/sum_amd64.go | 58 + .../golang.org/x/crypto/poly1305/sum_amd64.s | 108 + .../x/crypto/poly1305/sum_generic.go | 307 +++ .../golang.org/x/crypto/poly1305/sum_noasm.go | 13 + .../x/crypto/poly1305/sum_ppc64le.go | 58 + .../x/crypto/poly1305/sum_ppc64le.s | 181 ++ .../golang.org/x/crypto/poly1305/sum_s390x.go | 39 + .../golang.org/x/crypto/poly1305/sum_s390x.s | 378 ++++ .../x/crypto/poly1305/sum_vmsl_s390x.s | 909 +++++++++ vendor/golang.org/x/crypto/ssh/buffer.go | 97 + vendor/golang.org/x/crypto/ssh/certs.go | 546 +++++ vendor/golang.org/x/crypto/ssh/channel.go | 633 ++++++ vendor/golang.org/x/crypto/ssh/cipher.go | 781 +++++++ vendor/golang.org/x/crypto/ssh/client.go | 278 +++ vendor/golang.org/x/crypto/ssh/client_auth.go | 639 ++++++ vendor/golang.org/x/crypto/ssh/common.go | 404 ++++ vendor/golang.org/x/crypto/ssh/connection.go | 143 ++ vendor/golang.org/x/crypto/ssh/doc.go | 21 + vendor/golang.org/x/crypto/ssh/handshake.go | 647 ++++++ .../ssh/internal/bcrypt_pbkdf/bcrypt_pbkdf.go | 93 + vendor/golang.org/x/crypto/ssh/kex.go | 789 ++++++++ vendor/golang.org/x/crypto/ssh/keys.go | 1403 +++++++++++++ vendor/golang.org/x/crypto/ssh/mac.go | 61 + vendor/golang.org/x/crypto/ssh/messages.go | 866 ++++++++ vendor/golang.org/x/crypto/ssh/mux.go | 330 +++ vendor/golang.org/x/crypto/ssh/server.go | 716 +++++++ vendor/golang.org/x/crypto/ssh/session.go | 647 ++++++ vendor/golang.org/x/crypto/ssh/ssh_gss.go | 139 ++ vendor/golang.org/x/crypto/ssh/streamlocal.go | 116 ++ vendor/golang.org/x/crypto/ssh/tcpip.go | 474 +++++ vendor/golang.org/x/crypto/ssh/transport.go | 353 ++++ .../apimachinery/pkg/util/httpstream/doc.go | 19 + .../pkg/util/httpstream/httpstream.go | 149 ++ .../pkg/util/httpstream/spdy/connection.go | 145 ++ .../pkg/util/httpstream/spdy/roundtripper.go | 335 +++ .../pkg/util/httpstream/spdy/upgrade.go | 107 + .../third_party/forked/golang/netutil/addr.go | 27 + .../k8s.io/client-go/tools/portforward/doc.go | 19 + .../tools/portforward/portforward.go | 429 ++++ .../client-go/tools/watch/informerwatcher.go | 150 ++ .../client-go/tools/watch/retrywatcher.go | 287 +++ vendor/k8s.io/client-go/tools/watch/until.go | 236 +++ .../k8s.io/client-go/transport/spdy/spdy.go | 94 + vendor/modules.txt | 16 + .../input/googlepubsub/pubsub_test.go | 2 +- x-pack/filebeat/magefile.go | 16 +- x-pack/metricbeat/magefile.go | 12 +- 130 files changed, 25780 insertions(+), 470 deletions(-) create mode 100755 .ci/scripts/install-kind.sh create mode 100755 .ci/scripts/install-kubectl.sh create mode 100644 dev-tools/mage/integtest_docker.go create mode 100644 dev-tools/mage/integtest_mage.go create mode 100644 dev-tools/mage/kubectl.go create mode 100644 dev-tools/mage/kuberemote.go create mode 100644 dev-tools/mage/kubernetes/kind.go create mode 100644 dev-tools/mage/kubernetes/kuberemote.go create mode 100644 dev-tools/mage/kubernetes/kubernetes.go delete mode 100644 metricbeat/module/kubernetes/_meta/Dockerfile delete mode 100644 metricbeat/module/kubernetes/_meta/Dockerfile.kube-state create mode 100644 metricbeat/module/kubernetes/_meta/README.md delete mode 100644 metricbeat/module/kubernetes/_meta/kubeconfig create mode 100644 metricbeat/module/kubernetes/apiserver/apiserver_integration_test.go create mode 100644 metricbeat/module/kubernetes/controllermanager/controllermanager_integration_test.go delete mode 100644 metricbeat/module/kubernetes/docker-compose.yml create mode 100644 metricbeat/module/kubernetes/kubernetes.yml create mode 100644 metricbeat/module/kubernetes/state_container/state_container_integration_test.go create mode 100644 metricbeat/module/kubernetes/state_cronjob/state_cronjob_integration_test.go create mode 100644 metricbeat/module/kubernetes/state_deployment/state_deployment_integration_test.go create mode 100644 metricbeat/module/kubernetes/state_node/state_node_integration_test.go create mode 100644 metricbeat/module/kubernetes/state_persistentvolume/state_persistentvolume_integration_test.go create mode 100644 metricbeat/module/kubernetes/state_persistentvolumeclaim/state_persistentvolumeclaim_integration_test.go create mode 100644 metricbeat/module/kubernetes/state_pod/state_pod_integration_test.go create mode 100644 metricbeat/module/kubernetes/state_replicaset/state_replicaset_integration_test.go create mode 100644 metricbeat/module/kubernetes/state_resourcequota/state_resourcequota_integration_test.go create mode 100644 metricbeat/module/kubernetes/state_service/state_service_integration_test.go create mode 100644 metricbeat/module/kubernetes/state_statefulset/state_statefulset_integration_test.go create mode 100644 metricbeat/module/kubernetes/state_storageclass/state_storageclass_integration_test.go create mode 100644 metricbeat/module/kubernetes/test/integration.go delete mode 100644 metricbeat/module/kubernetes/test_kubernetes.py create mode 100644 vendor/github.com/docker/spdystream/CONTRIBUTING.md create mode 100644 vendor/github.com/docker/spdystream/LICENSE create mode 100644 vendor/github.com/docker/spdystream/LICENSE.docs create mode 100644 vendor/github.com/docker/spdystream/MAINTAINERS create mode 100644 vendor/github.com/docker/spdystream/README.md create mode 100644 vendor/github.com/docker/spdystream/connection.go create mode 100644 vendor/github.com/docker/spdystream/handlers.go create mode 100644 vendor/github.com/docker/spdystream/priority.go create mode 100644 vendor/github.com/docker/spdystream/spdy/dictionary.go create mode 100644 vendor/github.com/docker/spdystream/spdy/read.go create mode 100644 vendor/github.com/docker/spdystream/spdy/types.go create mode 100644 vendor/github.com/docker/spdystream/spdy/write.go create mode 100644 vendor/github.com/docker/spdystream/stream.go create mode 100644 vendor/github.com/docker/spdystream/utils.go create mode 100644 vendor/golang.org/x/crypto/blowfish/block.go create mode 100644 vendor/golang.org/x/crypto/blowfish/cipher.go create mode 100644 vendor/golang.org/x/crypto/blowfish/const.go create mode 100644 vendor/golang.org/x/crypto/chacha20/chacha_arm64.go create mode 100644 vendor/golang.org/x/crypto/chacha20/chacha_arm64.s create mode 100644 vendor/golang.org/x/crypto/chacha20/chacha_generic.go create mode 100644 vendor/golang.org/x/crypto/chacha20/chacha_noasm.go create mode 100644 vendor/golang.org/x/crypto/chacha20/chacha_ppc64le.go create mode 100644 vendor/golang.org/x/crypto/chacha20/chacha_ppc64le.s create mode 100644 vendor/golang.org/x/crypto/chacha20/chacha_s390x.go create mode 100644 vendor/golang.org/x/crypto/chacha20/chacha_s390x.s create mode 100644 vendor/golang.org/x/crypto/chacha20/xor.go create mode 100644 vendor/golang.org/x/crypto/curve25519/curve25519.go create mode 100644 vendor/golang.org/x/crypto/curve25519/curve25519_amd64.go create mode 100644 vendor/golang.org/x/crypto/curve25519/curve25519_amd64.s create mode 100644 vendor/golang.org/x/crypto/curve25519/curve25519_generic.go create mode 100644 vendor/golang.org/x/crypto/curve25519/curve25519_noasm.go create mode 100644 vendor/golang.org/x/crypto/internal/subtle/aliasing.go create mode 100644 vendor/golang.org/x/crypto/internal/subtle/aliasing_appengine.go create mode 100644 vendor/golang.org/x/crypto/poly1305/bits_compat.go create mode 100644 vendor/golang.org/x/crypto/poly1305/bits_go1.13.go create mode 100644 vendor/golang.org/x/crypto/poly1305/mac_noasm.go create mode 100644 vendor/golang.org/x/crypto/poly1305/poly1305.go create mode 100644 vendor/golang.org/x/crypto/poly1305/sum_amd64.go create mode 100644 vendor/golang.org/x/crypto/poly1305/sum_amd64.s create mode 100644 vendor/golang.org/x/crypto/poly1305/sum_generic.go create mode 100644 vendor/golang.org/x/crypto/poly1305/sum_noasm.go create mode 100644 vendor/golang.org/x/crypto/poly1305/sum_ppc64le.go create mode 100644 vendor/golang.org/x/crypto/poly1305/sum_ppc64le.s create mode 100644 vendor/golang.org/x/crypto/poly1305/sum_s390x.go create mode 100644 vendor/golang.org/x/crypto/poly1305/sum_s390x.s create mode 100644 vendor/golang.org/x/crypto/poly1305/sum_vmsl_s390x.s create mode 100644 vendor/golang.org/x/crypto/ssh/buffer.go create mode 100644 vendor/golang.org/x/crypto/ssh/certs.go create mode 100644 vendor/golang.org/x/crypto/ssh/channel.go create mode 100644 vendor/golang.org/x/crypto/ssh/cipher.go create mode 100644 vendor/golang.org/x/crypto/ssh/client.go create mode 100644 vendor/golang.org/x/crypto/ssh/client_auth.go create mode 100644 vendor/golang.org/x/crypto/ssh/common.go create mode 100644 vendor/golang.org/x/crypto/ssh/connection.go create mode 100644 vendor/golang.org/x/crypto/ssh/doc.go create mode 100644 vendor/golang.org/x/crypto/ssh/handshake.go create mode 100644 vendor/golang.org/x/crypto/ssh/internal/bcrypt_pbkdf/bcrypt_pbkdf.go create mode 100644 vendor/golang.org/x/crypto/ssh/kex.go create mode 100644 vendor/golang.org/x/crypto/ssh/keys.go create mode 100644 vendor/golang.org/x/crypto/ssh/mac.go create mode 100644 vendor/golang.org/x/crypto/ssh/messages.go create mode 100644 vendor/golang.org/x/crypto/ssh/mux.go create mode 100644 vendor/golang.org/x/crypto/ssh/server.go create mode 100644 vendor/golang.org/x/crypto/ssh/session.go create mode 100644 vendor/golang.org/x/crypto/ssh/ssh_gss.go create mode 100644 vendor/golang.org/x/crypto/ssh/streamlocal.go create mode 100644 vendor/golang.org/x/crypto/ssh/tcpip.go create mode 100644 vendor/golang.org/x/crypto/ssh/transport.go create mode 100644 vendor/k8s.io/apimachinery/pkg/util/httpstream/doc.go create mode 100644 vendor/k8s.io/apimachinery/pkg/util/httpstream/httpstream.go create mode 100644 vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/connection.go create mode 100644 vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/roundtripper.go create mode 100644 vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/upgrade.go create mode 100644 vendor/k8s.io/apimachinery/third_party/forked/golang/netutil/addr.go create mode 100644 vendor/k8s.io/client-go/tools/portforward/doc.go create mode 100644 vendor/k8s.io/client-go/tools/portforward/portforward.go create mode 100644 vendor/k8s.io/client-go/tools/watch/informerwatcher.go create mode 100644 vendor/k8s.io/client-go/tools/watch/retrywatcher.go create mode 100644 vendor/k8s.io/client-go/tools/watch/until.go create mode 100644 vendor/k8s.io/client-go/transport/spdy/spdy.go diff --git a/.ci/scripts/install-kind.sh b/.ci/scripts/install-kind.sh new file mode 100755 index 00000000000..dc83bb4cd2a --- /dev/null +++ b/.ci/scripts/install-kind.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -exuo pipefail + +MSG="parameter missing." +DEFAULT_HOME="/usr/local" +KIND_VERSION=${KIND_VERSION:?$MSG} +HOME=${HOME:?$DEFAULT_HOME} +KIND_CMD="${HOME}/bin/kind" + +mkdir -p "${HOME}/bin" + +curl -sSLo "${KIND_CMD}" "https://github.com/kubernetes-sigs/kind/releases/download/${KIND_VERSION}/kind-linux-amd64" +chmod +x "${KIND_CMD}" diff --git a/.ci/scripts/install-kubectl.sh b/.ci/scripts/install-kubectl.sh new file mode 100755 index 00000000000..d0b7080d188 --- /dev/null +++ b/.ci/scripts/install-kubectl.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -exuo pipefail + +MSG="parameter missing." +DEFAULT_HOME="/usr/local" +K8S_VERSION=${K8S_VERSION:?$MSG} +HOME=${HOME:?$DEFAULT_HOME} +KUBECTL_CMD="${HOME}/bin/kubectl" + +mkdir -p "${HOME}/bin" + +curl -sSLo "${KUBECTL_CMD}" "https://storage.googleapis.com/kubernetes-release/release/${K8S_VERSION}/bin/linux/amd64/kubectl" +chmod +x "${KUBECTL_CMD}" + diff --git a/.ci/scripts/kind-setup.sh b/.ci/scripts/kind-setup.sh index 4ac8fb0f6c3..fa4f66dd6e6 100755 --- a/.ci/scripts/kind-setup.sh +++ b/.ci/scripts/kind-setup.sh @@ -1,18 +1,5 @@ #!/usr/bin/env bash set -exuo pipefail -MSG="parameter missing." -K8S_VERSION=${K8S_VERSION:?$MSG} -HOME=${HOME:?$MSG} -KBC_CMD="${HOME}/bin/kubectl" - -mkdir -p "${HOME}/bin" - -curl -sSLo "${KBC_CMD}" "https://storage.googleapis.com/kubernetes-release/release/${K8S_VERSION}/bin/linux/amd64/kubectl" -chmod +x "${KBC_CMD}" - -GO111MODULE="on" go get sigs.k8s.io/kind@v0.5.1 kind create cluster --image kindest/node:${K8S_VERSION} - -export KUBECONFIG="$(kind get kubeconfig-path)" kubectl cluster-info diff --git a/.travis.yml b/.travis.yml index 463f57f27e3..b9f903b023b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -116,7 +116,13 @@ jobs: stage: test - os: linux before_install: .ci/scripts/travis_has_changes.sh metricbeat libbeat || travis_terminate 0 - env: TARGETS="-C metricbeat integration-tests" + install: + - .ci/scripts/install-kind.sh + - .ci/scripts/install-kubectl.sh + env: + - TARGETS="-C metricbeat integration-tests" + - K8S_VERSION=v1.17.2 + - KIND_VERSION=v0.7.0 go: $TRAVIS_GO_VERSION stage: test - os: linux diff --git a/Jenkinsfile b/Jenkinsfile index c2b5c94fed6..f15ded84e83 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -587,7 +587,7 @@ pipeline { } } steps { - k8sTest(["v1.16.2","v1.15.3","v1.14.6","v1.13.10","v1.12.10","v1.11.10"]) + k8sTest(["v1.18.2","v1.17.2","v1.16.4","v1.15.7","v1.14.10"]) } } } @@ -806,14 +806,14 @@ def dumpFilteredEnvironment(){ def k8sTest(versions){ versions.each{ v -> stage("k8s ${v}"){ - withEnv(["K8S_VERSION=${v}"]){ + withEnv(["K8S_VERSION=${v}", "KIND_VERSION=v0.7.0", "KUBECONFIG=${env.WORKSPACE}/kubecfg"]){ withGithubNotify(context: "K8s ${v}") { withBeatsEnv(false) { - sh(label: "Install k8s", script: """ - eval "\$(gvm use ${GO_VERSION} --format=bash)" - .ci/scripts/kind-setup.sh - """) - sh(label: "Kubernetes Kind",script: "make KUBECONFIG=\"\$(kind get kubeconfig-path)\" -C deploy/kubernetes test") + sh(label: "Install kind", script: ".ci/scripts/install-kind.sh") + sh(label: "Install kubectl", script: ".ci/scripts/install-kubectl.sh") + sh(label: "Integration tests", script: "MODULE=kubernetes make -C metricbeat integration-tests") + sh(label: "Setup kind", script: ".ci/scripts/kind-setup.sh") + sh(label: "Deploy to kubernetes",script: "make -C deploy/kubernetes test") sh(label: 'Delete cluster', script: 'kind delete cluster') } } diff --git a/NOTICE.txt b/NOTICE.txt index ffad1013734..abea1705477 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1371,6 +1371,15 @@ License type (autodetected): Apache-2.0 Apache License 2.0 +-------------------------------------------------------------------- +Dependency: github.com/docker/spdystream +Revision: 449fdfce4d96 +License type (autodetected): Apache-2.0 +./vendor/github.com/docker/spdystream/LICENSE: +-------------------------------------------------------------------- +Apache License 2.0 + + -------------------------------------------------------------------- Dependency: github.com/dop251/goja Overwrite: github.com/andrewkroh/goja diff --git a/dev-tools/mage/common.go b/dev-tools/mage/common.go index 7d31038d06d..21c07ba4398 100644 --- a/dev-tools/mage/common.go +++ b/dev-tools/mage/common.go @@ -220,7 +220,19 @@ func dockerInfo() (*DockerInfo, error) { // PATH. func HaveDockerCompose() error { _, err := exec.LookPath("docker-compose") - return errors.Wrap(err, "docker-compose was not found on the PATH") + if err != nil { + return fmt.Errorf("docker-compose is not available") + } + return nil +} + +// HaveKubectl returns an error if kind is not found on the PATH. +func HaveKubectl() error { + _, err := exec.LookPath("kubectl") + if err != nil { + return fmt.Errorf("kubectl is not available") + } + return nil } // FindReplace reads a file, performs a find/replace operation, then writes the diff --git a/dev-tools/mage/gotest.go b/dev-tools/mage/gotest.go index 9f0050025f9..2eb7f9a0b7d 100644 --- a/dev-tools/mage/gotest.go +++ b/dev-tools/mage/gotest.go @@ -26,6 +26,7 @@ import ( "log" "os" "os/exec" + "path" "path/filepath" "runtime" "sort" @@ -137,33 +138,47 @@ func DefaultTestBinaryArgs() TestBinaryArgs { // Use RACE_DETECTOR=true to enable the race detector. // Use MODULE=module to run only tests for `module`. func GoTestIntegrationForModule(ctx context.Context) error { - return RunIntegTest("goIntegTest", func() error { - module := EnvOr("MODULE", "") - if module != "" { - err := GoTest(ctx, GoTestIntegrationArgsForModule(module)) - return errors.Wrapf(err, "integration tests failed for module %s", module) + module := EnvOr("MODULE", "") + modulesFileInfo, err := ioutil.ReadDir("./module") + if err != nil { + return err + } + + foundModule := false + failedModules := []string{} + for _, fi := range modulesFileInfo { + if !fi.IsDir() { + continue + } + if module != "" && module != fi.Name() { + continue } + foundModule = true - modulesFileInfo, err := ioutil.ReadDir("./module") + // Set MODULE because only want that modules tests to run inside the testing environment. + runners, err := NewIntegrationRunners(path.Join("./module", fi.Name()), map[string]string{"MODULE": fi.Name()}) if err != nil { - return err + return errors.Wrapf(err, "test setup failed for module %s", fi.Name()) } - - var failed bool - for _, fi := range modulesFileInfo { - if !fi.IsDir() { - continue - } + err = runners.Test("goIntegTest", func() error { err := GoTest(ctx, GoTestIntegrationArgsForModule(fi.Name())) if err != nil { - failed = true + return err } + return nil + }) + if err != nil { + // err will already be report to stdout, collect failed module to report at end + failedModules = append(failedModules, fi.Name()) } - if failed { - return errors.New("integration tests failed") - } - return nil - }) + } + if module != "" && !foundModule { + return fmt.Errorf("no module %s", module) + } + if len(failedModules) > 0 { + return fmt.Errorf("failed modules: %s", strings.Join(failedModules, ", ")) + } + return nil } // GoTest invokes "go test" and reports the results to stdout. It returns an diff --git a/dev-tools/mage/integtest.go b/dev-tools/mage/integtest.go index 6fc26915415..f573d3df80a 100644 --- a/dev-tools/mage/integtest.go +++ b/dev-tools/mage/integtest.go @@ -19,334 +19,334 @@ package mage import ( "fmt" - "io/ioutil" - "log" "os" "path/filepath" - "runtime" "strconv" - "strings" - "sync" - - "github.com/pkg/errors" + "github.com/joeshaw/multierror" "github.com/magefile/mage/mg" - "github.com/magefile/mage/sh" + "github.com/pkg/errors" ) const ( - // BEATS_DOCKER_INTEGRATION_TEST_ENV is used to indicate that we are inside - // of the Docker integration test environment (e.g. in a container). - beatsDockerIntegrationTestEnvVar = "BEATS_DOCKER_INTEGRATION_TEST_ENV" + // BEATS_INSIDE_INTEGRATION_TEST_ENV is used to indicate that we are inside + // of the integration test environment. + insideIntegrationTestEnvVar = "BEATS_INSIDE_INTEGRATION_TEST_ENV" ) var ( - integTestUseCount int32 // Reference count for the integ test env. - integTestUseCountLock sync.Mutex // Lock to guard integTestUseCount. - - integTestLock sync.Mutex // Only allow one integration test at a time. + globalIntegrationTesters map[string]IntegrationTester + globalIntegrationTestSetupSteps IntegrationTestSteps - integTestBuildImagesOnce sync.Once // Build images one time for all integ testing. -) - -// Integration Test Configuration -var ( - // StackEnvironment specifies what testing environment - // to use (like snapshot (default), latest, 5x). Formerly known as - // TESTING_ENVIRONMENT. - StackEnvironment = EnvOr("STACK_ENVIRONMENT", "snapshot") + defaultPassthroughEnvVars = []string{ + "TEST_COVERAGE", + "RACE_DETECTOR", + "TEST_TAGS", + "PYTHON_EXE", + "MODULE", + "KUBECONFIG", + "KUBE_CONFIG", + } ) -// AddIntegTestUsage increments the use count for the integration test -// environment and prevents it from being stopped until the last call to -// StopIntegTestEnv(). You should also pair this with -// 'defer StopIntegTestEnv()'. -// -// This allows for the same environment to be reused by multiple tests (like -// both Go and Python) without tearing it down in between runs. -func AddIntegTestUsage() { - if IsInIntegTestEnv() { - return +// RegisterIntegrationTester registers a integration tester. +func RegisterIntegrationTester(tester IntegrationTester) { + if globalIntegrationTesters == nil { + globalIntegrationTesters = make(map[string]IntegrationTester) } - - integTestUseCountLock.Lock() - defer integTestUseCountLock.Unlock() - integTestUseCount++ + globalIntegrationTesters[tester.Name()] = tester } -// StopIntegTestEnv will stop and removing the integration test environment -// (e.g. docker-compose rm --stop --force) when there are no more users -// of the environment. -func StopIntegTestEnv() error { - if IsInIntegTestEnv() { - return nil - } - - integTestUseCountLock.Lock() - defer integTestUseCountLock.Unlock() - if integTestUseCount == 0 { - panic("integTestUseCount is 0. Did you call AddIntegTestUsage()?") - } - - integTestUseCount-- - if integTestUseCount > 0 { - return nil - } - - if err := haveIntegTestEnvRequirements(); err != nil { - // Ignore error because it will be logged by RunIntegTest. - return nil - } +// RegisterIntegrationTestSetupStep registers a integration step. +func RegisterIntegrationTestSetupStep(step IntegrationTestSetupStep) { + globalIntegrationTestSetupSteps = append(globalIntegrationTestSetupSteps, step) +} - if _, skip := skipIntegTest(); skip { - return nil - } +// IntegrationTestSetupStep is interface used by a step in the integration setup +// chain. Example could be: Terraform -> Kind -> Kubernetes (IntegrationTester). +type IntegrationTestSetupStep interface { + // Name is the name of the step. + Name() string + // Use returns true in the case that the step should be used. Not called + // when a step is defined as a dependency of a tester. + Use(dir string) (bool, error) + // Setup sets up the environment for the integration test. + Setup(env map[string]string) error + // Teardown brings down the environment for the integration test. + Teardown(env map[string]string) error +} - composeEnv, err := integTestDockerComposeEnvVars() - if err != nil { - return err - } +// IntegrationTestSteps wraps all the steps and completes the in the order added. +type IntegrationTestSteps []IntegrationTestSetupStep - // Stop docker-compose when reference count hits 0. - fmt.Println(">> Stopping Docker test environment...") +// Name is the name of the step. +func (steps IntegrationTestSteps) Name() string { + return "IntegrationTestSteps" +} - // Docker-compose rm is noisy. So only pass through stderr when in verbose. - out := ioutil.Discard - if mg.Verbose() { - out = os.Stderr +// Setup calls Setup on each step in the order defined. +// +// In the case that Setup fails on a step, Teardown will be called on the previous +// successful steps. +func (steps IntegrationTestSteps) Setup(env map[string]string) error { + for i, step := range steps { + if mg.Verbose() { + fmt.Printf("Setup %s...\n", step.Name()) + } + if err := step.Setup(env); err != nil { + prev := i - 1 + if prev >= 0 { + // errors ignored + _ = steps.teardownFrom(prev, env) + } + return errors.Wrapf(err, "%s setup failed", step.Name()) + } } - - _, err = sh.Exec( - composeEnv, - ioutil.Discard, - out, - "docker-compose", - "-p", dockerComposeProjectName(), - "rm", "--stop", "--force", - ) - return err + return nil } -// RunIntegTest executes the given target inside the integration testing -// environment (Docker). -// Use TEST_COVERAGE=true to enable code coverage profiling. -// Use RACE_DETECTOR=true to enable the race detector. -// Use STACK_ENVIRONMENT=env to specify what testing environment -// to use (like snapshot (default), latest, 5x). +// Teardown calls Teardown in the reverse order defined. // -// Always use this with AddIntegTestUsage() and defer StopIntegTestEnv(). -func RunIntegTest(mageTarget string, test func() error, passThroughEnvVars ...string) error { - if reason, skip := skipIntegTest(); skip { - fmt.Printf(">> %v: Skipping because %v\n", mageTarget, reason) - return nil +// In the case a teardown step fails the error is recorded but the +// previous steps teardown is still called. This guarantees that teardown +// will always be called for each step. +func (steps IntegrationTestSteps) Teardown(env map[string]string) error { + return steps.teardownFrom(len(steps)-1, env) +} + +func (steps IntegrationTestSteps) teardownFrom(start int, env map[string]string) error { + var errs multierror.Errors + for i := start; i >= 0; i-- { + if mg.Verbose() { + fmt.Printf("Teardown %s...\n", steps[i].Name()) + } + if err := steps[i].Teardown(env); err != nil { + errs = append(errs, errors.Wrapf(err, "%s teardown failed", steps[i].Name())) + } } + return errs.Err() +} - AddIntegTestUsage() - defer StopIntegTestEnv() +// IntegrationTester is interface used by the actual test runner. +type IntegrationTester interface { + // Name returns the name of the tester. + Name() string + // Use returns true in the case that the tester should be used. + Use(dir string) (bool, error) + // HasRequirements returns an error if requirements are missing. + HasRequirements() error + // Test performs excecuting the test inside the environment. + Test(dir string, mageTarget string, env map[string]string) error + // InsideTest performs the actual test on the inside of environment. + InsideTest(test func() error) error + // StepRequirements returns the steps this tester requires. These + // are always placed before other autodiscover steps. + StepRequirements() IntegrationTestSteps +} - env := []string{ - "TEST_COVERAGE", - "RACE_DETECTOR", - "TEST_TAGS", - "PYTHON_EXE", - "MODULE", - } - env = append(env, passThroughEnvVars...) - return runInIntegTestEnv(mageTarget, test, env...) +// IntegrationRunner performs the running of the integration tests. +type IntegrationRunner struct { + steps IntegrationTestSteps + tester IntegrationTester + dir string + env map[string]string } -func runInIntegTestEnv(mageTarget string, test func() error, passThroughEnvVars ...string) error { - if IsInIntegTestEnv() { - // Fix file permissions after test is done writing files as root. - if runtime.GOOS != "windows" { - defer DockerChown(".") - } - return test() - } +// IntegrationRunners is an array of multiple runners. +type IntegrationRunners []*IntegrationRunner - var err error - integTestBuildImagesOnce.Do(func() { err = dockerComposeBuildImages() }) +// NewIntegrationRunners returns the integration test runners discovered from the provided path. +func NewIntegrationRunners(path string, passInEnv map[string]string) (IntegrationRunners, error) { + cwd, err := os.Getwd() if err != nil { - return err + return nil, err } - - // Test that we actually have Docker and docker-compose. - if err := haveIntegTestEnvRequirements(); err != nil { - return errors.Wrapf(err, "failed to run %v target in integration environment", mageTarget) + dir := filepath.Join(cwd, path) + + // Load the overall steps to use (skipped inside of test environment, as they are never ran on the inside). + // These steps are duplicated per scenario. + var steps IntegrationTestSteps + if !IsInIntegTestEnv() { + for _, step := range globalIntegrationTestSetupSteps { + use, err := step.Use(dir) + if err != nil { + return nil, errors.Wrapf(err, "%s step failed on Use", step.Name()) + } + if use { + steps = append(steps, step) + } + } } - // Pre-build a mage binary to execute inside docker so that we don't need to - // have mage installed inside the container. - mg.Deps(buildMage) + // Create the runners (can only be multiple). + var runners IntegrationRunners + for _, t := range globalIntegrationTesters { + use, err := t.Use(dir) + if err != nil { + return nil, errors.Wrapf(err, "%s tester failed on Use", t.Name()) + } + if use { + // Create the steps for the specific runner. + var runnerSteps IntegrationTestSteps + requirements := t.StepRequirements() + if requirements != nil { + runnerSteps = append(runnerSteps, requirements...) + } + runnerSteps = append(runnerSteps, steps...) + + // Create the custom env for the runner. + env := map[string]string{} + for k, v := range passInEnv { + env[k] = v + } + env[insideIntegrationTestEnvVar] = "true" + passThroughEnvs(env, defaultPassthroughEnvVars...) + if mg.Verbose() { + env["MAGEFILE_VERBOSE"] = "1" + } + if UseVendor { + env["GOFLAGS"] = "-mod=vendor" + } + + runner := &IntegrationRunner{ + steps: runnerSteps, + tester: t, + dir: dir, + env: env, + } + runners = append(runners, runner) + } + } + return runners, nil +} - // Determine the path to use inside the container. - repo, err := GetProjectRepoInfo() +// NewDockerIntegrationRunner returns an intergration runner configured only for docker. +func NewDockerIntegrationRunner(passThroughEnvVars ...string) (*IntegrationRunner, error) { + cwd, err := os.Getwd() if err != nil { - return err - } - magePath := filepath.Join("/go/src", repo.CanonicalRootImportPath, repo.SubDir, "build/mage-linux-amd64") - - // Build docker-compose args. - args := []string{"-p", dockerComposeProjectName(), "run", - "-e", "DOCKER_COMPOSE_PROJECT_NAME=" + dockerComposeProjectName(), - // Disable strict.perms because we moust host dirs inside containers - // and the UID/GID won't meet the strict requirements. - "-e", "BEAT_STRICT_PERMS=false", - // compose.EnsureUp needs to know the environment type. - "-e", "STACK_ENVIRONMENT=" + StackEnvironment, - "-e", "TESTING_ENVIRONMENT=" + StackEnvironment, + return nil, err } - if UseVendor { - args = append(args, "-e", "GOFLAGS=-mod=vendor") + tester, ok := globalIntegrationTesters["docker"] + if !ok { + return nil, fmt.Errorf("docker integration test runner not registered") } - args, err = addUidGidEnvArgs(args) - if err != nil { - return err + var runnerSteps IntegrationTestSteps + requirements := tester.StepRequirements() + if requirements != nil { + runnerSteps = append(runnerSteps, requirements...) } - for _, envVar := range passThroughEnvVars { - args = append(args, "-e", envVar+"="+os.Getenv(envVar)) + + // Create the custom env for the runner. + env := map[string]string{ + insideIntegrationTestEnvVar: "true", } + passThroughEnvs(env, defaultPassthroughEnvVars...) + passThroughEnvs(env, passThroughEnvVars...) if mg.Verbose() { - args = append(args, "-e", "MAGEFILE_VERBOSE=1") + env["MAGEFILE_VERBOSE"] = "1" } - args = append(args, - "-e", beatsDockerIntegrationTestEnvVar+"=true", - "beat", // Docker compose container name. - magePath, - mageTarget, - ) - - composeEnv, err := integTestDockerComposeEnvVars() - if err != nil { - return err + if UseVendor { + env["GOFLAGS"] = "-mod=vendor" } - // Only allow one usage at a time. - integTestLock.Lock() - defer integTestLock.Unlock() - - _, err = sh.Exec( - composeEnv, - os.Stdout, - os.Stderr, - "docker-compose", - args..., - ) - return err -} - -// IsInIntegTestEnv return true if executing inside the integration test -// environment. -func IsInIntegTestEnv() bool { - _, found := os.LookupEnv(beatsDockerIntegrationTestEnvVar) - return found -} - -func haveIntegTestEnvRequirements() error { - if err := HaveDockerCompose(); err != nil { - return err - } - if err := HaveDocker(); err != nil { - return err + runner := &IntegrationRunner{ + steps: runnerSteps, + tester: tester, + dir: cwd, + env: env, } - return nil + return runner, nil } -// skipIntegTest returns true if integ tests should be skipped. -func skipIntegTest() (reason string, skip bool) { +// Test actually performs the test. +func (r *IntegrationRunner) Test(mageTarget string, test func() error) (err error) { + // Inside the testing environment just run the test. if IsInIntegTestEnv() { - return "", false + err = r.tester.InsideTest(test) + return } // Honor the TEST_ENVIRONMENT value if set. if testEnvVar, isSet := os.LookupEnv("TEST_ENVIRONMENT"); isSet { - enabled, err := strconv.ParseBool(testEnvVar) + var enabled bool + enabled, err = strconv.ParseBool(testEnvVar) if err != nil { - panic(errors.Wrap(err, "failed to parse TEST_ENVIRONMENT value")) + err = errors.Wrap(err, "failed to parse TEST_ENVIRONMENT value") + return + } + if !enabled { + err = fmt.Errorf("TEST_ENVIRONMENT=%s", testEnvVar) + return } - return "TEST_ENVIRONMENT=" + testEnvVar, !enabled - } - - // Otherwise skip if we don't have all the right dependencies. - if err := haveIntegTestEnvRequirements(); err != nil { - // Skip if we don't meet the requirements. - log.Println("Skipping integ test because:", err) - return "docker is not available", true } - return "", false -} - -// integTestDockerComposeEnvVars returns the environment variables used for -// executing docker-compose (not the variables passed into the containers). -// docker-compose uses these when evaluating docker-compose.yml files. -func integTestDockerComposeEnvVars() (map[string]string, error) { - esBeatsDir, err := ElasticBeatsDir() + // log missing requirements and do nothing + err = r.tester.HasRequirements() if err != nil { - return nil, err + // log error; and return (otherwise on machines without requirements it will mark the tests as failed) + fmt.Printf("skipping test run with %s due to missing requirements: %s\n", r.tester.Name(), err) + err = nil + return } - return map[string]string{ - "ES_BEATS": esBeatsDir, - "STACK_ENVIRONMENT": StackEnvironment, - // Deprecated use STACK_ENVIRONMENT instead (it's more descriptive). - "TESTING_ENVIRONMENT": StackEnvironment, - }, nil -} - -// dockerComposeProjectName returns the project name to use with docker-compose. -// It is passed to docker-compose using the `-p` flag. And is passed to our -// Go and Python testing libraries through the DOCKER_COMPOSE_PROJECT_NAME -// environment variable. -func dockerComposeProjectName() string { - commit, err := CommitHash() - if err != nil { - panic(errors.Wrap(err, "failed to construct docker compose project name")) + if err = r.steps.Setup(r.env); err != nil { + return } - version, err := BeatQualifiedVersion() - if err != nil { - panic(errors.Wrap(err, "failed to construct docker compose project name")) + // catch any panics to run teardown + inTeardown := false + defer func() { + if recoverErr := recover(); recoverErr != nil { + err = recoverErr.(error) + if !inTeardown { + // ignore errors + _ = r.steps.Teardown(r.env) + } + } + }() + + if mg.Verbose() { + fmt.Printf(">> Running testing inside of %s...\n", r.tester.Name()) } - version = strings.NewReplacer(".", "_").Replace(version) - - projectName := "{{.BeatName}}_{{.Version}}_{{.ShortCommit}}-{{.StackEnvironment}}" - projectName = MustExpand(projectName, map[string]interface{}{ - "StackEnvironment": StackEnvironment, - "ShortCommit": commit[:10], - "Version": version, - }) - return projectName -} -// dockerComposeBuildImages builds all images in the docker-compose.yml file. -func dockerComposeBuildImages() error { - fmt.Println(">> Building docker images") + err = r.tester.Test(r.dir, mageTarget, r.env) - composeEnv, err := integTestDockerComposeEnvVars() - if err != nil { - return err + if mg.Verbose() { + fmt.Printf(">> Done running testing inside of %s...\n", r.tester.Name()) } - args := []string{"-p", dockerComposeProjectName(), "build", "--force-rm"} - if _, noCache := os.LookupEnv("DOCKER_NOCACHE"); noCache { - args = append(args, "--no-cache") + inTeardown = true + if teardownErr := r.steps.Teardown(r.env); teardownErr != nil { + if err == nil { + // test didn't error, but teardown did + err = teardownErr + } } + return +} - if _, forcePull := os.LookupEnv("DOCKER_PULL"); forcePull { - args = append(args, "--pull") +// Test runs the test on each runner and collects the errors. +func (r IntegrationRunners) Test(mageTarget string, test func() error) error { + var errs multierror.Errors + for _, runner := range r { + if err := runner.Test(mageTarget, test); err != nil { + errs = append(errs, err) + } } + return errs.Err() +} - out := ioutil.Discard - if mg.Verbose() { - out = os.Stderr +func passThroughEnvs(env map[string]string, passthrough ...string) { + for _, envName := range passthrough { + val, set := os.LookupEnv(envName) + if set { + env[envName] = val + } } +} - _, err = sh.Exec( - composeEnv, - out, - os.Stderr, - "docker-compose", args..., - ) - return err +// IsInIntegTestEnv return true if executing inside the integration test environment. +func IsInIntegTestEnv() bool { + _, found := os.LookupEnv(insideIntegrationTestEnvVar) + return found } diff --git a/dev-tools/mage/integtest_docker.go b/dev-tools/mage/integtest_docker.go new file mode 100644 index 00000000000..6bbca1f6d64 --- /dev/null +++ b/dev-tools/mage/integtest_docker.go @@ -0,0 +1,233 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package mage + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "runtime" + "strings" + "sync" + + "github.com/pkg/errors" + + "github.com/magefile/mage/mg" + "github.com/magefile/mage/sh" +) + +var ( + // StackEnvironment specifies what testing environment + // to use (like snapshot (default), latest, 5x). Formerly known as + // TESTING_ENVIRONMENT. + StackEnvironment = EnvOr("STACK_ENVIRONMENT", "snapshot") +) + +func init() { + RegisterIntegrationTester(&DockerIntegrationTester{}) +} + +type DockerIntegrationTester struct { + buildImagesOnce sync.Once +} + +// Name returns docker name. +func (d *DockerIntegrationTester) Name() string { + return "docker" +} + +// Use determines if this tester should be used. +func (d *DockerIntegrationTester) Use(dir string) (bool, error) { + dockerFile := filepath.Join(dir, "docker-compose.yml") + if _, err := os.Stat(dockerFile); !os.IsNotExist(err) { + return true, nil + } + return false, nil +} + +// HasRequirements ensures that the required docker and docker-compose are installed. +func (d *DockerIntegrationTester) HasRequirements() error { + if err := HaveDocker(); err != nil { + return err + } + if err := HaveDockerCompose(); err != nil { + return err + } + return nil +} + +// StepRequirements returns the steps required for this tester. +func (d *DockerIntegrationTester) StepRequirements() IntegrationTestSteps { + return IntegrationTestSteps{&MageIntegrationTestStep{}} +} + +// Test performs the tests with docker-compose. +func (d *DockerIntegrationTester) Test(_ string, mageTarget string, env map[string]string) error { + var err error + d.buildImagesOnce.Do(func() { err = dockerComposeBuildImages() }) + if err != nil { + return err + } + + // Determine the path to use inside the container. + repo, err := GetProjectRepoInfo() + if err != nil { + return err + } + magePath := filepath.Join("/go/src", repo.CanonicalRootImportPath, repo.SubDir, "build/mage-linux-amd64") + + // Execute the inside of docker-compose. + args := []string{"-p", dockerComposeProjectName(), "run", + "-e", "DOCKER_COMPOSE_PROJECT_NAME=" + dockerComposeProjectName(), + // Disable strict.perms because we moust host dirs inside containers + // and the UID/GID won't meet the strict requirements. + "-e", "BEAT_STRICT_PERMS=false", + // compose.EnsureUp needs to know the environment type. + "-e", "STACK_ENVIRONMENT=" + StackEnvironment, + "-e", "TESTING_ENVIRONMENT=" + StackEnvironment, + } + args, err = addUidGidEnvArgs(args) + if err != nil { + return err + } + for envVame, envVal := range env { + args = append(args, "-e", fmt.Sprintf("%s=%s", envVame, envVal)) + } + args = append(args, + "beat", // Docker compose container name. + magePath, + mageTarget, + ) + + composeEnv, err := integTestDockerComposeEnvVars() + if err != nil { + return err + } + + _, testErr := sh.Exec( + composeEnv, + os.Stdout, + os.Stderr, + "docker-compose", + args..., + ) + + // Docker-compose rm is noisy. So only pass through stderr when in verbose. + out := ioutil.Discard + if mg.Verbose() { + out = os.Stderr + } + + _, err = sh.Exec( + composeEnv, + ioutil.Discard, + out, + "docker-compose", + "-p", dockerComposeProjectName(), + "rm", "--stop", "--force", + ) + if err != nil && testErr == nil { + // docker-compose rm failed but the test didn't + return err + } + return testErr +} + +// InsideTest performs the tests inside of environment. +func (d *DockerIntegrationTester) InsideTest(test func() error) error { + // Fix file permissions after test is done writing files as root. + if runtime.GOOS != "windows" { + defer DockerChown(".") + } + return test() +} + +// integTestDockerComposeEnvVars returns the environment variables used for +// executing docker-compose (not the variables passed into the containers). +// docker-compose uses these when evaluating docker-compose.yml files. +func integTestDockerComposeEnvVars() (map[string]string, error) { + esBeatsDir, err := ElasticBeatsDir() + if err != nil { + return nil, err + } + + return map[string]string{ + "ES_BEATS": esBeatsDir, + "STACK_ENVIRONMENT": StackEnvironment, + // Deprecated use STACK_ENVIRONMENT instead (it's more descriptive). + "TESTING_ENVIRONMENT": StackEnvironment, + }, nil +} + +// dockerComposeProjectName returns the project name to use with docker-compose. +// It is passed to docker-compose using the `-p` flag. And is passed to our +// Go and Python testing libraries through the DOCKER_COMPOSE_PROJECT_NAME +// environment variable. +func dockerComposeProjectName() string { + commit, err := CommitHash() + if err != nil { + panic(errors.Wrap(err, "failed to construct docker compose project name")) + } + + version, err := BeatQualifiedVersion() + if err != nil { + panic(errors.Wrap(err, "failed to construct docker compose project name")) + } + version = strings.NewReplacer(".", "_").Replace(version) + + projectName := "{{.BeatName}}_{{.Version}}_{{.ShortCommit}}-{{.StackEnvironment}}" + projectName = MustExpand(projectName, map[string]interface{}{ + "StackEnvironment": StackEnvironment, + "ShortCommit": commit[:10], + "Version": version, + }) + return projectName +} + +// dockerComposeBuildImages builds all images in the docker-compose.yml file. +func dockerComposeBuildImages() error { + fmt.Println(">> Building docker images") + + composeEnv, err := integTestDockerComposeEnvVars() + if err != nil { + return err + } + + args := []string{"-p", dockerComposeProjectName(), "build", "--force-rm"} + if _, noCache := os.LookupEnv("DOCKER_NOCACHE"); noCache { + args = append(args, "--no-cache") + } + + if _, forcePull := os.LookupEnv("DOCKER_PULL"); forcePull { + args = append(args, "--pull") + } + + out := ioutil.Discard + if mg.Verbose() { + out = os.Stderr + } + + _, err = sh.Exec( + composeEnv, + out, + os.Stderr, + "docker-compose", args..., + ) + return err +} diff --git a/dev-tools/mage/integtest_mage.go b/dev-tools/mage/integtest_mage.go new file mode 100644 index 00000000000..82dcb90fefd --- /dev/null +++ b/dev-tools/mage/integtest_mage.go @@ -0,0 +1,58 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package mage + +import ( + "sync" + + "github.com/magefile/mage/mg" +) + +var ( + buildMageOnce sync.Once +) + +// MageIntegrationTestStep setups mage to be ran. +type MageIntegrationTestStep struct{} + +// Name returns the mage name. +func (m *MageIntegrationTestStep) Name() string { + return "mage" +} + +// Use always returns false. +// +// This step should be defined in `StepRequirements` for the tester, for it +// to be used. It cannot be autodiscovered for usage. +func (m *MageIntegrationTestStep) Use(dir string) (bool, error) { + return false, nil +} + +// Setup ensures the mage binary is built. +// +// Multiple uses of this step will only build the mage binary once. +func (m *MageIntegrationTestStep) Setup(_ map[string]string) error { + // Pre-build a mage binary to execute. + buildMageOnce.Do(func() { mg.Deps(buildMage) }) + return nil +} + +// Teardown does nothing. +func (m *MageIntegrationTestStep) Teardown(_ map[string]string) error { + return nil +} diff --git a/dev-tools/mage/kubectl.go b/dev-tools/mage/kubectl.go new file mode 100644 index 00000000000..df42bc8049d --- /dev/null +++ b/dev-tools/mage/kubectl.go @@ -0,0 +1,129 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package mage + +import ( + "fmt" + "io" + "os" + "os/exec" + "strings" + + "github.com/magefile/mage/mg" + "github.com/magefile/mage/sh" +) + +// KubectlApply applys the manifest file to the kubernetes cluster. +// +// KUBECONFIG must be in `env` to target a specific cluster. +func KubectlApply(env map[string]string, stdout, stderr io.Writer, filepath string) error { + _, err := sh.Exec( + env, + stdout, + stderr, + "kubectl", + "apply", + "-f", + filepath, + ) + return err +} + +// KubectlDelete deletes the resources from the manifest file from the kubernetes cluster. +// +// KUBECONFIG must be in `env` to target a specific cluster. +func KubectlDelete(env map[string]string, stdout, stderr io.Writer, filepath string) error { + _, err := sh.Exec( + env, + stdout, + stderr, + "kubectl", + "delete", + "-f", + filepath, + ) + return err +} + +// KubectlApplyInput applys the manifest string to the kubernetes cluster. +// +// KUBECONFIG must be in `env` to target a specific cluster. +func KubectlApplyInput(env map[string]string, stdout, stderr io.Writer, manifest string) error { + return kubectlIn(env, stdout, stderr, manifest, "apply", "-f", "-") +} + +// KubectlDeleteInput deletes the resources from the manifest string from the kubernetes cluster. +// +// KUBECONFIG must be in `env` to target a specific cluster. +func KubectlDeleteInput(env map[string]string, stdout, stderr io.Writer, manifest string) error { + return kubectlIn(env, stdout, stderr, manifest, "delete", "-f", "-") +} + +// KubectlWait waits for a condition to occur for a resource in the kubernetes cluster. +// +// KUBECONFIG must be in `env` to target a specific cluster. +func KubectlWait(env map[string]string, stdout, stderr io.Writer, waitFor, resource string) error { + _, err := sh.Exec( + env, + stdout, + stderr, + "kubectl", + "wait", + "--timeout=300s", + fmt.Sprintf("--for=%s", waitFor), + resource, + ) + return err +} + +func kubectlIn(env map[string]string, stdout, stderr io.Writer, input string, args ...string) error { + c := exec.Command("kubectl", args...) + c.Env = os.Environ() + for k, v := range env { + c.Env = append(c.Env, k+"="+v) + } + c.Stdout = stdout + c.Stderr = stderr + c.Stdin = strings.NewReader(input) + + if mg.Verbose() { + fmt.Println("exec:", "kubectl", strings.Join(args, " ")) + } + + return c.Run() +} + +func kubectlStart(env map[string]string, stdout, stderr io.Writer, args ...string) (*exec.Cmd, error) { + c := exec.Command("kubectl", args...) + c.Env = os.Environ() + for k, v := range env { + c.Env = append(c.Env, k+"="+v) + } + c.Stdout = stdout + c.Stderr = stderr + c.Stdin = nil + + if mg.Verbose() { + fmt.Println("exec:", "kubectl", strings.Join(args, " ")) + } + + if err := c.Start(); err != nil { + return nil, err + } + return c, nil +} diff --git a/dev-tools/mage/kuberemote.go b/dev-tools/mage/kuberemote.go new file mode 100644 index 00000000000..078680fbf0b --- /dev/null +++ b/dev-tools/mage/kuberemote.go @@ -0,0 +1,612 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package mage + +import ( + "bufio" + "context" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/hex" + "encoding/pem" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "net/url" + "os/exec" + "strings" + "time" + + "github.com/pkg/errors" + "golang.org/x/crypto/ssh" + + apiv1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/tools/portforward" + watchtools "k8s.io/client-go/tools/watch" + "k8s.io/client-go/transport/spdy" +) + +const sshBitSize = 4096 + +var mode = int32(256) + +// KubeRemote rsyncs the passed directory to a pod and runs the command inside of that pod. +type KubeRemote struct { + cfg *rest.Config + cs *kubernetes.Clientset + namespace string + name string + workDir string + destDir string + syncDir string + + svcAccName string + secretName string + privateKey []byte + publicKey []byte +} + +// NewKubeRemote creates a new kubernetes remote runner. +func NewKubeRemote(kubeconfig string, namespace string, name string, workDir string, destDir string, syncDir string) (*KubeRemote, error) { + config, err := clientcmd.BuildConfigFromFlags("", kubeconfig) + if err != nil { + return nil, err + } + cs, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, err + } + name = strings.Replace(name, "_", "-", -1) + svcAccName := fmt.Sprintf("%s-sa", name) + secretName := fmt.Sprintf("%s-ssh-key", name) + privateKey, publicKey, err := generateSSHKeyPair() + if err != nil { + return nil, err + } + return &KubeRemote{config, cs, namespace, name, workDir, destDir, syncDir, svcAccName, secretName, privateKey, publicKey}, nil +} + +// Run runs the command remotely on the kubernetes cluster. +func (r *KubeRemote) Run(env map[string]string, stdout io.Writer, stderr io.Writer, args ...string) error { + if err := r.syncSSHKey(); err != nil { + return errors.Wrap(err, "failed to sync SSH secret") + } + defer r.deleteSSHKey() + if err := r.syncServiceAccount(); err != nil { + return err + } + defer r.deleteServiceAccount() + _, err := r.createPod(env, args...) + if err != nil { + return errors.Wrap(err, "failed to create execute pod") + } + defer r.deletePod() + + // wait for SSH to be up inside the init container. + _, err = r.waitForPod(5*time.Minute, podInitReady) + if err != nil { + return errors.Wrap(err, "execute pod init container never started") + } + time.Sleep(1 * time.Second) // SSH inside of container can take a moment + + // forward the SSH port so rsync can be ran. + randomPort, err := getFreePort() + if err != nil { + return errors.Wrap(err, "failed to find a free port") + } + stopChannel := make(chan struct{}, 1) + readyChannel := make(chan struct{}, 1) + f, err := r.portForward([]string{fmt.Sprintf("%d:%d", randomPort, 22)}, stopChannel, readyChannel, stderr, stderr) + if err != nil { + return err + } + go f.ForwardPorts() + <-readyChannel + + // perform the rsync + r.rsync(randomPort, stderr, stderr) + + // stop port forwarding + close(stopChannel) + + // wait for exec container to be running + _, err = r.waitForPod(5*time.Minute, containerRunning("exec")) + if err != nil { + return errors.Wrap(err, "execute pod container never started") + } + + // stream the logs of the container + err = r.streamLogs("exec", stdout) + if err != nil { + return errors.Wrap(err, "failed to stream the logs") + } + + // wait for exec container to be completely done + pod, err := r.waitForPod(30*time.Second, podDone) + if err != nil { + return errors.Wrap(err, "execute pod didn't terminate after 30 seconds of log stream") + } + + // return error on failure + if pod.Status.Phase == apiv1.PodFailed { + return fmt.Errorf("execute pod test failed") + } + return nil +} + +// deleteSSHKey deletes SSH key from the cluster. +func (r *KubeRemote) deleteSSHKey() { + _ = r.cs.CoreV1().Secrets(r.namespace).Delete(r.secretName, &metav1.DeleteOptions{}) +} + +// syncSSHKey syncs the SSH key to the cluster. +func (r *KubeRemote) syncSSHKey() error { + // delete before create + r.deleteSSHKey() + _, err := r.cs.CoreV1().Secrets(r.namespace).Create(createSecretManifest(r.secretName, r.publicKey)) + if err != nil { + return err + } + return nil +} + +// deleteServiceAccount syncs required service account. +func (r *KubeRemote) deleteServiceAccount() { + _ = r.cs.RbacV1().ClusterRoleBindings().Delete(r.name, &metav1.DeleteOptions{}) + _ = r.cs.RbacV1().ClusterRoles().Delete(r.name, &metav1.DeleteOptions{}) + _ = r.cs.CoreV1().ServiceAccounts(r.namespace).Delete(r.svcAccName, &metav1.DeleteOptions{}) +} + +// syncServiceAccount syncs required service account. +func (r *KubeRemote) syncServiceAccount() error { + // delete before create + r.deleteServiceAccount() + _, err := r.cs.CoreV1().ServiceAccounts(r.namespace).Create(createServiceAccountManifest(r.svcAccName)) + if err != nil { + return errors.Wrap(err, "failed to create service account") + } + _, err = r.cs.RbacV1().ClusterRoles().Create(createClusterRoleManifest(r.name)) + if err != nil { + return errors.Wrap(err, "failed to create cluster role") + } + _, err = r.cs.RbacV1().ClusterRoleBindings().Create(createClusterRoleBindingManifest(r.name, r.namespace, r.svcAccName)) + if err != nil { + return errors.Wrap(err, "failed to create cluster role binding") + } + return nil +} + +// createPod creates the pod. +func (r *KubeRemote) createPod(env map[string]string, cmd ...string) (*apiv1.Pod, error) { + r.deletePod() // ensure it doesn't already exist + return r.cs.CoreV1().Pods(r.namespace).Create(createPodManifest(r.name, "golang:1.13.9", env, cmd, r.workDir, r.destDir, r.secretName, r.svcAccName)) +} + +// deletePod deletes the pod. +func (r *KubeRemote) deletePod() { + _ = r.cs.CoreV1().Pods(r.namespace).Delete(r.name, &metav1.DeleteOptions{}) +} + +// waitForPod waits for the created pod to match the given condition. +func (r *KubeRemote) waitForPod(wait time.Duration, condition watchtools.ConditionFunc) (*apiv1.Pod, error) { + w, err := r.cs.CoreV1().Pods(r.namespace).Watch(metav1.SingleObject(metav1.ObjectMeta{Name: r.name})) + if err != nil { + return nil, err + } + + ctx, _ := watchtools.ContextWithOptionalTimeout(context.Background(), wait) + ev, err := watchtools.UntilWithoutRetry(ctx, w, func(ev watch.Event) (bool, error) { + return condition(ev) + }) + if ev != nil { + return ev.Object.(*apiv1.Pod), err + } + return nil, err +} + +// portFoward runs the port forwarding so SSH rsync can be ran into the pod. +func (r *KubeRemote) portForward(ports []string, stopChannel, readyChannel chan struct{}, stdout, stderr io.Writer) (*portforward.PortForwarder, error) { + roundTripper, upgrader, err := spdy.RoundTripperFor(r.cfg) + if err != nil { + return nil, err + } + + path := fmt.Sprintf("/api/v1/namespaces/%s/pods/%s/portforward", r.namespace, r.name) + hostIP := strings.TrimLeft(r.cfg.Host, "https://") + serverURL := url.URL{Scheme: "https", Path: path, Host: hostIP} + dialer := spdy.NewDialer(upgrader, &http.Client{Transport: roundTripper}, http.MethodPost, &serverURL) + return portforward.New(dialer, ports, stopChannel, readyChannel, stdout, stderr) +} + +// rsync performs the rsync of sync directory to destination directory inside of the pod. +func (r *KubeRemote) rsync(port uint16, stdout, stderr io.Writer) error { + privateKeyFile, err := createTempFile(r.privateKey) + if err != nil { + return err + } + + rsh := fmt.Sprintf("/usr/bin/ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -p %d -i %s", port, privateKeyFile) + args := []string{ + "--rsh", rsh, + "-a", fmt.Sprintf("%s/", r.syncDir), + fmt.Sprintf("root@localhost:%s", r.destDir), + } + cmd := exec.Command("rsync", args...) + cmd.Stdout = stdout + cmd.Stderr = stderr + return cmd.Run() +} + +// streamLogs streams the logs from the execution pod until the pod is terminated. +func (r *KubeRemote) streamLogs(container string, stdout io.Writer) error { + req := r.cs.CoreV1().Pods(r.namespace).GetLogs(r.name, &apiv1.PodLogOptions{ + Container: container, + Follow: true, + }) + logs, err := req.Stream() + if err != nil { + return err + } + defer logs.Close() + + reader := bufio.NewReader(logs) + for { + bytes, err := reader.ReadBytes('\n') + if _, err := stdout.Write(bytes); err != nil { + return err + } + if err != nil { + if err != io.EOF { + return err + } + return nil + } + } +} + +// generateSSHKeyPair generates a new SSH key pair. +func generateSSHKeyPair() ([]byte, []byte, error) { + private, err := rsa.GenerateKey(rand.Reader, sshBitSize) + if err != nil { + return nil, nil, err + } + if err = private.Validate(); err != nil { + return nil, nil, err + } + public, err := ssh.NewPublicKey(&private.PublicKey) + if err != nil { + return nil, nil, err + } + return encodePrivateKeyToPEM(private), ssh.MarshalAuthorizedKey(public), nil +} + +// encodePrivateKeyToPEM encodes private key from RSA to PEM format. +func encodePrivateKeyToPEM(privateKey *rsa.PrivateKey) []byte { + privDER := x509.MarshalPKCS1PrivateKey(privateKey) + privBlock := pem.Block{ + Type: "RSA PRIVATE KEY", + Headers: nil, + Bytes: privDER, + } + return pem.EncodeToMemory(&privBlock) +} + +// getFreePort finds a free port. +func getFreePort() (uint16, error) { + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + if err != nil { + return 0, err + } + l, err := net.ListenTCP("tcp", addr) + if err != nil { + return 0, err + } + defer l.Close() + return uint16(l.Addr().(*net.TCPAddr).Port), nil +} + +// createSecretManifest creates the secret object to create in the cluster. +// +// This is the public key that the sshd uses as the authorized key. +func createSecretManifest(name string, publicKey []byte) *apiv1.Secret { + return &apiv1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + StringData: map[string]string{ + "authorized_keys": string(publicKey), + }, + } +} + +// createServiceAccountManifest creates the service account the pod will used. +func createServiceAccountManifest(name string) *apiv1.ServiceAccount { + return &apiv1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + } +} + +// createClusterRoleManifest creates the cluster role the pod will used. +// +// This gives the pod all permissions on everything! +func createClusterRoleManifest(name string) *rbacv1.ClusterRole { + return &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Rules: []rbacv1.PolicyRule{ + rbacv1.PolicyRule{ + Verbs: []string{"*"}, + APIGroups: []string{"*"}, + Resources: []string{"*"}, + }, + rbacv1.PolicyRule{ + Verbs: []string{"*"}, + NonResourceURLs: []string{"*"}, + }, + }, + } +} + +// createClusterRoleBindingManifest creates the cluster role binding the pod will used. +// +// This binds the service account to the cluster role. +func createClusterRoleBindingManifest(name string, namespace string, svcAccName string) *rbacv1.ClusterRoleBinding { + return &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Subjects: []rbacv1.Subject{ + rbacv1.Subject{ + Kind: "ServiceAccount", + Name: svcAccName, + Namespace: namespace, + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "ClusterRole", + Name: name, + }, + } +} + +// createPodManifest creates the pod inside of the cluster that will be used for remote execution. +// +// Creates a pod with an init container that runs sshd-rsync, once the first connection closes the init container +// exits then the exec container starts using the rsync'd directory as its work directory. +func createPodManifest(name string, image string, env map[string]string, cmd []string, workDir string, destDir string, secretName string, svcAccName string) *apiv1.Pod { + execEnv := []apiv1.EnvVar{ + apiv1.EnvVar{ + Name: "NODE_NAME", + ValueFrom: &apiv1.EnvVarSource{ + FieldRef: &apiv1.ObjectFieldSelector{ + FieldPath: "spec.nodeName", + }, + }, + }, + } + for k, v := range env { + execEnv = append(execEnv, apiv1.EnvVar{ + Name: k, + Value: v, + }) + } + return &apiv1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: apiv1.PodSpec{ + ServiceAccountName: svcAccName, + RestartPolicy: apiv1.RestartPolicyNever, + InitContainers: []apiv1.Container{ + { + Name: "sync-init", + Image: "ernoaapa/sshd-rsync", + Ports: []apiv1.ContainerPort{ + { + Name: "ssh", + Protocol: apiv1.ProtocolTCP, + ContainerPort: 22, + }, + }, + Env: []apiv1.EnvVar{ + { + Name: "ONE_TIME", + Value: "true", + }, + }, + VolumeMounts: []apiv1.VolumeMount{ + { + Name: "ssh-config", + MountPath: "/root/.ssh/authorized_keys", + SubPath: "authorized_keys", + }, + { + Name: "destdir", + MountPath: destDir, + }, + }, + }, + }, + Containers: []apiv1.Container{ + apiv1.Container{ + Name: "exec", + Image: image, + Command: cmd, + WorkingDir: workDir, + Env: execEnv, + VolumeMounts: []apiv1.VolumeMount{ + { + Name: "destdir", + MountPath: destDir, + }, + }, + }, + }, + Volumes: []apiv1.Volume{ + { + Name: "ssh-config", + VolumeSource: apiv1.VolumeSource{ + Secret: &apiv1.SecretVolumeSource{ + SecretName: secretName, + DefaultMode: &mode, + }, + }, + }, + { + Name: "destdir", + VolumeSource: apiv1.VolumeSource{ + EmptyDir: &apiv1.EmptyDirVolumeSource{}, + }, + }, + }, + }, + } +} + +func podInitReady(event watch.Event) (bool, error) { + switch event.Type { + case watch.Deleted: + return false, k8serrors.NewNotFound(schema.GroupResource{Resource: "pods"}, "") + } + switch t := event.Object.(type) { + case *apiv1.Pod: + switch t.Status.Phase { + case apiv1.PodFailed, apiv1.PodSucceeded: + return false, nil + case apiv1.PodRunning: + return false, nil + case apiv1.PodPending: + return isInitContainersReady(t), nil + } + } + return false, nil +} + +func isInitContainersReady(pod *apiv1.Pod) bool { + if isScheduled(pod) && isInitContainersRunning(pod) { + return true + } + return false +} + +func isScheduled(pod *apiv1.Pod) bool { + if &pod.Status != nil && len(pod.Status.Conditions) > 0 { + for _, condition := range pod.Status.Conditions { + if condition.Type == apiv1.PodScheduled && + condition.Status == apiv1.ConditionTrue { + return true + } + } + } + return false +} + +func isInitContainersRunning(pod *apiv1.Pod) bool { + if &pod.Status != nil { + if len(pod.Spec.InitContainers) != len(pod.Status.InitContainerStatuses) { + return false + } + for _, status := range pod.Status.InitContainerStatuses { + if status.State.Running == nil { + return false + } + } + return true + } + return false +} + +func containerRunning(containerName string) func(watch.Event) (bool, error) { + return func(event watch.Event) (bool, error) { + switch event.Type { + case watch.Deleted: + return false, k8serrors.NewNotFound(schema.GroupResource{Resource: "pods"}, "") + } + switch t := event.Object.(type) { + case *apiv1.Pod: + switch t.Status.Phase { + case apiv1.PodFailed, apiv1.PodSucceeded: + return false, nil + case apiv1.PodRunning: + return isContainerRunning(t, containerName) + } + } + return false, nil + } +} + +func isContainerRunning(pod *apiv1.Pod, containerName string) (bool, error) { + for _, status := range pod.Status.ContainerStatuses { + if status.Name == containerName { + if status.State.Waiting != nil { + return false, nil + } else if status.State.Running != nil { + return true, nil + } else if status.State.Terminated != nil { + return false, nil + } else { + return false, fmt.Errorf("Unknown container state") + } + } + } + return false, nil +} + +func podDone(event watch.Event) (bool, error) { + switch event.Type { + case watch.Deleted: + return false, k8serrors.NewNotFound(schema.GroupResource{Resource: "pods"}, "") + } + switch t := event.Object.(type) { + case *apiv1.Pod: + switch t.Status.Phase { + case apiv1.PodFailed, apiv1.PodSucceeded: + return true, nil + } + } + return false, nil +} + +func createTempFile(content []byte) (string, error) { + randBytes := make([]byte, 16) + rand.Read(randBytes) + tmpfile, err := ioutil.TempFile("", hex.EncodeToString(randBytes)) + if err != nil { + return "", err + } + defer tmpfile.Close() + if _, err := tmpfile.Write(content); err != nil { + return "", err + } + return tmpfile.Name(), nil +} diff --git a/dev-tools/mage/kubernetes/kind.go b/dev-tools/mage/kubernetes/kind.go new file mode 100644 index 00000000000..c4a94649ca7 --- /dev/null +++ b/dev-tools/mage/kubernetes/kind.go @@ -0,0 +1,143 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package kubernetes + +import ( + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + + "github.com/magefile/mage/mg" + "github.com/magefile/mage/sh" +) + +// KindIntegrationTestStep setups a kind environment. +type KindIntegrationTestStep struct{} + +// Name returns the kind name. +func (m *KindIntegrationTestStep) Name() string { + return "kind" +} + +// Use always returns false. +// +// This step should be defined in `StepRequirements` for the tester, for it +// to be used. It cannot be autodiscovered for usage. +func (m *KindIntegrationTestStep) Use(dir string) (bool, error) { + return false, nil +} + +// Setup ensures that a kubernetes cluster is up and running. +// +// If `KUBECONFIG` is already deinfed in the env then it will do nothing. +func (m *KindIntegrationTestStep) Setup(env map[string]string) error { + _, exists := env["KUBECONFIG"] + if exists { + // do nothing + return nil + } + _, exists = env["KUBE_CONFIG"] + if exists { + // do nothing + return nil + } + _, err := exec.LookPath("kind") + if err != nil { + if mg.Verbose() { + fmt.Println("Skipping kind setup; kind command missing") + } + return nil + } + + clusterName := kubernetesPodName() + stdOut := ioutil.Discard + stdErr := ioutil.Discard + if mg.Verbose() { + stdOut = os.Stdout + stdErr = os.Stderr + } + + kubeCfgDir := filepath.Join("build", "kind", clusterName) + kubeCfgDir, err = filepath.Abs(kubeCfgDir) + if err != nil { + return err + } + kubeConfig := filepath.Join(kubeCfgDir, "kubecfg") + if err := os.MkdirAll(kubeCfgDir, os.ModePerm); err != nil { + return err + } + + args := []string{ + "create", + "cluster", + "--name", clusterName, + "--kubeconfig", kubeConfig, + "--wait", + "300s", + } + kubeVersion := os.Getenv("K8S_VERSION") + if kubeVersion != "" { + args = append(args, "--image", fmt.Sprintf("kindest/node:%s", kubeVersion)) + } + + _, err = sh.Exec( + map[string]string{}, + stdOut, + stdErr, + "kind", + args..., + ) + if err != nil { + return err + } + env["KUBECONFIG"] = kubeConfig + env["KIND_CLUSTER"] = clusterName + return nil +} + +// Teardown destroys the kubernetes cluster. +func (m *KindIntegrationTestStep) Teardown(env map[string]string) error { + stdOut := ioutil.Discard + stdErr := ioutil.Discard + if mg.Verbose() { + stdOut = os.Stdout + stdErr = os.Stderr + } + + name, created := env["KIND_CLUSTER"] + _, keepUp := os.LookupEnv("KIND_SKIP_DELETE") + if created && !keepUp { + _, err := sh.Exec( + env, + stdOut, + stdErr, + "kind", + "delete", + "cluster", + "--name", + name, + ) + if err != nil { + return err + } + delete(env, "KIND_CLUSTER") + } + return nil +} diff --git a/dev-tools/mage/kubernetes/kuberemote.go b/dev-tools/mage/kubernetes/kuberemote.go new file mode 100644 index 00000000000..4e98b536a10 --- /dev/null +++ b/dev-tools/mage/kubernetes/kuberemote.go @@ -0,0 +1,612 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package kubernetes + +import ( + "bufio" + "context" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/hex" + "encoding/pem" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "net/url" + "os/exec" + "strings" + "time" + + "github.com/pkg/errors" + "golang.org/x/crypto/ssh" + + apiv1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/tools/portforward" + watchtools "k8s.io/client-go/tools/watch" + "k8s.io/client-go/transport/spdy" +) + +const sshBitSize = 4096 + +var mode = int32(256) + +// KubeRemote rsyncs the passed directory to a pod and runs the command inside of that pod. +type KubeRemote struct { + cfg *rest.Config + cs *kubernetes.Clientset + namespace string + name string + workDir string + destDir string + syncDir string + + svcAccName string + secretName string + privateKey []byte + publicKey []byte +} + +// New creates a new kubernetes remote runner. +func New(kubeconfig string, namespace string, name string, workDir string, destDir string, syncDir string) (*KubeRemote, error) { + config, err := clientcmd.BuildConfigFromFlags("", kubeconfig) + if err != nil { + return nil, err + } + cs, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, err + } + name = strings.Replace(name, "_", "-", -1) + svcAccName := fmt.Sprintf("%s-sa", name) + secretName := fmt.Sprintf("%s-ssh-key", name) + privateKey, publicKey, err := generateSSHKeyPair() + if err != nil { + return nil, err + } + return &KubeRemote{config, cs, namespace, name, workDir, destDir, syncDir, svcAccName, secretName, privateKey, publicKey}, nil +} + +// Run runs the command remotely on the kubernetes cluster. +func (r *KubeRemote) Run(env map[string]string, stdout io.Writer, stderr io.Writer, args ...string) error { + if err := r.syncSSHKey(); err != nil { + return errors.Wrap(err, "failed to sync SSH secret") + } + defer r.deleteSSHKey() + if err := r.syncServiceAccount(); err != nil { + return err + } + defer r.deleteServiceAccount() + _, err := r.createPod(env, args...) + if err != nil { + return errors.Wrap(err, "failed to create execute pod") + } + defer r.deletePod() + + // wait for SSH to be up inside the init container. + _, err = r.waitForPod(5*time.Minute, podInitReady) + if err != nil { + return errors.Wrap(err, "execute pod init container never started") + } + time.Sleep(1 * time.Second) // SSH inside of container can take a moment + + // forward the SSH port so rsync can be ran. + randomPort, err := getFreePort() + if err != nil { + return errors.Wrap(err, "failed to find a free port") + } + stopChannel := make(chan struct{}, 1) + readyChannel := make(chan struct{}, 1) + f, err := r.portForward([]string{fmt.Sprintf("%d:%d", randomPort, 22)}, stopChannel, readyChannel, stderr, stderr) + if err != nil { + return err + } + go f.ForwardPorts() + <-readyChannel + + // perform the rsync + r.rsync(randomPort, stderr, stderr) + + // stop port forwarding + close(stopChannel) + + // wait for exec container to be running + _, err = r.waitForPod(5*time.Minute, containerRunning("exec")) + if err != nil { + return errors.Wrap(err, "execute pod container never started") + } + + // stream the logs of the container + err = r.streamLogs("exec", stdout) + if err != nil { + return errors.Wrap(err, "failed to stream the logs") + } + + // wait for exec container to be completely done + pod, err := r.waitForPod(30*time.Second, podDone) + if err != nil { + return errors.Wrap(err, "execute pod didn't terminate after 30 seconds of log stream") + } + + // return error on failure + if pod.Status.Phase == apiv1.PodFailed { + return fmt.Errorf("execute pod test failed") + } + return nil +} + +// deleteSSHKey deletes SSH key from the cluster. +func (r *KubeRemote) deleteSSHKey() { + _ = r.cs.CoreV1().Secrets(r.namespace).Delete(r.secretName, &metav1.DeleteOptions{}) +} + +// syncSSHKey syncs the SSH key to the cluster. +func (r *KubeRemote) syncSSHKey() error { + // delete before create + r.deleteSSHKey() + _, err := r.cs.CoreV1().Secrets(r.namespace).Create(createSecretManifest(r.secretName, r.publicKey)) + if err != nil { + return err + } + return nil +} + +// deleteServiceAccount syncs required service account. +func (r *KubeRemote) deleteServiceAccount() { + _ = r.cs.RbacV1().ClusterRoleBindings().Delete(r.name, &metav1.DeleteOptions{}) + _ = r.cs.RbacV1().ClusterRoles().Delete(r.name, &metav1.DeleteOptions{}) + _ = r.cs.CoreV1().ServiceAccounts(r.namespace).Delete(r.svcAccName, &metav1.DeleteOptions{}) +} + +// syncServiceAccount syncs required service account. +func (r *KubeRemote) syncServiceAccount() error { + // delete before create + r.deleteServiceAccount() + _, err := r.cs.CoreV1().ServiceAccounts(r.namespace).Create(createServiceAccountManifest(r.svcAccName)) + if err != nil { + return errors.Wrap(err, "failed to create service account") + } + _, err = r.cs.RbacV1().ClusterRoles().Create(createClusterRoleManifest(r.name)) + if err != nil { + return errors.Wrap(err, "failed to create cluster role") + } + _, err = r.cs.RbacV1().ClusterRoleBindings().Create(createClusterRoleBindingManifest(r.name, r.namespace, r.svcAccName)) + if err != nil { + return errors.Wrap(err, "failed to create cluster role binding") + } + return nil +} + +// createPod creates the pod. +func (r *KubeRemote) createPod(env map[string]string, cmd ...string) (*apiv1.Pod, error) { + r.deletePod() // ensure it doesn't already exist + return r.cs.CoreV1().Pods(r.namespace).Create(createPodManifest(r.name, "golang:1.13.9", env, cmd, r.workDir, r.destDir, r.secretName, r.svcAccName)) +} + +// deletePod deletes the pod. +func (r *KubeRemote) deletePod() { + _ = r.cs.CoreV1().Pods(r.namespace).Delete(r.name, &metav1.DeleteOptions{}) +} + +// waitForPod waits for the created pod to match the given condition. +func (r *KubeRemote) waitForPod(wait time.Duration, condition watchtools.ConditionFunc) (*apiv1.Pod, error) { + w, err := r.cs.CoreV1().Pods(r.namespace).Watch(metav1.SingleObject(metav1.ObjectMeta{Name: r.name})) + if err != nil { + return nil, err + } + + ctx, _ := watchtools.ContextWithOptionalTimeout(context.Background(), wait) + ev, err := watchtools.UntilWithoutRetry(ctx, w, func(ev watch.Event) (bool, error) { + return condition(ev) + }) + if ev != nil { + return ev.Object.(*apiv1.Pod), err + } + return nil, err +} + +// portFoward runs the port forwarding so SSH rsync can be ran into the pod. +func (r *KubeRemote) portForward(ports []string, stopChannel, readyChannel chan struct{}, stdout, stderr io.Writer) (*portforward.PortForwarder, error) { + roundTripper, upgrader, err := spdy.RoundTripperFor(r.cfg) + if err != nil { + return nil, err + } + + path := fmt.Sprintf("/api/v1/namespaces/%s/pods/%s/portforward", r.namespace, r.name) + hostIP := strings.TrimLeft(r.cfg.Host, "https://") + serverURL := url.URL{Scheme: "https", Path: path, Host: hostIP} + dialer := spdy.NewDialer(upgrader, &http.Client{Transport: roundTripper}, http.MethodPost, &serverURL) + return portforward.New(dialer, ports, stopChannel, readyChannel, stdout, stderr) +} + +// rsync performs the rsync of sync directory to destination directory inside of the pod. +func (r *KubeRemote) rsync(port uint16, stdout, stderr io.Writer) error { + privateKeyFile, err := createTempFile(r.privateKey) + if err != nil { + return err + } + + rsh := fmt.Sprintf("ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -p %d -i %s", port, privateKeyFile) + args := []string{ + "--rsh", rsh, + "-a", fmt.Sprintf("%s/", r.syncDir), + fmt.Sprintf("root@localhost:%s", r.destDir), + } + cmd := exec.Command("rsync", args...) + cmd.Stdout = stdout + cmd.Stderr = stderr + return cmd.Run() +} + +// streamLogs streams the logs from the execution pod until the pod is terminated. +func (r *KubeRemote) streamLogs(container string, stdout io.Writer) error { + req := r.cs.CoreV1().Pods(r.namespace).GetLogs(r.name, &apiv1.PodLogOptions{ + Container: container, + Follow: true, + }) + logs, err := req.Stream() + if err != nil { + return err + } + defer logs.Close() + + reader := bufio.NewReader(logs) + for { + bytes, err := reader.ReadBytes('\n') + if _, err := stdout.Write(bytes); err != nil { + return err + } + if err != nil { + if err != io.EOF { + return err + } + return nil + } + } +} + +// generateSSHKeyPair generates a new SSH key pair. +func generateSSHKeyPair() ([]byte, []byte, error) { + private, err := rsa.GenerateKey(rand.Reader, sshBitSize) + if err != nil { + return nil, nil, err + } + if err = private.Validate(); err != nil { + return nil, nil, err + } + public, err := ssh.NewPublicKey(&private.PublicKey) + if err != nil { + return nil, nil, err + } + return encodePrivateKeyToPEM(private), ssh.MarshalAuthorizedKey(public), nil +} + +// encodePrivateKeyToPEM encodes private key from RSA to PEM format. +func encodePrivateKeyToPEM(privateKey *rsa.PrivateKey) []byte { + privDER := x509.MarshalPKCS1PrivateKey(privateKey) + privBlock := pem.Block{ + Type: "RSA PRIVATE KEY", + Headers: nil, + Bytes: privDER, + } + return pem.EncodeToMemory(&privBlock) +} + +// getFreePort finds a free port. +func getFreePort() (uint16, error) { + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + if err != nil { + return 0, err + } + l, err := net.ListenTCP("tcp", addr) + if err != nil { + return 0, err + } + defer l.Close() + return uint16(l.Addr().(*net.TCPAddr).Port), nil +} + +// createSecretManifest creates the secret object to create in the cluster. +// +// This is the public key that the sshd uses as the authorized key. +func createSecretManifest(name string, publicKey []byte) *apiv1.Secret { + return &apiv1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + StringData: map[string]string{ + "authorized_keys": string(publicKey), + }, + } +} + +// createServiceAccountManifest creates the service account the pod will used. +func createServiceAccountManifest(name string) *apiv1.ServiceAccount { + return &apiv1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + } +} + +// createClusterRoleManifest creates the cluster role the pod will used. +// +// This gives the pod all permissions on everything! +func createClusterRoleManifest(name string) *rbacv1.ClusterRole { + return &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Rules: []rbacv1.PolicyRule{ + rbacv1.PolicyRule{ + Verbs: []string{"*"}, + APIGroups: []string{"*"}, + Resources: []string{"*"}, + }, + rbacv1.PolicyRule{ + Verbs: []string{"*"}, + NonResourceURLs: []string{"*"}, + }, + }, + } +} + +// createClusterRoleBindingManifest creates the cluster role binding the pod will used. +// +// This binds the service account to the cluster role. +func createClusterRoleBindingManifest(name string, namespace string, svcAccName string) *rbacv1.ClusterRoleBinding { + return &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Subjects: []rbacv1.Subject{ + rbacv1.Subject{ + Kind: "ServiceAccount", + Name: svcAccName, + Namespace: namespace, + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "ClusterRole", + Name: name, + }, + } +} + +// createPodManifest creates the pod inside of the cluster that will be used for remote execution. +// +// Creates a pod with an init container that runs sshd-rsync, once the first connection closes the init container +// exits then the exec container starts using the rsync'd directory as its work directory. +func createPodManifest(name string, image string, env map[string]string, cmd []string, workDir string, destDir string, secretName string, svcAccName string) *apiv1.Pod { + execEnv := []apiv1.EnvVar{ + apiv1.EnvVar{ + Name: "NODE_NAME", + ValueFrom: &apiv1.EnvVarSource{ + FieldRef: &apiv1.ObjectFieldSelector{ + FieldPath: "spec.nodeName", + }, + }, + }, + } + for k, v := range env { + execEnv = append(execEnv, apiv1.EnvVar{ + Name: k, + Value: v, + }) + } + return &apiv1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: apiv1.PodSpec{ + ServiceAccountName: svcAccName, + RestartPolicy: apiv1.RestartPolicyNever, + InitContainers: []apiv1.Container{ + { + Name: "sync-init", + Image: "ernoaapa/sshd-rsync", + Ports: []apiv1.ContainerPort{ + { + Name: "ssh", + Protocol: apiv1.ProtocolTCP, + ContainerPort: 22, + }, + }, + Env: []apiv1.EnvVar{ + { + Name: "ONE_TIME", + Value: "true", + }, + }, + VolumeMounts: []apiv1.VolumeMount{ + { + Name: "ssh-config", + MountPath: "/root/.ssh/authorized_keys", + SubPath: "authorized_keys", + }, + { + Name: "destdir", + MountPath: destDir, + }, + }, + }, + }, + Containers: []apiv1.Container{ + apiv1.Container{ + Name: "exec", + Image: image, + Command: cmd, + WorkingDir: workDir, + Env: execEnv, + VolumeMounts: []apiv1.VolumeMount{ + { + Name: "destdir", + MountPath: destDir, + }, + }, + }, + }, + Volumes: []apiv1.Volume{ + { + Name: "ssh-config", + VolumeSource: apiv1.VolumeSource{ + Secret: &apiv1.SecretVolumeSource{ + SecretName: secretName, + DefaultMode: &mode, + }, + }, + }, + { + Name: "destdir", + VolumeSource: apiv1.VolumeSource{ + EmptyDir: &apiv1.EmptyDirVolumeSource{}, + }, + }, + }, + }, + } +} + +func podInitReady(event watch.Event) (bool, error) { + switch event.Type { + case watch.Deleted: + return false, k8serrors.NewNotFound(schema.GroupResource{Resource: "pods"}, "") + } + switch t := event.Object.(type) { + case *apiv1.Pod: + switch t.Status.Phase { + case apiv1.PodFailed, apiv1.PodSucceeded: + return false, nil + case apiv1.PodRunning: + return false, nil + case apiv1.PodPending: + return isInitContainersReady(t), nil + } + } + return false, nil +} + +func isInitContainersReady(pod *apiv1.Pod) bool { + if isScheduled(pod) && isInitContainersRunning(pod) { + return true + } + return false +} + +func isScheduled(pod *apiv1.Pod) bool { + if &pod.Status != nil && len(pod.Status.Conditions) > 0 { + for _, condition := range pod.Status.Conditions { + if condition.Type == apiv1.PodScheduled && + condition.Status == apiv1.ConditionTrue { + return true + } + } + } + return false +} + +func isInitContainersRunning(pod *apiv1.Pod) bool { + if &pod.Status != nil { + if len(pod.Spec.InitContainers) != len(pod.Status.InitContainerStatuses) { + return false + } + for _, status := range pod.Status.InitContainerStatuses { + if status.State.Running == nil { + return false + } + } + return true + } + return false +} + +func containerRunning(containerName string) func(watch.Event) (bool, error) { + return func(event watch.Event) (bool, error) { + switch event.Type { + case watch.Deleted: + return false, k8serrors.NewNotFound(schema.GroupResource{Resource: "pods"}, "") + } + switch t := event.Object.(type) { + case *apiv1.Pod: + switch t.Status.Phase { + case apiv1.PodFailed, apiv1.PodSucceeded: + return false, nil + case apiv1.PodRunning: + return isContainerRunning(t, containerName) + } + } + return false, nil + } +} + +func isContainerRunning(pod *apiv1.Pod, containerName string) (bool, error) { + for _, status := range pod.Status.ContainerStatuses { + if status.Name == containerName { + if status.State.Waiting != nil { + return false, nil + } else if status.State.Running != nil { + return true, nil + } else if status.State.Terminated != nil { + return false, nil + } else { + return false, fmt.Errorf("Unknown container state") + } + } + } + return false, nil +} + +func podDone(event watch.Event) (bool, error) { + switch event.Type { + case watch.Deleted: + return false, k8serrors.NewNotFound(schema.GroupResource{Resource: "pods"}, "") + } + switch t := event.Object.(type) { + case *apiv1.Pod: + switch t.Status.Phase { + case apiv1.PodFailed, apiv1.PodSucceeded: + return true, nil + } + } + return false, nil +} + +func createTempFile(content []byte) (string, error) { + randBytes := make([]byte, 16) + rand.Read(randBytes) + tmpfile, err := ioutil.TempFile("", hex.EncodeToString(randBytes)) + if err != nil { + return "", err + } + defer tmpfile.Close() + if _, err := tmpfile.Write(content); err != nil { + return "", err + } + return tmpfile.Name(), nil +} diff --git a/dev-tools/mage/kubernetes/kubernetes.go b/dev-tools/mage/kubernetes/kubernetes.go new file mode 100644 index 00000000000..2f929da9e16 --- /dev/null +++ b/dev-tools/mage/kubernetes/kubernetes.go @@ -0,0 +1,165 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package kubernetes + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "path/filepath" + "strings" + + "github.com/magefile/mage/mg" + "github.com/pkg/errors" + + "github.com/elastic/beats/v7/dev-tools/mage" +) + +func init() { + mage.RegisterIntegrationTester(&KubernetesIntegrationTester{}) +} + +type KubernetesIntegrationTester struct { +} + +// Name returns kubernetes name. +func (d *KubernetesIntegrationTester) Name() string { + return "kubernetes" +} + +// Use determines if this tester should be used. +func (d *KubernetesIntegrationTester) Use(dir string) (bool, error) { + kubernetesFile := filepath.Join(dir, "kubernetes.yml") + if _, err := os.Stat(kubernetesFile); !os.IsNotExist(err) { + return true, nil + } + return false, nil +} + +// HasRequirements ensures that the required kubectl are installed. +func (d *KubernetesIntegrationTester) HasRequirements() error { + if err := mage.HaveKubectl(); err != nil { + return err + } + return nil +} + +// StepRequirements returns the steps required for this tester. +func (d *KubernetesIntegrationTester) StepRequirements() mage.IntegrationTestSteps { + return mage.IntegrationTestSteps{&mage.MageIntegrationTestStep{}, &KindIntegrationTestStep{}} +} + +// Test performs the tests with kubernetes. +func (d *KubernetesIntegrationTester) Test(dir string, mageTarget string, env map[string]string) error { + stdOut := ioutil.Discard + stdErr := ioutil.Discard + if mg.Verbose() { + stdOut = os.Stdout + stdErr = os.Stderr + } + + manifestPath := filepath.Join(dir, "kubernetes.yml") + if _, err := os.Stat(manifestPath); os.IsNotExist(err) { + // defensive, as `Use` should cause this runner not to be used if no file. + return fmt.Errorf("no kubernetes.yml") + } + + kubeConfig := env["KUBECONFIG"] + if kubeConfig == "" { + kubeConfig = env["KUBE_CONFIG"] + } + if kubeConfig == "" { + fmt.Println("Skip running tests inside of kubernetes no KUBECONFIG defined.") + return nil + } + + if mg.Verbose() { + fmt.Println(">> Applying module manifest to cluster...") + } + + // Determine the path to use inside the pod. + repo, err := mage.GetProjectRepoInfo() + if err != nil { + return err + } + magePath := filepath.Join("/go/src", repo.CanonicalRootImportPath, repo.SubDir, "build/mage-linux-amd64") + + // Apply the manifest from the dir. This is the requirements for the tests that will + // run inside the cluster. + if err := mage.KubectlApply(env, stdOut, stdErr, manifestPath); err != nil { + return errors.Wrapf(err, "failed to apply manifest %s", manifestPath) + } + defer func() { + if mg.Verbose() { + fmt.Println(">> Deleting module manifest from cluster...") + } + if err := mage.KubectlDelete(env, stdOut, stdErr, manifestPath); err != nil { + log.Printf("%s", errors.Wrapf(err, "failed to apply manifest %s", manifestPath)) + } + }() + + // Pass all environment variables inside the pod, except for KUBECONFIG as the test + // should use the environment set by kubernetes on the pod. + insideEnv := map[string]string{} + for envKey, envVal := range env { + if envKey != "KUBECONFIG" && envKey != "KUBE_CONFIG" { + insideEnv[envKey] = envVal + } + } + + destDir := filepath.Join("/go/src", repo.CanonicalRootImportPath) + workDir := filepath.Join(destDir, repo.SubDir) + remote, err := mage.NewKubeRemote(kubeConfig, "default", kubernetesPodName(), workDir, destDir, repo.RootDir) + if err != nil { + return err + } + // Uses `os.Stdout` directly as its output should always be shown. + err = remote.Run(insideEnv, os.Stdout, stdErr, magePath, mageTarget) + if err != nil { + return err + } + return nil +} + +// InsideTest performs the tests inside of environment. +func (d *KubernetesIntegrationTester) InsideTest(test func() error) error { + return test() +} + +// kubernetesPodName returns the pod name to use with kubernetes. +func kubernetesPodName() string { + commit, err := mage.CommitHash() + if err != nil { + panic(errors.Wrap(err, "failed to construct kind cluster name")) + } + + version, err := mage.BeatQualifiedVersion() + if err != nil { + panic(errors.Wrap(err, "failed to construct kind cluster name")) + } + version = strings.NewReplacer(".", "_").Replace(version) + + clusterName := "{{.BeatName}}_{{.Version}}_{{.ShortCommit}}-{{.StackEnvironment}}" + clusterName = mage.MustExpand(clusterName, map[string]interface{}{ + "StackEnvironment": mage.StackEnvironment, + "ShortCommit": commit[:10], + "Version": version, + }) + return clusterName +} diff --git a/dev-tools/mage/target/integtest/integtest.go b/dev-tools/mage/target/integtest/integtest.go index 324fa25e732..62d601cea6d 100644 --- a/dev-tools/mage/target/integtest/integtest.go +++ b/dev-tools/mage/target/integtest/integtest.go @@ -53,8 +53,6 @@ func WhitelistEnvVar(key ...string) { // IntegTest executes integration tests (it uses Docker to run the tests). func IntegTest() { - devtools.AddIntegTestUsage() - defer devtools.StopIntegTestEnv() mg.SerialDeps(GoIntegTest, PythonIntegTest) } @@ -65,9 +63,13 @@ func GoIntegTest(ctx context.Context) error { if !devtools.IsInIntegTestEnv() { mg.SerialDeps(goTestDeps...) } - return devtools.RunIntegTest("goIntegTest", func() error { + runner, err := devtools.NewDockerIntegrationRunner(whitelistedEnvVars...) + if err != nil { + return err + } + return runner.Test("goIntegTest", func() error { return devtools.GoTest(ctx, devtools.DefaultGoTestIntegrationArgs()) - }, whitelistedEnvVars...) + }) } // PythonIntegTest executes the python system tests in the integration @@ -79,8 +81,12 @@ func PythonIntegTest(ctx context.Context) error { if !devtools.IsInIntegTestEnv() { mg.SerialDeps(pythonTestDeps...) } - return devtools.RunIntegTest("pythonIntegTest", func() error { + runner, err := devtools.NewDockerIntegrationRunner(append(whitelistedEnvVars, devtools.ListMatchingEnvVars("NOSE_")...)...) + if err != nil { + return err + } + return runner.Test("pythonIntegTest", func() error { mg.Deps(devtools.BuildSystemTestBinary) return devtools.PythonNoseTest(devtools.DefaultPythonTestIntegrationArgs()) - }, append(whitelistedEnvVars, devtools.ListMatchingEnvVars("NOSE_")...)...) + }) } diff --git a/filebeat/magefile.go b/filebeat/magefile.go index c65d99a910d..6270c51f205 100644 --- a/filebeat/magefile.go +++ b/filebeat/magefile.go @@ -176,8 +176,6 @@ func ExportDashboard() error { // IntegTest executes integration tests (it uses Docker to run the tests). func IntegTest() { - devtools.AddIntegTestUsage() - defer devtools.StopIntegTestEnv() mg.SerialDeps(GoIntegTest, PythonIntegTest) } @@ -185,7 +183,11 @@ func IntegTest() { // Use TEST_COVERAGE=true to enable code coverage profiling. // Use RACE_DETECTOR=true to enable the race detector. func GoIntegTest(ctx context.Context) error { - return devtools.RunIntegTest("goIntegTest", func() error { + runner, err := devtools.NewDockerIntegrationRunner() + if err != nil { + return err + } + return runner.Test("goIntegTest", func() error { return devtools.GoTest(ctx, devtools.DefaultGoTestIntegrationArgs()) }) } @@ -198,10 +200,14 @@ func PythonIntegTest(ctx context.Context) error { if !devtools.IsInIntegTestEnv() { mg.Deps(Fields) } - return devtools.RunIntegTest("pythonIntegTest", func() error { + runner, err := devtools.NewDockerIntegrationRunner(append(devtools.ListMatchingEnvVars("TESTING_FILEBEAT_", "NOSE_"), "GENERATE")...) + if err != nil { + return err + } + return runner.Test("pythonIntegTest", func() error { mg.Deps(devtools.BuildSystemTestBinary) args := devtools.DefaultPythonTestIntegrationArgs() args.Env["MODULES_PATH"] = devtools.CWD("module") return devtools.PythonNoseTest(args) - }, append(devtools.ListMatchingEnvVars("TESTING_FILEBEAT_", "NOSE_"), "GENERATE")...) + }) } diff --git a/go.sum b/go.sum index 5c8338626cc..c018b305485 100644 --- a/go.sum +++ b/go.sum @@ -204,6 +204,7 @@ github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQ github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 h1:cenwrSVm+Z7QLSV/BsnenAOcDXdX4cMv4wP0B/5QbPg= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/dop251/goja_nodejs v0.0.0-20171011081505-adff31b136e6 h1:RrkoB0pT3gnjXhL/t10BSP1mcr/0Ldea2uMyuBr2SWk= github.com/dop251/goja_nodejs v0.0.0-20171011081505-adff31b136e6/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= @@ -254,12 +255,14 @@ github.com/elastic/gosigar v0.10.5 h1:GzPQ+78RaAb4J63unidA/JavQRKrB6s8IOzN6Ib59j github.com/elastic/gosigar v0.10.5/go.mod h1:cdorVVzy1fhmEqmtgqkoE3bYtCfSCkVyjTyCIo22xvs= github.com/elastic/sarama v0.0.0-20191122160421-355d120d0970 h1:rSo6gsz4zOanqtJ5fmZYQJvEJnA5YsVOB25casIwqUw= github.com/elastic/sarama v0.0.0-20191122160421-355d120d0970/go.mod h1:fGP8eQ6PugKEI0iUETYYtnP6d1pH/bdDMTel1X5ajsU= +github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e h1:p1yVGRW3nmb85p1Sh1ZJSDm4A4iKLS5QNbvUHMgGu/M= github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6vRfwrJatElLBEf0I= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.5.0 h1:vBh+kQp8lg9XPr56u1CPrWjFXtdphMoGWVHr9/1c+A0= github.com/fatih/color v1.5.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -922,7 +925,9 @@ k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.4-0.20190719014911-6a023d6d0e09 h1:w2hB+DoJsxpuO4hxMXfs44k1riAXX5kaV40564cWMUc= k8s.io/klog v0.3.4-0.20190719014911-6a023d6d0e09/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= +k8s.io/kube-openapi v0.0.0-20190709113604-33be087ad058 h1:di3XCwddOR9cWBNpfgXaskhh6cgJuwcK54rvtwUaC10= k8s.io/kube-openapi v0.0.0-20190709113604-33be087ad058/go.mod h1:nfDlWeOsu3pUf4yWGL+ERqohP4YsZcBJXWMK+gkzOA4= +k8s.io/kubernetes v1.13.0 h1:qTfB+u5M92k2fCCCVP2iuhgwwSOv1EkAkvQY1tQODD8= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20190221042446-c2654d5206da/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0= k8s.io/utils v0.0.0-20190712204705-3dccf664f023 h1:1H4Jyzb0z2X0GfBMTwRjnt5ejffRHrGftUgJcV/ZfDc= diff --git a/libbeat/scripts/Makefile b/libbeat/scripts/Makefile index 99509ecf7e1..f634e9fd40f 100755 --- a/libbeat/scripts/Makefile +++ b/libbeat/scripts/Makefile @@ -230,7 +230,7 @@ integration-tests-environment: prepare-tests build-image -e RACE_DETECTOR=$(RACE_DETECTOR) \ -e DOCKER_COMPOSE_PROJECT_NAME=${DOCKER_COMPOSE_PROJECT_NAME} \ -e TEST_ENVIRONMENT=${TEST_ENVIRONMENT} \ - -e BEATS_DOCKER_INTEGRATION_TEST_ENV=${BEATS_DOCKER_INTEGRATION_TEST_ENV} \ + -e BEATS_INSIDE_INTEGRATION_TEST_ENV=${BEATS_INSIDE_INTEGRATION_TEST_ENV} \ -e GOFLAGS=${INSTALL_FLAG} \ beat make integration-tests diff --git a/metricbeat/docs/fields.asciidoc b/metricbeat/docs/fields.asciidoc index 0b3ca4edff7..263124a748a 100644 --- a/metricbeat/docs/fields.asciidoc +++ b/metricbeat/docs/fields.asciidoc @@ -24621,6 +24621,16 @@ type: date -- +*`kubernetes.event.metadata.generate_name`*:: ++ +-- +Generate name of the event + + +type: keyword + +-- + *`kubernetes.event.metadata.name`*:: + -- diff --git a/metricbeat/magefile.go b/metricbeat/magefile.go index cf7f3154ad8..6e78e1559b9 100644 --- a/metricbeat/magefile.go +++ b/metricbeat/magefile.go @@ -31,6 +31,9 @@ import ( devtools "github.com/elastic/beats/v7/dev-tools/mage" metricbeat "github.com/elastic/beats/v7/metricbeat/scripts/mage" + // register kubernetes runner + _ "github.com/elastic/beats/v7/dev-tools/mage/kubernetes" + // mage:import "github.com/elastic/beats/v7/dev-tools/mage/target/build" // mage:import @@ -194,8 +197,12 @@ func PythonIntegTest(ctx context.Context) error { if !devtools.IsInIntegTestEnv() { mg.SerialDeps(Fields, Dashboards) } - return devtools.RunIntegTest("pythonIntegTest", func() error { + runner, err := devtools.NewDockerIntegrationRunner(devtools.ListMatchingEnvVars("NOSE_")...) + if err != nil { + return err + } + return runner.Test("pythonIntegTest", func() error { mg.Deps(devtools.BuildSystemTestBinary) return devtools.PythonNoseTest(devtools.DefaultPythonTestIntegrationArgs()) - }, devtools.ListMatchingEnvVars("NOSE_")...) + }) } diff --git a/metricbeat/module/kubernetes/_meta/Dockerfile b/metricbeat/module/kubernetes/_meta/Dockerfile deleted file mode 100644 index b3dac95e01f..00000000000 --- a/metricbeat/module/kubernetes/_meta/Dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -FROM exekias/localkube-image -RUN apt-get update && apt-get install -y curl && apt-get clean -HEALTHCHECK --interval=1s --retries=300 CMD curl -f localhost:10255/stats/summary | grep kube-addon-manager -CMD exec /localkube start \ - --apiserver-insecure-address=0.0.0.0 \ - --apiserver-insecure-port=8080 \ - --logtostderr=true \ - --containerized diff --git a/metricbeat/module/kubernetes/_meta/Dockerfile.kube-state b/metricbeat/module/kubernetes/_meta/Dockerfile.kube-state deleted file mode 100644 index b064dc3065e..00000000000 --- a/metricbeat/module/kubernetes/_meta/Dockerfile.kube-state +++ /dev/null @@ -1,8 +0,0 @@ -FROM gcr.io/google_containers/kube-state-metrics:v0.5.0 - -ADD kubeconfig / - -HEALTHCHECK --interval=1s --retries=90 CMD curl -f http://localhost:8080/metrics - -ENTRYPOINT ["/kube-state-metrics"] -CMD ["--port=8080", "--in-cluster=false", "--apiserver=http://172.17.0.1:8080", "--kubeconfig=/kubeconfig"] diff --git a/metricbeat/module/kubernetes/_meta/README.md b/metricbeat/module/kubernetes/_meta/README.md new file mode 100644 index 00000000000..903e9010018 --- /dev/null +++ b/metricbeat/module/kubernetes/_meta/README.md @@ -0,0 +1,23 @@ +# Running integration tests. + +Running the integration tests for the kubernetes module has the requirement of: + +* docker +* kind +* kubectl + +Once those tools are installed its as simple as: + +``` +MODULE="kubernetes" mage goIntegTest +``` + +The integration tester will use the default context from the kubectl configuration defined +in the `KUBECONFIG` environment variable. There is no requirement that the kubernetes even +be local to your development machine, it just needs to be accessible. + +If no `KUBECONFIG` is set and `kind` is installed then the runner will use `kind` to create +a local cluster inside of your local docker to perform the intergation tests inside. The +`kind` cluster will be created and destroy before and after the test. If you would like to +keep the `kind` cluster running after the test has finished you can set `KIND_SKIP_DELETE=1` +inside of your environment. diff --git a/metricbeat/module/kubernetes/_meta/kubeconfig b/metricbeat/module/kubernetes/_meta/kubeconfig deleted file mode 100644 index cad24101463..00000000000 --- a/metricbeat/module/kubernetes/_meta/kubeconfig +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: v1 -clusters: -- cluster: - server: http://172.17.0.1:8080 - name: kubernetes -contexts: -- context: - cluster: kubernetes - user: kubernetes - name: kubernetes -current-context: kubernetes -kind: Config -preferences: {} -users: -- name: kubernetes - user: - client-certificate: - client-key: diff --git a/metricbeat/module/kubernetes/apiserver/apiserver_integration_test.go b/metricbeat/module/kubernetes/apiserver/apiserver_integration_test.go new file mode 100644 index 00000000000..807b2c0760e --- /dev/null +++ b/metricbeat/module/kubernetes/apiserver/apiserver_integration_test.go @@ -0,0 +1,39 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build integration,linux + +package apiserver + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing" + "github.com/elastic/beats/v7/metricbeat/module/kubernetes/test" +) + +func TestFetchMetricset(t *testing.T) { + config := test.GetAPIServerConfig(t, "apiserver") + metricSet := mbtest.NewFetcher(t, config) + events, errs := metricSet.FetchEvents() + if len(errs) > 0 { + t.Fatalf("Expected 0 error, had %d. %v\n", len(errs), errs) + } + assert.NotEmpty(t, events) +} diff --git a/metricbeat/module/kubernetes/controllermanager/controllermanager_integration_test.go b/metricbeat/module/kubernetes/controllermanager/controllermanager_integration_test.go new file mode 100644 index 00000000000..f07bb7b1071 --- /dev/null +++ b/metricbeat/module/kubernetes/controllermanager/controllermanager_integration_test.go @@ -0,0 +1,39 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build integration,linux + +package controllermanager + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing" + "github.com/elastic/beats/v7/metricbeat/module/kubernetes/test" +) + +func TestFetchMetricset(t *testing.T) { + config := test.GetAPIServerConfig(t, "controllermanager") + metricSet := mbtest.NewFetcher(t, config) + events, errs := metricSet.FetchEvents() + if len(errs) > 0 { + t.Fatalf("Expected 0 error, had %d. %v\n", len(errs), errs) + } + assert.NotEmpty(t, events) +} diff --git a/metricbeat/module/kubernetes/docker-compose.yml b/metricbeat/module/kubernetes/docker-compose.yml deleted file mode 100644 index 083e326f325..00000000000 --- a/metricbeat/module/kubernetes/docker-compose.yml +++ /dev/null @@ -1,24 +0,0 @@ -version: '2.3' - -services: - #kubernetes: - # build: ./module/kubernetes/_meta - # network_mode: host - # pid: host - # privileged: true - # volumes: - # - /:/rootfs:ro - # - /sys:/sys - # - /var/lib/docker:/var/lib/docker - # - /var/run:/var/run - # ports: - # - 10255 - - #kubestate: - # build: - # context: ./_meta - # dockerfile: Dockerfile.kube-state - # depends_on: - # - kubernetes - # ports: - # - 18080 diff --git a/metricbeat/module/kubernetes/event/_meta/fields.yml b/metricbeat/module/kubernetes/event/_meta/fields.yml index 023e81d2d11..bf66a8aec45 100644 --- a/metricbeat/module/kubernetes/event/_meta/fields.yml +++ b/metricbeat/module/kubernetes/event/_meta/fields.yml @@ -58,6 +58,10 @@ type: date description: > Timestamp of creation of the given event + - name: generate_name + type: keyword + description: > + Generate name of the event - name: name type: keyword description: > diff --git a/metricbeat/module/kubernetes/fields.go b/metricbeat/module/kubernetes/fields.go index dad0ab4252e..02fae8082af 100644 --- a/metricbeat/module/kubernetes/fields.go +++ b/metricbeat/module/kubernetes/fields.go @@ -32,5 +32,5 @@ func init() { // AssetKubernetes returns asset data. // This is the base64 encoded gzipped contents of module/kubernetes. func AssetKubernetes() string { - return "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" + return "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" } diff --git a/metricbeat/module/kubernetes/kubernetes.yml b/metricbeat/module/kubernetes/kubernetes.yml new file mode 100644 index 00000000000..d87cb4f5f9b --- /dev/null +++ b/metricbeat/module/kubernetes/kubernetes.yml @@ -0,0 +1,135 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: kube-state-metrics +rules: +- apiGroups: ["*"] + resources: ["*"] + verbs: ["*"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: kube-state-metrics +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: kube-state-metrics +subjects: +- kind: ServiceAccount + name: kube-state-metrics + namespace: default +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: kube-state-metrics +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: kube-state-metrics + labels: + app: kube-state-metrics +spec: + replicas: 1 + selector: + matchLabels: + app: kube-state-metrics + template: + metadata: + labels: + app: kube-state-metrics + spec: + containers: + - name: kube-state-metrics + image: quay.io/coreos/kube-state-metrics:v1.8.0 + livenessProbe: + httpGet: + path: /healthz + port: 8080 + initialDelaySeconds: 5 + timeoutSeconds: 5 + ports: + - containerPort: 8080 + name: http-metrics + - containerPort: 8081 + name: telemetry + readinessProbe: + httpGet: + path: / + port: 8081 + initialDelaySeconds: 5 + timeoutSeconds: 5 + serviceAccountName: kube-state-metrics +--- +apiVersion: v1 +kind: Service +metadata: + name: kube-state-metrics +spec: + type: ClusterIP + selector: + app: kube-state-metrics + ports: + - port: 8080 + targetPort: 8080 +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: basic-sts + labels: + app: basic-sts +spec: + serviceName: basic-sts + replicas: 1 + selector: + matchLabels: + app: basic-sts + template: + metadata: + labels: + app: basic-sts + spec: + containers: + - name: sh + image: alpine:3 + command: ["sh", "-c", "sleep infinity"] + volumeMounts: + - name: mnt + mountPath: /mnt + volumeClaimTemplates: + - metadata: + name: mnt + spec: + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: 1Mi +--- +apiVersion: batch/v1beta1 +kind: CronJob +metadata: + name: basic-cronjob +spec: + schedule: "* * * * *" + jobTemplate: + spec: + template: + metadata: + name: basic-job + spec: + containers: + - name: hello + image: alpine:3 + command: ["sh", "-c", "echo Hello!"] + restartPolicy: Never +--- +apiVersion: v1 +kind: ResourceQuota +metadata: + name: object-counts +spec: + hard: + configmaps: "99" diff --git a/metricbeat/module/kubernetes/state_container/state_container_integration_test.go b/metricbeat/module/kubernetes/state_container/state_container_integration_test.go new file mode 100644 index 00000000000..6d7a7978250 --- /dev/null +++ b/metricbeat/module/kubernetes/state_container/state_container_integration_test.go @@ -0,0 +1,39 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build integration,linux + +package state_container + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing" + "github.com/elastic/beats/v7/metricbeat/module/kubernetes/test" +) + +func TestFetchMetricset(t *testing.T) { + config := test.GetKubeStateMetricsConfig(t, "state_container") + metricSet := mbtest.NewFetcher(t, config) + events, errs := metricSet.FetchEvents() + if len(errs) > 0 { + t.Fatalf("Expected 0 error, had %d. %v\n", len(errs), errs) + } + assert.NotEmpty(t, events) +} diff --git a/metricbeat/module/kubernetes/state_cronjob/state_cronjob_integration_test.go b/metricbeat/module/kubernetes/state_cronjob/state_cronjob_integration_test.go new file mode 100644 index 00000000000..3a0f4ff3659 --- /dev/null +++ b/metricbeat/module/kubernetes/state_cronjob/state_cronjob_integration_test.go @@ -0,0 +1,39 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build integration,linux + +package state_cronjob + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing" + "github.com/elastic/beats/v7/metricbeat/module/kubernetes/test" +) + +func TestFetchMetricset(t *testing.T) { + config := test.GetKubeStateMetricsConfig(t, "state_cronjob") + metricSet := mbtest.NewFetcher(t, config) + events, errs := metricSet.FetchEvents() + if len(errs) > 0 { + t.Fatalf("Expected 0 error, had %d. %v\n", len(errs), errs) + } + assert.NotEmpty(t, events) +} diff --git a/metricbeat/module/kubernetes/state_deployment/state_deployment_integration_test.go b/metricbeat/module/kubernetes/state_deployment/state_deployment_integration_test.go new file mode 100644 index 00000000000..17a38d56ecd --- /dev/null +++ b/metricbeat/module/kubernetes/state_deployment/state_deployment_integration_test.go @@ -0,0 +1,39 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build integration,linux + +package state_deployment + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing" + "github.com/elastic/beats/v7/metricbeat/module/kubernetes/test" +) + +func TestFetchMetricset(t *testing.T) { + config := test.GetKubeStateMetricsConfig(t, "state_deployment") + metricSet := mbtest.NewFetcher(t, config) + events, errs := metricSet.FetchEvents() + if len(errs) > 0 { + t.Fatalf("Expected 0 error, had %d. %v\n", len(errs), errs) + } + assert.NotEmpty(t, events) +} diff --git a/metricbeat/module/kubernetes/state_node/state_node_integration_test.go b/metricbeat/module/kubernetes/state_node/state_node_integration_test.go new file mode 100644 index 00000000000..13b9af0f929 --- /dev/null +++ b/metricbeat/module/kubernetes/state_node/state_node_integration_test.go @@ -0,0 +1,39 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build integration,linux + +package state_node + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing" + "github.com/elastic/beats/v7/metricbeat/module/kubernetes/test" +) + +func TestFetchMetricset(t *testing.T) { + config := test.GetKubeStateMetricsConfig(t, "state_node") + metricSet := mbtest.NewFetcher(t, config) + events, errs := metricSet.FetchEvents() + if len(errs) > 0 { + t.Fatalf("Expected 0 error, had %d. %v\n", len(errs), errs) + } + assert.NotEmpty(t, events) +} diff --git a/metricbeat/module/kubernetes/state_persistentvolume/state_persistentvolume_integration_test.go b/metricbeat/module/kubernetes/state_persistentvolume/state_persistentvolume_integration_test.go new file mode 100644 index 00000000000..7840febd337 --- /dev/null +++ b/metricbeat/module/kubernetes/state_persistentvolume/state_persistentvolume_integration_test.go @@ -0,0 +1,39 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build integration,linux + +package state_persistentvolume + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing" + "github.com/elastic/beats/v7/metricbeat/module/kubernetes/test" +) + +func TestFetchMetricset(t *testing.T) { + config := test.GetKubeStateMetricsConfig(t, "state_persistentvolume") + metricSet := mbtest.NewFetcher(t, config) + events, errs := metricSet.FetchEvents() + if len(errs) > 0 { + t.Fatalf("Expected 0 error, had %d. %v\n", len(errs), errs) + } + assert.NotEmpty(t, events) +} diff --git a/metricbeat/module/kubernetes/state_persistentvolumeclaim/state_persistentvolumeclaim_integration_test.go b/metricbeat/module/kubernetes/state_persistentvolumeclaim/state_persistentvolumeclaim_integration_test.go new file mode 100644 index 00000000000..24529123d5d --- /dev/null +++ b/metricbeat/module/kubernetes/state_persistentvolumeclaim/state_persistentvolumeclaim_integration_test.go @@ -0,0 +1,39 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build integration,linux + +package state_persistentvolumeclaim + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing" + "github.com/elastic/beats/v7/metricbeat/module/kubernetes/test" +) + +func TestFetchMetricset(t *testing.T) { + config := test.GetKubeStateMetricsConfig(t, "state_persistentvolumeclaim") + metricSet := mbtest.NewFetcher(t, config) + events, errs := metricSet.FetchEvents() + if len(errs) > 0 { + t.Fatalf("Expected 0 error, had %d. %v\n", len(errs), errs) + } + assert.NotEmpty(t, events) +} diff --git a/metricbeat/module/kubernetes/state_pod/state_pod_integration_test.go b/metricbeat/module/kubernetes/state_pod/state_pod_integration_test.go new file mode 100644 index 00000000000..c269092f067 --- /dev/null +++ b/metricbeat/module/kubernetes/state_pod/state_pod_integration_test.go @@ -0,0 +1,39 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build integration,linux + +package state_pod + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing" + "github.com/elastic/beats/v7/metricbeat/module/kubernetes/test" +) + +func TestFetchMetricset(t *testing.T) { + config := test.GetKubeStateMetricsConfig(t, "state_pod") + metricSet := mbtest.NewFetcher(t, config) + events, errs := metricSet.FetchEvents() + if len(errs) > 0 { + t.Fatalf("Expected 0 error, had %d. %v\n", len(errs), errs) + } + assert.NotEmpty(t, events) +} diff --git a/metricbeat/module/kubernetes/state_replicaset/state_replicaset_integration_test.go b/metricbeat/module/kubernetes/state_replicaset/state_replicaset_integration_test.go new file mode 100644 index 00000000000..c8b55192706 --- /dev/null +++ b/metricbeat/module/kubernetes/state_replicaset/state_replicaset_integration_test.go @@ -0,0 +1,39 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build integration,linux + +package state_replicaset + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing" + "github.com/elastic/beats/v7/metricbeat/module/kubernetes/test" +) + +func TestFetchMetricset(t *testing.T) { + config := test.GetKubeStateMetricsConfig(t, "state_replicaset") + metricSet := mbtest.NewFetcher(t, config) + events, errs := metricSet.FetchEvents() + if len(errs) > 0 { + t.Fatalf("Expected 0 error, had %d. %v\n", len(errs), errs) + } + assert.NotEmpty(t, events) +} diff --git a/metricbeat/module/kubernetes/state_resourcequota/state_resourcequota_integration_test.go b/metricbeat/module/kubernetes/state_resourcequota/state_resourcequota_integration_test.go new file mode 100644 index 00000000000..0d3bff3f706 --- /dev/null +++ b/metricbeat/module/kubernetes/state_resourcequota/state_resourcequota_integration_test.go @@ -0,0 +1,39 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build integration,linux + +package state_resourcequota + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing" + "github.com/elastic/beats/v7/metricbeat/module/kubernetes/test" +) + +func TestFetchMetricset(t *testing.T) { + config := test.GetKubeStateMetricsConfig(t, "state_resourcequota") + metricSet := mbtest.NewFetcher(t, config) + events, errs := metricSet.FetchEvents() + if len(errs) > 0 { + t.Fatalf("Expected 0 error, had %d. %v\n", len(errs), errs) + } + assert.NotEmpty(t, events) +} diff --git a/metricbeat/module/kubernetes/state_service/state_service_integration_test.go b/metricbeat/module/kubernetes/state_service/state_service_integration_test.go new file mode 100644 index 00000000000..895b4c6cd7d --- /dev/null +++ b/metricbeat/module/kubernetes/state_service/state_service_integration_test.go @@ -0,0 +1,39 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build integration,linux + +package state_service + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing" + "github.com/elastic/beats/v7/metricbeat/module/kubernetes/test" +) + +func TestFetchMetricset(t *testing.T) { + config := test.GetKubeStateMetricsConfig(t, "state_service") + metricSet := mbtest.NewFetcher(t, config) + events, errs := metricSet.FetchEvents() + if len(errs) > 0 { + t.Fatalf("Expected 0 error, had %d. %v\n", len(errs), errs) + } + assert.NotEmpty(t, events) +} diff --git a/metricbeat/module/kubernetes/state_statefulset/state_statefulset_integration_test.go b/metricbeat/module/kubernetes/state_statefulset/state_statefulset_integration_test.go new file mode 100644 index 00000000000..bce92bc6ce3 --- /dev/null +++ b/metricbeat/module/kubernetes/state_statefulset/state_statefulset_integration_test.go @@ -0,0 +1,39 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build integration,linux + +package state_statefulset + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing" + "github.com/elastic/beats/v7/metricbeat/module/kubernetes/test" +) + +func TestFetchMetricset(t *testing.T) { + config := test.GetKubeStateMetricsConfig(t, "state_statefulset") + metricSet := mbtest.NewFetcher(t, config) + events, errs := metricSet.FetchEvents() + if len(errs) > 0 { + t.Fatalf("Expected 0 error, had %d. %v\n", len(errs), errs) + } + assert.NotEmpty(t, events) +} diff --git a/metricbeat/module/kubernetes/state_storageclass/state_storageclass_integration_test.go b/metricbeat/module/kubernetes/state_storageclass/state_storageclass_integration_test.go new file mode 100644 index 00000000000..2db797d0ffb --- /dev/null +++ b/metricbeat/module/kubernetes/state_storageclass/state_storageclass_integration_test.go @@ -0,0 +1,39 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build integration,linux + +package state_storageclass + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing" + "github.com/elastic/beats/v7/metricbeat/module/kubernetes/test" +) + +func TestFetchMetricset(t *testing.T) { + config := test.GetKubeStateMetricsConfig(t, "state_storageclass") + metricSet := mbtest.NewFetcher(t, config) + events, errs := metricSet.FetchEvents() + if len(errs) > 0 { + t.Fatalf("Expected 0 error, had %d. %v\n", len(errs), errs) + } + assert.NotEmpty(t, events) +} diff --git a/metricbeat/module/kubernetes/test/integration.go b/metricbeat/module/kubernetes/test/integration.go new file mode 100644 index 00000000000..3e6dd7f11ea --- /dev/null +++ b/metricbeat/module/kubernetes/test/integration.go @@ -0,0 +1,50 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package test + +import ( + "testing" +) + +// GetAPIServerConfig function returns configuration for talking to Kubernetes API server. +func GetAPIServerConfig(t *testing.T, metricSetName string) map[string]interface{} { + t.Helper() + return map[string]interface{}{ + "module": "kubernetes", + "metricsets": []string{metricSetName}, + "host": "${NODE_NAME}", + "hosts": []string{"https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT}"}, + "bearer_token_file": "/var/run/secrets/kubernetes.io/serviceaccount/token", + "ssl": map[string]interface{}{ + "certificate_authorities": []string{ + "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt", + }, + }, + } +} + +// GetKubeStateMetricsConfig function returns configuration for talking to kube-state-metrics. +func GetKubeStateMetricsConfig(t *testing.T, metricSetName string) map[string]interface{} { + t.Helper() + return map[string]interface{}{ + "module": "kubernetes", + "metricsets": []string{metricSetName}, + "host": "${NODE_NAME}", + "hosts": []string{"kube-state-metrics:8080"}, + } +} diff --git a/metricbeat/module/kubernetes/test_kubernetes.py b/metricbeat/module/kubernetes/test_kubernetes.py deleted file mode 100644 index 911f29cfcfc..00000000000 --- a/metricbeat/module/kubernetes/test_kubernetes.py +++ /dev/null @@ -1,88 +0,0 @@ -import os -import sys -import unittest - -sys.path.append(os.path.join(os.path.dirname(__file__), '../../tests/system')) -import metricbeat - - -KUBERNETES_FIELDS = metricbeat.COMMON_FIELDS + ["kubernetes"] - - -class Test(metricbeat.BaseTest): - - # Tests are disabled as current docker-compose settings fail to start in many cases: - # COMPOSE_SERVICES = ['kubernetes'] # 'kubestate'] - - @unittest.skipUnless(False and metricbeat.INTEGRATION_TESTS, "integration test") - def test_kubelet_node(self): - """ Kubernetes kubelet node metricset tests """ - self._test_metricset('node', 1, self.get_kubelet_hosts()) - - @unittest.skipUnless(False and metricbeat.INTEGRATION_TESTS, "integration test") - def test_kubelet_system(self): - """ Kubernetes kubelet system metricset tests """ - self._test_metricset('system', 2, self.get_kubelet_hosts()) - - @unittest.skipUnless(False and metricbeat.INTEGRATION_TESTS, "integration test") - def test_kubelet_pod(self): - """ Kubernetes kubelet pod metricset tests """ - self._test_metricset('pod', 1, self.get_kubelet_hosts()) - - @unittest.skipUnless(False and metricbeat.INTEGRATION_TESTS, "integration test") - def test_kubelet_container(self): - """ Kubernetes kubelet container metricset tests """ - self._test_metricset('container', 1, self.get_kubelet_hosts()) - - @unittest.skipUnless(metricbeat.INTEGRATION_TESTS, "integration test") - @unittest.skip("flacky kube-state-metrics container healthcheck") - def test_state_node(self): - """ Kubernetes state node metricset tests """ - self._test_metricset('state_node', 1, self.get_kube_state_hosts()) - - @unittest.skipUnless(False and metricbeat.INTEGRATION_TESTS, "integration test") - @unittest.skip("flacky kube-state-metrics container healthcheck") - def test_state_pod(self): - """ Kubernetes state pod metricset tests """ - self._test_metricset('state_pod', 1, self.get_kube_state_hosts()) - - @unittest.skipUnless(False and metricbeat.INTEGRATION_TESTS, "integration test") - @unittest.skip("flacky kube-state-metrics container healthcheck") - def test_state_container(self): - """ Kubernetes state container metricset tests """ - self._test_metricset('state_container', 1, self.get_kube_state_hosts()) - - def _test_metricset(self, metricset, expected_events, hosts): - self.render_config_template(modules=[{ - "name": "kubernetes", - "enabled": "true", - "metricsets": [metricset], - "hosts": hosts, - "period": "5s", - "extras": { - "add_metadata": "false", - } - }]) - - proc = self.start_beat() - self.wait_until(lambda: self.output_lines() > 0) - proc.check_kill_and_wait() - - # Ensure no errors or warnings exist in the log. - self.assert_no_logged_warnings() - - output = self.read_output_json() - self.assertEqual(len(output), expected_events) - evt = output[0] - - self.assertCountEqual(self.de_dot(KUBERNETES_FIELDS), evt.keys(), evt) - - self.assert_fields_are_documented(evt) - - @classmethod - def get_kubelet_hosts(cls): - return [self.compose_host("kubernetes")] - - @classmethod - def get_kube_state_hosts(cls): - return [self.compose_host("kubestate")] diff --git a/vendor/github.com/docker/spdystream/CONTRIBUTING.md b/vendor/github.com/docker/spdystream/CONTRIBUTING.md new file mode 100644 index 00000000000..d4eddcc5396 --- /dev/null +++ b/vendor/github.com/docker/spdystream/CONTRIBUTING.md @@ -0,0 +1,13 @@ +# Contributing to SpdyStream + +Want to hack on spdystream? Awesome! Here are instructions to get you +started. + +SpdyStream is a part of the [Docker](https://docker.io) project, and follows +the same rules and principles. If you're already familiar with the way +Docker does things, you'll feel right at home. + +Otherwise, go read +[Docker's contributions guidelines](https://github.com/dotcloud/docker/blob/master/CONTRIBUTING.md). + +Happy hacking! diff --git a/vendor/github.com/docker/spdystream/LICENSE b/vendor/github.com/docker/spdystream/LICENSE new file mode 100644 index 00000000000..9e4bd4dbee9 --- /dev/null +++ b/vendor/github.com/docker/spdystream/LICENSE @@ -0,0 +1,191 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2014-2015 Docker, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/docker/spdystream/LICENSE.docs b/vendor/github.com/docker/spdystream/LICENSE.docs new file mode 100644 index 00000000000..e26cd4fc8ed --- /dev/null +++ b/vendor/github.com/docker/spdystream/LICENSE.docs @@ -0,0 +1,425 @@ +Attribution-ShareAlike 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More_considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution-ShareAlike 4.0 International Public +License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-ShareAlike 4.0 International Public License ("Public +License"). To the extent this Public License may be interpreted as a +contract, You are granted the Licensed Rights in consideration of Your +acceptance of these terms and conditions, and the Licensor grants You +such rights in consideration of benefits the Licensor receives from +making the Licensed Material available under these terms and +conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. BY-SA Compatible License means a license listed at + creativecommons.org/compatiblelicenses, approved by Creative + Commons as essentially the equivalent of this Public License. + + d. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + e. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + f. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + g. License Elements means the license attributes listed in the name + of a Creative Commons Public License. The License Elements of this + Public License are Attribution and ShareAlike. + + h. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + i. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + j. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + k. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + l. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + m. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. Additional offer from the Licensor -- Adapted Material. + Every recipient of Adapted Material from You + automatically receives an offer from the Licensor to + exercise the Licensed Rights in the Adapted Material + under the conditions of the Adapter's License You apply. + + c. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + b. ShareAlike. + + In addition to the conditions in Section 3(a), if You Share + Adapted Material You produce, the following conditions also apply. + + 1. The Adapter's License You apply must be a Creative Commons + license with the same License Elements, this version or + later, or a BY-SA Compatible License. + + 2. You must include the text of, or the URI or hyperlink to, the + Adapter's License You apply. You may satisfy this condition + in any reasonable manner based on the medium, means, and + context in which You Share Adapted Material. + + 3. You may not offer or impose any additional or different terms + or conditions on, or apply any Effective Technological + Measures to, Adapted Material that restrict exercise of the + rights granted under the Adapter's License You apply. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material, + + including for purposes of Section 3(b); and + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public licenses. +Notwithstanding, Creative Commons may elect to apply one of its public +licenses to material it publishes and in those instances will be +considered the "Licensor." Except for the limited purpose of indicating +that material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the public +licenses. + +Creative Commons may be contacted at creativecommons.org. diff --git a/vendor/github.com/docker/spdystream/MAINTAINERS b/vendor/github.com/docker/spdystream/MAINTAINERS new file mode 100644 index 00000000000..14e263325c7 --- /dev/null +++ b/vendor/github.com/docker/spdystream/MAINTAINERS @@ -0,0 +1,28 @@ +# Spdystream maintainers file +# +# This file describes who runs the docker/spdystream project and how. +# This is a living document - if you see something out of date or missing, speak up! +# +# It is structured to be consumable by both humans and programs. +# To extract its contents programmatically, use any TOML-compliant parser. +# +# This file is compiled into the MAINTAINERS file in docker/opensource. +# +[Org] + [Org."Core maintainers"] + people = [ + "dmcgowan", + ] + +[people] + +# A reference list of all people associated with the project. +# All other sections should refer to people by their canonical key +# in the people section. + + # ADD YOURSELF HERE IN ALPHABETICAL ORDER + + [people.dmcgowan] + Name = "Derek McGowan" + Email = "derek@docker.com" + GitHub = "dmcgowan" diff --git a/vendor/github.com/docker/spdystream/README.md b/vendor/github.com/docker/spdystream/README.md new file mode 100644 index 00000000000..11cccd0a09e --- /dev/null +++ b/vendor/github.com/docker/spdystream/README.md @@ -0,0 +1,77 @@ +# SpdyStream + +A multiplexed stream library using spdy + +## Usage + +Client example (connecting to mirroring server without auth) + +```go +package main + +import ( + "fmt" + "github.com/docker/spdystream" + "net" + "net/http" +) + +func main() { + conn, err := net.Dial("tcp", "localhost:8080") + if err != nil { + panic(err) + } + spdyConn, err := spdystream.NewConnection(conn, false) + if err != nil { + panic(err) + } + go spdyConn.Serve(spdystream.NoOpStreamHandler) + stream, err := spdyConn.CreateStream(http.Header{}, nil, false) + if err != nil { + panic(err) + } + + stream.Wait() + + fmt.Fprint(stream, "Writing to stream") + + buf := make([]byte, 25) + stream.Read(buf) + fmt.Println(string(buf)) + + stream.Close() +} +``` + +Server example (mirroring server without auth) + +```go +package main + +import ( + "github.com/docker/spdystream" + "net" +) + +func main() { + listener, err := net.Listen("tcp", "localhost:8080") + if err != nil { + panic(err) + } + for { + conn, err := listener.Accept() + if err != nil { + panic(err) + } + spdyConn, err := spdystream.NewConnection(conn, true) + if err != nil { + panic(err) + } + go spdyConn.Serve(spdystream.MirrorStreamHandler) + } +} +``` + +## Copyright and license + +Copyright © 2014-2015 Docker, Inc. All rights reserved, except as follows. Code is released under the Apache 2.0 license. The README.md file, and files in the "docs" folder are licensed under the Creative Commons Attribution 4.0 International License under the terms and conditions set forth in the file "LICENSE.docs". You may obtain a duplicate copy of the same license, titled CC-BY-SA-4.0, at http://creativecommons.org/licenses/by/4.0/. diff --git a/vendor/github.com/docker/spdystream/connection.go b/vendor/github.com/docker/spdystream/connection.go new file mode 100644 index 00000000000..6031a0db1ab --- /dev/null +++ b/vendor/github.com/docker/spdystream/connection.go @@ -0,0 +1,958 @@ +package spdystream + +import ( + "errors" + "fmt" + "io" + "net" + "net/http" + "sync" + "time" + + "github.com/docker/spdystream/spdy" +) + +var ( + ErrInvalidStreamId = errors.New("Invalid stream id") + ErrTimeout = errors.New("Timeout occured") + ErrReset = errors.New("Stream reset") + ErrWriteClosedStream = errors.New("Write on closed stream") +) + +const ( + FRAME_WORKERS = 5 + QUEUE_SIZE = 50 +) + +type StreamHandler func(stream *Stream) + +type AuthHandler func(header http.Header, slot uint8, parent uint32) bool + +type idleAwareFramer struct { + f *spdy.Framer + conn *Connection + writeLock sync.Mutex + resetChan chan struct{} + setTimeoutLock sync.Mutex + setTimeoutChan chan time.Duration + timeout time.Duration +} + +func newIdleAwareFramer(framer *spdy.Framer) *idleAwareFramer { + iaf := &idleAwareFramer{ + f: framer, + resetChan: make(chan struct{}, 2), + // setTimeoutChan needs to be buffered to avoid deadlocks when calling setIdleTimeout at about + // the same time the connection is being closed + setTimeoutChan: make(chan time.Duration, 1), + } + return iaf +} + +func (i *idleAwareFramer) monitor() { + var ( + timer *time.Timer + expired <-chan time.Time + resetChan = i.resetChan + setTimeoutChan = i.setTimeoutChan + ) +Loop: + for { + select { + case timeout := <-i.setTimeoutChan: + i.timeout = timeout + if timeout == 0 { + if timer != nil { + timer.Stop() + } + } else { + if timer == nil { + timer = time.NewTimer(timeout) + expired = timer.C + } else { + timer.Reset(timeout) + } + } + case <-resetChan: + if timer != nil && i.timeout > 0 { + timer.Reset(i.timeout) + } + case <-expired: + i.conn.streamCond.L.Lock() + streams := i.conn.streams + i.conn.streams = make(map[spdy.StreamId]*Stream) + i.conn.streamCond.Broadcast() + i.conn.streamCond.L.Unlock() + go func() { + for _, stream := range streams { + stream.resetStream() + } + i.conn.Close() + }() + case <-i.conn.closeChan: + if timer != nil { + timer.Stop() + } + + // Start a goroutine to drain resetChan. This is needed because we've seen + // some unit tests with large numbers of goroutines get into a situation + // where resetChan fills up, at least 1 call to Write() is still trying to + // send to resetChan, the connection gets closed, and this case statement + // attempts to grab the write lock that Write() already has, causing a + // deadlock. + // + // See https://github.com/docker/spdystream/issues/49 for more details. + go func() { + for _ = range resetChan { + } + }() + + go func() { + for _ = range setTimeoutChan { + } + }() + + i.writeLock.Lock() + close(resetChan) + i.resetChan = nil + i.writeLock.Unlock() + + i.setTimeoutLock.Lock() + close(i.setTimeoutChan) + i.setTimeoutChan = nil + i.setTimeoutLock.Unlock() + + break Loop + } + } + + // Drain resetChan + for _ = range resetChan { + } +} + +func (i *idleAwareFramer) WriteFrame(frame spdy.Frame) error { + i.writeLock.Lock() + defer i.writeLock.Unlock() + if i.resetChan == nil { + return io.EOF + } + err := i.f.WriteFrame(frame) + if err != nil { + return err + } + + i.resetChan <- struct{}{} + + return nil +} + +func (i *idleAwareFramer) ReadFrame() (spdy.Frame, error) { + frame, err := i.f.ReadFrame() + if err != nil { + return nil, err + } + + // resetChan should never be closed since it is only closed + // when the connection has closed its closeChan. This closure + // only occurs after all Reads have finished + // TODO (dmcgowan): refactor relationship into connection + i.resetChan <- struct{}{} + + return frame, nil +} + +func (i *idleAwareFramer) setIdleTimeout(timeout time.Duration) { + i.setTimeoutLock.Lock() + defer i.setTimeoutLock.Unlock() + + if i.setTimeoutChan == nil { + return + } + + i.setTimeoutChan <- timeout +} + +type Connection struct { + conn net.Conn + framer *idleAwareFramer + + closeChan chan bool + goneAway bool + lastStreamChan chan<- *Stream + goAwayTimeout time.Duration + closeTimeout time.Duration + + streamLock *sync.RWMutex + streamCond *sync.Cond + streams map[spdy.StreamId]*Stream + + nextIdLock sync.Mutex + receiveIdLock sync.Mutex + nextStreamId spdy.StreamId + receivedStreamId spdy.StreamId + + pingIdLock sync.Mutex + pingId uint32 + pingChans map[uint32]chan error + + shutdownLock sync.Mutex + shutdownChan chan error + hasShutdown bool + + // for testing https://github.com/docker/spdystream/pull/56 + dataFrameHandler func(*spdy.DataFrame) error +} + +// NewConnection creates a new spdy connection from an existing +// network connection. +func NewConnection(conn net.Conn, server bool) (*Connection, error) { + framer, framerErr := spdy.NewFramer(conn, conn) + if framerErr != nil { + return nil, framerErr + } + idleAwareFramer := newIdleAwareFramer(framer) + var sid spdy.StreamId + var rid spdy.StreamId + var pid uint32 + if server { + sid = 2 + rid = 1 + pid = 2 + } else { + sid = 1 + rid = 2 + pid = 1 + } + + streamLock := new(sync.RWMutex) + streamCond := sync.NewCond(streamLock) + + session := &Connection{ + conn: conn, + framer: idleAwareFramer, + + closeChan: make(chan bool), + goAwayTimeout: time.Duration(0), + closeTimeout: time.Duration(0), + + streamLock: streamLock, + streamCond: streamCond, + streams: make(map[spdy.StreamId]*Stream), + nextStreamId: sid, + receivedStreamId: rid, + + pingId: pid, + pingChans: make(map[uint32]chan error), + + shutdownChan: make(chan error), + } + session.dataFrameHandler = session.handleDataFrame + idleAwareFramer.conn = session + go idleAwareFramer.monitor() + + return session, nil +} + +// Ping sends a ping frame across the connection and +// returns the response time +func (s *Connection) Ping() (time.Duration, error) { + pid := s.pingId + s.pingIdLock.Lock() + if s.pingId > 0x7ffffffe { + s.pingId = s.pingId - 0x7ffffffe + } else { + s.pingId = s.pingId + 2 + } + s.pingIdLock.Unlock() + pingChan := make(chan error) + s.pingChans[pid] = pingChan + defer delete(s.pingChans, pid) + + frame := &spdy.PingFrame{Id: pid} + startTime := time.Now() + writeErr := s.framer.WriteFrame(frame) + if writeErr != nil { + return time.Duration(0), writeErr + } + select { + case <-s.closeChan: + return time.Duration(0), errors.New("connection closed") + case err, ok := <-pingChan: + if ok && err != nil { + return time.Duration(0), err + } + break + } + return time.Now().Sub(startTime), nil +} + +// Serve handles frames sent from the server, including reply frames +// which are needed to fully initiate connections. Both clients and servers +// should call Serve in a separate goroutine before creating streams. +func (s *Connection) Serve(newHandler StreamHandler) { + // use a WaitGroup to wait for all frames to be drained after receiving + // go-away. + var wg sync.WaitGroup + + // Parition queues to ensure stream frames are handled + // by the same worker, ensuring order is maintained + frameQueues := make([]*PriorityFrameQueue, FRAME_WORKERS) + for i := 0; i < FRAME_WORKERS; i++ { + frameQueues[i] = NewPriorityFrameQueue(QUEUE_SIZE) + + // Ensure frame queue is drained when connection is closed + go func(frameQueue *PriorityFrameQueue) { + <-s.closeChan + frameQueue.Drain() + }(frameQueues[i]) + + wg.Add(1) + go func(frameQueue *PriorityFrameQueue) { + // let the WaitGroup know this worker is done + defer wg.Done() + + s.frameHandler(frameQueue, newHandler) + }(frameQueues[i]) + } + + var ( + partitionRoundRobin int + goAwayFrame *spdy.GoAwayFrame + ) +Loop: + for { + readFrame, err := s.framer.ReadFrame() + if err != nil { + if err != io.EOF { + fmt.Errorf("frame read error: %s", err) + } else { + debugMessage("(%p) EOF received", s) + } + break + } + var priority uint8 + var partition int + switch frame := readFrame.(type) { + case *spdy.SynStreamFrame: + if s.checkStreamFrame(frame) { + priority = frame.Priority + partition = int(frame.StreamId % FRAME_WORKERS) + debugMessage("(%p) Add stream frame: %d ", s, frame.StreamId) + s.addStreamFrame(frame) + } else { + debugMessage("(%p) Rejected stream frame: %d ", s, frame.StreamId) + continue + } + case *spdy.SynReplyFrame: + priority = s.getStreamPriority(frame.StreamId) + partition = int(frame.StreamId % FRAME_WORKERS) + case *spdy.DataFrame: + priority = s.getStreamPriority(frame.StreamId) + partition = int(frame.StreamId % FRAME_WORKERS) + case *spdy.RstStreamFrame: + priority = s.getStreamPriority(frame.StreamId) + partition = int(frame.StreamId % FRAME_WORKERS) + case *spdy.HeadersFrame: + priority = s.getStreamPriority(frame.StreamId) + partition = int(frame.StreamId % FRAME_WORKERS) + case *spdy.PingFrame: + priority = 0 + partition = partitionRoundRobin + partitionRoundRobin = (partitionRoundRobin + 1) % FRAME_WORKERS + case *spdy.GoAwayFrame: + // hold on to the go away frame and exit the loop + goAwayFrame = frame + break Loop + default: + priority = 7 + partition = partitionRoundRobin + partitionRoundRobin = (partitionRoundRobin + 1) % FRAME_WORKERS + } + frameQueues[partition].Push(readFrame, priority) + } + close(s.closeChan) + + // wait for all frame handler workers to indicate they've drained their queues + // before handling the go away frame + wg.Wait() + + if goAwayFrame != nil { + s.handleGoAwayFrame(goAwayFrame) + } + + // now it's safe to close remote channels and empty s.streams + s.streamCond.L.Lock() + // notify streams that they're now closed, which will + // unblock any stream Read() calls + for _, stream := range s.streams { + stream.closeRemoteChannels() + } + s.streams = make(map[spdy.StreamId]*Stream) + s.streamCond.Broadcast() + s.streamCond.L.Unlock() +} + +func (s *Connection) frameHandler(frameQueue *PriorityFrameQueue, newHandler StreamHandler) { + for { + popFrame := frameQueue.Pop() + if popFrame == nil { + return + } + + var frameErr error + switch frame := popFrame.(type) { + case *spdy.SynStreamFrame: + frameErr = s.handleStreamFrame(frame, newHandler) + case *spdy.SynReplyFrame: + frameErr = s.handleReplyFrame(frame) + case *spdy.DataFrame: + frameErr = s.dataFrameHandler(frame) + case *spdy.RstStreamFrame: + frameErr = s.handleResetFrame(frame) + case *spdy.HeadersFrame: + frameErr = s.handleHeaderFrame(frame) + case *spdy.PingFrame: + frameErr = s.handlePingFrame(frame) + case *spdy.GoAwayFrame: + frameErr = s.handleGoAwayFrame(frame) + default: + frameErr = fmt.Errorf("unhandled frame type: %T", frame) + } + + if frameErr != nil { + fmt.Errorf("frame handling error: %s", frameErr) + } + } +} + +func (s *Connection) getStreamPriority(streamId spdy.StreamId) uint8 { + stream, streamOk := s.getStream(streamId) + if !streamOk { + return 7 + } + return stream.priority +} + +func (s *Connection) addStreamFrame(frame *spdy.SynStreamFrame) { + var parent *Stream + if frame.AssociatedToStreamId != spdy.StreamId(0) { + parent, _ = s.getStream(frame.AssociatedToStreamId) + } + + stream := &Stream{ + streamId: frame.StreamId, + parent: parent, + conn: s, + startChan: make(chan error), + headers: frame.Headers, + finished: (frame.CFHeader.Flags & spdy.ControlFlagUnidirectional) != 0x00, + replyCond: sync.NewCond(new(sync.Mutex)), + dataChan: make(chan []byte), + headerChan: make(chan http.Header), + closeChan: make(chan bool), + } + if frame.CFHeader.Flags&spdy.ControlFlagFin != 0x00 { + stream.closeRemoteChannels() + } + + s.addStream(stream) +} + +// checkStreamFrame checks to see if a stream frame is allowed. +// If the stream is invalid, then a reset frame with protocol error +// will be returned. +func (s *Connection) checkStreamFrame(frame *spdy.SynStreamFrame) bool { + s.receiveIdLock.Lock() + defer s.receiveIdLock.Unlock() + if s.goneAway { + return false + } + validationErr := s.validateStreamId(frame.StreamId) + if validationErr != nil { + go func() { + resetErr := s.sendResetFrame(spdy.ProtocolError, frame.StreamId) + if resetErr != nil { + fmt.Errorf("reset error: %s", resetErr) + } + }() + return false + } + return true +} + +func (s *Connection) handleStreamFrame(frame *spdy.SynStreamFrame, newHandler StreamHandler) error { + stream, ok := s.getStream(frame.StreamId) + if !ok { + return fmt.Errorf("Missing stream: %d", frame.StreamId) + } + + newHandler(stream) + + return nil +} + +func (s *Connection) handleReplyFrame(frame *spdy.SynReplyFrame) error { + debugMessage("(%p) Reply frame received for %d", s, frame.StreamId) + stream, streamOk := s.getStream(frame.StreamId) + if !streamOk { + debugMessage("Reply frame gone away for %d", frame.StreamId) + // Stream has already gone away + return nil + } + if stream.replied { + // Stream has already received reply + return nil + } + stream.replied = true + + // TODO Check for error + if (frame.CFHeader.Flags & spdy.ControlFlagFin) != 0x00 { + s.remoteStreamFinish(stream) + } + + close(stream.startChan) + + return nil +} + +func (s *Connection) handleResetFrame(frame *spdy.RstStreamFrame) error { + stream, streamOk := s.getStream(frame.StreamId) + if !streamOk { + // Stream has already been removed + return nil + } + s.removeStream(stream) + stream.closeRemoteChannels() + + if !stream.replied { + stream.replied = true + stream.startChan <- ErrReset + close(stream.startChan) + } + + stream.finishLock.Lock() + stream.finished = true + stream.finishLock.Unlock() + + return nil +} + +func (s *Connection) handleHeaderFrame(frame *spdy.HeadersFrame) error { + stream, streamOk := s.getStream(frame.StreamId) + if !streamOk { + // Stream has already gone away + return nil + } + if !stream.replied { + // No reply received...Protocol error? + return nil + } + + // TODO limit headers while not blocking (use buffered chan or goroutine?) + select { + case <-stream.closeChan: + return nil + case stream.headerChan <- frame.Headers: + } + + if (frame.CFHeader.Flags & spdy.ControlFlagFin) != 0x00 { + s.remoteStreamFinish(stream) + } + + return nil +} + +func (s *Connection) handleDataFrame(frame *spdy.DataFrame) error { + debugMessage("(%p) Data frame received for %d", s, frame.StreamId) + stream, streamOk := s.getStream(frame.StreamId) + if !streamOk { + debugMessage("(%p) Data frame gone away for %d", s, frame.StreamId) + // Stream has already gone away + return nil + } + if !stream.replied { + debugMessage("(%p) Data frame not replied %d", s, frame.StreamId) + // No reply received...Protocol error? + return nil + } + + debugMessage("(%p) (%d) Data frame handling", stream, stream.streamId) + if len(frame.Data) > 0 { + stream.dataLock.RLock() + select { + case <-stream.closeChan: + debugMessage("(%p) (%d) Data frame not sent (stream shut down)", stream, stream.streamId) + case stream.dataChan <- frame.Data: + debugMessage("(%p) (%d) Data frame sent", stream, stream.streamId) + } + stream.dataLock.RUnlock() + } + if (frame.Flags & spdy.DataFlagFin) != 0x00 { + s.remoteStreamFinish(stream) + } + return nil +} + +func (s *Connection) handlePingFrame(frame *spdy.PingFrame) error { + if s.pingId&0x01 != frame.Id&0x01 { + return s.framer.WriteFrame(frame) + } + pingChan, pingOk := s.pingChans[frame.Id] + if pingOk { + close(pingChan) + } + return nil +} + +func (s *Connection) handleGoAwayFrame(frame *spdy.GoAwayFrame) error { + debugMessage("(%p) Go away received", s) + s.receiveIdLock.Lock() + if s.goneAway { + s.receiveIdLock.Unlock() + return nil + } + s.goneAway = true + s.receiveIdLock.Unlock() + + if s.lastStreamChan != nil { + stream, _ := s.getStream(frame.LastGoodStreamId) + go func() { + s.lastStreamChan <- stream + }() + } + + // Do not block frame handler waiting for closure + go s.shutdown(s.goAwayTimeout) + + return nil +} + +func (s *Connection) remoteStreamFinish(stream *Stream) { + stream.closeRemoteChannels() + + stream.finishLock.Lock() + if stream.finished { + // Stream is fully closed, cleanup + s.removeStream(stream) + } + stream.finishLock.Unlock() +} + +// CreateStream creates a new spdy stream using the parameters for +// creating the stream frame. The stream frame will be sent upon +// calling this function, however this function does not wait for +// the reply frame. If waiting for the reply is desired, use +// the stream Wait or WaitTimeout function on the stream returned +// by this function. +func (s *Connection) CreateStream(headers http.Header, parent *Stream, fin bool) (*Stream, error) { + // MUST synchronize stream creation (all the way to writing the frame) + // as stream IDs **MUST** increase monotonically. + s.nextIdLock.Lock() + defer s.nextIdLock.Unlock() + + streamId := s.getNextStreamId() + if streamId == 0 { + return nil, fmt.Errorf("Unable to get new stream id") + } + + stream := &Stream{ + streamId: streamId, + parent: parent, + conn: s, + startChan: make(chan error), + headers: headers, + dataChan: make(chan []byte), + headerChan: make(chan http.Header), + closeChan: make(chan bool), + } + + debugMessage("(%p) (%p) Create stream", s, stream) + + s.addStream(stream) + + return stream, s.sendStream(stream, fin) +} + +func (s *Connection) shutdown(closeTimeout time.Duration) { + // TODO Ensure this isn't called multiple times + s.shutdownLock.Lock() + if s.hasShutdown { + s.shutdownLock.Unlock() + return + } + s.hasShutdown = true + s.shutdownLock.Unlock() + + var timeout <-chan time.Time + if closeTimeout > time.Duration(0) { + timeout = time.After(closeTimeout) + } + streamsClosed := make(chan bool) + + go func() { + s.streamCond.L.Lock() + for len(s.streams) > 0 { + debugMessage("Streams opened: %d, %#v", len(s.streams), s.streams) + s.streamCond.Wait() + } + s.streamCond.L.Unlock() + close(streamsClosed) + }() + + var err error + select { + case <-streamsClosed: + // No active streams, close should be safe + err = s.conn.Close() + case <-timeout: + // Force ungraceful close + err = s.conn.Close() + // Wait for cleanup to clear active streams + <-streamsClosed + } + + if err != nil { + duration := 10 * time.Minute + time.AfterFunc(duration, func() { + select { + case err, ok := <-s.shutdownChan: + if ok { + fmt.Errorf("Unhandled close error after %s: %s", duration, err) + } + default: + } + }) + s.shutdownChan <- err + } + close(s.shutdownChan) + + return +} + +// Closes spdy connection by sending GoAway frame and initiating shutdown +func (s *Connection) Close() error { + s.receiveIdLock.Lock() + if s.goneAway { + s.receiveIdLock.Unlock() + return nil + } + s.goneAway = true + s.receiveIdLock.Unlock() + + var lastStreamId spdy.StreamId + if s.receivedStreamId > 2 { + lastStreamId = s.receivedStreamId - 2 + } + + goAwayFrame := &spdy.GoAwayFrame{ + LastGoodStreamId: lastStreamId, + Status: spdy.GoAwayOK, + } + + err := s.framer.WriteFrame(goAwayFrame) + if err != nil { + return err + } + + go s.shutdown(s.closeTimeout) + + return nil +} + +// CloseWait closes the connection and waits for shutdown +// to finish. Note the underlying network Connection +// is not closed until the end of shutdown. +func (s *Connection) CloseWait() error { + closeErr := s.Close() + if closeErr != nil { + return closeErr + } + shutdownErr, ok := <-s.shutdownChan + if ok { + return shutdownErr + } + return nil +} + +// Wait waits for the connection to finish shutdown or for +// the wait timeout duration to expire. This needs to be +// called either after Close has been called or the GOAWAYFRAME +// has been received. If the wait timeout is 0, this function +// will block until shutdown finishes. If wait is never called +// and a shutdown error occurs, that error will be logged as an +// unhandled error. +func (s *Connection) Wait(waitTimeout time.Duration) error { + var timeout <-chan time.Time + if waitTimeout > time.Duration(0) { + timeout = time.After(waitTimeout) + } + + select { + case err, ok := <-s.shutdownChan: + if ok { + return err + } + case <-timeout: + return ErrTimeout + } + return nil +} + +// NotifyClose registers a channel to be called when the remote +// peer inidicates connection closure. The last stream to be +// received by the remote will be sent on the channel. The notify +// timeout will determine the duration between go away received +// and the connection being closed. +func (s *Connection) NotifyClose(c chan<- *Stream, timeout time.Duration) { + s.goAwayTimeout = timeout + s.lastStreamChan = c +} + +// SetCloseTimeout sets the amount of time close will wait for +// streams to finish before terminating the underlying network +// connection. Setting the timeout to 0 will cause close to +// wait forever, which is the default. +func (s *Connection) SetCloseTimeout(timeout time.Duration) { + s.closeTimeout = timeout +} + +// SetIdleTimeout sets the amount of time the connection may sit idle before +// it is forcefully terminated. +func (s *Connection) SetIdleTimeout(timeout time.Duration) { + s.framer.setIdleTimeout(timeout) +} + +func (s *Connection) sendHeaders(headers http.Header, stream *Stream, fin bool) error { + var flags spdy.ControlFlags + if fin { + flags = spdy.ControlFlagFin + } + + headerFrame := &spdy.HeadersFrame{ + StreamId: stream.streamId, + Headers: headers, + CFHeader: spdy.ControlFrameHeader{Flags: flags}, + } + + return s.framer.WriteFrame(headerFrame) +} + +func (s *Connection) sendReply(headers http.Header, stream *Stream, fin bool) error { + var flags spdy.ControlFlags + if fin { + flags = spdy.ControlFlagFin + } + + replyFrame := &spdy.SynReplyFrame{ + StreamId: stream.streamId, + Headers: headers, + CFHeader: spdy.ControlFrameHeader{Flags: flags}, + } + + return s.framer.WriteFrame(replyFrame) +} + +func (s *Connection) sendResetFrame(status spdy.RstStreamStatus, streamId spdy.StreamId) error { + resetFrame := &spdy.RstStreamFrame{ + StreamId: streamId, + Status: status, + } + + return s.framer.WriteFrame(resetFrame) +} + +func (s *Connection) sendReset(status spdy.RstStreamStatus, stream *Stream) error { + return s.sendResetFrame(status, stream.streamId) +} + +func (s *Connection) sendStream(stream *Stream, fin bool) error { + var flags spdy.ControlFlags + if fin { + flags = spdy.ControlFlagFin + stream.finished = true + } + + var parentId spdy.StreamId + if stream.parent != nil { + parentId = stream.parent.streamId + } + + streamFrame := &spdy.SynStreamFrame{ + StreamId: spdy.StreamId(stream.streamId), + AssociatedToStreamId: spdy.StreamId(parentId), + Headers: stream.headers, + CFHeader: spdy.ControlFrameHeader{Flags: flags}, + } + + return s.framer.WriteFrame(streamFrame) +} + +// getNextStreamId returns the next sequential id +// every call should produce a unique value or an error +func (s *Connection) getNextStreamId() spdy.StreamId { + sid := s.nextStreamId + if sid > 0x7fffffff { + return 0 + } + s.nextStreamId = s.nextStreamId + 2 + return sid +} + +// PeekNextStreamId returns the next sequential id and keeps the next id untouched +func (s *Connection) PeekNextStreamId() spdy.StreamId { + sid := s.nextStreamId + return sid +} + +func (s *Connection) validateStreamId(rid spdy.StreamId) error { + if rid > 0x7fffffff || rid < s.receivedStreamId { + return ErrInvalidStreamId + } + s.receivedStreamId = rid + 2 + return nil +} + +func (s *Connection) addStream(stream *Stream) { + s.streamCond.L.Lock() + s.streams[stream.streamId] = stream + debugMessage("(%p) (%p) Stream added, broadcasting: %d", s, stream, stream.streamId) + s.streamCond.Broadcast() + s.streamCond.L.Unlock() +} + +func (s *Connection) removeStream(stream *Stream) { + s.streamCond.L.Lock() + delete(s.streams, stream.streamId) + debugMessage("(%p) (%p) Stream removed, broadcasting: %d", s, stream, stream.streamId) + s.streamCond.Broadcast() + s.streamCond.L.Unlock() +} + +func (s *Connection) getStream(streamId spdy.StreamId) (stream *Stream, ok bool) { + s.streamLock.RLock() + stream, ok = s.streams[streamId] + s.streamLock.RUnlock() + return +} + +// FindStream looks up the given stream id and either waits for the +// stream to be found or returns nil if the stream id is no longer +// valid. +func (s *Connection) FindStream(streamId uint32) *Stream { + var stream *Stream + var ok bool + s.streamCond.L.Lock() + stream, ok = s.streams[spdy.StreamId(streamId)] + debugMessage("(%p) Found stream %d? %t", s, spdy.StreamId(streamId), ok) + for !ok && streamId >= uint32(s.receivedStreamId) { + s.streamCond.Wait() + stream, ok = s.streams[spdy.StreamId(streamId)] + } + s.streamCond.L.Unlock() + return stream +} + +func (s *Connection) CloseChan() <-chan bool { + return s.closeChan +} diff --git a/vendor/github.com/docker/spdystream/handlers.go b/vendor/github.com/docker/spdystream/handlers.go new file mode 100644 index 00000000000..b59fa5fdcd0 --- /dev/null +++ b/vendor/github.com/docker/spdystream/handlers.go @@ -0,0 +1,38 @@ +package spdystream + +import ( + "io" + "net/http" +) + +// MirrorStreamHandler mirrors all streams. +func MirrorStreamHandler(stream *Stream) { + replyErr := stream.SendReply(http.Header{}, false) + if replyErr != nil { + return + } + + go func() { + io.Copy(stream, stream) + stream.Close() + }() + go func() { + for { + header, receiveErr := stream.ReceiveHeader() + if receiveErr != nil { + return + } + sendErr := stream.SendHeader(header, false) + if sendErr != nil { + return + } + } + }() +} + +// NoopStreamHandler does nothing when stream connects, most +// likely used with RejectAuthHandler which will not allow any +// streams to make it to the stream handler. +func NoOpStreamHandler(stream *Stream) { + stream.SendReply(http.Header{}, false) +} diff --git a/vendor/github.com/docker/spdystream/priority.go b/vendor/github.com/docker/spdystream/priority.go new file mode 100644 index 00000000000..fc8582b5c6f --- /dev/null +++ b/vendor/github.com/docker/spdystream/priority.go @@ -0,0 +1,98 @@ +package spdystream + +import ( + "container/heap" + "sync" + + "github.com/docker/spdystream/spdy" +) + +type prioritizedFrame struct { + frame spdy.Frame + priority uint8 + insertId uint64 +} + +type frameQueue []*prioritizedFrame + +func (fq frameQueue) Len() int { + return len(fq) +} + +func (fq frameQueue) Less(i, j int) bool { + if fq[i].priority == fq[j].priority { + return fq[i].insertId < fq[j].insertId + } + return fq[i].priority < fq[j].priority +} + +func (fq frameQueue) Swap(i, j int) { + fq[i], fq[j] = fq[j], fq[i] +} + +func (fq *frameQueue) Push(x interface{}) { + *fq = append(*fq, x.(*prioritizedFrame)) +} + +func (fq *frameQueue) Pop() interface{} { + old := *fq + n := len(old) + *fq = old[0 : n-1] + return old[n-1] +} + +type PriorityFrameQueue struct { + queue *frameQueue + c *sync.Cond + size int + nextInsertId uint64 + drain bool +} + +func NewPriorityFrameQueue(size int) *PriorityFrameQueue { + queue := make(frameQueue, 0, size) + heap.Init(&queue) + + return &PriorityFrameQueue{ + queue: &queue, + size: size, + c: sync.NewCond(&sync.Mutex{}), + } +} + +func (q *PriorityFrameQueue) Push(frame spdy.Frame, priority uint8) { + q.c.L.Lock() + defer q.c.L.Unlock() + for q.queue.Len() >= q.size { + q.c.Wait() + } + pFrame := &prioritizedFrame{ + frame: frame, + priority: priority, + insertId: q.nextInsertId, + } + q.nextInsertId = q.nextInsertId + 1 + heap.Push(q.queue, pFrame) + q.c.Signal() +} + +func (q *PriorityFrameQueue) Pop() spdy.Frame { + q.c.L.Lock() + defer q.c.L.Unlock() + for q.queue.Len() == 0 { + if q.drain { + return nil + } + q.c.Wait() + } + frame := heap.Pop(q.queue).(*prioritizedFrame).frame + q.c.Signal() + return frame +} + +func (q *PriorityFrameQueue) Drain() { + q.c.L.Lock() + defer q.c.L.Unlock() + q.drain = true + q.c.Broadcast() +} diff --git a/vendor/github.com/docker/spdystream/spdy/dictionary.go b/vendor/github.com/docker/spdystream/spdy/dictionary.go new file mode 100644 index 00000000000..5a5ff0e14cd --- /dev/null +++ b/vendor/github.com/docker/spdystream/spdy/dictionary.go @@ -0,0 +1,187 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package spdy + +// headerDictionary is the dictionary sent to the zlib compressor/decompressor. +var headerDictionary = []byte{ + 0x00, 0x00, 0x00, 0x07, 0x6f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x00, 0x00, 0x00, 0x04, 0x68, + 0x65, 0x61, 0x64, 0x00, 0x00, 0x00, 0x04, 0x70, + 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x03, 0x70, + 0x75, 0x74, 0x00, 0x00, 0x00, 0x06, 0x64, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x00, 0x00, 0x00, 0x05, + 0x74, 0x72, 0x61, 0x63, 0x65, 0x00, 0x00, 0x00, + 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x00, + 0x00, 0x00, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, + 0x74, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, + 0x74, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x63, 0x63, + 0x65, 0x70, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, + 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x0f, + 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c, + 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x00, + 0x00, 0x00, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70, + 0x74, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, + 0x00, 0x00, 0x00, 0x03, 0x61, 0x67, 0x65, 0x00, + 0x00, 0x00, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77, + 0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x00, 0x00, 0x00, 0x0d, 0x63, 0x61, 0x63, + 0x68, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x00, 0x00, 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x2d, 0x62, 0x61, 0x73, 0x65, + 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, + 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, + 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, + 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, + 0x00, 0x00, 0x00, 0x0e, 0x63, 0x6f, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67, + 0x74, 0x68, 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x6f, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, + 0x00, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, + 0x74, 0x2d, 0x6d, 0x64, 0x35, 0x00, 0x00, 0x00, + 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, + 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, + 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x00, 0x00, + 0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x00, 0x00, + 0x00, 0x04, 0x65, 0x74, 0x61, 0x67, 0x00, 0x00, + 0x00, 0x06, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, + 0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x70, 0x69, + 0x72, 0x65, 0x73, 0x00, 0x00, 0x00, 0x04, 0x66, + 0x72, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x04, 0x68, + 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x08, 0x69, + 0x66, 0x2d, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, + 0x00, 0x00, 0x11, 0x69, 0x66, 0x2d, 0x6d, 0x6f, + 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2d, 0x73, + 0x69, 0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x0d, + 0x69, 0x66, 0x2d, 0x6e, 0x6f, 0x6e, 0x65, 0x2d, + 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, 0x00, 0x00, + 0x08, 0x69, 0x66, 0x2d, 0x72, 0x61, 0x6e, 0x67, + 0x65, 0x00, 0x00, 0x00, 0x13, 0x69, 0x66, 0x2d, + 0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, + 0x65, 0x64, 0x2d, 0x73, 0x69, 0x6e, 0x63, 0x65, + 0x00, 0x00, 0x00, 0x0d, 0x6c, 0x61, 0x73, 0x74, + 0x2d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, + 0x64, 0x00, 0x00, 0x00, 0x08, 0x6c, 0x6f, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, + 0x0c, 0x6d, 0x61, 0x78, 0x2d, 0x66, 0x6f, 0x72, + 0x77, 0x61, 0x72, 0x64, 0x73, 0x00, 0x00, 0x00, + 0x06, 0x70, 0x72, 0x61, 0x67, 0x6d, 0x61, 0x00, + 0x00, 0x00, 0x12, 0x70, 0x72, 0x6f, 0x78, 0x79, + 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, + 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00, + 0x13, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2d, 0x61, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x05, + 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, 0x00, + 0x07, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72, + 0x00, 0x00, 0x00, 0x0b, 0x72, 0x65, 0x74, 0x72, + 0x79, 0x2d, 0x61, 0x66, 0x74, 0x65, 0x72, 0x00, + 0x00, 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x00, 0x00, 0x00, 0x02, 0x74, 0x65, 0x00, + 0x00, 0x00, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c, + 0x65, 0x72, 0x00, 0x00, 0x00, 0x11, 0x74, 0x72, + 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x65, + 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x00, + 0x00, 0x00, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61, + 0x64, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x73, + 0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74, + 0x00, 0x00, 0x00, 0x04, 0x76, 0x61, 0x72, 0x79, + 0x00, 0x00, 0x00, 0x03, 0x76, 0x69, 0x61, 0x00, + 0x00, 0x00, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, + 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, 0x77, 0x77, + 0x77, 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, + 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, + 0x00, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, + 0x00, 0x00, 0x00, 0x03, 0x67, 0x65, 0x74, 0x00, + 0x00, 0x00, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x00, 0x00, 0x00, 0x06, 0x32, 0x30, 0x30, + 0x20, 0x4f, 0x4b, 0x00, 0x00, 0x00, 0x07, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00, + 0x00, 0x08, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, + 0x2e, 0x31, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72, + 0x6c, 0x00, 0x00, 0x00, 0x06, 0x70, 0x75, 0x62, + 0x6c, 0x69, 0x63, 0x00, 0x00, 0x00, 0x0a, 0x73, + 0x65, 0x74, 0x2d, 0x63, 0x6f, 0x6f, 0x6b, 0x69, + 0x65, 0x00, 0x00, 0x00, 0x0a, 0x6b, 0x65, 0x65, + 0x70, 0x2d, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x00, + 0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, + 0x6e, 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, 0x32, + 0x30, 0x31, 0x32, 0x30, 0x32, 0x32, 0x30, 0x35, + 0x32, 0x30, 0x36, 0x33, 0x30, 0x30, 0x33, 0x30, + 0x32, 0x33, 0x30, 0x33, 0x33, 0x30, 0x34, 0x33, + 0x30, 0x35, 0x33, 0x30, 0x36, 0x33, 0x30, 0x37, + 0x34, 0x30, 0x32, 0x34, 0x30, 0x35, 0x34, 0x30, + 0x36, 0x34, 0x30, 0x37, 0x34, 0x30, 0x38, 0x34, + 0x30, 0x39, 0x34, 0x31, 0x30, 0x34, 0x31, 0x31, + 0x34, 0x31, 0x32, 0x34, 0x31, 0x33, 0x34, 0x31, + 0x34, 0x34, 0x31, 0x35, 0x34, 0x31, 0x36, 0x34, + 0x31, 0x37, 0x35, 0x30, 0x32, 0x35, 0x30, 0x34, + 0x35, 0x30, 0x35, 0x32, 0x30, 0x33, 0x20, 0x4e, + 0x6f, 0x6e, 0x2d, 0x41, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x74, 0x61, 0x74, 0x69, 0x76, 0x65, + 0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x32, 0x30, 0x34, 0x20, + 0x4e, 0x6f, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x33, 0x30, 0x31, 0x20, 0x4d, 0x6f, + 0x76, 0x65, 0x64, 0x20, 0x50, 0x65, 0x72, 0x6d, + 0x61, 0x6e, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x34, + 0x30, 0x30, 0x20, 0x42, 0x61, 0x64, 0x20, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x34, 0x30, + 0x31, 0x20, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x34, 0x30, + 0x33, 0x20, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64, + 0x64, 0x65, 0x6e, 0x34, 0x30, 0x34, 0x20, 0x4e, + 0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, + 0x35, 0x30, 0x30, 0x20, 0x49, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, + 0x72, 0x35, 0x30, 0x31, 0x20, 0x4e, 0x6f, 0x74, + 0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x65, 0x64, 0x35, 0x30, 0x33, 0x20, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, + 0x55, 0x6e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, + 0x62, 0x6c, 0x65, 0x4a, 0x61, 0x6e, 0x20, 0x46, + 0x65, 0x62, 0x20, 0x4d, 0x61, 0x72, 0x20, 0x41, + 0x70, 0x72, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x4a, + 0x75, 0x6e, 0x20, 0x4a, 0x75, 0x6c, 0x20, 0x41, + 0x75, 0x67, 0x20, 0x53, 0x65, 0x70, 0x74, 0x20, + 0x4f, 0x63, 0x74, 0x20, 0x4e, 0x6f, 0x76, 0x20, + 0x44, 0x65, 0x63, 0x20, 0x30, 0x30, 0x3a, 0x30, + 0x30, 0x3a, 0x30, 0x30, 0x20, 0x4d, 0x6f, 0x6e, + 0x2c, 0x20, 0x54, 0x75, 0x65, 0x2c, 0x20, 0x57, + 0x65, 0x64, 0x2c, 0x20, 0x54, 0x68, 0x75, 0x2c, + 0x20, 0x46, 0x72, 0x69, 0x2c, 0x20, 0x53, 0x61, + 0x74, 0x2c, 0x20, 0x53, 0x75, 0x6e, 0x2c, 0x20, + 0x47, 0x4d, 0x54, 0x63, 0x68, 0x75, 0x6e, 0x6b, + 0x65, 0x64, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, + 0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6d, 0x61, + 0x67, 0x65, 0x2f, 0x70, 0x6e, 0x67, 0x2c, 0x69, + 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x6a, 0x70, 0x67, + 0x2c, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, + 0x69, 0x66, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, + 0x6d, 0x6c, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, + 0x68, 0x74, 0x6d, 0x6c, 0x2b, 0x78, 0x6d, 0x6c, + 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c, + 0x61, 0x69, 0x6e, 0x2c, 0x74, 0x65, 0x78, 0x74, + 0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x2c, 0x70, 0x75, 0x62, 0x6c, + 0x69, 0x63, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, + 0x65, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65, + 0x3d, 0x67, 0x7a, 0x69, 0x70, 0x2c, 0x64, 0x65, + 0x66, 0x6c, 0x61, 0x74, 0x65, 0x2c, 0x73, 0x64, + 0x63, 0x68, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, + 0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x63, + 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x69, + 0x73, 0x6f, 0x2d, 0x38, 0x38, 0x35, 0x39, 0x2d, + 0x31, 0x2c, 0x75, 0x74, 0x66, 0x2d, 0x2c, 0x2a, + 0x2c, 0x65, 0x6e, 0x71, 0x3d, 0x30, 0x2e, +} diff --git a/vendor/github.com/docker/spdystream/spdy/read.go b/vendor/github.com/docker/spdystream/spdy/read.go new file mode 100644 index 00000000000..9359a95015c --- /dev/null +++ b/vendor/github.com/docker/spdystream/spdy/read.go @@ -0,0 +1,348 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package spdy + +import ( + "compress/zlib" + "encoding/binary" + "io" + "net/http" + "strings" +) + +func (frame *SynStreamFrame) read(h ControlFrameHeader, f *Framer) error { + return f.readSynStreamFrame(h, frame) +} + +func (frame *SynReplyFrame) read(h ControlFrameHeader, f *Framer) error { + return f.readSynReplyFrame(h, frame) +} + +func (frame *RstStreamFrame) read(h ControlFrameHeader, f *Framer) error { + frame.CFHeader = h + if err := binary.Read(f.r, binary.BigEndian, &frame.StreamId); err != nil { + return err + } + if err := binary.Read(f.r, binary.BigEndian, &frame.Status); err != nil { + return err + } + if frame.Status == 0 { + return &Error{InvalidControlFrame, frame.StreamId} + } + if frame.StreamId == 0 { + return &Error{ZeroStreamId, 0} + } + return nil +} + +func (frame *SettingsFrame) read(h ControlFrameHeader, f *Framer) error { + frame.CFHeader = h + var numSettings uint32 + if err := binary.Read(f.r, binary.BigEndian, &numSettings); err != nil { + return err + } + frame.FlagIdValues = make([]SettingsFlagIdValue, numSettings) + for i := uint32(0); i < numSettings; i++ { + if err := binary.Read(f.r, binary.BigEndian, &frame.FlagIdValues[i].Id); err != nil { + return err + } + frame.FlagIdValues[i].Flag = SettingsFlag((frame.FlagIdValues[i].Id & 0xff000000) >> 24) + frame.FlagIdValues[i].Id &= 0xffffff + if err := binary.Read(f.r, binary.BigEndian, &frame.FlagIdValues[i].Value); err != nil { + return err + } + } + return nil +} + +func (frame *PingFrame) read(h ControlFrameHeader, f *Framer) error { + frame.CFHeader = h + if err := binary.Read(f.r, binary.BigEndian, &frame.Id); err != nil { + return err + } + if frame.Id == 0 { + return &Error{ZeroStreamId, 0} + } + if frame.CFHeader.Flags != 0 { + return &Error{InvalidControlFrame, StreamId(frame.Id)} + } + return nil +} + +func (frame *GoAwayFrame) read(h ControlFrameHeader, f *Framer) error { + frame.CFHeader = h + if err := binary.Read(f.r, binary.BigEndian, &frame.LastGoodStreamId); err != nil { + return err + } + if frame.CFHeader.Flags != 0 { + return &Error{InvalidControlFrame, frame.LastGoodStreamId} + } + if frame.CFHeader.length != 8 { + return &Error{InvalidControlFrame, frame.LastGoodStreamId} + } + if err := binary.Read(f.r, binary.BigEndian, &frame.Status); err != nil { + return err + } + return nil +} + +func (frame *HeadersFrame) read(h ControlFrameHeader, f *Framer) error { + return f.readHeadersFrame(h, frame) +} + +func (frame *WindowUpdateFrame) read(h ControlFrameHeader, f *Framer) error { + frame.CFHeader = h + if err := binary.Read(f.r, binary.BigEndian, &frame.StreamId); err != nil { + return err + } + if frame.CFHeader.Flags != 0 { + return &Error{InvalidControlFrame, frame.StreamId} + } + if frame.CFHeader.length != 8 { + return &Error{InvalidControlFrame, frame.StreamId} + } + if err := binary.Read(f.r, binary.BigEndian, &frame.DeltaWindowSize); err != nil { + return err + } + return nil +} + +func newControlFrame(frameType ControlFrameType) (controlFrame, error) { + ctor, ok := cframeCtor[frameType] + if !ok { + return nil, &Error{Err: InvalidControlFrame} + } + return ctor(), nil +} + +var cframeCtor = map[ControlFrameType]func() controlFrame{ + TypeSynStream: func() controlFrame { return new(SynStreamFrame) }, + TypeSynReply: func() controlFrame { return new(SynReplyFrame) }, + TypeRstStream: func() controlFrame { return new(RstStreamFrame) }, + TypeSettings: func() controlFrame { return new(SettingsFrame) }, + TypePing: func() controlFrame { return new(PingFrame) }, + TypeGoAway: func() controlFrame { return new(GoAwayFrame) }, + TypeHeaders: func() controlFrame { return new(HeadersFrame) }, + TypeWindowUpdate: func() controlFrame { return new(WindowUpdateFrame) }, +} + +func (f *Framer) uncorkHeaderDecompressor(payloadSize int64) error { + if f.headerDecompressor != nil { + f.headerReader.N = payloadSize + return nil + } + f.headerReader = io.LimitedReader{R: f.r, N: payloadSize} + decompressor, err := zlib.NewReaderDict(&f.headerReader, []byte(headerDictionary)) + if err != nil { + return err + } + f.headerDecompressor = decompressor + return nil +} + +// ReadFrame reads SPDY encoded data and returns a decompressed Frame. +func (f *Framer) ReadFrame() (Frame, error) { + var firstWord uint32 + if err := binary.Read(f.r, binary.BigEndian, &firstWord); err != nil { + return nil, err + } + if firstWord&0x80000000 != 0 { + frameType := ControlFrameType(firstWord & 0xffff) + version := uint16(firstWord >> 16 & 0x7fff) + return f.parseControlFrame(version, frameType) + } + return f.parseDataFrame(StreamId(firstWord & 0x7fffffff)) +} + +func (f *Framer) parseControlFrame(version uint16, frameType ControlFrameType) (Frame, error) { + var length uint32 + if err := binary.Read(f.r, binary.BigEndian, &length); err != nil { + return nil, err + } + flags := ControlFlags((length & 0xff000000) >> 24) + length &= 0xffffff + header := ControlFrameHeader{version, frameType, flags, length} + cframe, err := newControlFrame(frameType) + if err != nil { + return nil, err + } + if err = cframe.read(header, f); err != nil { + return nil, err + } + return cframe, nil +} + +func parseHeaderValueBlock(r io.Reader, streamId StreamId) (http.Header, error) { + var numHeaders uint32 + if err := binary.Read(r, binary.BigEndian, &numHeaders); err != nil { + return nil, err + } + var e error + h := make(http.Header, int(numHeaders)) + for i := 0; i < int(numHeaders); i++ { + var length uint32 + if err := binary.Read(r, binary.BigEndian, &length); err != nil { + return nil, err + } + nameBytes := make([]byte, length) + if _, err := io.ReadFull(r, nameBytes); err != nil { + return nil, err + } + name := string(nameBytes) + if name != strings.ToLower(name) { + e = &Error{UnlowercasedHeaderName, streamId} + name = strings.ToLower(name) + } + if h[name] != nil { + e = &Error{DuplicateHeaders, streamId} + } + if err := binary.Read(r, binary.BigEndian, &length); err != nil { + return nil, err + } + value := make([]byte, length) + if _, err := io.ReadFull(r, value); err != nil { + return nil, err + } + valueList := strings.Split(string(value), headerValueSeparator) + for _, v := range valueList { + h.Add(name, v) + } + } + if e != nil { + return h, e + } + return h, nil +} + +func (f *Framer) readSynStreamFrame(h ControlFrameHeader, frame *SynStreamFrame) error { + frame.CFHeader = h + var err error + if err = binary.Read(f.r, binary.BigEndian, &frame.StreamId); err != nil { + return err + } + if err = binary.Read(f.r, binary.BigEndian, &frame.AssociatedToStreamId); err != nil { + return err + } + if err = binary.Read(f.r, binary.BigEndian, &frame.Priority); err != nil { + return err + } + frame.Priority >>= 5 + if err = binary.Read(f.r, binary.BigEndian, &frame.Slot); err != nil { + return err + } + reader := f.r + if !f.headerCompressionDisabled { + err := f.uncorkHeaderDecompressor(int64(h.length - 10)) + if err != nil { + return err + } + reader = f.headerDecompressor + } + frame.Headers, err = parseHeaderValueBlock(reader, frame.StreamId) + if !f.headerCompressionDisabled && (err == io.EOF && f.headerReader.N == 0 || f.headerReader.N != 0) { + err = &Error{WrongCompressedPayloadSize, 0} + } + if err != nil { + return err + } + for h := range frame.Headers { + if invalidReqHeaders[h] { + return &Error{InvalidHeaderPresent, frame.StreamId} + } + } + if frame.StreamId == 0 { + return &Error{ZeroStreamId, 0} + } + return nil +} + +func (f *Framer) readSynReplyFrame(h ControlFrameHeader, frame *SynReplyFrame) error { + frame.CFHeader = h + var err error + if err = binary.Read(f.r, binary.BigEndian, &frame.StreamId); err != nil { + return err + } + reader := f.r + if !f.headerCompressionDisabled { + err := f.uncorkHeaderDecompressor(int64(h.length - 4)) + if err != nil { + return err + } + reader = f.headerDecompressor + } + frame.Headers, err = parseHeaderValueBlock(reader, frame.StreamId) + if !f.headerCompressionDisabled && (err == io.EOF && f.headerReader.N == 0 || f.headerReader.N != 0) { + err = &Error{WrongCompressedPayloadSize, 0} + } + if err != nil { + return err + } + for h := range frame.Headers { + if invalidRespHeaders[h] { + return &Error{InvalidHeaderPresent, frame.StreamId} + } + } + if frame.StreamId == 0 { + return &Error{ZeroStreamId, 0} + } + return nil +} + +func (f *Framer) readHeadersFrame(h ControlFrameHeader, frame *HeadersFrame) error { + frame.CFHeader = h + var err error + if err = binary.Read(f.r, binary.BigEndian, &frame.StreamId); err != nil { + return err + } + reader := f.r + if !f.headerCompressionDisabled { + err := f.uncorkHeaderDecompressor(int64(h.length - 4)) + if err != nil { + return err + } + reader = f.headerDecompressor + } + frame.Headers, err = parseHeaderValueBlock(reader, frame.StreamId) + if !f.headerCompressionDisabled && (err == io.EOF && f.headerReader.N == 0 || f.headerReader.N != 0) { + err = &Error{WrongCompressedPayloadSize, 0} + } + if err != nil { + return err + } + var invalidHeaders map[string]bool + if frame.StreamId%2 == 0 { + invalidHeaders = invalidReqHeaders + } else { + invalidHeaders = invalidRespHeaders + } + for h := range frame.Headers { + if invalidHeaders[h] { + return &Error{InvalidHeaderPresent, frame.StreamId} + } + } + if frame.StreamId == 0 { + return &Error{ZeroStreamId, 0} + } + return nil +} + +func (f *Framer) parseDataFrame(streamId StreamId) (*DataFrame, error) { + var length uint32 + if err := binary.Read(f.r, binary.BigEndian, &length); err != nil { + return nil, err + } + var frame DataFrame + frame.StreamId = streamId + frame.Flags = DataFlags(length >> 24) + length &= 0xffffff + frame.Data = make([]byte, length) + if _, err := io.ReadFull(f.r, frame.Data); err != nil { + return nil, err + } + if frame.StreamId == 0 { + return nil, &Error{ZeroStreamId, 0} + } + return &frame, nil +} diff --git a/vendor/github.com/docker/spdystream/spdy/types.go b/vendor/github.com/docker/spdystream/spdy/types.go new file mode 100644 index 00000000000..7b6ee9c6f2b --- /dev/null +++ b/vendor/github.com/docker/spdystream/spdy/types.go @@ -0,0 +1,275 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package spdy implements the SPDY protocol (currently SPDY/3), described in +// http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3. +package spdy + +import ( + "bytes" + "compress/zlib" + "io" + "net/http" +) + +// Version is the protocol version number that this package implements. +const Version = 3 + +// ControlFrameType stores the type field in a control frame header. +type ControlFrameType uint16 + +const ( + TypeSynStream ControlFrameType = 0x0001 + TypeSynReply = 0x0002 + TypeRstStream = 0x0003 + TypeSettings = 0x0004 + TypePing = 0x0006 + TypeGoAway = 0x0007 + TypeHeaders = 0x0008 + TypeWindowUpdate = 0x0009 +) + +// ControlFlags are the flags that can be set on a control frame. +type ControlFlags uint8 + +const ( + ControlFlagFin ControlFlags = 0x01 + ControlFlagUnidirectional = 0x02 + ControlFlagSettingsClearSettings = 0x01 +) + +// DataFlags are the flags that can be set on a data frame. +type DataFlags uint8 + +const ( + DataFlagFin DataFlags = 0x01 +) + +// MaxDataLength is the maximum number of bytes that can be stored in one frame. +const MaxDataLength = 1<<24 - 1 + +// headerValueSepator separates multiple header values. +const headerValueSeparator = "\x00" + +// Frame is a single SPDY frame in its unpacked in-memory representation. Use +// Framer to read and write it. +type Frame interface { + write(f *Framer) error +} + +// ControlFrameHeader contains all the fields in a control frame header, +// in its unpacked in-memory representation. +type ControlFrameHeader struct { + // Note, high bit is the "Control" bit. + version uint16 // spdy version number + frameType ControlFrameType + Flags ControlFlags + length uint32 // length of data field +} + +type controlFrame interface { + Frame + read(h ControlFrameHeader, f *Framer) error +} + +// StreamId represents a 31-bit value identifying the stream. +type StreamId uint32 + +// SynStreamFrame is the unpacked, in-memory representation of a SYN_STREAM +// frame. +type SynStreamFrame struct { + CFHeader ControlFrameHeader + StreamId StreamId + AssociatedToStreamId StreamId // stream id for a stream which this stream is associated to + Priority uint8 // priority of this frame (3-bit) + Slot uint8 // index in the server's credential vector of the client certificate + Headers http.Header +} + +// SynReplyFrame is the unpacked, in-memory representation of a SYN_REPLY frame. +type SynReplyFrame struct { + CFHeader ControlFrameHeader + StreamId StreamId + Headers http.Header +} + +// RstStreamStatus represents the status that led to a RST_STREAM. +type RstStreamStatus uint32 + +const ( + ProtocolError RstStreamStatus = iota + 1 + InvalidStream + RefusedStream + UnsupportedVersion + Cancel + InternalError + FlowControlError + StreamInUse + StreamAlreadyClosed + InvalidCredentials + FrameTooLarge +) + +// RstStreamFrame is the unpacked, in-memory representation of a RST_STREAM +// frame. +type RstStreamFrame struct { + CFHeader ControlFrameHeader + StreamId StreamId + Status RstStreamStatus +} + +// SettingsFlag represents a flag in a SETTINGS frame. +type SettingsFlag uint8 + +const ( + FlagSettingsPersistValue SettingsFlag = 0x1 + FlagSettingsPersisted = 0x2 +) + +// SettingsFlag represents the id of an id/value pair in a SETTINGS frame. +type SettingsId uint32 + +const ( + SettingsUploadBandwidth SettingsId = iota + 1 + SettingsDownloadBandwidth + SettingsRoundTripTime + SettingsMaxConcurrentStreams + SettingsCurrentCwnd + SettingsDownloadRetransRate + SettingsInitialWindowSize + SettingsClientCretificateVectorSize +) + +// SettingsFlagIdValue is the unpacked, in-memory representation of the +// combined flag/id/value for a setting in a SETTINGS frame. +type SettingsFlagIdValue struct { + Flag SettingsFlag + Id SettingsId + Value uint32 +} + +// SettingsFrame is the unpacked, in-memory representation of a SPDY +// SETTINGS frame. +type SettingsFrame struct { + CFHeader ControlFrameHeader + FlagIdValues []SettingsFlagIdValue +} + +// PingFrame is the unpacked, in-memory representation of a PING frame. +type PingFrame struct { + CFHeader ControlFrameHeader + Id uint32 // unique id for this ping, from server is even, from client is odd. +} + +// GoAwayStatus represents the status in a GoAwayFrame. +type GoAwayStatus uint32 + +const ( + GoAwayOK GoAwayStatus = iota + GoAwayProtocolError + GoAwayInternalError +) + +// GoAwayFrame is the unpacked, in-memory representation of a GOAWAY frame. +type GoAwayFrame struct { + CFHeader ControlFrameHeader + LastGoodStreamId StreamId // last stream id which was accepted by sender + Status GoAwayStatus +} + +// HeadersFrame is the unpacked, in-memory representation of a HEADERS frame. +type HeadersFrame struct { + CFHeader ControlFrameHeader + StreamId StreamId + Headers http.Header +} + +// WindowUpdateFrame is the unpacked, in-memory representation of a +// WINDOW_UPDATE frame. +type WindowUpdateFrame struct { + CFHeader ControlFrameHeader + StreamId StreamId + DeltaWindowSize uint32 // additional number of bytes to existing window size +} + +// TODO: Implement credential frame and related methods. + +// DataFrame is the unpacked, in-memory representation of a DATA frame. +type DataFrame struct { + // Note, high bit is the "Control" bit. Should be 0 for data frames. + StreamId StreamId + Flags DataFlags + Data []byte // payload data of this frame +} + +// A SPDY specific error. +type ErrorCode string + +const ( + UnlowercasedHeaderName ErrorCode = "header was not lowercased" + DuplicateHeaders = "multiple headers with same name" + WrongCompressedPayloadSize = "compressed payload size was incorrect" + UnknownFrameType = "unknown frame type" + InvalidControlFrame = "invalid control frame" + InvalidDataFrame = "invalid data frame" + InvalidHeaderPresent = "frame contained invalid header" + ZeroStreamId = "stream id zero is disallowed" +) + +// Error contains both the type of error and additional values. StreamId is 0 +// if Error is not associated with a stream. +type Error struct { + Err ErrorCode + StreamId StreamId +} + +func (e *Error) Error() string { + return string(e.Err) +} + +var invalidReqHeaders = map[string]bool{ + "Connection": true, + "Host": true, + "Keep-Alive": true, + "Proxy-Connection": true, + "Transfer-Encoding": true, +} + +var invalidRespHeaders = map[string]bool{ + "Connection": true, + "Keep-Alive": true, + "Proxy-Connection": true, + "Transfer-Encoding": true, +} + +// Framer handles serializing/deserializing SPDY frames, including compressing/ +// decompressing payloads. +type Framer struct { + headerCompressionDisabled bool + w io.Writer + headerBuf *bytes.Buffer + headerCompressor *zlib.Writer + r io.Reader + headerReader io.LimitedReader + headerDecompressor io.ReadCloser +} + +// NewFramer allocates a new Framer for a given SPDY connection, represented by +// a io.Writer and io.Reader. Note that Framer will read and write individual fields +// from/to the Reader and Writer, so the caller should pass in an appropriately +// buffered implementation to optimize performance. +func NewFramer(w io.Writer, r io.Reader) (*Framer, error) { + compressBuf := new(bytes.Buffer) + compressor, err := zlib.NewWriterLevelDict(compressBuf, zlib.BestCompression, []byte(headerDictionary)) + if err != nil { + return nil, err + } + framer := &Framer{ + w: w, + headerBuf: compressBuf, + headerCompressor: compressor, + r: r, + } + return framer, nil +} diff --git a/vendor/github.com/docker/spdystream/spdy/write.go b/vendor/github.com/docker/spdystream/spdy/write.go new file mode 100644 index 00000000000..b212f66a235 --- /dev/null +++ b/vendor/github.com/docker/spdystream/spdy/write.go @@ -0,0 +1,318 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package spdy + +import ( + "encoding/binary" + "io" + "net/http" + "strings" +) + +func (frame *SynStreamFrame) write(f *Framer) error { + return f.writeSynStreamFrame(frame) +} + +func (frame *SynReplyFrame) write(f *Framer) error { + return f.writeSynReplyFrame(frame) +} + +func (frame *RstStreamFrame) write(f *Framer) (err error) { + if frame.StreamId == 0 { + return &Error{ZeroStreamId, 0} + } + frame.CFHeader.version = Version + frame.CFHeader.frameType = TypeRstStream + frame.CFHeader.Flags = 0 + frame.CFHeader.length = 8 + + // Serialize frame to Writer. + if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil { + return + } + if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil { + return + } + if frame.Status == 0 { + return &Error{InvalidControlFrame, frame.StreamId} + } + if err = binary.Write(f.w, binary.BigEndian, frame.Status); err != nil { + return + } + return +} + +func (frame *SettingsFrame) write(f *Framer) (err error) { + frame.CFHeader.version = Version + frame.CFHeader.frameType = TypeSettings + frame.CFHeader.length = uint32(len(frame.FlagIdValues)*8 + 4) + + // Serialize frame to Writer. + if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil { + return + } + if err = binary.Write(f.w, binary.BigEndian, uint32(len(frame.FlagIdValues))); err != nil { + return + } + for _, flagIdValue := range frame.FlagIdValues { + flagId := uint32(flagIdValue.Flag)<<24 | uint32(flagIdValue.Id) + if err = binary.Write(f.w, binary.BigEndian, flagId); err != nil { + return + } + if err = binary.Write(f.w, binary.BigEndian, flagIdValue.Value); err != nil { + return + } + } + return +} + +func (frame *PingFrame) write(f *Framer) (err error) { + if frame.Id == 0 { + return &Error{ZeroStreamId, 0} + } + frame.CFHeader.version = Version + frame.CFHeader.frameType = TypePing + frame.CFHeader.Flags = 0 + frame.CFHeader.length = 4 + + // Serialize frame to Writer. + if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil { + return + } + if err = binary.Write(f.w, binary.BigEndian, frame.Id); err != nil { + return + } + return +} + +func (frame *GoAwayFrame) write(f *Framer) (err error) { + frame.CFHeader.version = Version + frame.CFHeader.frameType = TypeGoAway + frame.CFHeader.Flags = 0 + frame.CFHeader.length = 8 + + // Serialize frame to Writer. + if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil { + return + } + if err = binary.Write(f.w, binary.BigEndian, frame.LastGoodStreamId); err != nil { + return + } + if err = binary.Write(f.w, binary.BigEndian, frame.Status); err != nil { + return + } + return nil +} + +func (frame *HeadersFrame) write(f *Framer) error { + return f.writeHeadersFrame(frame) +} + +func (frame *WindowUpdateFrame) write(f *Framer) (err error) { + frame.CFHeader.version = Version + frame.CFHeader.frameType = TypeWindowUpdate + frame.CFHeader.Flags = 0 + frame.CFHeader.length = 8 + + // Serialize frame to Writer. + if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil { + return + } + if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil { + return + } + if err = binary.Write(f.w, binary.BigEndian, frame.DeltaWindowSize); err != nil { + return + } + return nil +} + +func (frame *DataFrame) write(f *Framer) error { + return f.writeDataFrame(frame) +} + +// WriteFrame writes a frame. +func (f *Framer) WriteFrame(frame Frame) error { + return frame.write(f) +} + +func writeControlFrameHeader(w io.Writer, h ControlFrameHeader) error { + if err := binary.Write(w, binary.BigEndian, 0x8000|h.version); err != nil { + return err + } + if err := binary.Write(w, binary.BigEndian, h.frameType); err != nil { + return err + } + flagsAndLength := uint32(h.Flags)<<24 | h.length + if err := binary.Write(w, binary.BigEndian, flagsAndLength); err != nil { + return err + } + return nil +} + +func writeHeaderValueBlock(w io.Writer, h http.Header) (n int, err error) { + n = 0 + if err = binary.Write(w, binary.BigEndian, uint32(len(h))); err != nil { + return + } + n += 2 + for name, values := range h { + if err = binary.Write(w, binary.BigEndian, uint32(len(name))); err != nil { + return + } + n += 2 + name = strings.ToLower(name) + if _, err = io.WriteString(w, name); err != nil { + return + } + n += len(name) + v := strings.Join(values, headerValueSeparator) + if err = binary.Write(w, binary.BigEndian, uint32(len(v))); err != nil { + return + } + n += 2 + if _, err = io.WriteString(w, v); err != nil { + return + } + n += len(v) + } + return +} + +func (f *Framer) writeSynStreamFrame(frame *SynStreamFrame) (err error) { + if frame.StreamId == 0 { + return &Error{ZeroStreamId, 0} + } + // Marshal the headers. + var writer io.Writer = f.headerBuf + if !f.headerCompressionDisabled { + writer = f.headerCompressor + } + if _, err = writeHeaderValueBlock(writer, frame.Headers); err != nil { + return + } + if !f.headerCompressionDisabled { + f.headerCompressor.Flush() + } + + // Set ControlFrameHeader. + frame.CFHeader.version = Version + frame.CFHeader.frameType = TypeSynStream + frame.CFHeader.length = uint32(len(f.headerBuf.Bytes()) + 10) + + // Serialize frame to Writer. + if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil { + return err + } + if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil { + return err + } + if err = binary.Write(f.w, binary.BigEndian, frame.AssociatedToStreamId); err != nil { + return err + } + if err = binary.Write(f.w, binary.BigEndian, frame.Priority<<5); err != nil { + return err + } + if err = binary.Write(f.w, binary.BigEndian, frame.Slot); err != nil { + return err + } + if _, err = f.w.Write(f.headerBuf.Bytes()); err != nil { + return err + } + f.headerBuf.Reset() + return nil +} + +func (f *Framer) writeSynReplyFrame(frame *SynReplyFrame) (err error) { + if frame.StreamId == 0 { + return &Error{ZeroStreamId, 0} + } + // Marshal the headers. + var writer io.Writer = f.headerBuf + if !f.headerCompressionDisabled { + writer = f.headerCompressor + } + if _, err = writeHeaderValueBlock(writer, frame.Headers); err != nil { + return + } + if !f.headerCompressionDisabled { + f.headerCompressor.Flush() + } + + // Set ControlFrameHeader. + frame.CFHeader.version = Version + frame.CFHeader.frameType = TypeSynReply + frame.CFHeader.length = uint32(len(f.headerBuf.Bytes()) + 4) + + // Serialize frame to Writer. + if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil { + return + } + if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil { + return + } + if _, err = f.w.Write(f.headerBuf.Bytes()); err != nil { + return + } + f.headerBuf.Reset() + return +} + +func (f *Framer) writeHeadersFrame(frame *HeadersFrame) (err error) { + if frame.StreamId == 0 { + return &Error{ZeroStreamId, 0} + } + // Marshal the headers. + var writer io.Writer = f.headerBuf + if !f.headerCompressionDisabled { + writer = f.headerCompressor + } + if _, err = writeHeaderValueBlock(writer, frame.Headers); err != nil { + return + } + if !f.headerCompressionDisabled { + f.headerCompressor.Flush() + } + + // Set ControlFrameHeader. + frame.CFHeader.version = Version + frame.CFHeader.frameType = TypeHeaders + frame.CFHeader.length = uint32(len(f.headerBuf.Bytes()) + 4) + + // Serialize frame to Writer. + if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil { + return + } + if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil { + return + } + if _, err = f.w.Write(f.headerBuf.Bytes()); err != nil { + return + } + f.headerBuf.Reset() + return +} + +func (f *Framer) writeDataFrame(frame *DataFrame) (err error) { + if frame.StreamId == 0 { + return &Error{ZeroStreamId, 0} + } + if frame.StreamId&0x80000000 != 0 || len(frame.Data) > MaxDataLength { + return &Error{InvalidDataFrame, frame.StreamId} + } + + // Serialize frame to Writer. + if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil { + return + } + flagsAndLength := uint32(frame.Flags)<<24 | uint32(len(frame.Data)) + if err = binary.Write(f.w, binary.BigEndian, flagsAndLength); err != nil { + return + } + if _, err = f.w.Write(frame.Data); err != nil { + return + } + return nil +} diff --git a/vendor/github.com/docker/spdystream/stream.go b/vendor/github.com/docker/spdystream/stream.go new file mode 100644 index 00000000000..f9e9ee267f8 --- /dev/null +++ b/vendor/github.com/docker/spdystream/stream.go @@ -0,0 +1,327 @@ +package spdystream + +import ( + "errors" + "fmt" + "io" + "net" + "net/http" + "sync" + "time" + + "github.com/docker/spdystream/spdy" +) + +var ( + ErrUnreadPartialData = errors.New("unread partial data") +) + +type Stream struct { + streamId spdy.StreamId + parent *Stream + conn *Connection + startChan chan error + + dataLock sync.RWMutex + dataChan chan []byte + unread []byte + + priority uint8 + headers http.Header + headerChan chan http.Header + finishLock sync.Mutex + finished bool + replyCond *sync.Cond + replied bool + closeLock sync.Mutex + closeChan chan bool +} + +// WriteData writes data to stream, sending a dataframe per call +func (s *Stream) WriteData(data []byte, fin bool) error { + s.waitWriteReply() + var flags spdy.DataFlags + + if fin { + flags = spdy.DataFlagFin + s.finishLock.Lock() + if s.finished { + s.finishLock.Unlock() + return ErrWriteClosedStream + } + s.finished = true + s.finishLock.Unlock() + } + + dataFrame := &spdy.DataFrame{ + StreamId: s.streamId, + Flags: flags, + Data: data, + } + + debugMessage("(%p) (%d) Writing data frame", s, s.streamId) + return s.conn.framer.WriteFrame(dataFrame) +} + +// Write writes bytes to a stream, calling write data for each call. +func (s *Stream) Write(data []byte) (n int, err error) { + err = s.WriteData(data, false) + if err == nil { + n = len(data) + } + return +} + +// Read reads bytes from a stream, a single read will never get more +// than what is sent on a single data frame, but a multiple calls to +// read may get data from the same data frame. +func (s *Stream) Read(p []byte) (n int, err error) { + if s.unread == nil { + select { + case <-s.closeChan: + return 0, io.EOF + case read, ok := <-s.dataChan: + if !ok { + return 0, io.EOF + } + s.unread = read + } + } + n = copy(p, s.unread) + if n < len(s.unread) { + s.unread = s.unread[n:] + } else { + s.unread = nil + } + return +} + +// ReadData reads an entire data frame and returns the byte array +// from the data frame. If there is unread data from the result +// of a Read call, this function will return an ErrUnreadPartialData. +func (s *Stream) ReadData() ([]byte, error) { + debugMessage("(%p) Reading data from %d", s, s.streamId) + if s.unread != nil { + return nil, ErrUnreadPartialData + } + select { + case <-s.closeChan: + return nil, io.EOF + case read, ok := <-s.dataChan: + if !ok { + return nil, io.EOF + } + return read, nil + } +} + +func (s *Stream) waitWriteReply() { + if s.replyCond != nil { + s.replyCond.L.Lock() + for !s.replied { + s.replyCond.Wait() + } + s.replyCond.L.Unlock() + } +} + +// Wait waits for the stream to receive a reply. +func (s *Stream) Wait() error { + return s.WaitTimeout(time.Duration(0)) +} + +// WaitTimeout waits for the stream to receive a reply or for timeout. +// When the timeout is reached, ErrTimeout will be returned. +func (s *Stream) WaitTimeout(timeout time.Duration) error { + var timeoutChan <-chan time.Time + if timeout > time.Duration(0) { + timeoutChan = time.After(timeout) + } + + select { + case err := <-s.startChan: + if err != nil { + return err + } + break + case <-timeoutChan: + return ErrTimeout + } + return nil +} + +// Close closes the stream by sending an empty data frame with the +// finish flag set, indicating this side is finished with the stream. +func (s *Stream) Close() error { + select { + case <-s.closeChan: + // Stream is now fully closed + s.conn.removeStream(s) + default: + break + } + return s.WriteData([]byte{}, true) +} + +// Reset sends a reset frame, putting the stream into the fully closed state. +func (s *Stream) Reset() error { + s.conn.removeStream(s) + return s.resetStream() +} + +func (s *Stream) resetStream() error { + // Always call closeRemoteChannels, even if s.finished is already true. + // This makes it so that stream.Close() followed by stream.Reset() allows + // stream.Read() to unblock. + s.closeRemoteChannels() + + s.finishLock.Lock() + if s.finished { + s.finishLock.Unlock() + return nil + } + s.finished = true + s.finishLock.Unlock() + + resetFrame := &spdy.RstStreamFrame{ + StreamId: s.streamId, + Status: spdy.Cancel, + } + return s.conn.framer.WriteFrame(resetFrame) +} + +// CreateSubStream creates a stream using the current as the parent +func (s *Stream) CreateSubStream(headers http.Header, fin bool) (*Stream, error) { + return s.conn.CreateStream(headers, s, fin) +} + +// SetPriority sets the stream priority, does not affect the +// remote priority of this stream after Open has been called. +// Valid values are 0 through 7, 0 being the highest priority +// and 7 the lowest. +func (s *Stream) SetPriority(priority uint8) { + s.priority = priority +} + +// SendHeader sends a header frame across the stream +func (s *Stream) SendHeader(headers http.Header, fin bool) error { + return s.conn.sendHeaders(headers, s, fin) +} + +// SendReply sends a reply on a stream, only valid to be called once +// when handling a new stream +func (s *Stream) SendReply(headers http.Header, fin bool) error { + if s.replyCond == nil { + return errors.New("cannot reply on initiated stream") + } + s.replyCond.L.Lock() + defer s.replyCond.L.Unlock() + if s.replied { + return nil + } + + err := s.conn.sendReply(headers, s, fin) + if err != nil { + return err + } + + s.replied = true + s.replyCond.Broadcast() + return nil +} + +// Refuse sends a reset frame with the status refuse, only +// valid to be called once when handling a new stream. This +// may be used to indicate that a stream is not allowed +// when http status codes are not being used. +func (s *Stream) Refuse() error { + if s.replied { + return nil + } + s.replied = true + return s.conn.sendReset(spdy.RefusedStream, s) +} + +// Cancel sends a reset frame with the status canceled. This +// can be used at any time by the creator of the Stream to +// indicate the stream is no longer needed. +func (s *Stream) Cancel() error { + return s.conn.sendReset(spdy.Cancel, s) +} + +// ReceiveHeader receives a header sent on the other side +// of the stream. This function will block until a header +// is received or stream is closed. +func (s *Stream) ReceiveHeader() (http.Header, error) { + select { + case <-s.closeChan: + break + case header, ok := <-s.headerChan: + if !ok { + return nil, fmt.Errorf("header chan closed") + } + return header, nil + } + return nil, fmt.Errorf("stream closed") +} + +// Parent returns the parent stream +func (s *Stream) Parent() *Stream { + return s.parent +} + +// Headers returns the headers used to create the stream +func (s *Stream) Headers() http.Header { + return s.headers +} + +// String returns the string version of stream using the +// streamId to uniquely identify the stream +func (s *Stream) String() string { + return fmt.Sprintf("stream:%d", s.streamId) +} + +// Identifier returns a 32 bit identifier for the stream +func (s *Stream) Identifier() uint32 { + return uint32(s.streamId) +} + +// IsFinished returns whether the stream has finished +// sending data +func (s *Stream) IsFinished() bool { + return s.finished +} + +// Implement net.Conn interface + +func (s *Stream) LocalAddr() net.Addr { + return s.conn.conn.LocalAddr() +} + +func (s *Stream) RemoteAddr() net.Addr { + return s.conn.conn.RemoteAddr() +} + +// TODO set per stream values instead of connection-wide + +func (s *Stream) SetDeadline(t time.Time) error { + return s.conn.conn.SetDeadline(t) +} + +func (s *Stream) SetReadDeadline(t time.Time) error { + return s.conn.conn.SetReadDeadline(t) +} + +func (s *Stream) SetWriteDeadline(t time.Time) error { + return s.conn.conn.SetWriteDeadline(t) +} + +func (s *Stream) closeRemoteChannels() { + s.closeLock.Lock() + defer s.closeLock.Unlock() + select { + case <-s.closeChan: + default: + close(s.closeChan) + } +} diff --git a/vendor/github.com/docker/spdystream/utils.go b/vendor/github.com/docker/spdystream/utils.go new file mode 100644 index 00000000000..1b2c199a402 --- /dev/null +++ b/vendor/github.com/docker/spdystream/utils.go @@ -0,0 +1,16 @@ +package spdystream + +import ( + "log" + "os" +) + +var ( + DEBUG = os.Getenv("DEBUG") +) + +func debugMessage(fmt string, args ...interface{}) { + if DEBUG != "" { + log.Printf(fmt, args...) + } +} diff --git a/vendor/golang.org/x/crypto/blowfish/block.go b/vendor/golang.org/x/crypto/blowfish/block.go new file mode 100644 index 00000000000..9d80f19521b --- /dev/null +++ b/vendor/golang.org/x/crypto/blowfish/block.go @@ -0,0 +1,159 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package blowfish + +// getNextWord returns the next big-endian uint32 value from the byte slice +// at the given position in a circular manner, updating the position. +func getNextWord(b []byte, pos *int) uint32 { + var w uint32 + j := *pos + for i := 0; i < 4; i++ { + w = w<<8 | uint32(b[j]) + j++ + if j >= len(b) { + j = 0 + } + } + *pos = j + return w +} + +// ExpandKey performs a key expansion on the given *Cipher. Specifically, it +// performs the Blowfish algorithm's key schedule which sets up the *Cipher's +// pi and substitution tables for calls to Encrypt. This is used, primarily, +// by the bcrypt package to reuse the Blowfish key schedule during its +// set up. It's unlikely that you need to use this directly. +func ExpandKey(key []byte, c *Cipher) { + j := 0 + for i := 0; i < 18; i++ { + // Using inlined getNextWord for performance. + var d uint32 + for k := 0; k < 4; k++ { + d = d<<8 | uint32(key[j]) + j++ + if j >= len(key) { + j = 0 + } + } + c.p[i] ^= d + } + + var l, r uint32 + for i := 0; i < 18; i += 2 { + l, r = encryptBlock(l, r, c) + c.p[i], c.p[i+1] = l, r + } + + for i := 0; i < 256; i += 2 { + l, r = encryptBlock(l, r, c) + c.s0[i], c.s0[i+1] = l, r + } + for i := 0; i < 256; i += 2 { + l, r = encryptBlock(l, r, c) + c.s1[i], c.s1[i+1] = l, r + } + for i := 0; i < 256; i += 2 { + l, r = encryptBlock(l, r, c) + c.s2[i], c.s2[i+1] = l, r + } + for i := 0; i < 256; i += 2 { + l, r = encryptBlock(l, r, c) + c.s3[i], c.s3[i+1] = l, r + } +} + +// This is similar to ExpandKey, but folds the salt during the key +// schedule. While ExpandKey is essentially expandKeyWithSalt with an all-zero +// salt passed in, reusing ExpandKey turns out to be a place of inefficiency +// and specializing it here is useful. +func expandKeyWithSalt(key []byte, salt []byte, c *Cipher) { + j := 0 + for i := 0; i < 18; i++ { + c.p[i] ^= getNextWord(key, &j) + } + + j = 0 + var l, r uint32 + for i := 0; i < 18; i += 2 { + l ^= getNextWord(salt, &j) + r ^= getNextWord(salt, &j) + l, r = encryptBlock(l, r, c) + c.p[i], c.p[i+1] = l, r + } + + for i := 0; i < 256; i += 2 { + l ^= getNextWord(salt, &j) + r ^= getNextWord(salt, &j) + l, r = encryptBlock(l, r, c) + c.s0[i], c.s0[i+1] = l, r + } + + for i := 0; i < 256; i += 2 { + l ^= getNextWord(salt, &j) + r ^= getNextWord(salt, &j) + l, r = encryptBlock(l, r, c) + c.s1[i], c.s1[i+1] = l, r + } + + for i := 0; i < 256; i += 2 { + l ^= getNextWord(salt, &j) + r ^= getNextWord(salt, &j) + l, r = encryptBlock(l, r, c) + c.s2[i], c.s2[i+1] = l, r + } + + for i := 0; i < 256; i += 2 { + l ^= getNextWord(salt, &j) + r ^= getNextWord(salt, &j) + l, r = encryptBlock(l, r, c) + c.s3[i], c.s3[i+1] = l, r + } +} + +func encryptBlock(l, r uint32, c *Cipher) (uint32, uint32) { + xl, xr := l, r + xl ^= c.p[0] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[1] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[2] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[3] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[4] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[5] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[6] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[7] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[8] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[9] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[10] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[11] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[12] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[13] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[14] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[15] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[16] + xr ^= c.p[17] + return xr, xl +} + +func decryptBlock(l, r uint32, c *Cipher) (uint32, uint32) { + xl, xr := l, r + xl ^= c.p[17] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[16] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[15] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[14] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[13] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[12] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[11] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[10] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[9] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[8] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[7] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[6] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[5] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[4] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[3] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[2] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[1] + xr ^= c.p[0] + return xr, xl +} diff --git a/vendor/golang.org/x/crypto/blowfish/cipher.go b/vendor/golang.org/x/crypto/blowfish/cipher.go new file mode 100644 index 00000000000..213bf204afe --- /dev/null +++ b/vendor/golang.org/x/crypto/blowfish/cipher.go @@ -0,0 +1,99 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package blowfish implements Bruce Schneier's Blowfish encryption algorithm. +// +// Blowfish is a legacy cipher and its short block size makes it vulnerable to +// birthday bound attacks (see https://sweet32.info). It should only be used +// where compatibility with legacy systems, not security, is the goal. +// +// Deprecated: any new system should use AES (from crypto/aes, if necessary in +// an AEAD mode like crypto/cipher.NewGCM) or XChaCha20-Poly1305 (from +// golang.org/x/crypto/chacha20poly1305). +package blowfish // import "golang.org/x/crypto/blowfish" + +// The code is a port of Bruce Schneier's C implementation. +// See https://www.schneier.com/blowfish.html. + +import "strconv" + +// The Blowfish block size in bytes. +const BlockSize = 8 + +// A Cipher is an instance of Blowfish encryption using a particular key. +type Cipher struct { + p [18]uint32 + s0, s1, s2, s3 [256]uint32 +} + +type KeySizeError int + +func (k KeySizeError) Error() string { + return "crypto/blowfish: invalid key size " + strconv.Itoa(int(k)) +} + +// NewCipher creates and returns a Cipher. +// The key argument should be the Blowfish key, from 1 to 56 bytes. +func NewCipher(key []byte) (*Cipher, error) { + var result Cipher + if k := len(key); k < 1 || k > 56 { + return nil, KeySizeError(k) + } + initCipher(&result) + ExpandKey(key, &result) + return &result, nil +} + +// NewSaltedCipher creates a returns a Cipher that folds a salt into its key +// schedule. For most purposes, NewCipher, instead of NewSaltedCipher, is +// sufficient and desirable. For bcrypt compatibility, the key can be over 56 +// bytes. +func NewSaltedCipher(key, salt []byte) (*Cipher, error) { + if len(salt) == 0 { + return NewCipher(key) + } + var result Cipher + if k := len(key); k < 1 { + return nil, KeySizeError(k) + } + initCipher(&result) + expandKeyWithSalt(key, salt, &result) + return &result, nil +} + +// BlockSize returns the Blowfish block size, 8 bytes. +// It is necessary to satisfy the Block interface in the +// package "crypto/cipher". +func (c *Cipher) BlockSize() int { return BlockSize } + +// Encrypt encrypts the 8-byte buffer src using the key k +// and stores the result in dst. +// Note that for amounts of data larger than a block, +// it is not safe to just call Encrypt on successive blocks; +// instead, use an encryption mode like CBC (see crypto/cipher/cbc.go). +func (c *Cipher) Encrypt(dst, src []byte) { + l := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3]) + r := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7]) + l, r = encryptBlock(l, r, c) + dst[0], dst[1], dst[2], dst[3] = byte(l>>24), byte(l>>16), byte(l>>8), byte(l) + dst[4], dst[5], dst[6], dst[7] = byte(r>>24), byte(r>>16), byte(r>>8), byte(r) +} + +// Decrypt decrypts the 8-byte buffer src using the key k +// and stores the result in dst. +func (c *Cipher) Decrypt(dst, src []byte) { + l := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3]) + r := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7]) + l, r = decryptBlock(l, r, c) + dst[0], dst[1], dst[2], dst[3] = byte(l>>24), byte(l>>16), byte(l>>8), byte(l) + dst[4], dst[5], dst[6], dst[7] = byte(r>>24), byte(r>>16), byte(r>>8), byte(r) +} + +func initCipher(c *Cipher) { + copy(c.p[0:], p[0:]) + copy(c.s0[0:], s0[0:]) + copy(c.s1[0:], s1[0:]) + copy(c.s2[0:], s2[0:]) + copy(c.s3[0:], s3[0:]) +} diff --git a/vendor/golang.org/x/crypto/blowfish/const.go b/vendor/golang.org/x/crypto/blowfish/const.go new file mode 100644 index 00000000000..d04077595ab --- /dev/null +++ b/vendor/golang.org/x/crypto/blowfish/const.go @@ -0,0 +1,199 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// The startup permutation array and substitution boxes. +// They are the hexadecimal digits of PI; see: +// https://www.schneier.com/code/constants.txt. + +package blowfish + +var s0 = [256]uint32{ + 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96, + 0xba7c9045, 0xf12c7f99, 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, + 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, 0x0d95748f, 0x728eb658, + 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, + 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e, + 0x6c9e0e8b, 0xb01e8a3e, 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, + 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, 0x55ca396a, 0x2aab10b6, + 0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, + 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c, + 0x7a325381, 0x28958677, 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, + 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, 0xef845d5d, 0xe98575b1, + 0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, + 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a, + 0x670c9c61, 0xabd388f0, 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, + 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, 0xa1f1651d, 0x39af0176, + 0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, + 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706, + 0x1bfedf72, 0x429b023d, 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, + 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, 0xe3fe501a, 0xb6794c3b, + 0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, + 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c, + 0xcc814544, 0xaf5ebd09, 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, + 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, 0x5579c0bd, 0x1a60320a, + 0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, + 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760, + 0x53317b48, 0x3e00df82, 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, + 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, 0x695b27b0, 0xbbca58c8, + 0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, + 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33, + 0x62fb1341, 0xcee4c6e8, 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, + 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, 0xd08ed1d0, 0xafc725e0, + 0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, + 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777, + 0xea752dfe, 0x8b021fa1, 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, + 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, 0x165fa266, 0x80957705, + 0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, + 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e, + 0x226800bb, 0x57b8e0af, 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, + 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, 0x83260376, 0x6295cfa9, + 0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, + 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f, + 0xf296ec6b, 0x2a0dd915, 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, + 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a, +} + +var s1 = [256]uint32{ + 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d, + 0x9cee60b8, 0x8fedb266, 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, + 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, 0x3f54989a, 0x5b429d65, + 0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, + 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9, + 0x3c971814, 0x6b6a70a1, 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, + 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, 0xb03ada37, 0xf0500c0d, + 0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, + 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc, + 0xc8b57634, 0x9af3dda7, 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, + 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, 0x4e548b38, 0x4f6db908, + 0x6f420d03, 0xf60a04bf, 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, + 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124, + 0x501adde6, 0x9f84cd87, 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, + 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, 0xef1c1847, 0x3215d908, + 0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, + 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b, + 0x3c11183b, 0x5924a509, 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, + 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, 0x771fe71c, 0x4e3d06fa, + 0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, + 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d, + 0x1939260f, 0x19c27960, 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, + 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, 0xc332ddef, 0xbe6c5aa5, + 0x65582185, 0x68ab9802, 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, + 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96, + 0x0334fe1e, 0xaa0363cf, 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, + 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, 0x648b1eaf, 0x19bdf0ca, + 0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, + 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77, + 0x11ed935f, 0x16681281, 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, + 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, 0xcdb30aeb, 0x532e3054, + 0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, + 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea, + 0xdb6c4f15, 0xfacb4fd0, 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, + 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, 0xcf62a1f2, 0x5b8d2646, + 0xfc8883a0, 0xc1c7b6a3, 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, + 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea, + 0x1dadf43e, 0x233f7061, 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, + 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, 0xa6078084, 0x19f8509e, + 0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, + 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd, + 0x675fda79, 0xe3674340, 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, + 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7, +} + +var s2 = [256]uint32{ + 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7, + 0xbcf46b2e, 0xd4a20068, 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, + 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, 0x4d95fc1d, 0x96b591af, + 0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, + 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, 0x28507825, 0x530429f4, + 0x0a2c86da, 0xe9b66dfb, 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, + 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, 0xaace1e7c, 0xd3375fec, + 0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, + 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332, + 0x6841e7f7, 0xca7820fb, 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, + 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, 0x55a867bc, 0xa1159a58, + 0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, + 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22, + 0x48c1133f, 0xc70f86dc, 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, + 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, 0x257b7834, 0x602a9c60, + 0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, + 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, 0x85b2a20e, 0xe6ba0d99, + 0xde720c8c, 0x2da2f728, 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, + 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, 0x0a476341, 0x992eff74, + 0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, + 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3, + 0xb5390f92, 0x690fed0b, 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, + 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, 0x37392eb3, 0xcc115979, + 0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, + 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa, + 0x3d25bdd8, 0xe2e1c3c9, 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, + 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, 0x9dbc8057, 0xf0f7c086, + 0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, + 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, 0x77a057be, 0xbde8ae24, + 0x55464299, 0xbf582e61, 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, + 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, 0x7aeb2661, 0x8b1ddf84, + 0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, + 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09, + 0x662d09a1, 0xc4324633, 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, + 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, 0xdcb7da83, 0x573906fe, + 0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, + 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0, + 0x006058aa, 0x30dc7d62, 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, + 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, 0x6f05e409, 0x4b7c0188, + 0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, + 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8, + 0xa28514d9, 0x6c51133c, 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, + 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0, +} + +var s3 = [256]uint32{ + 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742, + 0xd3822740, 0x99bc9bbe, 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, + 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, 0x5748ab2f, 0xbc946e79, + 0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, + 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a, + 0x63ef8ce2, 0x9a86ee22, 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, + 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, 0x2826a2f9, 0xa73a3ae1, + 0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, + 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797, + 0x2cf0b7d9, 0x022b8b51, 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, + 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, 0xe029ac71, 0xe019a5e6, + 0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, + 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba, + 0x03a16125, 0x0564f0bd, 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, + 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, 0x7533d928, 0xb155fdf5, + 0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, + 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce, + 0x5121ce64, 0x774fbe32, 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, + 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, 0xb39a460a, 0x6445c0dd, + 0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, + 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb, + 0x8d6612ae, 0xbf3c6f47, 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, + 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, 0x4040cb08, 0x4eb4e2cc, + 0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, + 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc, + 0xbb3a792b, 0x344525bd, 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, + 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, 0x1a908749, 0xd44fbd9a, + 0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, + 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a, + 0x0f91fc71, 0x9b941525, 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, + 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, 0xe0ec6e0e, 0x1698db3b, + 0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, + 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e, + 0xe60b6f47, 0x0fe3f11d, 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, + 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, 0xf523f357, 0xa6327623, + 0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, + 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a, + 0x45e1d006, 0xc3f27b9a, 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, + 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, 0x53113ec0, 0x1640e3d3, + 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, + 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c, + 0x01c36ae4, 0xd6ebe1f9, 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, + 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6, +} + +var p = [18]uint32{ + 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0, + 0x082efa98, 0xec4e6c89, 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, + 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, 0x9216d5d9, 0x8979fb1b, +} diff --git a/vendor/golang.org/x/crypto/chacha20/chacha_arm64.go b/vendor/golang.org/x/crypto/chacha20/chacha_arm64.go new file mode 100644 index 00000000000..87f1e369cc2 --- /dev/null +++ b/vendor/golang.org/x/crypto/chacha20/chacha_arm64.go @@ -0,0 +1,17 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build go1.11 +// +build !gccgo,!appengine + +package chacha20 + +const bufSize = 256 + +//go:noescape +func xorKeyStreamVX(dst, src []byte, key *[8]uint32, nonce *[3]uint32, counter *uint32) + +func (c *Cipher) xorKeyStreamBlocks(dst, src []byte) { + xorKeyStreamVX(dst, src, &c.key, &c.nonce, &c.counter) +} diff --git a/vendor/golang.org/x/crypto/chacha20/chacha_arm64.s b/vendor/golang.org/x/crypto/chacha20/chacha_arm64.s new file mode 100644 index 00000000000..b3a16ef751a --- /dev/null +++ b/vendor/golang.org/x/crypto/chacha20/chacha_arm64.s @@ -0,0 +1,308 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build go1.11 +// +build !gccgo,!appengine + +#include "textflag.h" + +#define NUM_ROUNDS 10 + +// func xorKeyStreamVX(dst, src []byte, key *[8]uint32, nonce *[3]uint32, counter *uint32) +TEXT ·xorKeyStreamVX(SB), NOSPLIT, $0 + MOVD dst+0(FP), R1 + MOVD src+24(FP), R2 + MOVD src_len+32(FP), R3 + MOVD key+48(FP), R4 + MOVD nonce+56(FP), R6 + MOVD counter+64(FP), R7 + + MOVD $·constants(SB), R10 + MOVD $·incRotMatrix(SB), R11 + + MOVW (R7), R20 + + AND $~255, R3, R13 + ADD R2, R13, R12 // R12 for block end + AND $255, R3, R13 +loop: + MOVD $NUM_ROUNDS, R21 + VLD1 (R11), [V30.S4, V31.S4] + + // load contants + // VLD4R (R10), [V0.S4, V1.S4, V2.S4, V3.S4] + WORD $0x4D60E940 + + // load keys + // VLD4R 16(R4), [V4.S4, V5.S4, V6.S4, V7.S4] + WORD $0x4DFFE884 + // VLD4R 16(R4), [V8.S4, V9.S4, V10.S4, V11.S4] + WORD $0x4DFFE888 + SUB $32, R4 + + // load counter + nonce + // VLD1R (R7), [V12.S4] + WORD $0x4D40C8EC + + // VLD3R (R6), [V13.S4, V14.S4, V15.S4] + WORD $0x4D40E8CD + + // update counter + VADD V30.S4, V12.S4, V12.S4 + +chacha: + // V0..V3 += V4..V7 + // V12..V15 <<<= ((V12..V15 XOR V0..V3), 16) + VADD V0.S4, V4.S4, V0.S4 + VADD V1.S4, V5.S4, V1.S4 + VADD V2.S4, V6.S4, V2.S4 + VADD V3.S4, V7.S4, V3.S4 + VEOR V12.B16, V0.B16, V12.B16 + VEOR V13.B16, V1.B16, V13.B16 + VEOR V14.B16, V2.B16, V14.B16 + VEOR V15.B16, V3.B16, V15.B16 + VREV32 V12.H8, V12.H8 + VREV32 V13.H8, V13.H8 + VREV32 V14.H8, V14.H8 + VREV32 V15.H8, V15.H8 + // V8..V11 += V12..V15 + // V4..V7 <<<= ((V4..V7 XOR V8..V11), 12) + VADD V8.S4, V12.S4, V8.S4 + VADD V9.S4, V13.S4, V9.S4 + VADD V10.S4, V14.S4, V10.S4 + VADD V11.S4, V15.S4, V11.S4 + VEOR V8.B16, V4.B16, V16.B16 + VEOR V9.B16, V5.B16, V17.B16 + VEOR V10.B16, V6.B16, V18.B16 + VEOR V11.B16, V7.B16, V19.B16 + VSHL $12, V16.S4, V4.S4 + VSHL $12, V17.S4, V5.S4 + VSHL $12, V18.S4, V6.S4 + VSHL $12, V19.S4, V7.S4 + VSRI $20, V16.S4, V4.S4 + VSRI $20, V17.S4, V5.S4 + VSRI $20, V18.S4, V6.S4 + VSRI $20, V19.S4, V7.S4 + + // V0..V3 += V4..V7 + // V12..V15 <<<= ((V12..V15 XOR V0..V3), 8) + VADD V0.S4, V4.S4, V0.S4 + VADD V1.S4, V5.S4, V1.S4 + VADD V2.S4, V6.S4, V2.S4 + VADD V3.S4, V7.S4, V3.S4 + VEOR V12.B16, V0.B16, V12.B16 + VEOR V13.B16, V1.B16, V13.B16 + VEOR V14.B16, V2.B16, V14.B16 + VEOR V15.B16, V3.B16, V15.B16 + VTBL V31.B16, [V12.B16], V12.B16 + VTBL V31.B16, [V13.B16], V13.B16 + VTBL V31.B16, [V14.B16], V14.B16 + VTBL V31.B16, [V15.B16], V15.B16 + + // V8..V11 += V12..V15 + // V4..V7 <<<= ((V4..V7 XOR V8..V11), 7) + VADD V12.S4, V8.S4, V8.S4 + VADD V13.S4, V9.S4, V9.S4 + VADD V14.S4, V10.S4, V10.S4 + VADD V15.S4, V11.S4, V11.S4 + VEOR V8.B16, V4.B16, V16.B16 + VEOR V9.B16, V5.B16, V17.B16 + VEOR V10.B16, V6.B16, V18.B16 + VEOR V11.B16, V7.B16, V19.B16 + VSHL $7, V16.S4, V4.S4 + VSHL $7, V17.S4, V5.S4 + VSHL $7, V18.S4, V6.S4 + VSHL $7, V19.S4, V7.S4 + VSRI $25, V16.S4, V4.S4 + VSRI $25, V17.S4, V5.S4 + VSRI $25, V18.S4, V6.S4 + VSRI $25, V19.S4, V7.S4 + + // V0..V3 += V5..V7, V4 + // V15,V12-V14 <<<= ((V15,V12-V14 XOR V0..V3), 16) + VADD V0.S4, V5.S4, V0.S4 + VADD V1.S4, V6.S4, V1.S4 + VADD V2.S4, V7.S4, V2.S4 + VADD V3.S4, V4.S4, V3.S4 + VEOR V15.B16, V0.B16, V15.B16 + VEOR V12.B16, V1.B16, V12.B16 + VEOR V13.B16, V2.B16, V13.B16 + VEOR V14.B16, V3.B16, V14.B16 + VREV32 V12.H8, V12.H8 + VREV32 V13.H8, V13.H8 + VREV32 V14.H8, V14.H8 + VREV32 V15.H8, V15.H8 + + // V10 += V15; V5 <<<= ((V10 XOR V5), 12) + // ... + VADD V15.S4, V10.S4, V10.S4 + VADD V12.S4, V11.S4, V11.S4 + VADD V13.S4, V8.S4, V8.S4 + VADD V14.S4, V9.S4, V9.S4 + VEOR V10.B16, V5.B16, V16.B16 + VEOR V11.B16, V6.B16, V17.B16 + VEOR V8.B16, V7.B16, V18.B16 + VEOR V9.B16, V4.B16, V19.B16 + VSHL $12, V16.S4, V5.S4 + VSHL $12, V17.S4, V6.S4 + VSHL $12, V18.S4, V7.S4 + VSHL $12, V19.S4, V4.S4 + VSRI $20, V16.S4, V5.S4 + VSRI $20, V17.S4, V6.S4 + VSRI $20, V18.S4, V7.S4 + VSRI $20, V19.S4, V4.S4 + + // V0 += V5; V15 <<<= ((V0 XOR V15), 8) + // ... + VADD V5.S4, V0.S4, V0.S4 + VADD V6.S4, V1.S4, V1.S4 + VADD V7.S4, V2.S4, V2.S4 + VADD V4.S4, V3.S4, V3.S4 + VEOR V0.B16, V15.B16, V15.B16 + VEOR V1.B16, V12.B16, V12.B16 + VEOR V2.B16, V13.B16, V13.B16 + VEOR V3.B16, V14.B16, V14.B16 + VTBL V31.B16, [V12.B16], V12.B16 + VTBL V31.B16, [V13.B16], V13.B16 + VTBL V31.B16, [V14.B16], V14.B16 + VTBL V31.B16, [V15.B16], V15.B16 + + // V10 += V15; V5 <<<= ((V10 XOR V5), 7) + // ... + VADD V15.S4, V10.S4, V10.S4 + VADD V12.S4, V11.S4, V11.S4 + VADD V13.S4, V8.S4, V8.S4 + VADD V14.S4, V9.S4, V9.S4 + VEOR V10.B16, V5.B16, V16.B16 + VEOR V11.B16, V6.B16, V17.B16 + VEOR V8.B16, V7.B16, V18.B16 + VEOR V9.B16, V4.B16, V19.B16 + VSHL $7, V16.S4, V5.S4 + VSHL $7, V17.S4, V6.S4 + VSHL $7, V18.S4, V7.S4 + VSHL $7, V19.S4, V4.S4 + VSRI $25, V16.S4, V5.S4 + VSRI $25, V17.S4, V6.S4 + VSRI $25, V18.S4, V7.S4 + VSRI $25, V19.S4, V4.S4 + + SUB $1, R21 + CBNZ R21, chacha + + // VLD4R (R10), [V16.S4, V17.S4, V18.S4, V19.S4] + WORD $0x4D60E950 + + // VLD4R 16(R4), [V20.S4, V21.S4, V22.S4, V23.S4] + WORD $0x4DFFE894 + VADD V30.S4, V12.S4, V12.S4 + VADD V16.S4, V0.S4, V0.S4 + VADD V17.S4, V1.S4, V1.S4 + VADD V18.S4, V2.S4, V2.S4 + VADD V19.S4, V3.S4, V3.S4 + // VLD4R 16(R4), [V24.S4, V25.S4, V26.S4, V27.S4] + WORD $0x4DFFE898 + // restore R4 + SUB $32, R4 + + // load counter + nonce + // VLD1R (R7), [V28.S4] + WORD $0x4D40C8FC + // VLD3R (R6), [V29.S4, V30.S4, V31.S4] + WORD $0x4D40E8DD + + VADD V20.S4, V4.S4, V4.S4 + VADD V21.S4, V5.S4, V5.S4 + VADD V22.S4, V6.S4, V6.S4 + VADD V23.S4, V7.S4, V7.S4 + VADD V24.S4, V8.S4, V8.S4 + VADD V25.S4, V9.S4, V9.S4 + VADD V26.S4, V10.S4, V10.S4 + VADD V27.S4, V11.S4, V11.S4 + VADD V28.S4, V12.S4, V12.S4 + VADD V29.S4, V13.S4, V13.S4 + VADD V30.S4, V14.S4, V14.S4 + VADD V31.S4, V15.S4, V15.S4 + + VZIP1 V1.S4, V0.S4, V16.S4 + VZIP2 V1.S4, V0.S4, V17.S4 + VZIP1 V3.S4, V2.S4, V18.S4 + VZIP2 V3.S4, V2.S4, V19.S4 + VZIP1 V5.S4, V4.S4, V20.S4 + VZIP2 V5.S4, V4.S4, V21.S4 + VZIP1 V7.S4, V6.S4, V22.S4 + VZIP2 V7.S4, V6.S4, V23.S4 + VZIP1 V9.S4, V8.S4, V24.S4 + VZIP2 V9.S4, V8.S4, V25.S4 + VZIP1 V11.S4, V10.S4, V26.S4 + VZIP2 V11.S4, V10.S4, V27.S4 + VZIP1 V13.S4, V12.S4, V28.S4 + VZIP2 V13.S4, V12.S4, V29.S4 + VZIP1 V15.S4, V14.S4, V30.S4 + VZIP2 V15.S4, V14.S4, V31.S4 + VZIP1 V18.D2, V16.D2, V0.D2 + VZIP2 V18.D2, V16.D2, V4.D2 + VZIP1 V19.D2, V17.D2, V8.D2 + VZIP2 V19.D2, V17.D2, V12.D2 + VLD1.P 64(R2), [V16.B16, V17.B16, V18.B16, V19.B16] + + VZIP1 V22.D2, V20.D2, V1.D2 + VZIP2 V22.D2, V20.D2, V5.D2 + VZIP1 V23.D2, V21.D2, V9.D2 + VZIP2 V23.D2, V21.D2, V13.D2 + VLD1.P 64(R2), [V20.B16, V21.B16, V22.B16, V23.B16] + VZIP1 V26.D2, V24.D2, V2.D2 + VZIP2 V26.D2, V24.D2, V6.D2 + VZIP1 V27.D2, V25.D2, V10.D2 + VZIP2 V27.D2, V25.D2, V14.D2 + VLD1.P 64(R2), [V24.B16, V25.B16, V26.B16, V27.B16] + VZIP1 V30.D2, V28.D2, V3.D2 + VZIP2 V30.D2, V28.D2, V7.D2 + VZIP1 V31.D2, V29.D2, V11.D2 + VZIP2 V31.D2, V29.D2, V15.D2 + VLD1.P 64(R2), [V28.B16, V29.B16, V30.B16, V31.B16] + VEOR V0.B16, V16.B16, V16.B16 + VEOR V1.B16, V17.B16, V17.B16 + VEOR V2.B16, V18.B16, V18.B16 + VEOR V3.B16, V19.B16, V19.B16 + VST1.P [V16.B16, V17.B16, V18.B16, V19.B16], 64(R1) + VEOR V4.B16, V20.B16, V20.B16 + VEOR V5.B16, V21.B16, V21.B16 + VEOR V6.B16, V22.B16, V22.B16 + VEOR V7.B16, V23.B16, V23.B16 + VST1.P [V20.B16, V21.B16, V22.B16, V23.B16], 64(R1) + VEOR V8.B16, V24.B16, V24.B16 + VEOR V9.B16, V25.B16, V25.B16 + VEOR V10.B16, V26.B16, V26.B16 + VEOR V11.B16, V27.B16, V27.B16 + VST1.P [V24.B16, V25.B16, V26.B16, V27.B16], 64(R1) + VEOR V12.B16, V28.B16, V28.B16 + VEOR V13.B16, V29.B16, V29.B16 + VEOR V14.B16, V30.B16, V30.B16 + VEOR V15.B16, V31.B16, V31.B16 + VST1.P [V28.B16, V29.B16, V30.B16, V31.B16], 64(R1) + + ADD $4, R20 + MOVW R20, (R7) // update counter + + CMP R2, R12 + BGT loop + + RET + + +DATA ·constants+0x00(SB)/4, $0x61707865 +DATA ·constants+0x04(SB)/4, $0x3320646e +DATA ·constants+0x08(SB)/4, $0x79622d32 +DATA ·constants+0x0c(SB)/4, $0x6b206574 +GLOBL ·constants(SB), NOPTR|RODATA, $32 + +DATA ·incRotMatrix+0x00(SB)/4, $0x00000000 +DATA ·incRotMatrix+0x04(SB)/4, $0x00000001 +DATA ·incRotMatrix+0x08(SB)/4, $0x00000002 +DATA ·incRotMatrix+0x0c(SB)/4, $0x00000003 +DATA ·incRotMatrix+0x10(SB)/4, $0x02010003 +DATA ·incRotMatrix+0x14(SB)/4, $0x06050407 +DATA ·incRotMatrix+0x18(SB)/4, $0x0A09080B +DATA ·incRotMatrix+0x1c(SB)/4, $0x0E0D0C0F +GLOBL ·incRotMatrix(SB), NOPTR|RODATA, $32 diff --git a/vendor/golang.org/x/crypto/chacha20/chacha_generic.go b/vendor/golang.org/x/crypto/chacha20/chacha_generic.go new file mode 100644 index 00000000000..098ec9f6be0 --- /dev/null +++ b/vendor/golang.org/x/crypto/chacha20/chacha_generic.go @@ -0,0 +1,364 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package chacha20 implements the ChaCha20 and XChaCha20 encryption algorithms +// as specified in RFC 8439 and draft-irtf-cfrg-xchacha-01. +package chacha20 + +import ( + "crypto/cipher" + "encoding/binary" + "errors" + "math/bits" + + "golang.org/x/crypto/internal/subtle" +) + +const ( + // KeySize is the size of the key used by this cipher, in bytes. + KeySize = 32 + + // NonceSize is the size of the nonce used with the standard variant of this + // cipher, in bytes. + // + // Note that this is too short to be safely generated at random if the same + // key is reused more than 2³² times. + NonceSize = 12 + + // NonceSizeX is the size of the nonce used with the XChaCha20 variant of + // this cipher, in bytes. + NonceSizeX = 24 +) + +// Cipher is a stateful instance of ChaCha20 or XChaCha20 using a particular key +// and nonce. A *Cipher implements the cipher.Stream interface. +type Cipher struct { + // The ChaCha20 state is 16 words: 4 constant, 8 of key, 1 of counter + // (incremented after each block), and 3 of nonce. + key [8]uint32 + counter uint32 + nonce [3]uint32 + + // The last len bytes of buf are leftover key stream bytes from the previous + // XORKeyStream invocation. The size of buf depends on how many blocks are + // computed at a time. + buf [bufSize]byte + len int + + // The counter-independent results of the first round are cached after they + // are computed the first time. + precompDone bool + p1, p5, p9, p13 uint32 + p2, p6, p10, p14 uint32 + p3, p7, p11, p15 uint32 +} + +var _ cipher.Stream = (*Cipher)(nil) + +// NewUnauthenticatedCipher creates a new ChaCha20 stream cipher with the given +// 32 bytes key and a 12 or 24 bytes nonce. If a nonce of 24 bytes is provided, +// the XChaCha20 construction will be used. It returns an error if key or nonce +// have any other length. +// +// Note that ChaCha20, like all stream ciphers, is not authenticated and allows +// attackers to silently tamper with the plaintext. For this reason, it is more +// appropriate as a building block than as a standalone encryption mechanism. +// Instead, consider using package golang.org/x/crypto/chacha20poly1305. +func NewUnauthenticatedCipher(key, nonce []byte) (*Cipher, error) { + // This function is split into a wrapper so that the Cipher allocation will + // be inlined, and depending on how the caller uses the return value, won't + // escape to the heap. + c := &Cipher{} + return newUnauthenticatedCipher(c, key, nonce) +} + +func newUnauthenticatedCipher(c *Cipher, key, nonce []byte) (*Cipher, error) { + if len(key) != KeySize { + return nil, errors.New("chacha20: wrong key size") + } + if len(nonce) == NonceSizeX { + // XChaCha20 uses the ChaCha20 core to mix 16 bytes of the nonce into a + // derived key, allowing it to operate on a nonce of 24 bytes. See + // draft-irtf-cfrg-xchacha-01, Section 2.3. + key, _ = HChaCha20(key, nonce[0:16]) + cNonce := make([]byte, NonceSize) + copy(cNonce[4:12], nonce[16:24]) + nonce = cNonce + } else if len(nonce) != NonceSize { + return nil, errors.New("chacha20: wrong nonce size") + } + + c.key = [8]uint32{ + binary.LittleEndian.Uint32(key[0:4]), + binary.LittleEndian.Uint32(key[4:8]), + binary.LittleEndian.Uint32(key[8:12]), + binary.LittleEndian.Uint32(key[12:16]), + binary.LittleEndian.Uint32(key[16:20]), + binary.LittleEndian.Uint32(key[20:24]), + binary.LittleEndian.Uint32(key[24:28]), + binary.LittleEndian.Uint32(key[28:32]), + } + c.nonce = [3]uint32{ + binary.LittleEndian.Uint32(nonce[0:4]), + binary.LittleEndian.Uint32(nonce[4:8]), + binary.LittleEndian.Uint32(nonce[8:12]), + } + return c, nil +} + +// The constant first 4 words of the ChaCha20 state. +const ( + j0 uint32 = 0x61707865 // expa + j1 uint32 = 0x3320646e // nd 3 + j2 uint32 = 0x79622d32 // 2-by + j3 uint32 = 0x6b206574 // te k +) + +const blockSize = 64 + +// quarterRound is the core of ChaCha20. It shuffles the bits of 4 state words. +// It's executed 4 times for each of the 20 ChaCha20 rounds, operating on all 16 +// words each round, in columnar or diagonal groups of 4 at a time. +func quarterRound(a, b, c, d uint32) (uint32, uint32, uint32, uint32) { + a += b + d ^= a + d = bits.RotateLeft32(d, 16) + c += d + b ^= c + b = bits.RotateLeft32(b, 12) + a += b + d ^= a + d = bits.RotateLeft32(d, 8) + c += d + b ^= c + b = bits.RotateLeft32(b, 7) + return a, b, c, d +} + +// XORKeyStream XORs each byte in the given slice with a byte from the +// cipher's key stream. Dst and src must overlap entirely or not at all. +// +// If len(dst) < len(src), XORKeyStream will panic. It is acceptable +// to pass a dst bigger than src, and in that case, XORKeyStream will +// only update dst[:len(src)] and will not touch the rest of dst. +// +// Multiple calls to XORKeyStream behave as if the concatenation of +// the src buffers was passed in a single run. That is, Cipher +// maintains state and does not reset at each XORKeyStream call. +func (s *Cipher) XORKeyStream(dst, src []byte) { + if len(src) == 0 { + return + } + if len(dst) < len(src) { + panic("chacha20: output smaller than input") + } + dst = dst[:len(src)] + if subtle.InexactOverlap(dst, src) { + panic("chacha20: invalid buffer overlap") + } + + // First, drain any remaining key stream from a previous XORKeyStream. + if s.len != 0 { + keyStream := s.buf[bufSize-s.len:] + if len(src) < len(keyStream) { + keyStream = keyStream[:len(src)] + } + _ = src[len(keyStream)-1] // bounds check elimination hint + for i, b := range keyStream { + dst[i] = src[i] ^ b + } + s.len -= len(keyStream) + src = src[len(keyStream):] + dst = dst[len(keyStream):] + } + + const blocksPerBuf = bufSize / blockSize + numBufs := (uint64(len(src)) + bufSize - 1) / bufSize + if uint64(s.counter)+numBufs*blocksPerBuf >= 1<<32 { + panic("chacha20: counter overflow") + } + + // xorKeyStreamBlocks implementations expect input lengths that are a + // multiple of bufSize. Platform-specific ones process multiple blocks at a + // time, so have bufSizes that are a multiple of blockSize. + + rem := len(src) % bufSize + full := len(src) - rem + + if full > 0 { + s.xorKeyStreamBlocks(dst[:full], src[:full]) + } + + // If we have a partial (multi-)block, pad it for xorKeyStreamBlocks, and + // keep the leftover keystream for the next XORKeyStream invocation. + if rem > 0 { + s.buf = [bufSize]byte{} + copy(s.buf[:], src[full:]) + s.xorKeyStreamBlocks(s.buf[:], s.buf[:]) + s.len = bufSize - copy(dst[full:], s.buf[:]) + } +} + +func (s *Cipher) xorKeyStreamBlocksGeneric(dst, src []byte) { + if len(dst) != len(src) || len(dst)%blockSize != 0 { + panic("chacha20: internal error: wrong dst and/or src length") + } + + // To generate each block of key stream, the initial cipher state + // (represented below) is passed through 20 rounds of shuffling, + // alternatively applying quarterRounds by columns (like 1, 5, 9, 13) + // or by diagonals (like 1, 6, 11, 12). + // + // 0:cccccccc 1:cccccccc 2:cccccccc 3:cccccccc + // 4:kkkkkkkk 5:kkkkkkkk 6:kkkkkkkk 7:kkkkkkkk + // 8:kkkkkkkk 9:kkkkkkkk 10:kkkkkkkk 11:kkkkkkkk + // 12:bbbbbbbb 13:nnnnnnnn 14:nnnnnnnn 15:nnnnnnnn + // + // c=constant k=key b=blockcount n=nonce + var ( + c0, c1, c2, c3 = j0, j1, j2, j3 + c4, c5, c6, c7 = s.key[0], s.key[1], s.key[2], s.key[3] + c8, c9, c10, c11 = s.key[4], s.key[5], s.key[6], s.key[7] + _, c13, c14, c15 = s.counter, s.nonce[0], s.nonce[1], s.nonce[2] + ) + + // Three quarters of the first round don't depend on the counter, so we can + // calculate them here, and reuse them for multiple blocks in the loop, and + // for future XORKeyStream invocations. + if !s.precompDone { + s.p1, s.p5, s.p9, s.p13 = quarterRound(c1, c5, c9, c13) + s.p2, s.p6, s.p10, s.p14 = quarterRound(c2, c6, c10, c14) + s.p3, s.p7, s.p11, s.p15 = quarterRound(c3, c7, c11, c15) + s.precompDone = true + } + + for i := 0; i < len(src); i += blockSize { + // The remainder of the first column round. + fcr0, fcr4, fcr8, fcr12 := quarterRound(c0, c4, c8, s.counter) + + // The second diagonal round. + x0, x5, x10, x15 := quarterRound(fcr0, s.p5, s.p10, s.p15) + x1, x6, x11, x12 := quarterRound(s.p1, s.p6, s.p11, fcr12) + x2, x7, x8, x13 := quarterRound(s.p2, s.p7, fcr8, s.p13) + x3, x4, x9, x14 := quarterRound(s.p3, fcr4, s.p9, s.p14) + + // The remaining 18 rounds. + for i := 0; i < 9; i++ { + // Column round. + x0, x4, x8, x12 = quarterRound(x0, x4, x8, x12) + x1, x5, x9, x13 = quarterRound(x1, x5, x9, x13) + x2, x6, x10, x14 = quarterRound(x2, x6, x10, x14) + x3, x7, x11, x15 = quarterRound(x3, x7, x11, x15) + + // Diagonal round. + x0, x5, x10, x15 = quarterRound(x0, x5, x10, x15) + x1, x6, x11, x12 = quarterRound(x1, x6, x11, x12) + x2, x7, x8, x13 = quarterRound(x2, x7, x8, x13) + x3, x4, x9, x14 = quarterRound(x3, x4, x9, x14) + } + + // Finally, add back the initial state to generate the key stream. + x0 += c0 + x1 += c1 + x2 += c2 + x3 += c3 + x4 += c4 + x5 += c5 + x6 += c6 + x7 += c7 + x8 += c8 + x9 += c9 + x10 += c10 + x11 += c11 + x12 += s.counter + x13 += c13 + x14 += c14 + x15 += c15 + + s.counter += 1 + if s.counter == 0 { + panic("chacha20: internal error: counter overflow") + } + + in, out := src[i:], dst[i:] + in, out = in[:blockSize], out[:blockSize] // bounds check elimination hint + + // XOR the key stream with the source and write out the result. + xor(out[0:], in[0:], x0) + xor(out[4:], in[4:], x1) + xor(out[8:], in[8:], x2) + xor(out[12:], in[12:], x3) + xor(out[16:], in[16:], x4) + xor(out[20:], in[20:], x5) + xor(out[24:], in[24:], x6) + xor(out[28:], in[28:], x7) + xor(out[32:], in[32:], x8) + xor(out[36:], in[36:], x9) + xor(out[40:], in[40:], x10) + xor(out[44:], in[44:], x11) + xor(out[48:], in[48:], x12) + xor(out[52:], in[52:], x13) + xor(out[56:], in[56:], x14) + xor(out[60:], in[60:], x15) + } +} + +// HChaCha20 uses the ChaCha20 core to generate a derived key from a 32 bytes +// key and a 16 bytes nonce. It returns an error if key or nonce have any other +// length. It is used as part of the XChaCha20 construction. +func HChaCha20(key, nonce []byte) ([]byte, error) { + // This function is split into a wrapper so that the slice allocation will + // be inlined, and depending on how the caller uses the return value, won't + // escape to the heap. + out := make([]byte, 32) + return hChaCha20(out, key, nonce) +} + +func hChaCha20(out, key, nonce []byte) ([]byte, error) { + if len(key) != KeySize { + return nil, errors.New("chacha20: wrong HChaCha20 key size") + } + if len(nonce) != 16 { + return nil, errors.New("chacha20: wrong HChaCha20 nonce size") + } + + x0, x1, x2, x3 := j0, j1, j2, j3 + x4 := binary.LittleEndian.Uint32(key[0:4]) + x5 := binary.LittleEndian.Uint32(key[4:8]) + x6 := binary.LittleEndian.Uint32(key[8:12]) + x7 := binary.LittleEndian.Uint32(key[12:16]) + x8 := binary.LittleEndian.Uint32(key[16:20]) + x9 := binary.LittleEndian.Uint32(key[20:24]) + x10 := binary.LittleEndian.Uint32(key[24:28]) + x11 := binary.LittleEndian.Uint32(key[28:32]) + x12 := binary.LittleEndian.Uint32(nonce[0:4]) + x13 := binary.LittleEndian.Uint32(nonce[4:8]) + x14 := binary.LittleEndian.Uint32(nonce[8:12]) + x15 := binary.LittleEndian.Uint32(nonce[12:16]) + + for i := 0; i < 10; i++ { + // Diagonal round. + x0, x4, x8, x12 = quarterRound(x0, x4, x8, x12) + x1, x5, x9, x13 = quarterRound(x1, x5, x9, x13) + x2, x6, x10, x14 = quarterRound(x2, x6, x10, x14) + x3, x7, x11, x15 = quarterRound(x3, x7, x11, x15) + + // Column round. + x0, x5, x10, x15 = quarterRound(x0, x5, x10, x15) + x1, x6, x11, x12 = quarterRound(x1, x6, x11, x12) + x2, x7, x8, x13 = quarterRound(x2, x7, x8, x13) + x3, x4, x9, x14 = quarterRound(x3, x4, x9, x14) + } + + _ = out[31] // bounds check elimination hint + binary.LittleEndian.PutUint32(out[0:4], x0) + binary.LittleEndian.PutUint32(out[4:8], x1) + binary.LittleEndian.PutUint32(out[8:12], x2) + binary.LittleEndian.PutUint32(out[12:16], x3) + binary.LittleEndian.PutUint32(out[16:20], x12) + binary.LittleEndian.PutUint32(out[20:24], x13) + binary.LittleEndian.PutUint32(out[24:28], x14) + binary.LittleEndian.PutUint32(out[28:32], x15) + return out, nil +} diff --git a/vendor/golang.org/x/crypto/chacha20/chacha_noasm.go b/vendor/golang.org/x/crypto/chacha20/chacha_noasm.go new file mode 100644 index 00000000000..ec609ed868b --- /dev/null +++ b/vendor/golang.org/x/crypto/chacha20/chacha_noasm.go @@ -0,0 +1,13 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !arm64,!s390x,!ppc64le arm64,!go1.11 gccgo appengine + +package chacha20 + +const bufSize = blockSize + +func (s *Cipher) xorKeyStreamBlocks(dst, src []byte) { + s.xorKeyStreamBlocksGeneric(dst, src) +} diff --git a/vendor/golang.org/x/crypto/chacha20/chacha_ppc64le.go b/vendor/golang.org/x/crypto/chacha20/chacha_ppc64le.go new file mode 100644 index 00000000000..d0ec61f08d9 --- /dev/null +++ b/vendor/golang.org/x/crypto/chacha20/chacha_ppc64le.go @@ -0,0 +1,16 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !gccgo,!appengine + +package chacha20 + +const bufSize = 256 + +//go:noescape +func chaCha20_ctr32_vsx(out, inp *byte, len int, key *[8]uint32, counter *uint32) + +func (c *Cipher) xorKeyStreamBlocks(dst, src []byte) { + chaCha20_ctr32_vsx(&dst[0], &src[0], len(src), &c.key, &c.counter) +} diff --git a/vendor/golang.org/x/crypto/chacha20/chacha_ppc64le.s b/vendor/golang.org/x/crypto/chacha20/chacha_ppc64le.s new file mode 100644 index 00000000000..533014ea3e8 --- /dev/null +++ b/vendor/golang.org/x/crypto/chacha20/chacha_ppc64le.s @@ -0,0 +1,449 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Based on CRYPTOGAMS code with the following comment: +// # ==================================================================== +// # Written by Andy Polyakov for the OpenSSL +// # project. The module is, however, dual licensed under OpenSSL and +// # CRYPTOGAMS licenses depending on where you obtain it. For further +// # details see http://www.openssl.org/~appro/cryptogams/. +// # ==================================================================== + +// Code for the perl script that generates the ppc64 assembler +// can be found in the cryptogams repository at the link below. It is based on +// the original from openssl. + +// https://github.com/dot-asm/cryptogams/commit/a60f5b50ed908e91 + +// The differences in this and the original implementation are +// due to the calling conventions and initialization of constants. + +// +build !gccgo,!appengine + +#include "textflag.h" + +#define OUT R3 +#define INP R4 +#define LEN R5 +#define KEY R6 +#define CNT R7 +#define TMP R15 + +#define CONSTBASE R16 +#define BLOCKS R17 + +DATA consts<>+0x00(SB)/8, $0x3320646e61707865 +DATA consts<>+0x08(SB)/8, $0x6b20657479622d32 +DATA consts<>+0x10(SB)/8, $0x0000000000000001 +DATA consts<>+0x18(SB)/8, $0x0000000000000000 +DATA consts<>+0x20(SB)/8, $0x0000000000000004 +DATA consts<>+0x28(SB)/8, $0x0000000000000000 +DATA consts<>+0x30(SB)/8, $0x0a0b08090e0f0c0d +DATA consts<>+0x38(SB)/8, $0x0203000106070405 +DATA consts<>+0x40(SB)/8, $0x090a0b080d0e0f0c +DATA consts<>+0x48(SB)/8, $0x0102030005060704 +DATA consts<>+0x50(SB)/8, $0x6170786561707865 +DATA consts<>+0x58(SB)/8, $0x6170786561707865 +DATA consts<>+0x60(SB)/8, $0x3320646e3320646e +DATA consts<>+0x68(SB)/8, $0x3320646e3320646e +DATA consts<>+0x70(SB)/8, $0x79622d3279622d32 +DATA consts<>+0x78(SB)/8, $0x79622d3279622d32 +DATA consts<>+0x80(SB)/8, $0x6b2065746b206574 +DATA consts<>+0x88(SB)/8, $0x6b2065746b206574 +DATA consts<>+0x90(SB)/8, $0x0000000100000000 +DATA consts<>+0x98(SB)/8, $0x0000000300000002 +GLOBL consts<>(SB), RODATA, $0xa0 + +//func chaCha20_ctr32_vsx(out, inp *byte, len int, key *[8]uint32, counter *uint32) +TEXT ·chaCha20_ctr32_vsx(SB),NOSPLIT,$64-40 + MOVD out+0(FP), OUT + MOVD inp+8(FP), INP + MOVD len+16(FP), LEN + MOVD key+24(FP), KEY + MOVD counter+32(FP), CNT + + // Addressing for constants + MOVD $consts<>+0x00(SB), CONSTBASE + MOVD $16, R8 + MOVD $32, R9 + MOVD $48, R10 + MOVD $64, R11 + SRD $6, LEN, BLOCKS + // V16 + LXVW4X (CONSTBASE)(R0), VS48 + ADD $80,CONSTBASE + + // Load key into V17,V18 + LXVW4X (KEY)(R0), VS49 + LXVW4X (KEY)(R8), VS50 + + // Load CNT, NONCE into V19 + LXVW4X (CNT)(R0), VS51 + + // Clear V27 + VXOR V27, V27, V27 + + // V28 + LXVW4X (CONSTBASE)(R11), VS60 + + // splat slot from V19 -> V26 + VSPLTW $0, V19, V26 + + VSLDOI $4, V19, V27, V19 + VSLDOI $12, V27, V19, V19 + + VADDUWM V26, V28, V26 + + MOVD $10, R14 + MOVD R14, CTR + +loop_outer_vsx: + // V0, V1, V2, V3 + LXVW4X (R0)(CONSTBASE), VS32 + LXVW4X (R8)(CONSTBASE), VS33 + LXVW4X (R9)(CONSTBASE), VS34 + LXVW4X (R10)(CONSTBASE), VS35 + + // splat values from V17, V18 into V4-V11 + VSPLTW $0, V17, V4 + VSPLTW $1, V17, V5 + VSPLTW $2, V17, V6 + VSPLTW $3, V17, V7 + VSPLTW $0, V18, V8 + VSPLTW $1, V18, V9 + VSPLTW $2, V18, V10 + VSPLTW $3, V18, V11 + + // VOR + VOR V26, V26, V12 + + // splat values from V19 -> V13, V14, V15 + VSPLTW $1, V19, V13 + VSPLTW $2, V19, V14 + VSPLTW $3, V19, V15 + + // splat const values + VSPLTISW $-16, V27 + VSPLTISW $12, V28 + VSPLTISW $8, V29 + VSPLTISW $7, V30 + +loop_vsx: + VADDUWM V0, V4, V0 + VADDUWM V1, V5, V1 + VADDUWM V2, V6, V2 + VADDUWM V3, V7, V3 + + VXOR V12, V0, V12 + VXOR V13, V1, V13 + VXOR V14, V2, V14 + VXOR V15, V3, V15 + + VRLW V12, V27, V12 + VRLW V13, V27, V13 + VRLW V14, V27, V14 + VRLW V15, V27, V15 + + VADDUWM V8, V12, V8 + VADDUWM V9, V13, V9 + VADDUWM V10, V14, V10 + VADDUWM V11, V15, V11 + + VXOR V4, V8, V4 + VXOR V5, V9, V5 + VXOR V6, V10, V6 + VXOR V7, V11, V7 + + VRLW V4, V28, V4 + VRLW V5, V28, V5 + VRLW V6, V28, V6 + VRLW V7, V28, V7 + + VADDUWM V0, V4, V0 + VADDUWM V1, V5, V1 + VADDUWM V2, V6, V2 + VADDUWM V3, V7, V3 + + VXOR V12, V0, V12 + VXOR V13, V1, V13 + VXOR V14, V2, V14 + VXOR V15, V3, V15 + + VRLW V12, V29, V12 + VRLW V13, V29, V13 + VRLW V14, V29, V14 + VRLW V15, V29, V15 + + VADDUWM V8, V12, V8 + VADDUWM V9, V13, V9 + VADDUWM V10, V14, V10 + VADDUWM V11, V15, V11 + + VXOR V4, V8, V4 + VXOR V5, V9, V5 + VXOR V6, V10, V6 + VXOR V7, V11, V7 + + VRLW V4, V30, V4 + VRLW V5, V30, V5 + VRLW V6, V30, V6 + VRLW V7, V30, V7 + + VADDUWM V0, V5, V0 + VADDUWM V1, V6, V1 + VADDUWM V2, V7, V2 + VADDUWM V3, V4, V3 + + VXOR V15, V0, V15 + VXOR V12, V1, V12 + VXOR V13, V2, V13 + VXOR V14, V3, V14 + + VRLW V15, V27, V15 + VRLW V12, V27, V12 + VRLW V13, V27, V13 + VRLW V14, V27, V14 + + VADDUWM V10, V15, V10 + VADDUWM V11, V12, V11 + VADDUWM V8, V13, V8 + VADDUWM V9, V14, V9 + + VXOR V5, V10, V5 + VXOR V6, V11, V6 + VXOR V7, V8, V7 + VXOR V4, V9, V4 + + VRLW V5, V28, V5 + VRLW V6, V28, V6 + VRLW V7, V28, V7 + VRLW V4, V28, V4 + + VADDUWM V0, V5, V0 + VADDUWM V1, V6, V1 + VADDUWM V2, V7, V2 + VADDUWM V3, V4, V3 + + VXOR V15, V0, V15 + VXOR V12, V1, V12 + VXOR V13, V2, V13 + VXOR V14, V3, V14 + + VRLW V15, V29, V15 + VRLW V12, V29, V12 + VRLW V13, V29, V13 + VRLW V14, V29, V14 + + VADDUWM V10, V15, V10 + VADDUWM V11, V12, V11 + VADDUWM V8, V13, V8 + VADDUWM V9, V14, V9 + + VXOR V5, V10, V5 + VXOR V6, V11, V6 + VXOR V7, V8, V7 + VXOR V4, V9, V4 + + VRLW V5, V30, V5 + VRLW V6, V30, V6 + VRLW V7, V30, V7 + VRLW V4, V30, V4 + BC 16, LT, loop_vsx + + VADDUWM V12, V26, V12 + + WORD $0x13600F8C // VMRGEW V0, V1, V27 + WORD $0x13821F8C // VMRGEW V2, V3, V28 + + WORD $0x10000E8C // VMRGOW V0, V1, V0 + WORD $0x10421E8C // VMRGOW V2, V3, V2 + + WORD $0x13A42F8C // VMRGEW V4, V5, V29 + WORD $0x13C63F8C // VMRGEW V6, V7, V30 + + XXPERMDI VS32, VS34, $0, VS33 + XXPERMDI VS32, VS34, $3, VS35 + XXPERMDI VS59, VS60, $0, VS32 + XXPERMDI VS59, VS60, $3, VS34 + + WORD $0x10842E8C // VMRGOW V4, V5, V4 + WORD $0x10C63E8C // VMRGOW V6, V7, V6 + + WORD $0x13684F8C // VMRGEW V8, V9, V27 + WORD $0x138A5F8C // VMRGEW V10, V11, V28 + + XXPERMDI VS36, VS38, $0, VS37 + XXPERMDI VS36, VS38, $3, VS39 + XXPERMDI VS61, VS62, $0, VS36 + XXPERMDI VS61, VS62, $3, VS38 + + WORD $0x11084E8C // VMRGOW V8, V9, V8 + WORD $0x114A5E8C // VMRGOW V10, V11, V10 + + WORD $0x13AC6F8C // VMRGEW V12, V13, V29 + WORD $0x13CE7F8C // VMRGEW V14, V15, V30 + + XXPERMDI VS40, VS42, $0, VS41 + XXPERMDI VS40, VS42, $3, VS43 + XXPERMDI VS59, VS60, $0, VS40 + XXPERMDI VS59, VS60, $3, VS42 + + WORD $0x118C6E8C // VMRGOW V12, V13, V12 + WORD $0x11CE7E8C // VMRGOW V14, V15, V14 + + VSPLTISW $4, V27 + VADDUWM V26, V27, V26 + + XXPERMDI VS44, VS46, $0, VS45 + XXPERMDI VS44, VS46, $3, VS47 + XXPERMDI VS61, VS62, $0, VS44 + XXPERMDI VS61, VS62, $3, VS46 + + VADDUWM V0, V16, V0 + VADDUWM V4, V17, V4 + VADDUWM V8, V18, V8 + VADDUWM V12, V19, V12 + + CMPU LEN, $64 + BLT tail_vsx + + // Bottom of loop + LXVW4X (INP)(R0), VS59 + LXVW4X (INP)(R8), VS60 + LXVW4X (INP)(R9), VS61 + LXVW4X (INP)(R10), VS62 + + VXOR V27, V0, V27 + VXOR V28, V4, V28 + VXOR V29, V8, V29 + VXOR V30, V12, V30 + + STXVW4X VS59, (OUT)(R0) + STXVW4X VS60, (OUT)(R8) + ADD $64, INP + STXVW4X VS61, (OUT)(R9) + ADD $-64, LEN + STXVW4X VS62, (OUT)(R10) + ADD $64, OUT + BEQ done_vsx + + VADDUWM V1, V16, V0 + VADDUWM V5, V17, V4 + VADDUWM V9, V18, V8 + VADDUWM V13, V19, V12 + + CMPU LEN, $64 + BLT tail_vsx + + LXVW4X (INP)(R0), VS59 + LXVW4X (INP)(R8), VS60 + LXVW4X (INP)(R9), VS61 + LXVW4X (INP)(R10), VS62 + VXOR V27, V0, V27 + + VXOR V28, V4, V28 + VXOR V29, V8, V29 + VXOR V30, V12, V30 + + STXVW4X VS59, (OUT)(R0) + STXVW4X VS60, (OUT)(R8) + ADD $64, INP + STXVW4X VS61, (OUT)(R9) + ADD $-64, LEN + STXVW4X VS62, (OUT)(V10) + ADD $64, OUT + BEQ done_vsx + + VADDUWM V2, V16, V0 + VADDUWM V6, V17, V4 + VADDUWM V10, V18, V8 + VADDUWM V14, V19, V12 + + CMPU LEN, $64 + BLT tail_vsx + + LXVW4X (INP)(R0), VS59 + LXVW4X (INP)(R8), VS60 + LXVW4X (INP)(R9), VS61 + LXVW4X (INP)(R10), VS62 + + VXOR V27, V0, V27 + VXOR V28, V4, V28 + VXOR V29, V8, V29 + VXOR V30, V12, V30 + + STXVW4X VS59, (OUT)(R0) + STXVW4X VS60, (OUT)(R8) + ADD $64, INP + STXVW4X VS61, (OUT)(R9) + ADD $-64, LEN + STXVW4X VS62, (OUT)(R10) + ADD $64, OUT + BEQ done_vsx + + VADDUWM V3, V16, V0 + VADDUWM V7, V17, V4 + VADDUWM V11, V18, V8 + VADDUWM V15, V19, V12 + + CMPU LEN, $64 + BLT tail_vsx + + LXVW4X (INP)(R0), VS59 + LXVW4X (INP)(R8), VS60 + LXVW4X (INP)(R9), VS61 + LXVW4X (INP)(R10), VS62 + + VXOR V27, V0, V27 + VXOR V28, V4, V28 + VXOR V29, V8, V29 + VXOR V30, V12, V30 + + STXVW4X VS59, (OUT)(R0) + STXVW4X VS60, (OUT)(R8) + ADD $64, INP + STXVW4X VS61, (OUT)(R9) + ADD $-64, LEN + STXVW4X VS62, (OUT)(R10) + ADD $64, OUT + + MOVD $10, R14 + MOVD R14, CTR + BNE loop_outer_vsx + +done_vsx: + // Increment counter by number of 64 byte blocks + MOVD (CNT), R14 + ADD BLOCKS, R14 + MOVD R14, (CNT) + RET + +tail_vsx: + ADD $32, R1, R11 + MOVD LEN, CTR + + // Save values on stack to copy from + STXVW4X VS32, (R11)(R0) + STXVW4X VS36, (R11)(R8) + STXVW4X VS40, (R11)(R9) + STXVW4X VS44, (R11)(R10) + ADD $-1, R11, R12 + ADD $-1, INP + ADD $-1, OUT + +looptail_vsx: + // Copying the result to OUT + // in bytes. + MOVBZU 1(R12), KEY + MOVBZU 1(INP), TMP + XOR KEY, TMP, KEY + MOVBU KEY, 1(OUT) + BC 16, LT, looptail_vsx + + // Clear the stack values + STXVW4X VS48, (R11)(R0) + STXVW4X VS48, (R11)(R8) + STXVW4X VS48, (R11)(R9) + STXVW4X VS48, (R11)(R10) + BR done_vsx diff --git a/vendor/golang.org/x/crypto/chacha20/chacha_s390x.go b/vendor/golang.org/x/crypto/chacha20/chacha_s390x.go new file mode 100644 index 00000000000..cd55f45a333 --- /dev/null +++ b/vendor/golang.org/x/crypto/chacha20/chacha_s390x.go @@ -0,0 +1,26 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !gccgo,!appengine + +package chacha20 + +import "golang.org/x/sys/cpu" + +var haveAsm = cpu.S390X.HasVX + +const bufSize = 256 + +// xorKeyStreamVX is an assembly implementation of XORKeyStream. It must only +// be called when the vector facility is available. Implementation in asm_s390x.s. +//go:noescape +func xorKeyStreamVX(dst, src []byte, key *[8]uint32, nonce *[3]uint32, counter *uint32) + +func (c *Cipher) xorKeyStreamBlocks(dst, src []byte) { + if cpu.S390X.HasVX { + xorKeyStreamVX(dst, src, &c.key, &c.nonce, &c.counter) + } else { + c.xorKeyStreamBlocksGeneric(dst, src) + } +} diff --git a/vendor/golang.org/x/crypto/chacha20/chacha_s390x.s b/vendor/golang.org/x/crypto/chacha20/chacha_s390x.s new file mode 100644 index 00000000000..de52a2ea8d1 --- /dev/null +++ b/vendor/golang.org/x/crypto/chacha20/chacha_s390x.s @@ -0,0 +1,224 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !gccgo,!appengine + +#include "go_asm.h" +#include "textflag.h" + +// This is an implementation of the ChaCha20 encryption algorithm as +// specified in RFC 7539. It uses vector instructions to compute +// 4 keystream blocks in parallel (256 bytes) which are then XORed +// with the bytes in the input slice. + +GLOBL ·constants<>(SB), RODATA|NOPTR, $32 +// BSWAP: swap bytes in each 4-byte element +DATA ·constants<>+0x00(SB)/4, $0x03020100 +DATA ·constants<>+0x04(SB)/4, $0x07060504 +DATA ·constants<>+0x08(SB)/4, $0x0b0a0908 +DATA ·constants<>+0x0c(SB)/4, $0x0f0e0d0c +// J0: [j0, j1, j2, j3] +DATA ·constants<>+0x10(SB)/4, $0x61707865 +DATA ·constants<>+0x14(SB)/4, $0x3320646e +DATA ·constants<>+0x18(SB)/4, $0x79622d32 +DATA ·constants<>+0x1c(SB)/4, $0x6b206574 + +#define BSWAP V5 +#define J0 V6 +#define KEY0 V7 +#define KEY1 V8 +#define NONCE V9 +#define CTR V10 +#define M0 V11 +#define M1 V12 +#define M2 V13 +#define M3 V14 +#define INC V15 +#define X0 V16 +#define X1 V17 +#define X2 V18 +#define X3 V19 +#define X4 V20 +#define X5 V21 +#define X6 V22 +#define X7 V23 +#define X8 V24 +#define X9 V25 +#define X10 V26 +#define X11 V27 +#define X12 V28 +#define X13 V29 +#define X14 V30 +#define X15 V31 + +#define NUM_ROUNDS 20 + +#define ROUND4(a0, a1, a2, a3, b0, b1, b2, b3, c0, c1, c2, c3, d0, d1, d2, d3) \ + VAF a1, a0, a0 \ + VAF b1, b0, b0 \ + VAF c1, c0, c0 \ + VAF d1, d0, d0 \ + VX a0, a2, a2 \ + VX b0, b2, b2 \ + VX c0, c2, c2 \ + VX d0, d2, d2 \ + VERLLF $16, a2, a2 \ + VERLLF $16, b2, b2 \ + VERLLF $16, c2, c2 \ + VERLLF $16, d2, d2 \ + VAF a2, a3, a3 \ + VAF b2, b3, b3 \ + VAF c2, c3, c3 \ + VAF d2, d3, d3 \ + VX a3, a1, a1 \ + VX b3, b1, b1 \ + VX c3, c1, c1 \ + VX d3, d1, d1 \ + VERLLF $12, a1, a1 \ + VERLLF $12, b1, b1 \ + VERLLF $12, c1, c1 \ + VERLLF $12, d1, d1 \ + VAF a1, a0, a0 \ + VAF b1, b0, b0 \ + VAF c1, c0, c0 \ + VAF d1, d0, d0 \ + VX a0, a2, a2 \ + VX b0, b2, b2 \ + VX c0, c2, c2 \ + VX d0, d2, d2 \ + VERLLF $8, a2, a2 \ + VERLLF $8, b2, b2 \ + VERLLF $8, c2, c2 \ + VERLLF $8, d2, d2 \ + VAF a2, a3, a3 \ + VAF b2, b3, b3 \ + VAF c2, c3, c3 \ + VAF d2, d3, d3 \ + VX a3, a1, a1 \ + VX b3, b1, b1 \ + VX c3, c1, c1 \ + VX d3, d1, d1 \ + VERLLF $7, a1, a1 \ + VERLLF $7, b1, b1 \ + VERLLF $7, c1, c1 \ + VERLLF $7, d1, d1 + +#define PERMUTE(mask, v0, v1, v2, v3) \ + VPERM v0, v0, mask, v0 \ + VPERM v1, v1, mask, v1 \ + VPERM v2, v2, mask, v2 \ + VPERM v3, v3, mask, v3 + +#define ADDV(x, v0, v1, v2, v3) \ + VAF x, v0, v0 \ + VAF x, v1, v1 \ + VAF x, v2, v2 \ + VAF x, v3, v3 + +#define XORV(off, dst, src, v0, v1, v2, v3) \ + VLM off(src), M0, M3 \ + PERMUTE(BSWAP, v0, v1, v2, v3) \ + VX v0, M0, M0 \ + VX v1, M1, M1 \ + VX v2, M2, M2 \ + VX v3, M3, M3 \ + VSTM M0, M3, off(dst) + +#define SHUFFLE(a, b, c, d, t, u, v, w) \ + VMRHF a, c, t \ // t = {a[0], c[0], a[1], c[1]} + VMRHF b, d, u \ // u = {b[0], d[0], b[1], d[1]} + VMRLF a, c, v \ // v = {a[2], c[2], a[3], c[3]} + VMRLF b, d, w \ // w = {b[2], d[2], b[3], d[3]} + VMRHF t, u, a \ // a = {a[0], b[0], c[0], d[0]} + VMRLF t, u, b \ // b = {a[1], b[1], c[1], d[1]} + VMRHF v, w, c \ // c = {a[2], b[2], c[2], d[2]} + VMRLF v, w, d // d = {a[3], b[3], c[3], d[3]} + +// func xorKeyStreamVX(dst, src []byte, key *[8]uint32, nonce *[3]uint32, counter *uint32) +TEXT ·xorKeyStreamVX(SB), NOSPLIT, $0 + MOVD $·constants<>(SB), R1 + MOVD dst+0(FP), R2 // R2=&dst[0] + LMG src+24(FP), R3, R4 // R3=&src[0] R4=len(src) + MOVD key+48(FP), R5 // R5=key + MOVD nonce+56(FP), R6 // R6=nonce + MOVD counter+64(FP), R7 // R7=counter + + // load BSWAP and J0 + VLM (R1), BSWAP, J0 + + // setup + MOVD $95, R0 + VLM (R5), KEY0, KEY1 + VLL R0, (R6), NONCE + VZERO M0 + VLEIB $7, $32, M0 + VSRLB M0, NONCE, NONCE + + // initialize counter values + VLREPF (R7), CTR + VZERO INC + VLEIF $1, $1, INC + VLEIF $2, $2, INC + VLEIF $3, $3, INC + VAF INC, CTR, CTR + VREPIF $4, INC + +chacha: + VREPF $0, J0, X0 + VREPF $1, J0, X1 + VREPF $2, J0, X2 + VREPF $3, J0, X3 + VREPF $0, KEY0, X4 + VREPF $1, KEY0, X5 + VREPF $2, KEY0, X6 + VREPF $3, KEY0, X7 + VREPF $0, KEY1, X8 + VREPF $1, KEY1, X9 + VREPF $2, KEY1, X10 + VREPF $3, KEY1, X11 + VLR CTR, X12 + VREPF $1, NONCE, X13 + VREPF $2, NONCE, X14 + VREPF $3, NONCE, X15 + + MOVD $(NUM_ROUNDS/2), R1 + +loop: + ROUND4(X0, X4, X12, X8, X1, X5, X13, X9, X2, X6, X14, X10, X3, X7, X15, X11) + ROUND4(X0, X5, X15, X10, X1, X6, X12, X11, X2, X7, X13, X8, X3, X4, X14, X9) + + ADD $-1, R1 + BNE loop + + // decrement length + ADD $-256, R4 + + // rearrange vectors + SHUFFLE(X0, X1, X2, X3, M0, M1, M2, M3) + ADDV(J0, X0, X1, X2, X3) + SHUFFLE(X4, X5, X6, X7, M0, M1, M2, M3) + ADDV(KEY0, X4, X5, X6, X7) + SHUFFLE(X8, X9, X10, X11, M0, M1, M2, M3) + ADDV(KEY1, X8, X9, X10, X11) + VAF CTR, X12, X12 + SHUFFLE(X12, X13, X14, X15, M0, M1, M2, M3) + ADDV(NONCE, X12, X13, X14, X15) + + // increment counters + VAF INC, CTR, CTR + + // xor keystream with plaintext + XORV(0*64, R2, R3, X0, X4, X8, X12) + XORV(1*64, R2, R3, X1, X5, X9, X13) + XORV(2*64, R2, R3, X2, X6, X10, X14) + XORV(3*64, R2, R3, X3, X7, X11, X15) + + // increment pointers + MOVD $256(R2), R2 + MOVD $256(R3), R3 + + CMPBNE R4, $0, chacha + + VSTEF $0, CTR, (R7) + RET diff --git a/vendor/golang.org/x/crypto/chacha20/xor.go b/vendor/golang.org/x/crypto/chacha20/xor.go new file mode 100644 index 00000000000..0110c9865af --- /dev/null +++ b/vendor/golang.org/x/crypto/chacha20/xor.go @@ -0,0 +1,41 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found src the LICENSE file. + +package chacha20 + +import "runtime" + +// Platforms that have fast unaligned 32-bit little endian accesses. +const unaligned = runtime.GOARCH == "386" || + runtime.GOARCH == "amd64" || + runtime.GOARCH == "arm64" || + runtime.GOARCH == "ppc64le" || + runtime.GOARCH == "s390x" + +// xor reads a little endian uint32 from src, XORs it with u and +// places the result in little endian byte order in dst. +func xor(dst, src []byte, u uint32) { + _, _ = src[3], dst[3] // eliminate bounds checks + if unaligned { + // The compiler should optimize this code into + // 32-bit unaligned little endian loads and stores. + // TODO: delete once the compiler does a reliably + // good job with the generic code below. + // See issue #25111 for more details. + v := uint32(src[0]) + v |= uint32(src[1]) << 8 + v |= uint32(src[2]) << 16 + v |= uint32(src[3]) << 24 + v ^= u + dst[0] = byte(v) + dst[1] = byte(v >> 8) + dst[2] = byte(v >> 16) + dst[3] = byte(v >> 24) + } else { + dst[0] = src[0] ^ byte(u) + dst[1] = src[1] ^ byte(u>>8) + dst[2] = src[2] ^ byte(u>>16) + dst[3] = src[3] ^ byte(u>>24) + } +} diff --git a/vendor/golang.org/x/crypto/curve25519/curve25519.go b/vendor/golang.org/x/crypto/curve25519/curve25519.go new file mode 100644 index 00000000000..4b9a655d1b5 --- /dev/null +++ b/vendor/golang.org/x/crypto/curve25519/curve25519.go @@ -0,0 +1,95 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package curve25519 provides an implementation of the X25519 function, which +// performs scalar multiplication on the elliptic curve known as Curve25519. +// See RFC 7748. +package curve25519 // import "golang.org/x/crypto/curve25519" + +import ( + "crypto/subtle" + "fmt" +) + +// ScalarMult sets dst to the product scalar * point. +// +// Deprecated: when provided a low-order point, ScalarMult will set dst to all +// zeroes, irrespective of the scalar. Instead, use the X25519 function, which +// will return an error. +func ScalarMult(dst, scalar, point *[32]byte) { + scalarMult(dst, scalar, point) +} + +// ScalarBaseMult sets dst to the product scalar * base where base is the +// standard generator. +// +// It is recommended to use the X25519 function with Basepoint instead, as +// copying into fixed size arrays can lead to unexpected bugs. +func ScalarBaseMult(dst, scalar *[32]byte) { + ScalarMult(dst, scalar, &basePoint) +} + +const ( + // ScalarSize is the size of the scalar input to X25519. + ScalarSize = 32 + // PointSize is the size of the point input to X25519. + PointSize = 32 +) + +// Basepoint is the canonical Curve25519 generator. +var Basepoint []byte + +var basePoint = [32]byte{9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + +func init() { Basepoint = basePoint[:] } + +func checkBasepoint() { + if subtle.ConstantTimeCompare(Basepoint, []byte{ + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }) != 1 { + panic("curve25519: global Basepoint value was modified") + } +} + +// X25519 returns the result of the scalar multiplication (scalar * point), +// according to RFC 7748, Section 5. scalar, point and the return value are +// slices of 32 bytes. +// +// scalar can be generated at random, for example with crypto/rand. point should +// be either Basepoint or the output of another X25519 call. +// +// If point is Basepoint (but not if it's a different slice with the same +// contents) a precomputed implementation might be used for performance. +func X25519(scalar, point []byte) ([]byte, error) { + // Outline the body of function, to let the allocation be inlined in the + // caller, and possibly avoid escaping to the heap. + var dst [32]byte + return x25519(&dst, scalar, point) +} + +func x25519(dst *[32]byte, scalar, point []byte) ([]byte, error) { + var in [32]byte + if l := len(scalar); l != 32 { + return nil, fmt.Errorf("bad scalar length: %d, expected %d", l, 32) + } + if l := len(point); l != 32 { + return nil, fmt.Errorf("bad point length: %d, expected %d", l, 32) + } + copy(in[:], scalar) + if &point[0] == &Basepoint[0] { + checkBasepoint() + ScalarBaseMult(dst, &in) + } else { + var base, zero [32]byte + copy(base[:], point) + ScalarMult(dst, &in, &base) + if subtle.ConstantTimeCompare(dst[:], zero[:]) == 1 { + return nil, fmt.Errorf("bad input point: low order point") + } + } + return dst[:], nil +} diff --git a/vendor/golang.org/x/crypto/curve25519/curve25519_amd64.go b/vendor/golang.org/x/crypto/curve25519/curve25519_amd64.go new file mode 100644 index 00000000000..5120b779b9b --- /dev/null +++ b/vendor/golang.org/x/crypto/curve25519/curve25519_amd64.go @@ -0,0 +1,240 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build amd64,!gccgo,!appengine,!purego + +package curve25519 + +// These functions are implemented in the .s files. The names of the functions +// in the rest of the file are also taken from the SUPERCOP sources to help +// people following along. + +//go:noescape + +func cswap(inout *[5]uint64, v uint64) + +//go:noescape + +func ladderstep(inout *[5][5]uint64) + +//go:noescape + +func freeze(inout *[5]uint64) + +//go:noescape + +func mul(dest, a, b *[5]uint64) + +//go:noescape + +func square(out, in *[5]uint64) + +// mladder uses a Montgomery ladder to calculate (xr/zr) *= s. +func mladder(xr, zr *[5]uint64, s *[32]byte) { + var work [5][5]uint64 + + work[0] = *xr + setint(&work[1], 1) + setint(&work[2], 0) + work[3] = *xr + setint(&work[4], 1) + + j := uint(6) + var prevbit byte + + for i := 31; i >= 0; i-- { + for j < 8 { + bit := ((*s)[i] >> j) & 1 + swap := bit ^ prevbit + prevbit = bit + cswap(&work[1], uint64(swap)) + ladderstep(&work) + j-- + } + j = 7 + } + + *xr = work[1] + *zr = work[2] +} + +func scalarMult(out, in, base *[32]byte) { + var e [32]byte + copy(e[:], (*in)[:]) + e[0] &= 248 + e[31] &= 127 + e[31] |= 64 + + var t, z [5]uint64 + unpack(&t, base) + mladder(&t, &z, &e) + invert(&z, &z) + mul(&t, &t, &z) + pack(out, &t) +} + +func setint(r *[5]uint64, v uint64) { + r[0] = v + r[1] = 0 + r[2] = 0 + r[3] = 0 + r[4] = 0 +} + +// unpack sets r = x where r consists of 5, 51-bit limbs in little-endian +// order. +func unpack(r *[5]uint64, x *[32]byte) { + r[0] = uint64(x[0]) | + uint64(x[1])<<8 | + uint64(x[2])<<16 | + uint64(x[3])<<24 | + uint64(x[4])<<32 | + uint64(x[5])<<40 | + uint64(x[6]&7)<<48 + + r[1] = uint64(x[6])>>3 | + uint64(x[7])<<5 | + uint64(x[8])<<13 | + uint64(x[9])<<21 | + uint64(x[10])<<29 | + uint64(x[11])<<37 | + uint64(x[12]&63)<<45 + + r[2] = uint64(x[12])>>6 | + uint64(x[13])<<2 | + uint64(x[14])<<10 | + uint64(x[15])<<18 | + uint64(x[16])<<26 | + uint64(x[17])<<34 | + uint64(x[18])<<42 | + uint64(x[19]&1)<<50 + + r[3] = uint64(x[19])>>1 | + uint64(x[20])<<7 | + uint64(x[21])<<15 | + uint64(x[22])<<23 | + uint64(x[23])<<31 | + uint64(x[24])<<39 | + uint64(x[25]&15)<<47 + + r[4] = uint64(x[25])>>4 | + uint64(x[26])<<4 | + uint64(x[27])<<12 | + uint64(x[28])<<20 | + uint64(x[29])<<28 | + uint64(x[30])<<36 | + uint64(x[31]&127)<<44 +} + +// pack sets out = x where out is the usual, little-endian form of the 5, +// 51-bit limbs in x. +func pack(out *[32]byte, x *[5]uint64) { + t := *x + freeze(&t) + + out[0] = byte(t[0]) + out[1] = byte(t[0] >> 8) + out[2] = byte(t[0] >> 16) + out[3] = byte(t[0] >> 24) + out[4] = byte(t[0] >> 32) + out[5] = byte(t[0] >> 40) + out[6] = byte(t[0] >> 48) + + out[6] ^= byte(t[1]<<3) & 0xf8 + out[7] = byte(t[1] >> 5) + out[8] = byte(t[1] >> 13) + out[9] = byte(t[1] >> 21) + out[10] = byte(t[1] >> 29) + out[11] = byte(t[1] >> 37) + out[12] = byte(t[1] >> 45) + + out[12] ^= byte(t[2]<<6) & 0xc0 + out[13] = byte(t[2] >> 2) + out[14] = byte(t[2] >> 10) + out[15] = byte(t[2] >> 18) + out[16] = byte(t[2] >> 26) + out[17] = byte(t[2] >> 34) + out[18] = byte(t[2] >> 42) + out[19] = byte(t[2] >> 50) + + out[19] ^= byte(t[3]<<1) & 0xfe + out[20] = byte(t[3] >> 7) + out[21] = byte(t[3] >> 15) + out[22] = byte(t[3] >> 23) + out[23] = byte(t[3] >> 31) + out[24] = byte(t[3] >> 39) + out[25] = byte(t[3] >> 47) + + out[25] ^= byte(t[4]<<4) & 0xf0 + out[26] = byte(t[4] >> 4) + out[27] = byte(t[4] >> 12) + out[28] = byte(t[4] >> 20) + out[29] = byte(t[4] >> 28) + out[30] = byte(t[4] >> 36) + out[31] = byte(t[4] >> 44) +} + +// invert calculates r = x^-1 mod p using Fermat's little theorem. +func invert(r *[5]uint64, x *[5]uint64) { + var z2, z9, z11, z2_5_0, z2_10_0, z2_20_0, z2_50_0, z2_100_0, t [5]uint64 + + square(&z2, x) /* 2 */ + square(&t, &z2) /* 4 */ + square(&t, &t) /* 8 */ + mul(&z9, &t, x) /* 9 */ + mul(&z11, &z9, &z2) /* 11 */ + square(&t, &z11) /* 22 */ + mul(&z2_5_0, &t, &z9) /* 2^5 - 2^0 = 31 */ + + square(&t, &z2_5_0) /* 2^6 - 2^1 */ + for i := 1; i < 5; i++ { /* 2^20 - 2^10 */ + square(&t, &t) + } + mul(&z2_10_0, &t, &z2_5_0) /* 2^10 - 2^0 */ + + square(&t, &z2_10_0) /* 2^11 - 2^1 */ + for i := 1; i < 10; i++ { /* 2^20 - 2^10 */ + square(&t, &t) + } + mul(&z2_20_0, &t, &z2_10_0) /* 2^20 - 2^0 */ + + square(&t, &z2_20_0) /* 2^21 - 2^1 */ + for i := 1; i < 20; i++ { /* 2^40 - 2^20 */ + square(&t, &t) + } + mul(&t, &t, &z2_20_0) /* 2^40 - 2^0 */ + + square(&t, &t) /* 2^41 - 2^1 */ + for i := 1; i < 10; i++ { /* 2^50 - 2^10 */ + square(&t, &t) + } + mul(&z2_50_0, &t, &z2_10_0) /* 2^50 - 2^0 */ + + square(&t, &z2_50_0) /* 2^51 - 2^1 */ + for i := 1; i < 50; i++ { /* 2^100 - 2^50 */ + square(&t, &t) + } + mul(&z2_100_0, &t, &z2_50_0) /* 2^100 - 2^0 */ + + square(&t, &z2_100_0) /* 2^101 - 2^1 */ + for i := 1; i < 100; i++ { /* 2^200 - 2^100 */ + square(&t, &t) + } + mul(&t, &t, &z2_100_0) /* 2^200 - 2^0 */ + + square(&t, &t) /* 2^201 - 2^1 */ + for i := 1; i < 50; i++ { /* 2^250 - 2^50 */ + square(&t, &t) + } + mul(&t, &t, &z2_50_0) /* 2^250 - 2^0 */ + + square(&t, &t) /* 2^251 - 2^1 */ + square(&t, &t) /* 2^252 - 2^2 */ + square(&t, &t) /* 2^253 - 2^3 */ + + square(&t, &t) /* 2^254 - 2^4 */ + + square(&t, &t) /* 2^255 - 2^5 */ + mul(r, &t, &z11) /* 2^255 - 21 */ +} diff --git a/vendor/golang.org/x/crypto/curve25519/curve25519_amd64.s b/vendor/golang.org/x/crypto/curve25519/curve25519_amd64.s new file mode 100644 index 00000000000..0250c888592 --- /dev/null +++ b/vendor/golang.org/x/crypto/curve25519/curve25519_amd64.s @@ -0,0 +1,1793 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This code was translated into a form compatible with 6a from the public +// domain sources in SUPERCOP: https://bench.cr.yp.to/supercop.html + +// +build amd64,!gccgo,!appengine,!purego + +#define REDMASK51 0x0007FFFFFFFFFFFF + +// These constants cannot be encoded in non-MOVQ immediates. +// We access them directly from memory instead. + +DATA ·_121666_213(SB)/8, $996687872 +GLOBL ·_121666_213(SB), 8, $8 + +DATA ·_2P0(SB)/8, $0xFFFFFFFFFFFDA +GLOBL ·_2P0(SB), 8, $8 + +DATA ·_2P1234(SB)/8, $0xFFFFFFFFFFFFE +GLOBL ·_2P1234(SB), 8, $8 + +// func freeze(inout *[5]uint64) +TEXT ·freeze(SB),7,$0-8 + MOVQ inout+0(FP), DI + + MOVQ 0(DI),SI + MOVQ 8(DI),DX + MOVQ 16(DI),CX + MOVQ 24(DI),R8 + MOVQ 32(DI),R9 + MOVQ $REDMASK51,AX + MOVQ AX,R10 + SUBQ $18,R10 + MOVQ $3,R11 +REDUCELOOP: + MOVQ SI,R12 + SHRQ $51,R12 + ANDQ AX,SI + ADDQ R12,DX + MOVQ DX,R12 + SHRQ $51,R12 + ANDQ AX,DX + ADDQ R12,CX + MOVQ CX,R12 + SHRQ $51,R12 + ANDQ AX,CX + ADDQ R12,R8 + MOVQ R8,R12 + SHRQ $51,R12 + ANDQ AX,R8 + ADDQ R12,R9 + MOVQ R9,R12 + SHRQ $51,R12 + ANDQ AX,R9 + IMUL3Q $19,R12,R12 + ADDQ R12,SI + SUBQ $1,R11 + JA REDUCELOOP + MOVQ $1,R12 + CMPQ R10,SI + CMOVQLT R11,R12 + CMPQ AX,DX + CMOVQNE R11,R12 + CMPQ AX,CX + CMOVQNE R11,R12 + CMPQ AX,R8 + CMOVQNE R11,R12 + CMPQ AX,R9 + CMOVQNE R11,R12 + NEGQ R12 + ANDQ R12,AX + ANDQ R12,R10 + SUBQ R10,SI + SUBQ AX,DX + SUBQ AX,CX + SUBQ AX,R8 + SUBQ AX,R9 + MOVQ SI,0(DI) + MOVQ DX,8(DI) + MOVQ CX,16(DI) + MOVQ R8,24(DI) + MOVQ R9,32(DI) + RET + +// func ladderstep(inout *[5][5]uint64) +TEXT ·ladderstep(SB),0,$296-8 + MOVQ inout+0(FP),DI + + MOVQ 40(DI),SI + MOVQ 48(DI),DX + MOVQ 56(DI),CX + MOVQ 64(DI),R8 + MOVQ 72(DI),R9 + MOVQ SI,AX + MOVQ DX,R10 + MOVQ CX,R11 + MOVQ R8,R12 + MOVQ R9,R13 + ADDQ ·_2P0(SB),AX + ADDQ ·_2P1234(SB),R10 + ADDQ ·_2P1234(SB),R11 + ADDQ ·_2P1234(SB),R12 + ADDQ ·_2P1234(SB),R13 + ADDQ 80(DI),SI + ADDQ 88(DI),DX + ADDQ 96(DI),CX + ADDQ 104(DI),R8 + ADDQ 112(DI),R9 + SUBQ 80(DI),AX + SUBQ 88(DI),R10 + SUBQ 96(DI),R11 + SUBQ 104(DI),R12 + SUBQ 112(DI),R13 + MOVQ SI,0(SP) + MOVQ DX,8(SP) + MOVQ CX,16(SP) + MOVQ R8,24(SP) + MOVQ R9,32(SP) + MOVQ AX,40(SP) + MOVQ R10,48(SP) + MOVQ R11,56(SP) + MOVQ R12,64(SP) + MOVQ R13,72(SP) + MOVQ 40(SP),AX + MULQ 40(SP) + MOVQ AX,SI + MOVQ DX,CX + MOVQ 40(SP),AX + SHLQ $1,AX + MULQ 48(SP) + MOVQ AX,R8 + MOVQ DX,R9 + MOVQ 40(SP),AX + SHLQ $1,AX + MULQ 56(SP) + MOVQ AX,R10 + MOVQ DX,R11 + MOVQ 40(SP),AX + SHLQ $1,AX + MULQ 64(SP) + MOVQ AX,R12 + MOVQ DX,R13 + MOVQ 40(SP),AX + SHLQ $1,AX + MULQ 72(SP) + MOVQ AX,R14 + MOVQ DX,R15 + MOVQ 48(SP),AX + MULQ 48(SP) + ADDQ AX,R10 + ADCQ DX,R11 + MOVQ 48(SP),AX + SHLQ $1,AX + MULQ 56(SP) + ADDQ AX,R12 + ADCQ DX,R13 + MOVQ 48(SP),AX + SHLQ $1,AX + MULQ 64(SP) + ADDQ AX,R14 + ADCQ DX,R15 + MOVQ 48(SP),DX + IMUL3Q $38,DX,AX + MULQ 72(SP) + ADDQ AX,SI + ADCQ DX,CX + MOVQ 56(SP),AX + MULQ 56(SP) + ADDQ AX,R14 + ADCQ DX,R15 + MOVQ 56(SP),DX + IMUL3Q $38,DX,AX + MULQ 64(SP) + ADDQ AX,SI + ADCQ DX,CX + MOVQ 56(SP),DX + IMUL3Q $38,DX,AX + MULQ 72(SP) + ADDQ AX,R8 + ADCQ DX,R9 + MOVQ 64(SP),DX + IMUL3Q $19,DX,AX + MULQ 64(SP) + ADDQ AX,R8 + ADCQ DX,R9 + MOVQ 64(SP),DX + IMUL3Q $38,DX,AX + MULQ 72(SP) + ADDQ AX,R10 + ADCQ DX,R11 + MOVQ 72(SP),DX + IMUL3Q $19,DX,AX + MULQ 72(SP) + ADDQ AX,R12 + ADCQ DX,R13 + MOVQ $REDMASK51,DX + SHLQ $13,SI,CX + ANDQ DX,SI + SHLQ $13,R8,R9 + ANDQ DX,R8 + ADDQ CX,R8 + SHLQ $13,R10,R11 + ANDQ DX,R10 + ADDQ R9,R10 + SHLQ $13,R12,R13 + ANDQ DX,R12 + ADDQ R11,R12 + SHLQ $13,R14,R15 + ANDQ DX,R14 + ADDQ R13,R14 + IMUL3Q $19,R15,CX + ADDQ CX,SI + MOVQ SI,CX + SHRQ $51,CX + ADDQ R8,CX + ANDQ DX,SI + MOVQ CX,R8 + SHRQ $51,CX + ADDQ R10,CX + ANDQ DX,R8 + MOVQ CX,R9 + SHRQ $51,CX + ADDQ R12,CX + ANDQ DX,R9 + MOVQ CX,AX + SHRQ $51,CX + ADDQ R14,CX + ANDQ DX,AX + MOVQ CX,R10 + SHRQ $51,CX + IMUL3Q $19,CX,CX + ADDQ CX,SI + ANDQ DX,R10 + MOVQ SI,80(SP) + MOVQ R8,88(SP) + MOVQ R9,96(SP) + MOVQ AX,104(SP) + MOVQ R10,112(SP) + MOVQ 0(SP),AX + MULQ 0(SP) + MOVQ AX,SI + MOVQ DX,CX + MOVQ 0(SP),AX + SHLQ $1,AX + MULQ 8(SP) + MOVQ AX,R8 + MOVQ DX,R9 + MOVQ 0(SP),AX + SHLQ $1,AX + MULQ 16(SP) + MOVQ AX,R10 + MOVQ DX,R11 + MOVQ 0(SP),AX + SHLQ $1,AX + MULQ 24(SP) + MOVQ AX,R12 + MOVQ DX,R13 + MOVQ 0(SP),AX + SHLQ $1,AX + MULQ 32(SP) + MOVQ AX,R14 + MOVQ DX,R15 + MOVQ 8(SP),AX + MULQ 8(SP) + ADDQ AX,R10 + ADCQ DX,R11 + MOVQ 8(SP),AX + SHLQ $1,AX + MULQ 16(SP) + ADDQ AX,R12 + ADCQ DX,R13 + MOVQ 8(SP),AX + SHLQ $1,AX + MULQ 24(SP) + ADDQ AX,R14 + ADCQ DX,R15 + MOVQ 8(SP),DX + IMUL3Q $38,DX,AX + MULQ 32(SP) + ADDQ AX,SI + ADCQ DX,CX + MOVQ 16(SP),AX + MULQ 16(SP) + ADDQ AX,R14 + ADCQ DX,R15 + MOVQ 16(SP),DX + IMUL3Q $38,DX,AX + MULQ 24(SP) + ADDQ AX,SI + ADCQ DX,CX + MOVQ 16(SP),DX + IMUL3Q $38,DX,AX + MULQ 32(SP) + ADDQ AX,R8 + ADCQ DX,R9 + MOVQ 24(SP),DX + IMUL3Q $19,DX,AX + MULQ 24(SP) + ADDQ AX,R8 + ADCQ DX,R9 + MOVQ 24(SP),DX + IMUL3Q $38,DX,AX + MULQ 32(SP) + ADDQ AX,R10 + ADCQ DX,R11 + MOVQ 32(SP),DX + IMUL3Q $19,DX,AX + MULQ 32(SP) + ADDQ AX,R12 + ADCQ DX,R13 + MOVQ $REDMASK51,DX + SHLQ $13,SI,CX + ANDQ DX,SI + SHLQ $13,R8,R9 + ANDQ DX,R8 + ADDQ CX,R8 + SHLQ $13,R10,R11 + ANDQ DX,R10 + ADDQ R9,R10 + SHLQ $13,R12,R13 + ANDQ DX,R12 + ADDQ R11,R12 + SHLQ $13,R14,R15 + ANDQ DX,R14 + ADDQ R13,R14 + IMUL3Q $19,R15,CX + ADDQ CX,SI + MOVQ SI,CX + SHRQ $51,CX + ADDQ R8,CX + ANDQ DX,SI + MOVQ CX,R8 + SHRQ $51,CX + ADDQ R10,CX + ANDQ DX,R8 + MOVQ CX,R9 + SHRQ $51,CX + ADDQ R12,CX + ANDQ DX,R9 + MOVQ CX,AX + SHRQ $51,CX + ADDQ R14,CX + ANDQ DX,AX + MOVQ CX,R10 + SHRQ $51,CX + IMUL3Q $19,CX,CX + ADDQ CX,SI + ANDQ DX,R10 + MOVQ SI,120(SP) + MOVQ R8,128(SP) + MOVQ R9,136(SP) + MOVQ AX,144(SP) + MOVQ R10,152(SP) + MOVQ SI,SI + MOVQ R8,DX + MOVQ R9,CX + MOVQ AX,R8 + MOVQ R10,R9 + ADDQ ·_2P0(SB),SI + ADDQ ·_2P1234(SB),DX + ADDQ ·_2P1234(SB),CX + ADDQ ·_2P1234(SB),R8 + ADDQ ·_2P1234(SB),R9 + SUBQ 80(SP),SI + SUBQ 88(SP),DX + SUBQ 96(SP),CX + SUBQ 104(SP),R8 + SUBQ 112(SP),R9 + MOVQ SI,160(SP) + MOVQ DX,168(SP) + MOVQ CX,176(SP) + MOVQ R8,184(SP) + MOVQ R9,192(SP) + MOVQ 120(DI),SI + MOVQ 128(DI),DX + MOVQ 136(DI),CX + MOVQ 144(DI),R8 + MOVQ 152(DI),R9 + MOVQ SI,AX + MOVQ DX,R10 + MOVQ CX,R11 + MOVQ R8,R12 + MOVQ R9,R13 + ADDQ ·_2P0(SB),AX + ADDQ ·_2P1234(SB),R10 + ADDQ ·_2P1234(SB),R11 + ADDQ ·_2P1234(SB),R12 + ADDQ ·_2P1234(SB),R13 + ADDQ 160(DI),SI + ADDQ 168(DI),DX + ADDQ 176(DI),CX + ADDQ 184(DI),R8 + ADDQ 192(DI),R9 + SUBQ 160(DI),AX + SUBQ 168(DI),R10 + SUBQ 176(DI),R11 + SUBQ 184(DI),R12 + SUBQ 192(DI),R13 + MOVQ SI,200(SP) + MOVQ DX,208(SP) + MOVQ CX,216(SP) + MOVQ R8,224(SP) + MOVQ R9,232(SP) + MOVQ AX,240(SP) + MOVQ R10,248(SP) + MOVQ R11,256(SP) + MOVQ R12,264(SP) + MOVQ R13,272(SP) + MOVQ 224(SP),SI + IMUL3Q $19,SI,AX + MOVQ AX,280(SP) + MULQ 56(SP) + MOVQ AX,SI + MOVQ DX,CX + MOVQ 232(SP),DX + IMUL3Q $19,DX,AX + MOVQ AX,288(SP) + MULQ 48(SP) + ADDQ AX,SI + ADCQ DX,CX + MOVQ 200(SP),AX + MULQ 40(SP) + ADDQ AX,SI + ADCQ DX,CX + MOVQ 200(SP),AX + MULQ 48(SP) + MOVQ AX,R8 + MOVQ DX,R9 + MOVQ 200(SP),AX + MULQ 56(SP) + MOVQ AX,R10 + MOVQ DX,R11 + MOVQ 200(SP),AX + MULQ 64(SP) + MOVQ AX,R12 + MOVQ DX,R13 + MOVQ 200(SP),AX + MULQ 72(SP) + MOVQ AX,R14 + MOVQ DX,R15 + MOVQ 208(SP),AX + MULQ 40(SP) + ADDQ AX,R8 + ADCQ DX,R9 + MOVQ 208(SP),AX + MULQ 48(SP) + ADDQ AX,R10 + ADCQ DX,R11 + MOVQ 208(SP),AX + MULQ 56(SP) + ADDQ AX,R12 + ADCQ DX,R13 + MOVQ 208(SP),AX + MULQ 64(SP) + ADDQ AX,R14 + ADCQ DX,R15 + MOVQ 208(SP),DX + IMUL3Q $19,DX,AX + MULQ 72(SP) + ADDQ AX,SI + ADCQ DX,CX + MOVQ 216(SP),AX + MULQ 40(SP) + ADDQ AX,R10 + ADCQ DX,R11 + MOVQ 216(SP),AX + MULQ 48(SP) + ADDQ AX,R12 + ADCQ DX,R13 + MOVQ 216(SP),AX + MULQ 56(SP) + ADDQ AX,R14 + ADCQ DX,R15 + MOVQ 216(SP),DX + IMUL3Q $19,DX,AX + MULQ 64(SP) + ADDQ AX,SI + ADCQ DX,CX + MOVQ 216(SP),DX + IMUL3Q $19,DX,AX + MULQ 72(SP) + ADDQ AX,R8 + ADCQ DX,R9 + MOVQ 224(SP),AX + MULQ 40(SP) + ADDQ AX,R12 + ADCQ DX,R13 + MOVQ 224(SP),AX + MULQ 48(SP) + ADDQ AX,R14 + ADCQ DX,R15 + MOVQ 280(SP),AX + MULQ 64(SP) + ADDQ AX,R8 + ADCQ DX,R9 + MOVQ 280(SP),AX + MULQ 72(SP) + ADDQ AX,R10 + ADCQ DX,R11 + MOVQ 232(SP),AX + MULQ 40(SP) + ADDQ AX,R14 + ADCQ DX,R15 + MOVQ 288(SP),AX + MULQ 56(SP) + ADDQ AX,R8 + ADCQ DX,R9 + MOVQ 288(SP),AX + MULQ 64(SP) + ADDQ AX,R10 + ADCQ DX,R11 + MOVQ 288(SP),AX + MULQ 72(SP) + ADDQ AX,R12 + ADCQ DX,R13 + MOVQ $REDMASK51,DX + SHLQ $13,SI,CX + ANDQ DX,SI + SHLQ $13,R8,R9 + ANDQ DX,R8 + ADDQ CX,R8 + SHLQ $13,R10,R11 + ANDQ DX,R10 + ADDQ R9,R10 + SHLQ $13,R12,R13 + ANDQ DX,R12 + ADDQ R11,R12 + SHLQ $13,R14,R15 + ANDQ DX,R14 + ADDQ R13,R14 + IMUL3Q $19,R15,CX + ADDQ CX,SI + MOVQ SI,CX + SHRQ $51,CX + ADDQ R8,CX + MOVQ CX,R8 + SHRQ $51,CX + ANDQ DX,SI + ADDQ R10,CX + MOVQ CX,R9 + SHRQ $51,CX + ANDQ DX,R8 + ADDQ R12,CX + MOVQ CX,AX + SHRQ $51,CX + ANDQ DX,R9 + ADDQ R14,CX + MOVQ CX,R10 + SHRQ $51,CX + ANDQ DX,AX + IMUL3Q $19,CX,CX + ADDQ CX,SI + ANDQ DX,R10 + MOVQ SI,40(SP) + MOVQ R8,48(SP) + MOVQ R9,56(SP) + MOVQ AX,64(SP) + MOVQ R10,72(SP) + MOVQ 264(SP),SI + IMUL3Q $19,SI,AX + MOVQ AX,200(SP) + MULQ 16(SP) + MOVQ AX,SI + MOVQ DX,CX + MOVQ 272(SP),DX + IMUL3Q $19,DX,AX + MOVQ AX,208(SP) + MULQ 8(SP) + ADDQ AX,SI + ADCQ DX,CX + MOVQ 240(SP),AX + MULQ 0(SP) + ADDQ AX,SI + ADCQ DX,CX + MOVQ 240(SP),AX + MULQ 8(SP) + MOVQ AX,R8 + MOVQ DX,R9 + MOVQ 240(SP),AX + MULQ 16(SP) + MOVQ AX,R10 + MOVQ DX,R11 + MOVQ 240(SP),AX + MULQ 24(SP) + MOVQ AX,R12 + MOVQ DX,R13 + MOVQ 240(SP),AX + MULQ 32(SP) + MOVQ AX,R14 + MOVQ DX,R15 + MOVQ 248(SP),AX + MULQ 0(SP) + ADDQ AX,R8 + ADCQ DX,R9 + MOVQ 248(SP),AX + MULQ 8(SP) + ADDQ AX,R10 + ADCQ DX,R11 + MOVQ 248(SP),AX + MULQ 16(SP) + ADDQ AX,R12 + ADCQ DX,R13 + MOVQ 248(SP),AX + MULQ 24(SP) + ADDQ AX,R14 + ADCQ DX,R15 + MOVQ 248(SP),DX + IMUL3Q $19,DX,AX + MULQ 32(SP) + ADDQ AX,SI + ADCQ DX,CX + MOVQ 256(SP),AX + MULQ 0(SP) + ADDQ AX,R10 + ADCQ DX,R11 + MOVQ 256(SP),AX + MULQ 8(SP) + ADDQ AX,R12 + ADCQ DX,R13 + MOVQ 256(SP),AX + MULQ 16(SP) + ADDQ AX,R14 + ADCQ DX,R15 + MOVQ 256(SP),DX + IMUL3Q $19,DX,AX + MULQ 24(SP) + ADDQ AX,SI + ADCQ DX,CX + MOVQ 256(SP),DX + IMUL3Q $19,DX,AX + MULQ 32(SP) + ADDQ AX,R8 + ADCQ DX,R9 + MOVQ 264(SP),AX + MULQ 0(SP) + ADDQ AX,R12 + ADCQ DX,R13 + MOVQ 264(SP),AX + MULQ 8(SP) + ADDQ AX,R14 + ADCQ DX,R15 + MOVQ 200(SP),AX + MULQ 24(SP) + ADDQ AX,R8 + ADCQ DX,R9 + MOVQ 200(SP),AX + MULQ 32(SP) + ADDQ AX,R10 + ADCQ DX,R11 + MOVQ 272(SP),AX + MULQ 0(SP) + ADDQ AX,R14 + ADCQ DX,R15 + MOVQ 208(SP),AX + MULQ 16(SP) + ADDQ AX,R8 + ADCQ DX,R9 + MOVQ 208(SP),AX + MULQ 24(SP) + ADDQ AX,R10 + ADCQ DX,R11 + MOVQ 208(SP),AX + MULQ 32(SP) + ADDQ AX,R12 + ADCQ DX,R13 + MOVQ $REDMASK51,DX + SHLQ $13,SI,CX + ANDQ DX,SI + SHLQ $13,R8,R9 + ANDQ DX,R8 + ADDQ CX,R8 + SHLQ $13,R10,R11 + ANDQ DX,R10 + ADDQ R9,R10 + SHLQ $13,R12,R13 + ANDQ DX,R12 + ADDQ R11,R12 + SHLQ $13,R14,R15 + ANDQ DX,R14 + ADDQ R13,R14 + IMUL3Q $19,R15,CX + ADDQ CX,SI + MOVQ SI,CX + SHRQ $51,CX + ADDQ R8,CX + MOVQ CX,R8 + SHRQ $51,CX + ANDQ DX,SI + ADDQ R10,CX + MOVQ CX,R9 + SHRQ $51,CX + ANDQ DX,R8 + ADDQ R12,CX + MOVQ CX,AX + SHRQ $51,CX + ANDQ DX,R9 + ADDQ R14,CX + MOVQ CX,R10 + SHRQ $51,CX + ANDQ DX,AX + IMUL3Q $19,CX,CX + ADDQ CX,SI + ANDQ DX,R10 + MOVQ SI,DX + MOVQ R8,CX + MOVQ R9,R11 + MOVQ AX,R12 + MOVQ R10,R13 + ADDQ ·_2P0(SB),DX + ADDQ ·_2P1234(SB),CX + ADDQ ·_2P1234(SB),R11 + ADDQ ·_2P1234(SB),R12 + ADDQ ·_2P1234(SB),R13 + ADDQ 40(SP),SI + ADDQ 48(SP),R8 + ADDQ 56(SP),R9 + ADDQ 64(SP),AX + ADDQ 72(SP),R10 + SUBQ 40(SP),DX + SUBQ 48(SP),CX + SUBQ 56(SP),R11 + SUBQ 64(SP),R12 + SUBQ 72(SP),R13 + MOVQ SI,120(DI) + MOVQ R8,128(DI) + MOVQ R9,136(DI) + MOVQ AX,144(DI) + MOVQ R10,152(DI) + MOVQ DX,160(DI) + MOVQ CX,168(DI) + MOVQ R11,176(DI) + MOVQ R12,184(DI) + MOVQ R13,192(DI) + MOVQ 120(DI),AX + MULQ 120(DI) + MOVQ AX,SI + MOVQ DX,CX + MOVQ 120(DI),AX + SHLQ $1,AX + MULQ 128(DI) + MOVQ AX,R8 + MOVQ DX,R9 + MOVQ 120(DI),AX + SHLQ $1,AX + MULQ 136(DI) + MOVQ AX,R10 + MOVQ DX,R11 + MOVQ 120(DI),AX + SHLQ $1,AX + MULQ 144(DI) + MOVQ AX,R12 + MOVQ DX,R13 + MOVQ 120(DI),AX + SHLQ $1,AX + MULQ 152(DI) + MOVQ AX,R14 + MOVQ DX,R15 + MOVQ 128(DI),AX + MULQ 128(DI) + ADDQ AX,R10 + ADCQ DX,R11 + MOVQ 128(DI),AX + SHLQ $1,AX + MULQ 136(DI) + ADDQ AX,R12 + ADCQ DX,R13 + MOVQ 128(DI),AX + SHLQ $1,AX + MULQ 144(DI) + ADDQ AX,R14 + ADCQ DX,R15 + MOVQ 128(DI),DX + IMUL3Q $38,DX,AX + MULQ 152(DI) + ADDQ AX,SI + ADCQ DX,CX + MOVQ 136(DI),AX + MULQ 136(DI) + ADDQ AX,R14 + ADCQ DX,R15 + MOVQ 136(DI),DX + IMUL3Q $38,DX,AX + MULQ 144(DI) + ADDQ AX,SI + ADCQ DX,CX + MOVQ 136(DI),DX + IMUL3Q $38,DX,AX + MULQ 152(DI) + ADDQ AX,R8 + ADCQ DX,R9 + MOVQ 144(DI),DX + IMUL3Q $19,DX,AX + MULQ 144(DI) + ADDQ AX,R8 + ADCQ DX,R9 + MOVQ 144(DI),DX + IMUL3Q $38,DX,AX + MULQ 152(DI) + ADDQ AX,R10 + ADCQ DX,R11 + MOVQ 152(DI),DX + IMUL3Q $19,DX,AX + MULQ 152(DI) + ADDQ AX,R12 + ADCQ DX,R13 + MOVQ $REDMASK51,DX + SHLQ $13,SI,CX + ANDQ DX,SI + SHLQ $13,R8,R9 + ANDQ DX,R8 + ADDQ CX,R8 + SHLQ $13,R10,R11 + ANDQ DX,R10 + ADDQ R9,R10 + SHLQ $13,R12,R13 + ANDQ DX,R12 + ADDQ R11,R12 + SHLQ $13,R14,R15 + ANDQ DX,R14 + ADDQ R13,R14 + IMUL3Q $19,R15,CX + ADDQ CX,SI + MOVQ SI,CX + SHRQ $51,CX + ADDQ R8,CX + ANDQ DX,SI + MOVQ CX,R8 + SHRQ $51,CX + ADDQ R10,CX + ANDQ DX,R8 + MOVQ CX,R9 + SHRQ $51,CX + ADDQ R12,CX + ANDQ DX,R9 + MOVQ CX,AX + SHRQ $51,CX + ADDQ R14,CX + ANDQ DX,AX + MOVQ CX,R10 + SHRQ $51,CX + IMUL3Q $19,CX,CX + ADDQ CX,SI + ANDQ DX,R10 + MOVQ SI,120(DI) + MOVQ R8,128(DI) + MOVQ R9,136(DI) + MOVQ AX,144(DI) + MOVQ R10,152(DI) + MOVQ 160(DI),AX + MULQ 160(DI) + MOVQ AX,SI + MOVQ DX,CX + MOVQ 160(DI),AX + SHLQ $1,AX + MULQ 168(DI) + MOVQ AX,R8 + MOVQ DX,R9 + MOVQ 160(DI),AX + SHLQ $1,AX + MULQ 176(DI) + MOVQ AX,R10 + MOVQ DX,R11 + MOVQ 160(DI),AX + SHLQ $1,AX + MULQ 184(DI) + MOVQ AX,R12 + MOVQ DX,R13 + MOVQ 160(DI),AX + SHLQ $1,AX + MULQ 192(DI) + MOVQ AX,R14 + MOVQ DX,R15 + MOVQ 168(DI),AX + MULQ 168(DI) + ADDQ AX,R10 + ADCQ DX,R11 + MOVQ 168(DI),AX + SHLQ $1,AX + MULQ 176(DI) + ADDQ AX,R12 + ADCQ DX,R13 + MOVQ 168(DI),AX + SHLQ $1,AX + MULQ 184(DI) + ADDQ AX,R14 + ADCQ DX,R15 + MOVQ 168(DI),DX + IMUL3Q $38,DX,AX + MULQ 192(DI) + ADDQ AX,SI + ADCQ DX,CX + MOVQ 176(DI),AX + MULQ 176(DI) + ADDQ AX,R14 + ADCQ DX,R15 + MOVQ 176(DI),DX + IMUL3Q $38,DX,AX + MULQ 184(DI) + ADDQ AX,SI + ADCQ DX,CX + MOVQ 176(DI),DX + IMUL3Q $38,DX,AX + MULQ 192(DI) + ADDQ AX,R8 + ADCQ DX,R9 + MOVQ 184(DI),DX + IMUL3Q $19,DX,AX + MULQ 184(DI) + ADDQ AX,R8 + ADCQ DX,R9 + MOVQ 184(DI),DX + IMUL3Q $38,DX,AX + MULQ 192(DI) + ADDQ AX,R10 + ADCQ DX,R11 + MOVQ 192(DI),DX + IMUL3Q $19,DX,AX + MULQ 192(DI) + ADDQ AX,R12 + ADCQ DX,R13 + MOVQ $REDMASK51,DX + SHLQ $13,SI,CX + ANDQ DX,SI + SHLQ $13,R8,R9 + ANDQ DX,R8 + ADDQ CX,R8 + SHLQ $13,R10,R11 + ANDQ DX,R10 + ADDQ R9,R10 + SHLQ $13,R12,R13 + ANDQ DX,R12 + ADDQ R11,R12 + SHLQ $13,R14,R15 + ANDQ DX,R14 + ADDQ R13,R14 + IMUL3Q $19,R15,CX + ADDQ CX,SI + MOVQ SI,CX + SHRQ $51,CX + ADDQ R8,CX + ANDQ DX,SI + MOVQ CX,R8 + SHRQ $51,CX + ADDQ R10,CX + ANDQ DX,R8 + MOVQ CX,R9 + SHRQ $51,CX + ADDQ R12,CX + ANDQ DX,R9 + MOVQ CX,AX + SHRQ $51,CX + ADDQ R14,CX + ANDQ DX,AX + MOVQ CX,R10 + SHRQ $51,CX + IMUL3Q $19,CX,CX + ADDQ CX,SI + ANDQ DX,R10 + MOVQ SI,160(DI) + MOVQ R8,168(DI) + MOVQ R9,176(DI) + MOVQ AX,184(DI) + MOVQ R10,192(DI) + MOVQ 184(DI),SI + IMUL3Q $19,SI,AX + MOVQ AX,0(SP) + MULQ 16(DI) + MOVQ AX,SI + MOVQ DX,CX + MOVQ 192(DI),DX + IMUL3Q $19,DX,AX + MOVQ AX,8(SP) + MULQ 8(DI) + ADDQ AX,SI + ADCQ DX,CX + MOVQ 160(DI),AX + MULQ 0(DI) + ADDQ AX,SI + ADCQ DX,CX + MOVQ 160(DI),AX + MULQ 8(DI) + MOVQ AX,R8 + MOVQ DX,R9 + MOVQ 160(DI),AX + MULQ 16(DI) + MOVQ AX,R10 + MOVQ DX,R11 + MOVQ 160(DI),AX + MULQ 24(DI) + MOVQ AX,R12 + MOVQ DX,R13 + MOVQ 160(DI),AX + MULQ 32(DI) + MOVQ AX,R14 + MOVQ DX,R15 + MOVQ 168(DI),AX + MULQ 0(DI) + ADDQ AX,R8 + ADCQ DX,R9 + MOVQ 168(DI),AX + MULQ 8(DI) + ADDQ AX,R10 + ADCQ DX,R11 + MOVQ 168(DI),AX + MULQ 16(DI) + ADDQ AX,R12 + ADCQ DX,R13 + MOVQ 168(DI),AX + MULQ 24(DI) + ADDQ AX,R14 + ADCQ DX,R15 + MOVQ 168(DI),DX + IMUL3Q $19,DX,AX + MULQ 32(DI) + ADDQ AX,SI + ADCQ DX,CX + MOVQ 176(DI),AX + MULQ 0(DI) + ADDQ AX,R10 + ADCQ DX,R11 + MOVQ 176(DI),AX + MULQ 8(DI) + ADDQ AX,R12 + ADCQ DX,R13 + MOVQ 176(DI),AX + MULQ 16(DI) + ADDQ AX,R14 + ADCQ DX,R15 + MOVQ 176(DI),DX + IMUL3Q $19,DX,AX + MULQ 24(DI) + ADDQ AX,SI + ADCQ DX,CX + MOVQ 176(DI),DX + IMUL3Q $19,DX,AX + MULQ 32(DI) + ADDQ AX,R8 + ADCQ DX,R9 + MOVQ 184(DI),AX + MULQ 0(DI) + ADDQ AX,R12 + ADCQ DX,R13 + MOVQ 184(DI),AX + MULQ 8(DI) + ADDQ AX,R14 + ADCQ DX,R15 + MOVQ 0(SP),AX + MULQ 24(DI) + ADDQ AX,R8 + ADCQ DX,R9 + MOVQ 0(SP),AX + MULQ 32(DI) + ADDQ AX,R10 + ADCQ DX,R11 + MOVQ 192(DI),AX + MULQ 0(DI) + ADDQ AX,R14 + ADCQ DX,R15 + MOVQ 8(SP),AX + MULQ 16(DI) + ADDQ AX,R8 + ADCQ DX,R9 + MOVQ 8(SP),AX + MULQ 24(DI) + ADDQ AX,R10 + ADCQ DX,R11 + MOVQ 8(SP),AX + MULQ 32(DI) + ADDQ AX,R12 + ADCQ DX,R13 + MOVQ $REDMASK51,DX + SHLQ $13,SI,CX + ANDQ DX,SI + SHLQ $13,R8,R9 + ANDQ DX,R8 + ADDQ CX,R8 + SHLQ $13,R10,R11 + ANDQ DX,R10 + ADDQ R9,R10 + SHLQ $13,R12,R13 + ANDQ DX,R12 + ADDQ R11,R12 + SHLQ $13,R14,R15 + ANDQ DX,R14 + ADDQ R13,R14 + IMUL3Q $19,R15,CX + ADDQ CX,SI + MOVQ SI,CX + SHRQ $51,CX + ADDQ R8,CX + MOVQ CX,R8 + SHRQ $51,CX + ANDQ DX,SI + ADDQ R10,CX + MOVQ CX,R9 + SHRQ $51,CX + ANDQ DX,R8 + ADDQ R12,CX + MOVQ CX,AX + SHRQ $51,CX + ANDQ DX,R9 + ADDQ R14,CX + MOVQ CX,R10 + SHRQ $51,CX + ANDQ DX,AX + IMUL3Q $19,CX,CX + ADDQ CX,SI + ANDQ DX,R10 + MOVQ SI,160(DI) + MOVQ R8,168(DI) + MOVQ R9,176(DI) + MOVQ AX,184(DI) + MOVQ R10,192(DI) + MOVQ 144(SP),SI + IMUL3Q $19,SI,AX + MOVQ AX,0(SP) + MULQ 96(SP) + MOVQ AX,SI + MOVQ DX,CX + MOVQ 152(SP),DX + IMUL3Q $19,DX,AX + MOVQ AX,8(SP) + MULQ 88(SP) + ADDQ AX,SI + ADCQ DX,CX + MOVQ 120(SP),AX + MULQ 80(SP) + ADDQ AX,SI + ADCQ DX,CX + MOVQ 120(SP),AX + MULQ 88(SP) + MOVQ AX,R8 + MOVQ DX,R9 + MOVQ 120(SP),AX + MULQ 96(SP) + MOVQ AX,R10 + MOVQ DX,R11 + MOVQ 120(SP),AX + MULQ 104(SP) + MOVQ AX,R12 + MOVQ DX,R13 + MOVQ 120(SP),AX + MULQ 112(SP) + MOVQ AX,R14 + MOVQ DX,R15 + MOVQ 128(SP),AX + MULQ 80(SP) + ADDQ AX,R8 + ADCQ DX,R9 + MOVQ 128(SP),AX + MULQ 88(SP) + ADDQ AX,R10 + ADCQ DX,R11 + MOVQ 128(SP),AX + MULQ 96(SP) + ADDQ AX,R12 + ADCQ DX,R13 + MOVQ 128(SP),AX + MULQ 104(SP) + ADDQ AX,R14 + ADCQ DX,R15 + MOVQ 128(SP),DX + IMUL3Q $19,DX,AX + MULQ 112(SP) + ADDQ AX,SI + ADCQ DX,CX + MOVQ 136(SP),AX + MULQ 80(SP) + ADDQ AX,R10 + ADCQ DX,R11 + MOVQ 136(SP),AX + MULQ 88(SP) + ADDQ AX,R12 + ADCQ DX,R13 + MOVQ 136(SP),AX + MULQ 96(SP) + ADDQ AX,R14 + ADCQ DX,R15 + MOVQ 136(SP),DX + IMUL3Q $19,DX,AX + MULQ 104(SP) + ADDQ AX,SI + ADCQ DX,CX + MOVQ 136(SP),DX + IMUL3Q $19,DX,AX + MULQ 112(SP) + ADDQ AX,R8 + ADCQ DX,R9 + MOVQ 144(SP),AX + MULQ 80(SP) + ADDQ AX,R12 + ADCQ DX,R13 + MOVQ 144(SP),AX + MULQ 88(SP) + ADDQ AX,R14 + ADCQ DX,R15 + MOVQ 0(SP),AX + MULQ 104(SP) + ADDQ AX,R8 + ADCQ DX,R9 + MOVQ 0(SP),AX + MULQ 112(SP) + ADDQ AX,R10 + ADCQ DX,R11 + MOVQ 152(SP),AX + MULQ 80(SP) + ADDQ AX,R14 + ADCQ DX,R15 + MOVQ 8(SP),AX + MULQ 96(SP) + ADDQ AX,R8 + ADCQ DX,R9 + MOVQ 8(SP),AX + MULQ 104(SP) + ADDQ AX,R10 + ADCQ DX,R11 + MOVQ 8(SP),AX + MULQ 112(SP) + ADDQ AX,R12 + ADCQ DX,R13 + MOVQ $REDMASK51,DX + SHLQ $13,SI,CX + ANDQ DX,SI + SHLQ $13,R8,R9 + ANDQ DX,R8 + ADDQ CX,R8 + SHLQ $13,R10,R11 + ANDQ DX,R10 + ADDQ R9,R10 + SHLQ $13,R12,R13 + ANDQ DX,R12 + ADDQ R11,R12 + SHLQ $13,R14,R15 + ANDQ DX,R14 + ADDQ R13,R14 + IMUL3Q $19,R15,CX + ADDQ CX,SI + MOVQ SI,CX + SHRQ $51,CX + ADDQ R8,CX + MOVQ CX,R8 + SHRQ $51,CX + ANDQ DX,SI + ADDQ R10,CX + MOVQ CX,R9 + SHRQ $51,CX + ANDQ DX,R8 + ADDQ R12,CX + MOVQ CX,AX + SHRQ $51,CX + ANDQ DX,R9 + ADDQ R14,CX + MOVQ CX,R10 + SHRQ $51,CX + ANDQ DX,AX + IMUL3Q $19,CX,CX + ADDQ CX,SI + ANDQ DX,R10 + MOVQ SI,40(DI) + MOVQ R8,48(DI) + MOVQ R9,56(DI) + MOVQ AX,64(DI) + MOVQ R10,72(DI) + MOVQ 160(SP),AX + MULQ ·_121666_213(SB) + SHRQ $13,AX + MOVQ AX,SI + MOVQ DX,CX + MOVQ 168(SP),AX + MULQ ·_121666_213(SB) + SHRQ $13,AX + ADDQ AX,CX + MOVQ DX,R8 + MOVQ 176(SP),AX + MULQ ·_121666_213(SB) + SHRQ $13,AX + ADDQ AX,R8 + MOVQ DX,R9 + MOVQ 184(SP),AX + MULQ ·_121666_213(SB) + SHRQ $13,AX + ADDQ AX,R9 + MOVQ DX,R10 + MOVQ 192(SP),AX + MULQ ·_121666_213(SB) + SHRQ $13,AX + ADDQ AX,R10 + IMUL3Q $19,DX,DX + ADDQ DX,SI + ADDQ 80(SP),SI + ADDQ 88(SP),CX + ADDQ 96(SP),R8 + ADDQ 104(SP),R9 + ADDQ 112(SP),R10 + MOVQ SI,80(DI) + MOVQ CX,88(DI) + MOVQ R8,96(DI) + MOVQ R9,104(DI) + MOVQ R10,112(DI) + MOVQ 104(DI),SI + IMUL3Q $19,SI,AX + MOVQ AX,0(SP) + MULQ 176(SP) + MOVQ AX,SI + MOVQ DX,CX + MOVQ 112(DI),DX + IMUL3Q $19,DX,AX + MOVQ AX,8(SP) + MULQ 168(SP) + ADDQ AX,SI + ADCQ DX,CX + MOVQ 80(DI),AX + MULQ 160(SP) + ADDQ AX,SI + ADCQ DX,CX + MOVQ 80(DI),AX + MULQ 168(SP) + MOVQ AX,R8 + MOVQ DX,R9 + MOVQ 80(DI),AX + MULQ 176(SP) + MOVQ AX,R10 + MOVQ DX,R11 + MOVQ 80(DI),AX + MULQ 184(SP) + MOVQ AX,R12 + MOVQ DX,R13 + MOVQ 80(DI),AX + MULQ 192(SP) + MOVQ AX,R14 + MOVQ DX,R15 + MOVQ 88(DI),AX + MULQ 160(SP) + ADDQ AX,R8 + ADCQ DX,R9 + MOVQ 88(DI),AX + MULQ 168(SP) + ADDQ AX,R10 + ADCQ DX,R11 + MOVQ 88(DI),AX + MULQ 176(SP) + ADDQ AX,R12 + ADCQ DX,R13 + MOVQ 88(DI),AX + MULQ 184(SP) + ADDQ AX,R14 + ADCQ DX,R15 + MOVQ 88(DI),DX + IMUL3Q $19,DX,AX + MULQ 192(SP) + ADDQ AX,SI + ADCQ DX,CX + MOVQ 96(DI),AX + MULQ 160(SP) + ADDQ AX,R10 + ADCQ DX,R11 + MOVQ 96(DI),AX + MULQ 168(SP) + ADDQ AX,R12 + ADCQ DX,R13 + MOVQ 96(DI),AX + MULQ 176(SP) + ADDQ AX,R14 + ADCQ DX,R15 + MOVQ 96(DI),DX + IMUL3Q $19,DX,AX + MULQ 184(SP) + ADDQ AX,SI + ADCQ DX,CX + MOVQ 96(DI),DX + IMUL3Q $19,DX,AX + MULQ 192(SP) + ADDQ AX,R8 + ADCQ DX,R9 + MOVQ 104(DI),AX + MULQ 160(SP) + ADDQ AX,R12 + ADCQ DX,R13 + MOVQ 104(DI),AX + MULQ 168(SP) + ADDQ AX,R14 + ADCQ DX,R15 + MOVQ 0(SP),AX + MULQ 184(SP) + ADDQ AX,R8 + ADCQ DX,R9 + MOVQ 0(SP),AX + MULQ 192(SP) + ADDQ AX,R10 + ADCQ DX,R11 + MOVQ 112(DI),AX + MULQ 160(SP) + ADDQ AX,R14 + ADCQ DX,R15 + MOVQ 8(SP),AX + MULQ 176(SP) + ADDQ AX,R8 + ADCQ DX,R9 + MOVQ 8(SP),AX + MULQ 184(SP) + ADDQ AX,R10 + ADCQ DX,R11 + MOVQ 8(SP),AX + MULQ 192(SP) + ADDQ AX,R12 + ADCQ DX,R13 + MOVQ $REDMASK51,DX + SHLQ $13,SI,CX + ANDQ DX,SI + SHLQ $13,R8,R9 + ANDQ DX,R8 + ADDQ CX,R8 + SHLQ $13,R10,R11 + ANDQ DX,R10 + ADDQ R9,R10 + SHLQ $13,R12,R13 + ANDQ DX,R12 + ADDQ R11,R12 + SHLQ $13,R14,R15 + ANDQ DX,R14 + ADDQ R13,R14 + IMUL3Q $19,R15,CX + ADDQ CX,SI + MOVQ SI,CX + SHRQ $51,CX + ADDQ R8,CX + MOVQ CX,R8 + SHRQ $51,CX + ANDQ DX,SI + ADDQ R10,CX + MOVQ CX,R9 + SHRQ $51,CX + ANDQ DX,R8 + ADDQ R12,CX + MOVQ CX,AX + SHRQ $51,CX + ANDQ DX,R9 + ADDQ R14,CX + MOVQ CX,R10 + SHRQ $51,CX + ANDQ DX,AX + IMUL3Q $19,CX,CX + ADDQ CX,SI + ANDQ DX,R10 + MOVQ SI,80(DI) + MOVQ R8,88(DI) + MOVQ R9,96(DI) + MOVQ AX,104(DI) + MOVQ R10,112(DI) + RET + +// func cswap(inout *[4][5]uint64, v uint64) +TEXT ·cswap(SB),7,$0 + MOVQ inout+0(FP),DI + MOVQ v+8(FP),SI + + SUBQ $1, SI + NOTQ SI + MOVQ SI, X15 + PSHUFD $0x44, X15, X15 + + MOVOU 0(DI), X0 + MOVOU 16(DI), X2 + MOVOU 32(DI), X4 + MOVOU 48(DI), X6 + MOVOU 64(DI), X8 + MOVOU 80(DI), X1 + MOVOU 96(DI), X3 + MOVOU 112(DI), X5 + MOVOU 128(DI), X7 + MOVOU 144(DI), X9 + + MOVO X1, X10 + MOVO X3, X11 + MOVO X5, X12 + MOVO X7, X13 + MOVO X9, X14 + + PXOR X0, X10 + PXOR X2, X11 + PXOR X4, X12 + PXOR X6, X13 + PXOR X8, X14 + PAND X15, X10 + PAND X15, X11 + PAND X15, X12 + PAND X15, X13 + PAND X15, X14 + PXOR X10, X0 + PXOR X10, X1 + PXOR X11, X2 + PXOR X11, X3 + PXOR X12, X4 + PXOR X12, X5 + PXOR X13, X6 + PXOR X13, X7 + PXOR X14, X8 + PXOR X14, X9 + + MOVOU X0, 0(DI) + MOVOU X2, 16(DI) + MOVOU X4, 32(DI) + MOVOU X6, 48(DI) + MOVOU X8, 64(DI) + MOVOU X1, 80(DI) + MOVOU X3, 96(DI) + MOVOU X5, 112(DI) + MOVOU X7, 128(DI) + MOVOU X9, 144(DI) + RET + +// func mul(dest, a, b *[5]uint64) +TEXT ·mul(SB),0,$16-24 + MOVQ dest+0(FP), DI + MOVQ a+8(FP), SI + MOVQ b+16(FP), DX + + MOVQ DX,CX + MOVQ 24(SI),DX + IMUL3Q $19,DX,AX + MOVQ AX,0(SP) + MULQ 16(CX) + MOVQ AX,R8 + MOVQ DX,R9 + MOVQ 32(SI),DX + IMUL3Q $19,DX,AX + MOVQ AX,8(SP) + MULQ 8(CX) + ADDQ AX,R8 + ADCQ DX,R9 + MOVQ 0(SI),AX + MULQ 0(CX) + ADDQ AX,R8 + ADCQ DX,R9 + MOVQ 0(SI),AX + MULQ 8(CX) + MOVQ AX,R10 + MOVQ DX,R11 + MOVQ 0(SI),AX + MULQ 16(CX) + MOVQ AX,R12 + MOVQ DX,R13 + MOVQ 0(SI),AX + MULQ 24(CX) + MOVQ AX,R14 + MOVQ DX,R15 + MOVQ 0(SI),AX + MULQ 32(CX) + MOVQ AX,BX + MOVQ DX,BP + MOVQ 8(SI),AX + MULQ 0(CX) + ADDQ AX,R10 + ADCQ DX,R11 + MOVQ 8(SI),AX + MULQ 8(CX) + ADDQ AX,R12 + ADCQ DX,R13 + MOVQ 8(SI),AX + MULQ 16(CX) + ADDQ AX,R14 + ADCQ DX,R15 + MOVQ 8(SI),AX + MULQ 24(CX) + ADDQ AX,BX + ADCQ DX,BP + MOVQ 8(SI),DX + IMUL3Q $19,DX,AX + MULQ 32(CX) + ADDQ AX,R8 + ADCQ DX,R9 + MOVQ 16(SI),AX + MULQ 0(CX) + ADDQ AX,R12 + ADCQ DX,R13 + MOVQ 16(SI),AX + MULQ 8(CX) + ADDQ AX,R14 + ADCQ DX,R15 + MOVQ 16(SI),AX + MULQ 16(CX) + ADDQ AX,BX + ADCQ DX,BP + MOVQ 16(SI),DX + IMUL3Q $19,DX,AX + MULQ 24(CX) + ADDQ AX,R8 + ADCQ DX,R9 + MOVQ 16(SI),DX + IMUL3Q $19,DX,AX + MULQ 32(CX) + ADDQ AX,R10 + ADCQ DX,R11 + MOVQ 24(SI),AX + MULQ 0(CX) + ADDQ AX,R14 + ADCQ DX,R15 + MOVQ 24(SI),AX + MULQ 8(CX) + ADDQ AX,BX + ADCQ DX,BP + MOVQ 0(SP),AX + MULQ 24(CX) + ADDQ AX,R10 + ADCQ DX,R11 + MOVQ 0(SP),AX + MULQ 32(CX) + ADDQ AX,R12 + ADCQ DX,R13 + MOVQ 32(SI),AX + MULQ 0(CX) + ADDQ AX,BX + ADCQ DX,BP + MOVQ 8(SP),AX + MULQ 16(CX) + ADDQ AX,R10 + ADCQ DX,R11 + MOVQ 8(SP),AX + MULQ 24(CX) + ADDQ AX,R12 + ADCQ DX,R13 + MOVQ 8(SP),AX + MULQ 32(CX) + ADDQ AX,R14 + ADCQ DX,R15 + MOVQ $REDMASK51,SI + SHLQ $13,R8,R9 + ANDQ SI,R8 + SHLQ $13,R10,R11 + ANDQ SI,R10 + ADDQ R9,R10 + SHLQ $13,R12,R13 + ANDQ SI,R12 + ADDQ R11,R12 + SHLQ $13,R14,R15 + ANDQ SI,R14 + ADDQ R13,R14 + SHLQ $13,BX,BP + ANDQ SI,BX + ADDQ R15,BX + IMUL3Q $19,BP,DX + ADDQ DX,R8 + MOVQ R8,DX + SHRQ $51,DX + ADDQ R10,DX + MOVQ DX,CX + SHRQ $51,DX + ANDQ SI,R8 + ADDQ R12,DX + MOVQ DX,R9 + SHRQ $51,DX + ANDQ SI,CX + ADDQ R14,DX + MOVQ DX,AX + SHRQ $51,DX + ANDQ SI,R9 + ADDQ BX,DX + MOVQ DX,R10 + SHRQ $51,DX + ANDQ SI,AX + IMUL3Q $19,DX,DX + ADDQ DX,R8 + ANDQ SI,R10 + MOVQ R8,0(DI) + MOVQ CX,8(DI) + MOVQ R9,16(DI) + MOVQ AX,24(DI) + MOVQ R10,32(DI) + RET + +// func square(out, in *[5]uint64) +TEXT ·square(SB),7,$0-16 + MOVQ out+0(FP), DI + MOVQ in+8(FP), SI + + MOVQ 0(SI),AX + MULQ 0(SI) + MOVQ AX,CX + MOVQ DX,R8 + MOVQ 0(SI),AX + SHLQ $1,AX + MULQ 8(SI) + MOVQ AX,R9 + MOVQ DX,R10 + MOVQ 0(SI),AX + SHLQ $1,AX + MULQ 16(SI) + MOVQ AX,R11 + MOVQ DX,R12 + MOVQ 0(SI),AX + SHLQ $1,AX + MULQ 24(SI) + MOVQ AX,R13 + MOVQ DX,R14 + MOVQ 0(SI),AX + SHLQ $1,AX + MULQ 32(SI) + MOVQ AX,R15 + MOVQ DX,BX + MOVQ 8(SI),AX + MULQ 8(SI) + ADDQ AX,R11 + ADCQ DX,R12 + MOVQ 8(SI),AX + SHLQ $1,AX + MULQ 16(SI) + ADDQ AX,R13 + ADCQ DX,R14 + MOVQ 8(SI),AX + SHLQ $1,AX + MULQ 24(SI) + ADDQ AX,R15 + ADCQ DX,BX + MOVQ 8(SI),DX + IMUL3Q $38,DX,AX + MULQ 32(SI) + ADDQ AX,CX + ADCQ DX,R8 + MOVQ 16(SI),AX + MULQ 16(SI) + ADDQ AX,R15 + ADCQ DX,BX + MOVQ 16(SI),DX + IMUL3Q $38,DX,AX + MULQ 24(SI) + ADDQ AX,CX + ADCQ DX,R8 + MOVQ 16(SI),DX + IMUL3Q $38,DX,AX + MULQ 32(SI) + ADDQ AX,R9 + ADCQ DX,R10 + MOVQ 24(SI),DX + IMUL3Q $19,DX,AX + MULQ 24(SI) + ADDQ AX,R9 + ADCQ DX,R10 + MOVQ 24(SI),DX + IMUL3Q $38,DX,AX + MULQ 32(SI) + ADDQ AX,R11 + ADCQ DX,R12 + MOVQ 32(SI),DX + IMUL3Q $19,DX,AX + MULQ 32(SI) + ADDQ AX,R13 + ADCQ DX,R14 + MOVQ $REDMASK51,SI + SHLQ $13,CX,R8 + ANDQ SI,CX + SHLQ $13,R9,R10 + ANDQ SI,R9 + ADDQ R8,R9 + SHLQ $13,R11,R12 + ANDQ SI,R11 + ADDQ R10,R11 + SHLQ $13,R13,R14 + ANDQ SI,R13 + ADDQ R12,R13 + SHLQ $13,R15,BX + ANDQ SI,R15 + ADDQ R14,R15 + IMUL3Q $19,BX,DX + ADDQ DX,CX + MOVQ CX,DX + SHRQ $51,DX + ADDQ R9,DX + ANDQ SI,CX + MOVQ DX,R8 + SHRQ $51,DX + ADDQ R11,DX + ANDQ SI,R8 + MOVQ DX,R9 + SHRQ $51,DX + ADDQ R13,DX + ANDQ SI,R9 + MOVQ DX,AX + SHRQ $51,DX + ADDQ R15,DX + ANDQ SI,AX + MOVQ DX,R10 + SHRQ $51,DX + IMUL3Q $19,DX,DX + ADDQ DX,CX + ANDQ SI,R10 + MOVQ CX,0(DI) + MOVQ R8,8(DI) + MOVQ R9,16(DI) + MOVQ AX,24(DI) + MOVQ R10,32(DI) + RET diff --git a/vendor/golang.org/x/crypto/curve25519/curve25519_generic.go b/vendor/golang.org/x/crypto/curve25519/curve25519_generic.go new file mode 100644 index 00000000000..c43b13fc83e --- /dev/null +++ b/vendor/golang.org/x/crypto/curve25519/curve25519_generic.go @@ -0,0 +1,828 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package curve25519 + +import "encoding/binary" + +// This code is a port of the public domain, "ref10" implementation of +// curve25519 from SUPERCOP 20130419 by D. J. Bernstein. + +// fieldElement represents an element of the field GF(2^255 - 19). An element +// t, entries t[0]...t[9], represents the integer t[0]+2^26 t[1]+2^51 t[2]+2^77 +// t[3]+2^102 t[4]+...+2^230 t[9]. Bounds on each t[i] vary depending on +// context. +type fieldElement [10]int32 + +func feZero(fe *fieldElement) { + for i := range fe { + fe[i] = 0 + } +} + +func feOne(fe *fieldElement) { + feZero(fe) + fe[0] = 1 +} + +func feAdd(dst, a, b *fieldElement) { + for i := range dst { + dst[i] = a[i] + b[i] + } +} + +func feSub(dst, a, b *fieldElement) { + for i := range dst { + dst[i] = a[i] - b[i] + } +} + +func feCopy(dst, src *fieldElement) { + for i := range dst { + dst[i] = src[i] + } +} + +// feCSwap replaces (f,g) with (g,f) if b == 1; replaces (f,g) with (f,g) if b == 0. +// +// Preconditions: b in {0,1}. +func feCSwap(f, g *fieldElement, b int32) { + b = -b + for i := range f { + t := b & (f[i] ^ g[i]) + f[i] ^= t + g[i] ^= t + } +} + +// load3 reads a 24-bit, little-endian value from in. +func load3(in []byte) int64 { + var r int64 + r = int64(in[0]) + r |= int64(in[1]) << 8 + r |= int64(in[2]) << 16 + return r +} + +// load4 reads a 32-bit, little-endian value from in. +func load4(in []byte) int64 { + return int64(binary.LittleEndian.Uint32(in)) +} + +func feFromBytes(dst *fieldElement, src *[32]byte) { + h0 := load4(src[:]) + h1 := load3(src[4:]) << 6 + h2 := load3(src[7:]) << 5 + h3 := load3(src[10:]) << 3 + h4 := load3(src[13:]) << 2 + h5 := load4(src[16:]) + h6 := load3(src[20:]) << 7 + h7 := load3(src[23:]) << 5 + h8 := load3(src[26:]) << 4 + h9 := (load3(src[29:]) & 0x7fffff) << 2 + + var carry [10]int64 + carry[9] = (h9 + 1<<24) >> 25 + h0 += carry[9] * 19 + h9 -= carry[9] << 25 + carry[1] = (h1 + 1<<24) >> 25 + h2 += carry[1] + h1 -= carry[1] << 25 + carry[3] = (h3 + 1<<24) >> 25 + h4 += carry[3] + h3 -= carry[3] << 25 + carry[5] = (h5 + 1<<24) >> 25 + h6 += carry[5] + h5 -= carry[5] << 25 + carry[7] = (h7 + 1<<24) >> 25 + h8 += carry[7] + h7 -= carry[7] << 25 + + carry[0] = (h0 + 1<<25) >> 26 + h1 += carry[0] + h0 -= carry[0] << 26 + carry[2] = (h2 + 1<<25) >> 26 + h3 += carry[2] + h2 -= carry[2] << 26 + carry[4] = (h4 + 1<<25) >> 26 + h5 += carry[4] + h4 -= carry[4] << 26 + carry[6] = (h6 + 1<<25) >> 26 + h7 += carry[6] + h6 -= carry[6] << 26 + carry[8] = (h8 + 1<<25) >> 26 + h9 += carry[8] + h8 -= carry[8] << 26 + + dst[0] = int32(h0) + dst[1] = int32(h1) + dst[2] = int32(h2) + dst[3] = int32(h3) + dst[4] = int32(h4) + dst[5] = int32(h5) + dst[6] = int32(h6) + dst[7] = int32(h7) + dst[8] = int32(h8) + dst[9] = int32(h9) +} + +// feToBytes marshals h to s. +// Preconditions: +// |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. +// +// Write p=2^255-19; q=floor(h/p). +// Basic claim: q = floor(2^(-255)(h + 19 2^(-25)h9 + 2^(-1))). +// +// Proof: +// Have |h|<=p so |q|<=1 so |19^2 2^(-255) q|<1/4. +// Also have |h-2^230 h9|<2^230 so |19 2^(-255)(h-2^230 h9)|<1/4. +// +// Write y=2^(-1)-19^2 2^(-255)q-19 2^(-255)(h-2^230 h9). +// Then 0> 25 + q = (h[0] + q) >> 26 + q = (h[1] + q) >> 25 + q = (h[2] + q) >> 26 + q = (h[3] + q) >> 25 + q = (h[4] + q) >> 26 + q = (h[5] + q) >> 25 + q = (h[6] + q) >> 26 + q = (h[7] + q) >> 25 + q = (h[8] + q) >> 26 + q = (h[9] + q) >> 25 + + // Goal: Output h-(2^255-19)q, which is between 0 and 2^255-20. + h[0] += 19 * q + // Goal: Output h-2^255 q, which is between 0 and 2^255-20. + + carry[0] = h[0] >> 26 + h[1] += carry[0] + h[0] -= carry[0] << 26 + carry[1] = h[1] >> 25 + h[2] += carry[1] + h[1] -= carry[1] << 25 + carry[2] = h[2] >> 26 + h[3] += carry[2] + h[2] -= carry[2] << 26 + carry[3] = h[3] >> 25 + h[4] += carry[3] + h[3] -= carry[3] << 25 + carry[4] = h[4] >> 26 + h[5] += carry[4] + h[4] -= carry[4] << 26 + carry[5] = h[5] >> 25 + h[6] += carry[5] + h[5] -= carry[5] << 25 + carry[6] = h[6] >> 26 + h[7] += carry[6] + h[6] -= carry[6] << 26 + carry[7] = h[7] >> 25 + h[8] += carry[7] + h[7] -= carry[7] << 25 + carry[8] = h[8] >> 26 + h[9] += carry[8] + h[8] -= carry[8] << 26 + carry[9] = h[9] >> 25 + h[9] -= carry[9] << 25 + // h10 = carry9 + + // Goal: Output h[0]+...+2^255 h10-2^255 q, which is between 0 and 2^255-20. + // Have h[0]+...+2^230 h[9] between 0 and 2^255-1; + // evidently 2^255 h10-2^255 q = 0. + // Goal: Output h[0]+...+2^230 h[9]. + + s[0] = byte(h[0] >> 0) + s[1] = byte(h[0] >> 8) + s[2] = byte(h[0] >> 16) + s[3] = byte((h[0] >> 24) | (h[1] << 2)) + s[4] = byte(h[1] >> 6) + s[5] = byte(h[1] >> 14) + s[6] = byte((h[1] >> 22) | (h[2] << 3)) + s[7] = byte(h[2] >> 5) + s[8] = byte(h[2] >> 13) + s[9] = byte((h[2] >> 21) | (h[3] << 5)) + s[10] = byte(h[3] >> 3) + s[11] = byte(h[3] >> 11) + s[12] = byte((h[3] >> 19) | (h[4] << 6)) + s[13] = byte(h[4] >> 2) + s[14] = byte(h[4] >> 10) + s[15] = byte(h[4] >> 18) + s[16] = byte(h[5] >> 0) + s[17] = byte(h[5] >> 8) + s[18] = byte(h[5] >> 16) + s[19] = byte((h[5] >> 24) | (h[6] << 1)) + s[20] = byte(h[6] >> 7) + s[21] = byte(h[6] >> 15) + s[22] = byte((h[6] >> 23) | (h[7] << 3)) + s[23] = byte(h[7] >> 5) + s[24] = byte(h[7] >> 13) + s[25] = byte((h[7] >> 21) | (h[8] << 4)) + s[26] = byte(h[8] >> 4) + s[27] = byte(h[8] >> 12) + s[28] = byte((h[8] >> 20) | (h[9] << 6)) + s[29] = byte(h[9] >> 2) + s[30] = byte(h[9] >> 10) + s[31] = byte(h[9] >> 18) +} + +// feMul calculates h = f * g +// Can overlap h with f or g. +// +// Preconditions: +// |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. +// |g| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. +// +// Postconditions: +// |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. +// +// Notes on implementation strategy: +// +// Using schoolbook multiplication. +// Karatsuba would save a little in some cost models. +// +// Most multiplications by 2 and 19 are 32-bit precomputations; +// cheaper than 64-bit postcomputations. +// +// There is one remaining multiplication by 19 in the carry chain; +// one *19 precomputation can be merged into this, +// but the resulting data flow is considerably less clean. +// +// There are 12 carries below. +// 10 of them are 2-way parallelizable and vectorizable. +// Can get away with 11 carries, but then data flow is much deeper. +// +// With tighter constraints on inputs can squeeze carries into int32. +func feMul(h, f, g *fieldElement) { + f0 := f[0] + f1 := f[1] + f2 := f[2] + f3 := f[3] + f4 := f[4] + f5 := f[5] + f6 := f[6] + f7 := f[7] + f8 := f[8] + f9 := f[9] + g0 := g[0] + g1 := g[1] + g2 := g[2] + g3 := g[3] + g4 := g[4] + g5 := g[5] + g6 := g[6] + g7 := g[7] + g8 := g[8] + g9 := g[9] + g1_19 := 19 * g1 // 1.4*2^29 + g2_19 := 19 * g2 // 1.4*2^30; still ok + g3_19 := 19 * g3 + g4_19 := 19 * g4 + g5_19 := 19 * g5 + g6_19 := 19 * g6 + g7_19 := 19 * g7 + g8_19 := 19 * g8 + g9_19 := 19 * g9 + f1_2 := 2 * f1 + f3_2 := 2 * f3 + f5_2 := 2 * f5 + f7_2 := 2 * f7 + f9_2 := 2 * f9 + f0g0 := int64(f0) * int64(g0) + f0g1 := int64(f0) * int64(g1) + f0g2 := int64(f0) * int64(g2) + f0g3 := int64(f0) * int64(g3) + f0g4 := int64(f0) * int64(g4) + f0g5 := int64(f0) * int64(g5) + f0g6 := int64(f0) * int64(g6) + f0g7 := int64(f0) * int64(g7) + f0g8 := int64(f0) * int64(g8) + f0g9 := int64(f0) * int64(g9) + f1g0 := int64(f1) * int64(g0) + f1g1_2 := int64(f1_2) * int64(g1) + f1g2 := int64(f1) * int64(g2) + f1g3_2 := int64(f1_2) * int64(g3) + f1g4 := int64(f1) * int64(g4) + f1g5_2 := int64(f1_2) * int64(g5) + f1g6 := int64(f1) * int64(g6) + f1g7_2 := int64(f1_2) * int64(g7) + f1g8 := int64(f1) * int64(g8) + f1g9_38 := int64(f1_2) * int64(g9_19) + f2g0 := int64(f2) * int64(g0) + f2g1 := int64(f2) * int64(g1) + f2g2 := int64(f2) * int64(g2) + f2g3 := int64(f2) * int64(g3) + f2g4 := int64(f2) * int64(g4) + f2g5 := int64(f2) * int64(g5) + f2g6 := int64(f2) * int64(g6) + f2g7 := int64(f2) * int64(g7) + f2g8_19 := int64(f2) * int64(g8_19) + f2g9_19 := int64(f2) * int64(g9_19) + f3g0 := int64(f3) * int64(g0) + f3g1_2 := int64(f3_2) * int64(g1) + f3g2 := int64(f3) * int64(g2) + f3g3_2 := int64(f3_2) * int64(g3) + f3g4 := int64(f3) * int64(g4) + f3g5_2 := int64(f3_2) * int64(g5) + f3g6 := int64(f3) * int64(g6) + f3g7_38 := int64(f3_2) * int64(g7_19) + f3g8_19 := int64(f3) * int64(g8_19) + f3g9_38 := int64(f3_2) * int64(g9_19) + f4g0 := int64(f4) * int64(g0) + f4g1 := int64(f4) * int64(g1) + f4g2 := int64(f4) * int64(g2) + f4g3 := int64(f4) * int64(g3) + f4g4 := int64(f4) * int64(g4) + f4g5 := int64(f4) * int64(g5) + f4g6_19 := int64(f4) * int64(g6_19) + f4g7_19 := int64(f4) * int64(g7_19) + f4g8_19 := int64(f4) * int64(g8_19) + f4g9_19 := int64(f4) * int64(g9_19) + f5g0 := int64(f5) * int64(g0) + f5g1_2 := int64(f5_2) * int64(g1) + f5g2 := int64(f5) * int64(g2) + f5g3_2 := int64(f5_2) * int64(g3) + f5g4 := int64(f5) * int64(g4) + f5g5_38 := int64(f5_2) * int64(g5_19) + f5g6_19 := int64(f5) * int64(g6_19) + f5g7_38 := int64(f5_2) * int64(g7_19) + f5g8_19 := int64(f5) * int64(g8_19) + f5g9_38 := int64(f5_2) * int64(g9_19) + f6g0 := int64(f6) * int64(g0) + f6g1 := int64(f6) * int64(g1) + f6g2 := int64(f6) * int64(g2) + f6g3 := int64(f6) * int64(g3) + f6g4_19 := int64(f6) * int64(g4_19) + f6g5_19 := int64(f6) * int64(g5_19) + f6g6_19 := int64(f6) * int64(g6_19) + f6g7_19 := int64(f6) * int64(g7_19) + f6g8_19 := int64(f6) * int64(g8_19) + f6g9_19 := int64(f6) * int64(g9_19) + f7g0 := int64(f7) * int64(g0) + f7g1_2 := int64(f7_2) * int64(g1) + f7g2 := int64(f7) * int64(g2) + f7g3_38 := int64(f7_2) * int64(g3_19) + f7g4_19 := int64(f7) * int64(g4_19) + f7g5_38 := int64(f7_2) * int64(g5_19) + f7g6_19 := int64(f7) * int64(g6_19) + f7g7_38 := int64(f7_2) * int64(g7_19) + f7g8_19 := int64(f7) * int64(g8_19) + f7g9_38 := int64(f7_2) * int64(g9_19) + f8g0 := int64(f8) * int64(g0) + f8g1 := int64(f8) * int64(g1) + f8g2_19 := int64(f8) * int64(g2_19) + f8g3_19 := int64(f8) * int64(g3_19) + f8g4_19 := int64(f8) * int64(g4_19) + f8g5_19 := int64(f8) * int64(g5_19) + f8g6_19 := int64(f8) * int64(g6_19) + f8g7_19 := int64(f8) * int64(g7_19) + f8g8_19 := int64(f8) * int64(g8_19) + f8g9_19 := int64(f8) * int64(g9_19) + f9g0 := int64(f9) * int64(g0) + f9g1_38 := int64(f9_2) * int64(g1_19) + f9g2_19 := int64(f9) * int64(g2_19) + f9g3_38 := int64(f9_2) * int64(g3_19) + f9g4_19 := int64(f9) * int64(g4_19) + f9g5_38 := int64(f9_2) * int64(g5_19) + f9g6_19 := int64(f9) * int64(g6_19) + f9g7_38 := int64(f9_2) * int64(g7_19) + f9g8_19 := int64(f9) * int64(g8_19) + f9g9_38 := int64(f9_2) * int64(g9_19) + h0 := f0g0 + f1g9_38 + f2g8_19 + f3g7_38 + f4g6_19 + f5g5_38 + f6g4_19 + f7g3_38 + f8g2_19 + f9g1_38 + h1 := f0g1 + f1g0 + f2g9_19 + f3g8_19 + f4g7_19 + f5g6_19 + f6g5_19 + f7g4_19 + f8g3_19 + f9g2_19 + h2 := f0g2 + f1g1_2 + f2g0 + f3g9_38 + f4g8_19 + f5g7_38 + f6g6_19 + f7g5_38 + f8g4_19 + f9g3_38 + h3 := f0g3 + f1g2 + f2g1 + f3g0 + f4g9_19 + f5g8_19 + f6g7_19 + f7g6_19 + f8g5_19 + f9g4_19 + h4 := f0g4 + f1g3_2 + f2g2 + f3g1_2 + f4g0 + f5g9_38 + f6g8_19 + f7g7_38 + f8g6_19 + f9g5_38 + h5 := f0g5 + f1g4 + f2g3 + f3g2 + f4g1 + f5g0 + f6g9_19 + f7g8_19 + f8g7_19 + f9g6_19 + h6 := f0g6 + f1g5_2 + f2g4 + f3g3_2 + f4g2 + f5g1_2 + f6g0 + f7g9_38 + f8g8_19 + f9g7_38 + h7 := f0g7 + f1g6 + f2g5 + f3g4 + f4g3 + f5g2 + f6g1 + f7g0 + f8g9_19 + f9g8_19 + h8 := f0g8 + f1g7_2 + f2g6 + f3g5_2 + f4g4 + f5g3_2 + f6g2 + f7g1_2 + f8g0 + f9g9_38 + h9 := f0g9 + f1g8 + f2g7 + f3g6 + f4g5 + f5g4 + f6g3 + f7g2 + f8g1 + f9g0 + var carry [10]int64 + + // |h0| <= (1.1*1.1*2^52*(1+19+19+19+19)+1.1*1.1*2^50*(38+38+38+38+38)) + // i.e. |h0| <= 1.2*2^59; narrower ranges for h2, h4, h6, h8 + // |h1| <= (1.1*1.1*2^51*(1+1+19+19+19+19+19+19+19+19)) + // i.e. |h1| <= 1.5*2^58; narrower ranges for h3, h5, h7, h9 + + carry[0] = (h0 + (1 << 25)) >> 26 + h1 += carry[0] + h0 -= carry[0] << 26 + carry[4] = (h4 + (1 << 25)) >> 26 + h5 += carry[4] + h4 -= carry[4] << 26 + // |h0| <= 2^25 + // |h4| <= 2^25 + // |h1| <= 1.51*2^58 + // |h5| <= 1.51*2^58 + + carry[1] = (h1 + (1 << 24)) >> 25 + h2 += carry[1] + h1 -= carry[1] << 25 + carry[5] = (h5 + (1 << 24)) >> 25 + h6 += carry[5] + h5 -= carry[5] << 25 + // |h1| <= 2^24; from now on fits into int32 + // |h5| <= 2^24; from now on fits into int32 + // |h2| <= 1.21*2^59 + // |h6| <= 1.21*2^59 + + carry[2] = (h2 + (1 << 25)) >> 26 + h3 += carry[2] + h2 -= carry[2] << 26 + carry[6] = (h6 + (1 << 25)) >> 26 + h7 += carry[6] + h6 -= carry[6] << 26 + // |h2| <= 2^25; from now on fits into int32 unchanged + // |h6| <= 2^25; from now on fits into int32 unchanged + // |h3| <= 1.51*2^58 + // |h7| <= 1.51*2^58 + + carry[3] = (h3 + (1 << 24)) >> 25 + h4 += carry[3] + h3 -= carry[3] << 25 + carry[7] = (h7 + (1 << 24)) >> 25 + h8 += carry[7] + h7 -= carry[7] << 25 + // |h3| <= 2^24; from now on fits into int32 unchanged + // |h7| <= 2^24; from now on fits into int32 unchanged + // |h4| <= 1.52*2^33 + // |h8| <= 1.52*2^33 + + carry[4] = (h4 + (1 << 25)) >> 26 + h5 += carry[4] + h4 -= carry[4] << 26 + carry[8] = (h8 + (1 << 25)) >> 26 + h9 += carry[8] + h8 -= carry[8] << 26 + // |h4| <= 2^25; from now on fits into int32 unchanged + // |h8| <= 2^25; from now on fits into int32 unchanged + // |h5| <= 1.01*2^24 + // |h9| <= 1.51*2^58 + + carry[9] = (h9 + (1 << 24)) >> 25 + h0 += carry[9] * 19 + h9 -= carry[9] << 25 + // |h9| <= 2^24; from now on fits into int32 unchanged + // |h0| <= 1.8*2^37 + + carry[0] = (h0 + (1 << 25)) >> 26 + h1 += carry[0] + h0 -= carry[0] << 26 + // |h0| <= 2^25; from now on fits into int32 unchanged + // |h1| <= 1.01*2^24 + + h[0] = int32(h0) + h[1] = int32(h1) + h[2] = int32(h2) + h[3] = int32(h3) + h[4] = int32(h4) + h[5] = int32(h5) + h[6] = int32(h6) + h[7] = int32(h7) + h[8] = int32(h8) + h[9] = int32(h9) +} + +// feSquare calculates h = f*f. Can overlap h with f. +// +// Preconditions: +// |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. +// +// Postconditions: +// |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. +func feSquare(h, f *fieldElement) { + f0 := f[0] + f1 := f[1] + f2 := f[2] + f3 := f[3] + f4 := f[4] + f5 := f[5] + f6 := f[6] + f7 := f[7] + f8 := f[8] + f9 := f[9] + f0_2 := 2 * f0 + f1_2 := 2 * f1 + f2_2 := 2 * f2 + f3_2 := 2 * f3 + f4_2 := 2 * f4 + f5_2 := 2 * f5 + f6_2 := 2 * f6 + f7_2 := 2 * f7 + f5_38 := 38 * f5 // 1.31*2^30 + f6_19 := 19 * f6 // 1.31*2^30 + f7_38 := 38 * f7 // 1.31*2^30 + f8_19 := 19 * f8 // 1.31*2^30 + f9_38 := 38 * f9 // 1.31*2^30 + f0f0 := int64(f0) * int64(f0) + f0f1_2 := int64(f0_2) * int64(f1) + f0f2_2 := int64(f0_2) * int64(f2) + f0f3_2 := int64(f0_2) * int64(f3) + f0f4_2 := int64(f0_2) * int64(f4) + f0f5_2 := int64(f0_2) * int64(f5) + f0f6_2 := int64(f0_2) * int64(f6) + f0f7_2 := int64(f0_2) * int64(f7) + f0f8_2 := int64(f0_2) * int64(f8) + f0f9_2 := int64(f0_2) * int64(f9) + f1f1_2 := int64(f1_2) * int64(f1) + f1f2_2 := int64(f1_2) * int64(f2) + f1f3_4 := int64(f1_2) * int64(f3_2) + f1f4_2 := int64(f1_2) * int64(f4) + f1f5_4 := int64(f1_2) * int64(f5_2) + f1f6_2 := int64(f1_2) * int64(f6) + f1f7_4 := int64(f1_2) * int64(f7_2) + f1f8_2 := int64(f1_2) * int64(f8) + f1f9_76 := int64(f1_2) * int64(f9_38) + f2f2 := int64(f2) * int64(f2) + f2f3_2 := int64(f2_2) * int64(f3) + f2f4_2 := int64(f2_2) * int64(f4) + f2f5_2 := int64(f2_2) * int64(f5) + f2f6_2 := int64(f2_2) * int64(f6) + f2f7_2 := int64(f2_2) * int64(f7) + f2f8_38 := int64(f2_2) * int64(f8_19) + f2f9_38 := int64(f2) * int64(f9_38) + f3f3_2 := int64(f3_2) * int64(f3) + f3f4_2 := int64(f3_2) * int64(f4) + f3f5_4 := int64(f3_2) * int64(f5_2) + f3f6_2 := int64(f3_2) * int64(f6) + f3f7_76 := int64(f3_2) * int64(f7_38) + f3f8_38 := int64(f3_2) * int64(f8_19) + f3f9_76 := int64(f3_2) * int64(f9_38) + f4f4 := int64(f4) * int64(f4) + f4f5_2 := int64(f4_2) * int64(f5) + f4f6_38 := int64(f4_2) * int64(f6_19) + f4f7_38 := int64(f4) * int64(f7_38) + f4f8_38 := int64(f4_2) * int64(f8_19) + f4f9_38 := int64(f4) * int64(f9_38) + f5f5_38 := int64(f5) * int64(f5_38) + f5f6_38 := int64(f5_2) * int64(f6_19) + f5f7_76 := int64(f5_2) * int64(f7_38) + f5f8_38 := int64(f5_2) * int64(f8_19) + f5f9_76 := int64(f5_2) * int64(f9_38) + f6f6_19 := int64(f6) * int64(f6_19) + f6f7_38 := int64(f6) * int64(f7_38) + f6f8_38 := int64(f6_2) * int64(f8_19) + f6f9_38 := int64(f6) * int64(f9_38) + f7f7_38 := int64(f7) * int64(f7_38) + f7f8_38 := int64(f7_2) * int64(f8_19) + f7f9_76 := int64(f7_2) * int64(f9_38) + f8f8_19 := int64(f8) * int64(f8_19) + f8f9_38 := int64(f8) * int64(f9_38) + f9f9_38 := int64(f9) * int64(f9_38) + h0 := f0f0 + f1f9_76 + f2f8_38 + f3f7_76 + f4f6_38 + f5f5_38 + h1 := f0f1_2 + f2f9_38 + f3f8_38 + f4f7_38 + f5f6_38 + h2 := f0f2_2 + f1f1_2 + f3f9_76 + f4f8_38 + f5f7_76 + f6f6_19 + h3 := f0f3_2 + f1f2_2 + f4f9_38 + f5f8_38 + f6f7_38 + h4 := f0f4_2 + f1f3_4 + f2f2 + f5f9_76 + f6f8_38 + f7f7_38 + h5 := f0f5_2 + f1f4_2 + f2f3_2 + f6f9_38 + f7f8_38 + h6 := f0f6_2 + f1f5_4 + f2f4_2 + f3f3_2 + f7f9_76 + f8f8_19 + h7 := f0f7_2 + f1f6_2 + f2f5_2 + f3f4_2 + f8f9_38 + h8 := f0f8_2 + f1f7_4 + f2f6_2 + f3f5_4 + f4f4 + f9f9_38 + h9 := f0f9_2 + f1f8_2 + f2f7_2 + f3f6_2 + f4f5_2 + var carry [10]int64 + + carry[0] = (h0 + (1 << 25)) >> 26 + h1 += carry[0] + h0 -= carry[0] << 26 + carry[4] = (h4 + (1 << 25)) >> 26 + h5 += carry[4] + h4 -= carry[4] << 26 + + carry[1] = (h1 + (1 << 24)) >> 25 + h2 += carry[1] + h1 -= carry[1] << 25 + carry[5] = (h5 + (1 << 24)) >> 25 + h6 += carry[5] + h5 -= carry[5] << 25 + + carry[2] = (h2 + (1 << 25)) >> 26 + h3 += carry[2] + h2 -= carry[2] << 26 + carry[6] = (h6 + (1 << 25)) >> 26 + h7 += carry[6] + h6 -= carry[6] << 26 + + carry[3] = (h3 + (1 << 24)) >> 25 + h4 += carry[3] + h3 -= carry[3] << 25 + carry[7] = (h7 + (1 << 24)) >> 25 + h8 += carry[7] + h7 -= carry[7] << 25 + + carry[4] = (h4 + (1 << 25)) >> 26 + h5 += carry[4] + h4 -= carry[4] << 26 + carry[8] = (h8 + (1 << 25)) >> 26 + h9 += carry[8] + h8 -= carry[8] << 26 + + carry[9] = (h9 + (1 << 24)) >> 25 + h0 += carry[9] * 19 + h9 -= carry[9] << 25 + + carry[0] = (h0 + (1 << 25)) >> 26 + h1 += carry[0] + h0 -= carry[0] << 26 + + h[0] = int32(h0) + h[1] = int32(h1) + h[2] = int32(h2) + h[3] = int32(h3) + h[4] = int32(h4) + h[5] = int32(h5) + h[6] = int32(h6) + h[7] = int32(h7) + h[8] = int32(h8) + h[9] = int32(h9) +} + +// feMul121666 calculates h = f * 121666. Can overlap h with f. +// +// Preconditions: +// |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. +// +// Postconditions: +// |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. +func feMul121666(h, f *fieldElement) { + h0 := int64(f[0]) * 121666 + h1 := int64(f[1]) * 121666 + h2 := int64(f[2]) * 121666 + h3 := int64(f[3]) * 121666 + h4 := int64(f[4]) * 121666 + h5 := int64(f[5]) * 121666 + h6 := int64(f[6]) * 121666 + h7 := int64(f[7]) * 121666 + h8 := int64(f[8]) * 121666 + h9 := int64(f[9]) * 121666 + var carry [10]int64 + + carry[9] = (h9 + (1 << 24)) >> 25 + h0 += carry[9] * 19 + h9 -= carry[9] << 25 + carry[1] = (h1 + (1 << 24)) >> 25 + h2 += carry[1] + h1 -= carry[1] << 25 + carry[3] = (h3 + (1 << 24)) >> 25 + h4 += carry[3] + h3 -= carry[3] << 25 + carry[5] = (h5 + (1 << 24)) >> 25 + h6 += carry[5] + h5 -= carry[5] << 25 + carry[7] = (h7 + (1 << 24)) >> 25 + h8 += carry[7] + h7 -= carry[7] << 25 + + carry[0] = (h0 + (1 << 25)) >> 26 + h1 += carry[0] + h0 -= carry[0] << 26 + carry[2] = (h2 + (1 << 25)) >> 26 + h3 += carry[2] + h2 -= carry[2] << 26 + carry[4] = (h4 + (1 << 25)) >> 26 + h5 += carry[4] + h4 -= carry[4] << 26 + carry[6] = (h6 + (1 << 25)) >> 26 + h7 += carry[6] + h6 -= carry[6] << 26 + carry[8] = (h8 + (1 << 25)) >> 26 + h9 += carry[8] + h8 -= carry[8] << 26 + + h[0] = int32(h0) + h[1] = int32(h1) + h[2] = int32(h2) + h[3] = int32(h3) + h[4] = int32(h4) + h[5] = int32(h5) + h[6] = int32(h6) + h[7] = int32(h7) + h[8] = int32(h8) + h[9] = int32(h9) +} + +// feInvert sets out = z^-1. +func feInvert(out, z *fieldElement) { + var t0, t1, t2, t3 fieldElement + var i int + + feSquare(&t0, z) + for i = 1; i < 1; i++ { + feSquare(&t0, &t0) + } + feSquare(&t1, &t0) + for i = 1; i < 2; i++ { + feSquare(&t1, &t1) + } + feMul(&t1, z, &t1) + feMul(&t0, &t0, &t1) + feSquare(&t2, &t0) + for i = 1; i < 1; i++ { + feSquare(&t2, &t2) + } + feMul(&t1, &t1, &t2) + feSquare(&t2, &t1) + for i = 1; i < 5; i++ { + feSquare(&t2, &t2) + } + feMul(&t1, &t2, &t1) + feSquare(&t2, &t1) + for i = 1; i < 10; i++ { + feSquare(&t2, &t2) + } + feMul(&t2, &t2, &t1) + feSquare(&t3, &t2) + for i = 1; i < 20; i++ { + feSquare(&t3, &t3) + } + feMul(&t2, &t3, &t2) + feSquare(&t2, &t2) + for i = 1; i < 10; i++ { + feSquare(&t2, &t2) + } + feMul(&t1, &t2, &t1) + feSquare(&t2, &t1) + for i = 1; i < 50; i++ { + feSquare(&t2, &t2) + } + feMul(&t2, &t2, &t1) + feSquare(&t3, &t2) + for i = 1; i < 100; i++ { + feSquare(&t3, &t3) + } + feMul(&t2, &t3, &t2) + feSquare(&t2, &t2) + for i = 1; i < 50; i++ { + feSquare(&t2, &t2) + } + feMul(&t1, &t2, &t1) + feSquare(&t1, &t1) + for i = 1; i < 5; i++ { + feSquare(&t1, &t1) + } + feMul(out, &t1, &t0) +} + +func scalarMultGeneric(out, in, base *[32]byte) { + var e [32]byte + + copy(e[:], in[:]) + e[0] &= 248 + e[31] &= 127 + e[31] |= 64 + + var x1, x2, z2, x3, z3, tmp0, tmp1 fieldElement + feFromBytes(&x1, base) + feOne(&x2) + feCopy(&x3, &x1) + feOne(&z3) + + swap := int32(0) + for pos := 254; pos >= 0; pos-- { + b := e[pos/8] >> uint(pos&7) + b &= 1 + swap ^= int32(b) + feCSwap(&x2, &x3, swap) + feCSwap(&z2, &z3, swap) + swap = int32(b) + + feSub(&tmp0, &x3, &z3) + feSub(&tmp1, &x2, &z2) + feAdd(&x2, &x2, &z2) + feAdd(&z2, &x3, &z3) + feMul(&z3, &tmp0, &x2) + feMul(&z2, &z2, &tmp1) + feSquare(&tmp0, &tmp1) + feSquare(&tmp1, &x2) + feAdd(&x3, &z3, &z2) + feSub(&z2, &z3, &z2) + feMul(&x2, &tmp1, &tmp0) + feSub(&tmp1, &tmp1, &tmp0) + feSquare(&z2, &z2) + feMul121666(&z3, &tmp1) + feSquare(&x3, &x3) + feAdd(&tmp0, &tmp0, &z3) + feMul(&z3, &x1, &z2) + feMul(&z2, &tmp1, &tmp0) + } + + feCSwap(&x2, &x3, swap) + feCSwap(&z2, &z3, swap) + + feInvert(&z2, &z2) + feMul(&x2, &x2, &z2) + feToBytes(out, &x2) +} diff --git a/vendor/golang.org/x/crypto/curve25519/curve25519_noasm.go b/vendor/golang.org/x/crypto/curve25519/curve25519_noasm.go new file mode 100644 index 00000000000..047d49afc27 --- /dev/null +++ b/vendor/golang.org/x/crypto/curve25519/curve25519_noasm.go @@ -0,0 +1,11 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !amd64 gccgo appengine purego + +package curve25519 + +func scalarMult(out, in, base *[32]byte) { + scalarMultGeneric(out, in, base) +} diff --git a/vendor/golang.org/x/crypto/internal/subtle/aliasing.go b/vendor/golang.org/x/crypto/internal/subtle/aliasing.go new file mode 100644 index 00000000000..f38797bfa1b --- /dev/null +++ b/vendor/golang.org/x/crypto/internal/subtle/aliasing.go @@ -0,0 +1,32 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !appengine + +// Package subtle implements functions that are often useful in cryptographic +// code but require careful thought to use correctly. +package subtle // import "golang.org/x/crypto/internal/subtle" + +import "unsafe" + +// AnyOverlap reports whether x and y share memory at any (not necessarily +// corresponding) index. The memory beyond the slice length is ignored. +func AnyOverlap(x, y []byte) bool { + return len(x) > 0 && len(y) > 0 && + uintptr(unsafe.Pointer(&x[0])) <= uintptr(unsafe.Pointer(&y[len(y)-1])) && + uintptr(unsafe.Pointer(&y[0])) <= uintptr(unsafe.Pointer(&x[len(x)-1])) +} + +// InexactOverlap reports whether x and y share memory at any non-corresponding +// index. The memory beyond the slice length is ignored. Note that x and y can +// have different lengths and still not have any inexact overlap. +// +// InexactOverlap can be used to implement the requirements of the crypto/cipher +// AEAD, Block, BlockMode and Stream interfaces. +func InexactOverlap(x, y []byte) bool { + if len(x) == 0 || len(y) == 0 || &x[0] == &y[0] { + return false + } + return AnyOverlap(x, y) +} diff --git a/vendor/golang.org/x/crypto/internal/subtle/aliasing_appengine.go b/vendor/golang.org/x/crypto/internal/subtle/aliasing_appengine.go new file mode 100644 index 00000000000..0cc4a8a642c --- /dev/null +++ b/vendor/golang.org/x/crypto/internal/subtle/aliasing_appengine.go @@ -0,0 +1,35 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build appengine + +// Package subtle implements functions that are often useful in cryptographic +// code but require careful thought to use correctly. +package subtle // import "golang.org/x/crypto/internal/subtle" + +// This is the Google App Engine standard variant based on reflect +// because the unsafe package and cgo are disallowed. + +import "reflect" + +// AnyOverlap reports whether x and y share memory at any (not necessarily +// corresponding) index. The memory beyond the slice length is ignored. +func AnyOverlap(x, y []byte) bool { + return len(x) > 0 && len(y) > 0 && + reflect.ValueOf(&x[0]).Pointer() <= reflect.ValueOf(&y[len(y)-1]).Pointer() && + reflect.ValueOf(&y[0]).Pointer() <= reflect.ValueOf(&x[len(x)-1]).Pointer() +} + +// InexactOverlap reports whether x and y share memory at any non-corresponding +// index. The memory beyond the slice length is ignored. Note that x and y can +// have different lengths and still not have any inexact overlap. +// +// InexactOverlap can be used to implement the requirements of the crypto/cipher +// AEAD, Block, BlockMode and Stream interfaces. +func InexactOverlap(x, y []byte) bool { + if len(x) == 0 || len(y) == 0 || &x[0] == &y[0] { + return false + } + return AnyOverlap(x, y) +} diff --git a/vendor/golang.org/x/crypto/poly1305/bits_compat.go b/vendor/golang.org/x/crypto/poly1305/bits_compat.go new file mode 100644 index 00000000000..157a69f61bd --- /dev/null +++ b/vendor/golang.org/x/crypto/poly1305/bits_compat.go @@ -0,0 +1,39 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !go1.13 + +package poly1305 + +// Generic fallbacks for the math/bits intrinsics, copied from +// src/math/bits/bits.go. They were added in Go 1.12, but Add64 and Sum64 had +// variable time fallbacks until Go 1.13. + +func bitsAdd64(x, y, carry uint64) (sum, carryOut uint64) { + sum = x + y + carry + carryOut = ((x & y) | ((x | y) &^ sum)) >> 63 + return +} + +func bitsSub64(x, y, borrow uint64) (diff, borrowOut uint64) { + diff = x - y - borrow + borrowOut = ((^x & y) | (^(x ^ y) & diff)) >> 63 + return +} + +func bitsMul64(x, y uint64) (hi, lo uint64) { + const mask32 = 1<<32 - 1 + x0 := x & mask32 + x1 := x >> 32 + y0 := y & mask32 + y1 := y >> 32 + w0 := x0 * y0 + t := x1*y0 + w0>>32 + w1 := t & mask32 + w2 := t >> 32 + w1 += x0 * y1 + hi = x1*y1 + w2 + w1>>32 + lo = x * y + return +} diff --git a/vendor/golang.org/x/crypto/poly1305/bits_go1.13.go b/vendor/golang.org/x/crypto/poly1305/bits_go1.13.go new file mode 100644 index 00000000000..a0a185f0fc7 --- /dev/null +++ b/vendor/golang.org/x/crypto/poly1305/bits_go1.13.go @@ -0,0 +1,21 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build go1.13 + +package poly1305 + +import "math/bits" + +func bitsAdd64(x, y, carry uint64) (sum, carryOut uint64) { + return bits.Add64(x, y, carry) +} + +func bitsSub64(x, y, borrow uint64) (diff, borrowOut uint64) { + return bits.Sub64(x, y, borrow) +} + +func bitsMul64(x, y uint64) (hi, lo uint64) { + return bits.Mul64(x, y) +} diff --git a/vendor/golang.org/x/crypto/poly1305/mac_noasm.go b/vendor/golang.org/x/crypto/poly1305/mac_noasm.go new file mode 100644 index 00000000000..a8dd589ae39 --- /dev/null +++ b/vendor/golang.org/x/crypto/poly1305/mac_noasm.go @@ -0,0 +1,11 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !amd64,!ppc64le gccgo appengine + +package poly1305 + +type mac struct{ macGeneric } + +func newMAC(key *[32]byte) mac { return mac{newMACGeneric(key)} } diff --git a/vendor/golang.org/x/crypto/poly1305/poly1305.go b/vendor/golang.org/x/crypto/poly1305/poly1305.go new file mode 100644 index 00000000000..066159b797d --- /dev/null +++ b/vendor/golang.org/x/crypto/poly1305/poly1305.go @@ -0,0 +1,89 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package poly1305 implements Poly1305 one-time message authentication code as +// specified in https://cr.yp.to/mac/poly1305-20050329.pdf. +// +// Poly1305 is a fast, one-time authentication function. It is infeasible for an +// attacker to generate an authenticator for a message without the key. However, a +// key must only be used for a single message. Authenticating two different +// messages with the same key allows an attacker to forge authenticators for other +// messages with the same key. +// +// Poly1305 was originally coupled with AES in order to make Poly1305-AES. AES was +// used with a fixed key in order to generate one-time keys from an nonce. +// However, in this package AES isn't used and the one-time key is specified +// directly. +package poly1305 // import "golang.org/x/crypto/poly1305" + +import "crypto/subtle" + +// TagSize is the size, in bytes, of a poly1305 authenticator. +const TagSize = 16 + +// Sum generates an authenticator for msg using a one-time key and puts the +// 16-byte result into out. Authenticating two different messages with the same +// key allows an attacker to forge messages at will. +func Sum(out *[16]byte, m []byte, key *[32]byte) { + sum(out, m, key) +} + +// Verify returns true if mac is a valid authenticator for m with the given key. +func Verify(mac *[16]byte, m []byte, key *[32]byte) bool { + var tmp [16]byte + Sum(&tmp, m, key) + return subtle.ConstantTimeCompare(tmp[:], mac[:]) == 1 +} + +// New returns a new MAC computing an authentication +// tag of all data written to it with the given key. +// This allows writing the message progressively instead +// of passing it as a single slice. Common users should use +// the Sum function instead. +// +// The key must be unique for each message, as authenticating +// two different messages with the same key allows an attacker +// to forge messages at will. +func New(key *[32]byte) *MAC { + return &MAC{ + mac: newMAC(key), + finalized: false, + } +} + +// MAC is an io.Writer computing an authentication tag +// of the data written to it. +// +// MAC cannot be used like common hash.Hash implementations, +// because using a poly1305 key twice breaks its security. +// Therefore writing data to a running MAC after calling +// Sum causes it to panic. +type MAC struct { + mac // platform-dependent implementation + + finalized bool +} + +// Size returns the number of bytes Sum will return. +func (h *MAC) Size() int { return TagSize } + +// Write adds more data to the running message authentication code. +// It never returns an error. +// +// It must not be called after the first call of Sum. +func (h *MAC) Write(p []byte) (n int, err error) { + if h.finalized { + panic("poly1305: write to MAC after Sum") + } + return h.mac.Write(p) +} + +// Sum computes the authenticator of all data written to the +// message authentication code. +func (h *MAC) Sum(b []byte) []byte { + var mac [TagSize]byte + h.mac.Sum(&mac) + h.finalized = true + return append(b, mac[:]...) +} diff --git a/vendor/golang.org/x/crypto/poly1305/sum_amd64.go b/vendor/golang.org/x/crypto/poly1305/sum_amd64.go new file mode 100644 index 00000000000..df56a652ff0 --- /dev/null +++ b/vendor/golang.org/x/crypto/poly1305/sum_amd64.go @@ -0,0 +1,58 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build amd64,!gccgo,!appengine + +package poly1305 + +//go:noescape +func update(state *macState, msg []byte) + +func sum(out *[16]byte, m []byte, key *[32]byte) { + h := newMAC(key) + h.Write(m) + h.Sum(out) +} + +func newMAC(key *[32]byte) (h mac) { + initialize(key, &h.r, &h.s) + return +} + +// mac is a wrapper for macGeneric that redirects calls that would have gone to +// updateGeneric to update. +// +// Its Write and Sum methods are otherwise identical to the macGeneric ones, but +// using function pointers would carry a major performance cost. +type mac struct{ macGeneric } + +func (h *mac) Write(p []byte) (int, error) { + nn := len(p) + if h.offset > 0 { + n := copy(h.buffer[h.offset:], p) + if h.offset+n < TagSize { + h.offset += n + return nn, nil + } + p = p[n:] + h.offset = 0 + update(&h.macState, h.buffer[:]) + } + if n := len(p) - (len(p) % TagSize); n > 0 { + update(&h.macState, p[:n]) + p = p[n:] + } + if len(p) > 0 { + h.offset += copy(h.buffer[h.offset:], p) + } + return nn, nil +} + +func (h *mac) Sum(out *[16]byte) { + state := h.macState + if h.offset > 0 { + update(&state, h.buffer[:h.offset]) + } + finalize(out, &state.h, &state.s) +} diff --git a/vendor/golang.org/x/crypto/poly1305/sum_amd64.s b/vendor/golang.org/x/crypto/poly1305/sum_amd64.s new file mode 100644 index 00000000000..8c0cefbb3cb --- /dev/null +++ b/vendor/golang.org/x/crypto/poly1305/sum_amd64.s @@ -0,0 +1,108 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build amd64,!gccgo,!appengine + +#include "textflag.h" + +#define POLY1305_ADD(msg, h0, h1, h2) \ + ADDQ 0(msg), h0; \ + ADCQ 8(msg), h1; \ + ADCQ $1, h2; \ + LEAQ 16(msg), msg + +#define POLY1305_MUL(h0, h1, h2, r0, r1, t0, t1, t2, t3) \ + MOVQ r0, AX; \ + MULQ h0; \ + MOVQ AX, t0; \ + MOVQ DX, t1; \ + MOVQ r0, AX; \ + MULQ h1; \ + ADDQ AX, t1; \ + ADCQ $0, DX; \ + MOVQ r0, t2; \ + IMULQ h2, t2; \ + ADDQ DX, t2; \ + \ + MOVQ r1, AX; \ + MULQ h0; \ + ADDQ AX, t1; \ + ADCQ $0, DX; \ + MOVQ DX, h0; \ + MOVQ r1, t3; \ + IMULQ h2, t3; \ + MOVQ r1, AX; \ + MULQ h1; \ + ADDQ AX, t2; \ + ADCQ DX, t3; \ + ADDQ h0, t2; \ + ADCQ $0, t3; \ + \ + MOVQ t0, h0; \ + MOVQ t1, h1; \ + MOVQ t2, h2; \ + ANDQ $3, h2; \ + MOVQ t2, t0; \ + ANDQ $0xFFFFFFFFFFFFFFFC, t0; \ + ADDQ t0, h0; \ + ADCQ t3, h1; \ + ADCQ $0, h2; \ + SHRQ $2, t3, t2; \ + SHRQ $2, t3; \ + ADDQ t2, h0; \ + ADCQ t3, h1; \ + ADCQ $0, h2 + +// func update(state *[7]uint64, msg []byte) +TEXT ·update(SB), $0-32 + MOVQ state+0(FP), DI + MOVQ msg_base+8(FP), SI + MOVQ msg_len+16(FP), R15 + + MOVQ 0(DI), R8 // h0 + MOVQ 8(DI), R9 // h1 + MOVQ 16(DI), R10 // h2 + MOVQ 24(DI), R11 // r0 + MOVQ 32(DI), R12 // r1 + + CMPQ R15, $16 + JB bytes_between_0_and_15 + +loop: + POLY1305_ADD(SI, R8, R9, R10) + +multiply: + POLY1305_MUL(R8, R9, R10, R11, R12, BX, CX, R13, R14) + SUBQ $16, R15 + CMPQ R15, $16 + JAE loop + +bytes_between_0_and_15: + TESTQ R15, R15 + JZ done + MOVQ $1, BX + XORQ CX, CX + XORQ R13, R13 + ADDQ R15, SI + +flush_buffer: + SHLQ $8, BX, CX + SHLQ $8, BX + MOVB -1(SI), R13 + XORQ R13, BX + DECQ SI + DECQ R15 + JNZ flush_buffer + + ADDQ BX, R8 + ADCQ CX, R9 + ADCQ $0, R10 + MOVQ $16, R15 + JMP multiply + +done: + MOVQ R8, 0(DI) + MOVQ R9, 8(DI) + MOVQ R10, 16(DI) + RET diff --git a/vendor/golang.org/x/crypto/poly1305/sum_generic.go b/vendor/golang.org/x/crypto/poly1305/sum_generic.go new file mode 100644 index 00000000000..1187eab78fd --- /dev/null +++ b/vendor/golang.org/x/crypto/poly1305/sum_generic.go @@ -0,0 +1,307 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file provides the generic implementation of Sum and MAC. Other files +// might provide optimized assembly implementations of some of this code. + +package poly1305 + +import "encoding/binary" + +// Poly1305 [RFC 7539] is a relatively simple algorithm: the authentication tag +// for a 64 bytes message is approximately +// +// s + m[0:16] * r⁴ + m[16:32] * r³ + m[32:48] * r² + m[48:64] * r mod 2¹³⁰ - 5 +// +// for some secret r and s. It can be computed sequentially like +// +// for len(msg) > 0: +// h += read(msg, 16) +// h *= r +// h %= 2¹³⁰ - 5 +// return h + s +// +// All the complexity is about doing performant constant-time math on numbers +// larger than any available numeric type. + +func sumGeneric(out *[TagSize]byte, msg []byte, key *[32]byte) { + h := newMACGeneric(key) + h.Write(msg) + h.Sum(out) +} + +func newMACGeneric(key *[32]byte) (h macGeneric) { + initialize(key, &h.r, &h.s) + return +} + +// macState holds numbers in saturated 64-bit little-endian limbs. That is, +// the value of [x0, x1, x2] is x[0] + x[1] * 2⁶⁴ + x[2] * 2¹²⁸. +type macState struct { + // h is the main accumulator. It is to be interpreted modulo 2¹³⁰ - 5, but + // can grow larger during and after rounds. + h [3]uint64 + // r and s are the private key components. + r [2]uint64 + s [2]uint64 +} + +type macGeneric struct { + macState + + buffer [TagSize]byte + offset int +} + +// Write splits the incoming message into TagSize chunks, and passes them to +// update. It buffers incomplete chunks. +func (h *macGeneric) Write(p []byte) (int, error) { + nn := len(p) + if h.offset > 0 { + n := copy(h.buffer[h.offset:], p) + if h.offset+n < TagSize { + h.offset += n + return nn, nil + } + p = p[n:] + h.offset = 0 + updateGeneric(&h.macState, h.buffer[:]) + } + if n := len(p) - (len(p) % TagSize); n > 0 { + updateGeneric(&h.macState, p[:n]) + p = p[n:] + } + if len(p) > 0 { + h.offset += copy(h.buffer[h.offset:], p) + } + return nn, nil +} + +// Sum flushes the last incomplete chunk from the buffer, if any, and generates +// the MAC output. It does not modify its state, in order to allow for multiple +// calls to Sum, even if no Write is allowed after Sum. +func (h *macGeneric) Sum(out *[TagSize]byte) { + state := h.macState + if h.offset > 0 { + updateGeneric(&state, h.buffer[:h.offset]) + } + finalize(out, &state.h, &state.s) +} + +// [rMask0, rMask1] is the specified Poly1305 clamping mask in little-endian. It +// clears some bits of the secret coefficient to make it possible to implement +// multiplication more efficiently. +const ( + rMask0 = 0x0FFFFFFC0FFFFFFF + rMask1 = 0x0FFFFFFC0FFFFFFC +) + +func initialize(key *[32]byte, r, s *[2]uint64) { + r[0] = binary.LittleEndian.Uint64(key[0:8]) & rMask0 + r[1] = binary.LittleEndian.Uint64(key[8:16]) & rMask1 + s[0] = binary.LittleEndian.Uint64(key[16:24]) + s[1] = binary.LittleEndian.Uint64(key[24:32]) +} + +// uint128 holds a 128-bit number as two 64-bit limbs, for use with the +// bits.Mul64 and bits.Add64 intrinsics. +type uint128 struct { + lo, hi uint64 +} + +func mul64(a, b uint64) uint128 { + hi, lo := bitsMul64(a, b) + return uint128{lo, hi} +} + +func add128(a, b uint128) uint128 { + lo, c := bitsAdd64(a.lo, b.lo, 0) + hi, c := bitsAdd64(a.hi, b.hi, c) + if c != 0 { + panic("poly1305: unexpected overflow") + } + return uint128{lo, hi} +} + +func shiftRightBy2(a uint128) uint128 { + a.lo = a.lo>>2 | (a.hi&3)<<62 + a.hi = a.hi >> 2 + return a +} + +// updateGeneric absorbs msg into the state.h accumulator. For each chunk m of +// 128 bits of message, it computes +// +// h₊ = (h + m) * r mod 2¹³⁰ - 5 +// +// If the msg length is not a multiple of TagSize, it assumes the last +// incomplete chunk is the final one. +func updateGeneric(state *macState, msg []byte) { + h0, h1, h2 := state.h[0], state.h[1], state.h[2] + r0, r1 := state.r[0], state.r[1] + + for len(msg) > 0 { + var c uint64 + + // For the first step, h + m, we use a chain of bits.Add64 intrinsics. + // The resulting value of h might exceed 2¹³⁰ - 5, but will be partially + // reduced at the end of the multiplication below. + // + // The spec requires us to set a bit just above the message size, not to + // hide leading zeroes. For full chunks, that's 1 << 128, so we can just + // add 1 to the most significant (2¹²⁸) limb, h2. + if len(msg) >= TagSize { + h0, c = bitsAdd64(h0, binary.LittleEndian.Uint64(msg[0:8]), 0) + h1, c = bitsAdd64(h1, binary.LittleEndian.Uint64(msg[8:16]), c) + h2 += c + 1 + + msg = msg[TagSize:] + } else { + var buf [TagSize]byte + copy(buf[:], msg) + buf[len(msg)] = 1 + + h0, c = bitsAdd64(h0, binary.LittleEndian.Uint64(buf[0:8]), 0) + h1, c = bitsAdd64(h1, binary.LittleEndian.Uint64(buf[8:16]), c) + h2 += c + + msg = nil + } + + // Multiplication of big number limbs is similar to elementary school + // columnar multiplication. Instead of digits, there are 64-bit limbs. + // + // We are multiplying a 3 limbs number, h, by a 2 limbs number, r. + // + // h2 h1 h0 x + // r1 r0 = + // ---------------- + // h2r0 h1r0 h0r0 <-- individual 128-bit products + // + h2r1 h1r1 h0r1 + // ------------------------ + // m3 m2 m1 m0 <-- result in 128-bit overlapping limbs + // ------------------------ + // m3.hi m2.hi m1.hi m0.hi <-- carry propagation + // + m3.lo m2.lo m1.lo m0.lo + // ------------------------------- + // t4 t3 t2 t1 t0 <-- final result in 64-bit limbs + // + // The main difference from pen-and-paper multiplication is that we do + // carry propagation in a separate step, as if we wrote two digit sums + // at first (the 128-bit limbs), and then carried the tens all at once. + + h0r0 := mul64(h0, r0) + h1r0 := mul64(h1, r0) + h2r0 := mul64(h2, r0) + h0r1 := mul64(h0, r1) + h1r1 := mul64(h1, r1) + h2r1 := mul64(h2, r1) + + // Since h2 is known to be at most 7 (5 + 1 + 1), and r0 and r1 have their + // top 4 bits cleared by rMask{0,1}, we know that their product is not going + // to overflow 64 bits, so we can ignore the high part of the products. + // + // This also means that the product doesn't have a fifth limb (t4). + if h2r0.hi != 0 { + panic("poly1305: unexpected overflow") + } + if h2r1.hi != 0 { + panic("poly1305: unexpected overflow") + } + + m0 := h0r0 + m1 := add128(h1r0, h0r1) // These two additions don't overflow thanks again + m2 := add128(h2r0, h1r1) // to the 4 masked bits at the top of r0 and r1. + m3 := h2r1 + + t0 := m0.lo + t1, c := bitsAdd64(m1.lo, m0.hi, 0) + t2, c := bitsAdd64(m2.lo, m1.hi, c) + t3, _ := bitsAdd64(m3.lo, m2.hi, c) + + // Now we have the result as 4 64-bit limbs, and we need to reduce it + // modulo 2¹³⁰ - 5. The special shape of this Crandall prime lets us do + // a cheap partial reduction according to the reduction identity + // + // c * 2¹³⁰ + n = c * 5 + n mod 2¹³⁰ - 5 + // + // because 2¹³⁰ = 5 mod 2¹³⁰ - 5. Partial reduction since the result is + // likely to be larger than 2¹³⁰ - 5, but still small enough to fit the + // assumptions we make about h in the rest of the code. + // + // See also https://speakerdeck.com/gtank/engineering-prime-numbers?slide=23 + + // We split the final result at the 2¹³⁰ mark into h and cc, the carry. + // Note that the carry bits are effectively shifted left by 2, in other + // words, cc = c * 4 for the c in the reduction identity. + h0, h1, h2 = t0, t1, t2&maskLow2Bits + cc := uint128{t2 & maskNotLow2Bits, t3} + + // To add c * 5 to h, we first add cc = c * 4, and then add (cc >> 2) = c. + + h0, c = bitsAdd64(h0, cc.lo, 0) + h1, c = bitsAdd64(h1, cc.hi, c) + h2 += c + + cc = shiftRightBy2(cc) + + h0, c = bitsAdd64(h0, cc.lo, 0) + h1, c = bitsAdd64(h1, cc.hi, c) + h2 += c + + // h2 is at most 3 + 1 + 1 = 5, making the whole of h at most + // + // 5 * 2¹²⁸ + (2¹²⁸ - 1) = 6 * 2¹²⁸ - 1 + } + + state.h[0], state.h[1], state.h[2] = h0, h1, h2 +} + +const ( + maskLow2Bits uint64 = 0x0000000000000003 + maskNotLow2Bits uint64 = ^maskLow2Bits +) + +// select64 returns x if v == 1 and y if v == 0, in constant time. +func select64(v, x, y uint64) uint64 { return ^(v-1)&x | (v-1)&y } + +// [p0, p1, p2] is 2¹³⁰ - 5 in little endian order. +const ( + p0 = 0xFFFFFFFFFFFFFFFB + p1 = 0xFFFFFFFFFFFFFFFF + p2 = 0x0000000000000003 +) + +// finalize completes the modular reduction of h and computes +// +// out = h + s mod 2¹²⁸ +// +func finalize(out *[TagSize]byte, h *[3]uint64, s *[2]uint64) { + h0, h1, h2 := h[0], h[1], h[2] + + // After the partial reduction in updateGeneric, h might be more than + // 2¹³⁰ - 5, but will be less than 2 * (2¹³⁰ - 5). To complete the reduction + // in constant time, we compute t = h - (2¹³⁰ - 5), and select h as the + // result if the subtraction underflows, and t otherwise. + + hMinusP0, b := bitsSub64(h0, p0, 0) + hMinusP1, b := bitsSub64(h1, p1, b) + _, b = bitsSub64(h2, p2, b) + + // h = h if h < p else h - p + h0 = select64(b, h0, hMinusP0) + h1 = select64(b, h1, hMinusP1) + + // Finally, we compute the last Poly1305 step + // + // tag = h + s mod 2¹²⁸ + // + // by just doing a wide addition with the 128 low bits of h and discarding + // the overflow. + h0, c := bitsAdd64(h0, s[0], 0) + h1, _ = bitsAdd64(h1, s[1], c) + + binary.LittleEndian.PutUint64(out[0:8], h0) + binary.LittleEndian.PutUint64(out[8:16], h1) +} diff --git a/vendor/golang.org/x/crypto/poly1305/sum_noasm.go b/vendor/golang.org/x/crypto/poly1305/sum_noasm.go new file mode 100644 index 00000000000..32a9cef6bbf --- /dev/null +++ b/vendor/golang.org/x/crypto/poly1305/sum_noasm.go @@ -0,0 +1,13 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build s390x,!go1.11 !amd64,!s390x,!ppc64le gccgo appengine nacl + +package poly1305 + +func sum(out *[TagSize]byte, msg []byte, key *[32]byte) { + h := newMAC(key) + h.Write(msg) + h.Sum(out) +} diff --git a/vendor/golang.org/x/crypto/poly1305/sum_ppc64le.go b/vendor/golang.org/x/crypto/poly1305/sum_ppc64le.go new file mode 100644 index 00000000000..3233616935b --- /dev/null +++ b/vendor/golang.org/x/crypto/poly1305/sum_ppc64le.go @@ -0,0 +1,58 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build ppc64le,!gccgo,!appengine + +package poly1305 + +//go:noescape +func update(state *macState, msg []byte) + +func sum(out *[16]byte, m []byte, key *[32]byte) { + h := newMAC(key) + h.Write(m) + h.Sum(out) +} + +func newMAC(key *[32]byte) (h mac) { + initialize(key, &h.r, &h.s) + return +} + +// mac is a wrapper for macGeneric that redirects calls that would have gone to +// updateGeneric to update. +// +// Its Write and Sum methods are otherwise identical to the macGeneric ones, but +// using function pointers would carry a major performance cost. +type mac struct{ macGeneric } + +func (h *mac) Write(p []byte) (int, error) { + nn := len(p) + if h.offset > 0 { + n := copy(h.buffer[h.offset:], p) + if h.offset+n < TagSize { + h.offset += n + return nn, nil + } + p = p[n:] + h.offset = 0 + update(&h.macState, h.buffer[:]) + } + if n := len(p) - (len(p) % TagSize); n > 0 { + update(&h.macState, p[:n]) + p = p[n:] + } + if len(p) > 0 { + h.offset += copy(h.buffer[h.offset:], p) + } + return nn, nil +} + +func (h *mac) Sum(out *[16]byte) { + state := h.macState + if h.offset > 0 { + update(&state, h.buffer[:h.offset]) + } + finalize(out, &state.h, &state.s) +} diff --git a/vendor/golang.org/x/crypto/poly1305/sum_ppc64le.s b/vendor/golang.org/x/crypto/poly1305/sum_ppc64le.s new file mode 100644 index 00000000000..4e20bf299a5 --- /dev/null +++ b/vendor/golang.org/x/crypto/poly1305/sum_ppc64le.s @@ -0,0 +1,181 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build ppc64le,!gccgo,!appengine + +#include "textflag.h" + +// This was ported from the amd64 implementation. + +#define POLY1305_ADD(msg, h0, h1, h2, t0, t1, t2) \ + MOVD (msg), t0; \ + MOVD 8(msg), t1; \ + MOVD $1, t2; \ + ADDC t0, h0, h0; \ + ADDE t1, h1, h1; \ + ADDE t2, h2; \ + ADD $16, msg + +#define POLY1305_MUL(h0, h1, h2, r0, r1, t0, t1, t2, t3, t4, t5) \ + MULLD r0, h0, t0; \ + MULLD r0, h1, t4; \ + MULHDU r0, h0, t1; \ + MULHDU r0, h1, t5; \ + ADDC t4, t1, t1; \ + MULLD r0, h2, t2; \ + ADDZE t5; \ + MULHDU r1, h0, t4; \ + MULLD r1, h0, h0; \ + ADD t5, t2, t2; \ + ADDC h0, t1, t1; \ + MULLD h2, r1, t3; \ + ADDZE t4, h0; \ + MULHDU r1, h1, t5; \ + MULLD r1, h1, t4; \ + ADDC t4, t2, t2; \ + ADDE t5, t3, t3; \ + ADDC h0, t2, t2; \ + MOVD $-4, t4; \ + MOVD t0, h0; \ + MOVD t1, h1; \ + ADDZE t3; \ + ANDCC $3, t2, h2; \ + AND t2, t4, t0; \ + ADDC t0, h0, h0; \ + ADDE t3, h1, h1; \ + SLD $62, t3, t4; \ + SRD $2, t2; \ + ADDZE h2; \ + OR t4, t2, t2; \ + SRD $2, t3; \ + ADDC t2, h0, h0; \ + ADDE t3, h1, h1; \ + ADDZE h2 + +DATA ·poly1305Mask<>+0x00(SB)/8, $0x0FFFFFFC0FFFFFFF +DATA ·poly1305Mask<>+0x08(SB)/8, $0x0FFFFFFC0FFFFFFC +GLOBL ·poly1305Mask<>(SB), RODATA, $16 + +// func update(state *[7]uint64, msg []byte) +TEXT ·update(SB), $0-32 + MOVD state+0(FP), R3 + MOVD msg_base+8(FP), R4 + MOVD msg_len+16(FP), R5 + + MOVD 0(R3), R8 // h0 + MOVD 8(R3), R9 // h1 + MOVD 16(R3), R10 // h2 + MOVD 24(R3), R11 // r0 + MOVD 32(R3), R12 // r1 + + CMP R5, $16 + BLT bytes_between_0_and_15 + +loop: + POLY1305_ADD(R4, R8, R9, R10, R20, R21, R22) + +multiply: + POLY1305_MUL(R8, R9, R10, R11, R12, R16, R17, R18, R14, R20, R21) + ADD $-16, R5 + CMP R5, $16 + BGE loop + +bytes_between_0_and_15: + CMP $0, R5 + BEQ done + MOVD $0, R16 // h0 + MOVD $0, R17 // h1 + +flush_buffer: + CMP R5, $8 + BLE just1 + + MOVD $8, R21 + SUB R21, R5, R21 + + // Greater than 8 -- load the rightmost remaining bytes in msg + // and put into R17 (h1) + MOVD (R4)(R21), R17 + MOVD $16, R22 + + // Find the offset to those bytes + SUB R5, R22, R22 + SLD $3, R22 + + // Shift to get only the bytes in msg + SRD R22, R17, R17 + + // Put 1 at high end + MOVD $1, R23 + SLD $3, R21 + SLD R21, R23, R23 + OR R23, R17, R17 + + // Remainder is 8 + MOVD $8, R5 + +just1: + CMP R5, $8 + BLT less8 + + // Exactly 8 + MOVD (R4), R16 + + CMP $0, R17 + + // Check if we've already set R17; if not + // set 1 to indicate end of msg. + BNE carry + MOVD $1, R17 + BR carry + +less8: + MOVD $0, R16 // h0 + MOVD $0, R22 // shift count + CMP R5, $4 + BLT less4 + MOVWZ (R4), R16 + ADD $4, R4 + ADD $-4, R5 + MOVD $32, R22 + +less4: + CMP R5, $2 + BLT less2 + MOVHZ (R4), R21 + SLD R22, R21, R21 + OR R16, R21, R16 + ADD $16, R22 + ADD $-2, R5 + ADD $2, R4 + +less2: + CMP $0, R5 + BEQ insert1 + MOVBZ (R4), R21 + SLD R22, R21, R21 + OR R16, R21, R16 + ADD $8, R22 + +insert1: + // Insert 1 at end of msg + MOVD $1, R21 + SLD R22, R21, R21 + OR R16, R21, R16 + +carry: + // Add new values to h0, h1, h2 + ADDC R16, R8 + ADDE R17, R9 + ADDE $0, R10 + MOVD $16, R5 + ADD R5, R4 + BR multiply + +done: + // Save h0, h1, h2 in state + MOVD R8, 0(R3) + MOVD R9, 8(R3) + MOVD R10, 16(R3) + RET diff --git a/vendor/golang.org/x/crypto/poly1305/sum_s390x.go b/vendor/golang.org/x/crypto/poly1305/sum_s390x.go new file mode 100644 index 00000000000..a8920ee9d21 --- /dev/null +++ b/vendor/golang.org/x/crypto/poly1305/sum_s390x.go @@ -0,0 +1,39 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build s390x,go1.11,!gccgo,!appengine + +package poly1305 + +import ( + "golang.org/x/sys/cpu" +) + +// poly1305vx is an assembly implementation of Poly1305 that uses vector +// instructions. It must only be called if the vector facility (vx) is +// available. +//go:noescape +func poly1305vx(out *[16]byte, m *byte, mlen uint64, key *[32]byte) + +// poly1305vmsl is an assembly implementation of Poly1305 that uses vector +// instructions, including VMSL. It must only be called if the vector facility (vx) is +// available and if VMSL is supported. +//go:noescape +func poly1305vmsl(out *[16]byte, m *byte, mlen uint64, key *[32]byte) + +func sum(out *[16]byte, m []byte, key *[32]byte) { + if cpu.S390X.HasVX { + var mPtr *byte + if len(m) > 0 { + mPtr = &m[0] + } + if cpu.S390X.HasVXE && len(m) > 256 { + poly1305vmsl(out, mPtr, uint64(len(m)), key) + } else { + poly1305vx(out, mPtr, uint64(len(m)), key) + } + } else { + sumGeneric(out, m, key) + } +} diff --git a/vendor/golang.org/x/crypto/poly1305/sum_s390x.s b/vendor/golang.org/x/crypto/poly1305/sum_s390x.s new file mode 100644 index 00000000000..ca5a309d867 --- /dev/null +++ b/vendor/golang.org/x/crypto/poly1305/sum_s390x.s @@ -0,0 +1,378 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build s390x,go1.11,!gccgo,!appengine + +#include "textflag.h" + +// Implementation of Poly1305 using the vector facility (vx). + +// constants +#define MOD26 V0 +#define EX0 V1 +#define EX1 V2 +#define EX2 V3 + +// temporaries +#define T_0 V4 +#define T_1 V5 +#define T_2 V6 +#define T_3 V7 +#define T_4 V8 + +// key (r) +#define R_0 V9 +#define R_1 V10 +#define R_2 V11 +#define R_3 V12 +#define R_4 V13 +#define R5_1 V14 +#define R5_2 V15 +#define R5_3 V16 +#define R5_4 V17 +#define RSAVE_0 R5 +#define RSAVE_1 R6 +#define RSAVE_2 R7 +#define RSAVE_3 R8 +#define RSAVE_4 R9 +#define R5SAVE_1 V28 +#define R5SAVE_2 V29 +#define R5SAVE_3 V30 +#define R5SAVE_4 V31 + +// message block +#define F_0 V18 +#define F_1 V19 +#define F_2 V20 +#define F_3 V21 +#define F_4 V22 + +// accumulator +#define H_0 V23 +#define H_1 V24 +#define H_2 V25 +#define H_3 V26 +#define H_4 V27 + +GLOBL ·keyMask<>(SB), RODATA, $16 +DATA ·keyMask<>+0(SB)/8, $0xffffff0ffcffff0f +DATA ·keyMask<>+8(SB)/8, $0xfcffff0ffcffff0f + +GLOBL ·bswapMask<>(SB), RODATA, $16 +DATA ·bswapMask<>+0(SB)/8, $0x0f0e0d0c0b0a0908 +DATA ·bswapMask<>+8(SB)/8, $0x0706050403020100 + +GLOBL ·constants<>(SB), RODATA, $64 +// MOD26 +DATA ·constants<>+0(SB)/8, $0x3ffffff +DATA ·constants<>+8(SB)/8, $0x3ffffff +// EX0 +DATA ·constants<>+16(SB)/8, $0x0006050403020100 +DATA ·constants<>+24(SB)/8, $0x1016151413121110 +// EX1 +DATA ·constants<>+32(SB)/8, $0x060c0b0a09080706 +DATA ·constants<>+40(SB)/8, $0x161c1b1a19181716 +// EX2 +DATA ·constants<>+48(SB)/8, $0x0d0d0d0d0d0f0e0d +DATA ·constants<>+56(SB)/8, $0x1d1d1d1d1d1f1e1d + +// h = (f*g) % (2**130-5) [partial reduction] +#define MULTIPLY(f0, f1, f2, f3, f4, g0, g1, g2, g3, g4, g51, g52, g53, g54, h0, h1, h2, h3, h4) \ + VMLOF f0, g0, h0 \ + VMLOF f0, g1, h1 \ + VMLOF f0, g2, h2 \ + VMLOF f0, g3, h3 \ + VMLOF f0, g4, h4 \ + VMLOF f1, g54, T_0 \ + VMLOF f1, g0, T_1 \ + VMLOF f1, g1, T_2 \ + VMLOF f1, g2, T_3 \ + VMLOF f1, g3, T_4 \ + VMALOF f2, g53, h0, h0 \ + VMALOF f2, g54, h1, h1 \ + VMALOF f2, g0, h2, h2 \ + VMALOF f2, g1, h3, h3 \ + VMALOF f2, g2, h4, h4 \ + VMALOF f3, g52, T_0, T_0 \ + VMALOF f3, g53, T_1, T_1 \ + VMALOF f3, g54, T_2, T_2 \ + VMALOF f3, g0, T_3, T_3 \ + VMALOF f3, g1, T_4, T_4 \ + VMALOF f4, g51, h0, h0 \ + VMALOF f4, g52, h1, h1 \ + VMALOF f4, g53, h2, h2 \ + VMALOF f4, g54, h3, h3 \ + VMALOF f4, g0, h4, h4 \ + VAG T_0, h0, h0 \ + VAG T_1, h1, h1 \ + VAG T_2, h2, h2 \ + VAG T_3, h3, h3 \ + VAG T_4, h4, h4 + +// carry h0->h1 h3->h4, h1->h2 h4->h0, h0->h1 h2->h3, h3->h4 +#define REDUCE(h0, h1, h2, h3, h4) \ + VESRLG $26, h0, T_0 \ + VESRLG $26, h3, T_1 \ + VN MOD26, h0, h0 \ + VN MOD26, h3, h3 \ + VAG T_0, h1, h1 \ + VAG T_1, h4, h4 \ + VESRLG $26, h1, T_2 \ + VESRLG $26, h4, T_3 \ + VN MOD26, h1, h1 \ + VN MOD26, h4, h4 \ + VESLG $2, T_3, T_4 \ + VAG T_3, T_4, T_4 \ + VAG T_2, h2, h2 \ + VAG T_4, h0, h0 \ + VESRLG $26, h2, T_0 \ + VESRLG $26, h0, T_1 \ + VN MOD26, h2, h2 \ + VN MOD26, h0, h0 \ + VAG T_0, h3, h3 \ + VAG T_1, h1, h1 \ + VESRLG $26, h3, T_2 \ + VN MOD26, h3, h3 \ + VAG T_2, h4, h4 + +// expand in0 into d[0] and in1 into d[1] +#define EXPAND(in0, in1, d0, d1, d2, d3, d4) \ + VGBM $0x0707, d1 \ // d1=tmp + VPERM in0, in1, EX2, d4 \ + VPERM in0, in1, EX0, d0 \ + VPERM in0, in1, EX1, d2 \ + VN d1, d4, d4 \ + VESRLG $26, d0, d1 \ + VESRLG $30, d2, d3 \ + VESRLG $4, d2, d2 \ + VN MOD26, d0, d0 \ + VN MOD26, d1, d1 \ + VN MOD26, d2, d2 \ + VN MOD26, d3, d3 + +// pack h4:h0 into h1:h0 (no carry) +#define PACK(h0, h1, h2, h3, h4) \ + VESLG $26, h1, h1 \ + VESLG $26, h3, h3 \ + VO h0, h1, h0 \ + VO h2, h3, h2 \ + VESLG $4, h2, h2 \ + VLEIB $7, $48, h1 \ + VSLB h1, h2, h2 \ + VO h0, h2, h0 \ + VLEIB $7, $104, h1 \ + VSLB h1, h4, h3 \ + VO h3, h0, h0 \ + VLEIB $7, $24, h1 \ + VSRLB h1, h4, h1 + +// if h > 2**130-5 then h -= 2**130-5 +#define MOD(h0, h1, t0, t1, t2) \ + VZERO t0 \ + VLEIG $1, $5, t0 \ + VACCQ h0, t0, t1 \ + VAQ h0, t0, t0 \ + VONE t2 \ + VLEIG $1, $-4, t2 \ + VAQ t2, t1, t1 \ + VACCQ h1, t1, t1 \ + VONE t2 \ + VAQ t2, t1, t1 \ + VN h0, t1, t2 \ + VNC t0, t1, t1 \ + VO t1, t2, h0 + +// func poly1305vx(out *[16]byte, m *byte, mlen uint64, key *[32]key) +TEXT ·poly1305vx(SB), $0-32 + // This code processes up to 2 blocks (32 bytes) per iteration + // using the algorithm described in: + // NEON crypto, Daniel J. Bernstein & Peter Schwabe + // https://cryptojedi.org/papers/neoncrypto-20120320.pdf + LMG out+0(FP), R1, R4 // R1=out, R2=m, R3=mlen, R4=key + + // load MOD26, EX0, EX1 and EX2 + MOVD $·constants<>(SB), R5 + VLM (R5), MOD26, EX2 + + // setup r + VL (R4), T_0 + MOVD $·keyMask<>(SB), R6 + VL (R6), T_1 + VN T_0, T_1, T_0 + EXPAND(T_0, T_0, R_0, R_1, R_2, R_3, R_4) + + // setup r*5 + VLEIG $0, $5, T_0 + VLEIG $1, $5, T_0 + + // store r (for final block) + VMLOF T_0, R_1, R5SAVE_1 + VMLOF T_0, R_2, R5SAVE_2 + VMLOF T_0, R_3, R5SAVE_3 + VMLOF T_0, R_4, R5SAVE_4 + VLGVG $0, R_0, RSAVE_0 + VLGVG $0, R_1, RSAVE_1 + VLGVG $0, R_2, RSAVE_2 + VLGVG $0, R_3, RSAVE_3 + VLGVG $0, R_4, RSAVE_4 + + // skip r**2 calculation + CMPBLE R3, $16, skip + + // calculate r**2 + MULTIPLY(R_0, R_1, R_2, R_3, R_4, R_0, R_1, R_2, R_3, R_4, R5SAVE_1, R5SAVE_2, R5SAVE_3, R5SAVE_4, H_0, H_1, H_2, H_3, H_4) + REDUCE(H_0, H_1, H_2, H_3, H_4) + VLEIG $0, $5, T_0 + VLEIG $1, $5, T_0 + VMLOF T_0, H_1, R5_1 + VMLOF T_0, H_2, R5_2 + VMLOF T_0, H_3, R5_3 + VMLOF T_0, H_4, R5_4 + VLR H_0, R_0 + VLR H_1, R_1 + VLR H_2, R_2 + VLR H_3, R_3 + VLR H_4, R_4 + + // initialize h + VZERO H_0 + VZERO H_1 + VZERO H_2 + VZERO H_3 + VZERO H_4 + +loop: + CMPBLE R3, $32, b2 + VLM (R2), T_0, T_1 + SUB $32, R3 + MOVD $32(R2), R2 + EXPAND(T_0, T_1, F_0, F_1, F_2, F_3, F_4) + VLEIB $4, $1, F_4 + VLEIB $12, $1, F_4 + +multiply: + VAG H_0, F_0, F_0 + VAG H_1, F_1, F_1 + VAG H_2, F_2, F_2 + VAG H_3, F_3, F_3 + VAG H_4, F_4, F_4 + MULTIPLY(F_0, F_1, F_2, F_3, F_4, R_0, R_1, R_2, R_3, R_4, R5_1, R5_2, R5_3, R5_4, H_0, H_1, H_2, H_3, H_4) + REDUCE(H_0, H_1, H_2, H_3, H_4) + CMPBNE R3, $0, loop + +finish: + // sum vectors + VZERO T_0 + VSUMQG H_0, T_0, H_0 + VSUMQG H_1, T_0, H_1 + VSUMQG H_2, T_0, H_2 + VSUMQG H_3, T_0, H_3 + VSUMQG H_4, T_0, H_4 + + // h may be >= 2*(2**130-5) so we need to reduce it again + REDUCE(H_0, H_1, H_2, H_3, H_4) + + // carry h1->h4 + VESRLG $26, H_1, T_1 + VN MOD26, H_1, H_1 + VAQ T_1, H_2, H_2 + VESRLG $26, H_2, T_2 + VN MOD26, H_2, H_2 + VAQ T_2, H_3, H_3 + VESRLG $26, H_3, T_3 + VN MOD26, H_3, H_3 + VAQ T_3, H_4, H_4 + + // h is now < 2*(2**130-5) + // pack h into h1 (hi) and h0 (lo) + PACK(H_0, H_1, H_2, H_3, H_4) + + // if h > 2**130-5 then h -= 2**130-5 + MOD(H_0, H_1, T_0, T_1, T_2) + + // h += s + MOVD $·bswapMask<>(SB), R5 + VL (R5), T_1 + VL 16(R4), T_0 + VPERM T_0, T_0, T_1, T_0 // reverse bytes (to big) + VAQ T_0, H_0, H_0 + VPERM H_0, H_0, T_1, H_0 // reverse bytes (to little) + VST H_0, (R1) + + RET + +b2: + CMPBLE R3, $16, b1 + + // 2 blocks remaining + SUB $17, R3 + VL (R2), T_0 + VLL R3, 16(R2), T_1 + ADD $1, R3 + MOVBZ $1, R0 + CMPBEQ R3, $16, 2(PC) + VLVGB R3, R0, T_1 + EXPAND(T_0, T_1, F_0, F_1, F_2, F_3, F_4) + CMPBNE R3, $16, 2(PC) + VLEIB $12, $1, F_4 + VLEIB $4, $1, F_4 + + // setup [r²,r] + VLVGG $1, RSAVE_0, R_0 + VLVGG $1, RSAVE_1, R_1 + VLVGG $1, RSAVE_2, R_2 + VLVGG $1, RSAVE_3, R_3 + VLVGG $1, RSAVE_4, R_4 + VPDI $0, R5_1, R5SAVE_1, R5_1 + VPDI $0, R5_2, R5SAVE_2, R5_2 + VPDI $0, R5_3, R5SAVE_3, R5_3 + VPDI $0, R5_4, R5SAVE_4, R5_4 + + MOVD $0, R3 + BR multiply + +skip: + VZERO H_0 + VZERO H_1 + VZERO H_2 + VZERO H_3 + VZERO H_4 + + CMPBEQ R3, $0, finish + +b1: + // 1 block remaining + SUB $1, R3 + VLL R3, (R2), T_0 + ADD $1, R3 + MOVBZ $1, R0 + CMPBEQ R3, $16, 2(PC) + VLVGB R3, R0, T_0 + VZERO T_1 + EXPAND(T_0, T_1, F_0, F_1, F_2, F_3, F_4) + CMPBNE R3, $16, 2(PC) + VLEIB $4, $1, F_4 + VLEIG $1, $1, R_0 + VZERO R_1 + VZERO R_2 + VZERO R_3 + VZERO R_4 + VZERO R5_1 + VZERO R5_2 + VZERO R5_3 + VZERO R5_4 + + // setup [r, 1] + VLVGG $0, RSAVE_0, R_0 + VLVGG $0, RSAVE_1, R_1 + VLVGG $0, RSAVE_2, R_2 + VLVGG $0, RSAVE_3, R_3 + VLVGG $0, RSAVE_4, R_4 + VPDI $0, R5SAVE_1, R5_1, R5_1 + VPDI $0, R5SAVE_2, R5_2, R5_2 + VPDI $0, R5SAVE_3, R5_3, R5_3 + VPDI $0, R5SAVE_4, R5_4, R5_4 + + MOVD $0, R3 + BR multiply diff --git a/vendor/golang.org/x/crypto/poly1305/sum_vmsl_s390x.s b/vendor/golang.org/x/crypto/poly1305/sum_vmsl_s390x.s new file mode 100644 index 00000000000..e60bbc1d7f8 --- /dev/null +++ b/vendor/golang.org/x/crypto/poly1305/sum_vmsl_s390x.s @@ -0,0 +1,909 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build s390x,go1.11,!gccgo,!appengine + +#include "textflag.h" + +// Implementation of Poly1305 using the vector facility (vx) and the VMSL instruction. + +// constants +#define EX0 V1 +#define EX1 V2 +#define EX2 V3 + +// temporaries +#define T_0 V4 +#define T_1 V5 +#define T_2 V6 +#define T_3 V7 +#define T_4 V8 +#define T_5 V9 +#define T_6 V10 +#define T_7 V11 +#define T_8 V12 +#define T_9 V13 +#define T_10 V14 + +// r**2 & r**4 +#define R_0 V15 +#define R_1 V16 +#define R_2 V17 +#define R5_1 V18 +#define R5_2 V19 +// key (r) +#define RSAVE_0 R7 +#define RSAVE_1 R8 +#define RSAVE_2 R9 +#define R5SAVE_1 R10 +#define R5SAVE_2 R11 + +// message block +#define M0 V20 +#define M1 V21 +#define M2 V22 +#define M3 V23 +#define M4 V24 +#define M5 V25 + +// accumulator +#define H0_0 V26 +#define H1_0 V27 +#define H2_0 V28 +#define H0_1 V29 +#define H1_1 V30 +#define H2_1 V31 + +GLOBL ·keyMask<>(SB), RODATA, $16 +DATA ·keyMask<>+0(SB)/8, $0xffffff0ffcffff0f +DATA ·keyMask<>+8(SB)/8, $0xfcffff0ffcffff0f + +GLOBL ·bswapMask<>(SB), RODATA, $16 +DATA ·bswapMask<>+0(SB)/8, $0x0f0e0d0c0b0a0908 +DATA ·bswapMask<>+8(SB)/8, $0x0706050403020100 + +GLOBL ·constants<>(SB), RODATA, $48 +// EX0 +DATA ·constants<>+0(SB)/8, $0x18191a1b1c1d1e1f +DATA ·constants<>+8(SB)/8, $0x0000050403020100 +// EX1 +DATA ·constants<>+16(SB)/8, $0x18191a1b1c1d1e1f +DATA ·constants<>+24(SB)/8, $0x00000a0908070605 +// EX2 +DATA ·constants<>+32(SB)/8, $0x18191a1b1c1d1e1f +DATA ·constants<>+40(SB)/8, $0x0000000f0e0d0c0b + +GLOBL ·c<>(SB), RODATA, $48 +// EX0 +DATA ·c<>+0(SB)/8, $0x0000050403020100 +DATA ·c<>+8(SB)/8, $0x0000151413121110 +// EX1 +DATA ·c<>+16(SB)/8, $0x00000a0908070605 +DATA ·c<>+24(SB)/8, $0x00001a1918171615 +// EX2 +DATA ·c<>+32(SB)/8, $0x0000000f0e0d0c0b +DATA ·c<>+40(SB)/8, $0x0000001f1e1d1c1b + +GLOBL ·reduce<>(SB), RODATA, $32 +// 44 bit +DATA ·reduce<>+0(SB)/8, $0x0 +DATA ·reduce<>+8(SB)/8, $0xfffffffffff +// 42 bit +DATA ·reduce<>+16(SB)/8, $0x0 +DATA ·reduce<>+24(SB)/8, $0x3ffffffffff + +// h = (f*g) % (2**130-5) [partial reduction] +// uses T_0...T_9 temporary registers +// input: m02_0, m02_1, m02_2, m13_0, m13_1, m13_2, r_0, r_1, r_2, r5_1, r5_2, m4_0, m4_1, m4_2, m5_0, m5_1, m5_2 +// temp: t0, t1, t2, t3, t4, t5, t6, t7, t8, t9 +// output: m02_0, m02_1, m02_2, m13_0, m13_1, m13_2 +#define MULTIPLY(m02_0, m02_1, m02_2, m13_0, m13_1, m13_2, r_0, r_1, r_2, r5_1, r5_2, m4_0, m4_1, m4_2, m5_0, m5_1, m5_2, t0, t1, t2, t3, t4, t5, t6, t7, t8, t9) \ + \ // Eliminate the dependency for the last 2 VMSLs + VMSLG m02_0, r_2, m4_2, m4_2 \ + VMSLG m13_0, r_2, m5_2, m5_2 \ // 8 VMSLs pipelined + VMSLG m02_0, r_0, m4_0, m4_0 \ + VMSLG m02_1, r5_2, V0, T_0 \ + VMSLG m02_0, r_1, m4_1, m4_1 \ + VMSLG m02_1, r_0, V0, T_1 \ + VMSLG m02_1, r_1, V0, T_2 \ + VMSLG m02_2, r5_1, V0, T_3 \ + VMSLG m02_2, r5_2, V0, T_4 \ + VMSLG m13_0, r_0, m5_0, m5_0 \ + VMSLG m13_1, r5_2, V0, T_5 \ + VMSLG m13_0, r_1, m5_1, m5_1 \ + VMSLG m13_1, r_0, V0, T_6 \ + VMSLG m13_1, r_1, V0, T_7 \ + VMSLG m13_2, r5_1, V0, T_8 \ + VMSLG m13_2, r5_2, V0, T_9 \ + VMSLG m02_2, r_0, m4_2, m4_2 \ + VMSLG m13_2, r_0, m5_2, m5_2 \ + VAQ m4_0, T_0, m02_0 \ + VAQ m4_1, T_1, m02_1 \ + VAQ m5_0, T_5, m13_0 \ + VAQ m5_1, T_6, m13_1 \ + VAQ m02_0, T_3, m02_0 \ + VAQ m02_1, T_4, m02_1 \ + VAQ m13_0, T_8, m13_0 \ + VAQ m13_1, T_9, m13_1 \ + VAQ m4_2, T_2, m02_2 \ + VAQ m5_2, T_7, m13_2 \ + +// SQUARE uses three limbs of r and r_2*5 to output square of r +// uses T_1, T_5 and T_7 temporary registers +// input: r_0, r_1, r_2, r5_2 +// temp: TEMP0, TEMP1, TEMP2 +// output: p0, p1, p2 +#define SQUARE(r_0, r_1, r_2, r5_2, p0, p1, p2, TEMP0, TEMP1, TEMP2) \ + VMSLG r_0, r_0, p0, p0 \ + VMSLG r_1, r5_2, V0, TEMP0 \ + VMSLG r_2, r5_2, p1, p1 \ + VMSLG r_0, r_1, V0, TEMP1 \ + VMSLG r_1, r_1, p2, p2 \ + VMSLG r_0, r_2, V0, TEMP2 \ + VAQ TEMP0, p0, p0 \ + VAQ TEMP1, p1, p1 \ + VAQ TEMP2, p2, p2 \ + VAQ TEMP0, p0, p0 \ + VAQ TEMP1, p1, p1 \ + VAQ TEMP2, p2, p2 \ + +// carry h0->h1->h2->h0 || h3->h4->h5->h3 +// uses T_2, T_4, T_5, T_7, T_8, T_9 +// t6, t7, t8, t9, t10, t11 +// input: h0, h1, h2, h3, h4, h5 +// temp: t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11 +// output: h0, h1, h2, h3, h4, h5 +#define REDUCE(h0, h1, h2, h3, h4, h5, t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11) \ + VLM (R12), t6, t7 \ // 44 and 42 bit clear mask + VLEIB $7, $0x28, t10 \ // 5 byte shift mask + VREPIB $4, t8 \ // 4 bit shift mask + VREPIB $2, t11 \ // 2 bit shift mask + VSRLB t10, h0, t0 \ // h0 byte shift + VSRLB t10, h1, t1 \ // h1 byte shift + VSRLB t10, h2, t2 \ // h2 byte shift + VSRLB t10, h3, t3 \ // h3 byte shift + VSRLB t10, h4, t4 \ // h4 byte shift + VSRLB t10, h5, t5 \ // h5 byte shift + VSRL t8, t0, t0 \ // h0 bit shift + VSRL t8, t1, t1 \ // h2 bit shift + VSRL t11, t2, t2 \ // h2 bit shift + VSRL t8, t3, t3 \ // h3 bit shift + VSRL t8, t4, t4 \ // h4 bit shift + VESLG $2, t2, t9 \ // h2 carry x5 + VSRL t11, t5, t5 \ // h5 bit shift + VN t6, h0, h0 \ // h0 clear carry + VAQ t2, t9, t2 \ // h2 carry x5 + VESLG $2, t5, t9 \ // h5 carry x5 + VN t6, h1, h1 \ // h1 clear carry + VN t7, h2, h2 \ // h2 clear carry + VAQ t5, t9, t5 \ // h5 carry x5 + VN t6, h3, h3 \ // h3 clear carry + VN t6, h4, h4 \ // h4 clear carry + VN t7, h5, h5 \ // h5 clear carry + VAQ t0, h1, h1 \ // h0->h1 + VAQ t3, h4, h4 \ // h3->h4 + VAQ t1, h2, h2 \ // h1->h2 + VAQ t4, h5, h5 \ // h4->h5 + VAQ t2, h0, h0 \ // h2->h0 + VAQ t5, h3, h3 \ // h5->h3 + VREPG $1, t6, t6 \ // 44 and 42 bit masks across both halves + VREPG $1, t7, t7 \ + VSLDB $8, h0, h0, h0 \ // set up [h0/1/2, h3/4/5] + VSLDB $8, h1, h1, h1 \ + VSLDB $8, h2, h2, h2 \ + VO h0, h3, h3 \ + VO h1, h4, h4 \ + VO h2, h5, h5 \ + VESRLG $44, h3, t0 \ // 44 bit shift right + VESRLG $44, h4, t1 \ + VESRLG $42, h5, t2 \ + VN t6, h3, h3 \ // clear carry bits + VN t6, h4, h4 \ + VN t7, h5, h5 \ + VESLG $2, t2, t9 \ // multiply carry by 5 + VAQ t9, t2, t2 \ + VAQ t0, h4, h4 \ + VAQ t1, h5, h5 \ + VAQ t2, h3, h3 \ + +// carry h0->h1->h2->h0 +// input: h0, h1, h2 +// temp: t0, t1, t2, t3, t4, t5, t6, t7, t8 +// output: h0, h1, h2 +#define REDUCE2(h0, h1, h2, t0, t1, t2, t3, t4, t5, t6, t7, t8) \ + VLEIB $7, $0x28, t3 \ // 5 byte shift mask + VREPIB $4, t4 \ // 4 bit shift mask + VREPIB $2, t7 \ // 2 bit shift mask + VGBM $0x003F, t5 \ // mask to clear carry bits + VSRLB t3, h0, t0 \ + VSRLB t3, h1, t1 \ + VSRLB t3, h2, t2 \ + VESRLG $4, t5, t5 \ // 44 bit clear mask + VSRL t4, t0, t0 \ + VSRL t4, t1, t1 \ + VSRL t7, t2, t2 \ + VESRLG $2, t5, t6 \ // 42 bit clear mask + VESLG $2, t2, t8 \ + VAQ t8, t2, t2 \ + VN t5, h0, h0 \ + VN t5, h1, h1 \ + VN t6, h2, h2 \ + VAQ t0, h1, h1 \ + VAQ t1, h2, h2 \ + VAQ t2, h0, h0 \ + VSRLB t3, h0, t0 \ + VSRLB t3, h1, t1 \ + VSRLB t3, h2, t2 \ + VSRL t4, t0, t0 \ + VSRL t4, t1, t1 \ + VSRL t7, t2, t2 \ + VN t5, h0, h0 \ + VN t5, h1, h1 \ + VESLG $2, t2, t8 \ + VN t6, h2, h2 \ + VAQ t0, h1, h1 \ + VAQ t8, t2, t2 \ + VAQ t1, h2, h2 \ + VAQ t2, h0, h0 \ + +// expands two message blocks into the lower halfs of the d registers +// moves the contents of the d registers into upper halfs +// input: in1, in2, d0, d1, d2, d3, d4, d5 +// temp: TEMP0, TEMP1, TEMP2, TEMP3 +// output: d0, d1, d2, d3, d4, d5 +#define EXPACC(in1, in2, d0, d1, d2, d3, d4, d5, TEMP0, TEMP1, TEMP2, TEMP3) \ + VGBM $0xff3f, TEMP0 \ + VGBM $0xff1f, TEMP1 \ + VESLG $4, d1, TEMP2 \ + VESLG $4, d4, TEMP3 \ + VESRLG $4, TEMP0, TEMP0 \ + VPERM in1, d0, EX0, d0 \ + VPERM in2, d3, EX0, d3 \ + VPERM in1, d2, EX2, d2 \ + VPERM in2, d5, EX2, d5 \ + VPERM in1, TEMP2, EX1, d1 \ + VPERM in2, TEMP3, EX1, d4 \ + VN TEMP0, d0, d0 \ + VN TEMP0, d3, d3 \ + VESRLG $4, d1, d1 \ + VESRLG $4, d4, d4 \ + VN TEMP1, d2, d2 \ + VN TEMP1, d5, d5 \ + VN TEMP0, d1, d1 \ + VN TEMP0, d4, d4 \ + +// expands one message block into the lower halfs of the d registers +// moves the contents of the d registers into upper halfs +// input: in, d0, d1, d2 +// temp: TEMP0, TEMP1, TEMP2 +// output: d0, d1, d2 +#define EXPACC2(in, d0, d1, d2, TEMP0, TEMP1, TEMP2) \ + VGBM $0xff3f, TEMP0 \ + VESLG $4, d1, TEMP2 \ + VGBM $0xff1f, TEMP1 \ + VPERM in, d0, EX0, d0 \ + VESRLG $4, TEMP0, TEMP0 \ + VPERM in, d2, EX2, d2 \ + VPERM in, TEMP2, EX1, d1 \ + VN TEMP0, d0, d0 \ + VN TEMP1, d2, d2 \ + VESRLG $4, d1, d1 \ + VN TEMP0, d1, d1 \ + +// pack h2:h0 into h1:h0 (no carry) +// input: h0, h1, h2 +// output: h0, h1, h2 +#define PACK(h0, h1, h2) \ + VMRLG h1, h2, h2 \ // copy h1 to upper half h2 + VESLG $44, h1, h1 \ // shift limb 1 44 bits, leaving 20 + VO h0, h1, h0 \ // combine h0 with 20 bits from limb 1 + VESRLG $20, h2, h1 \ // put top 24 bits of limb 1 into h1 + VLEIG $1, $0, h1 \ // clear h2 stuff from lower half of h1 + VO h0, h1, h0 \ // h0 now has 88 bits (limb 0 and 1) + VLEIG $0, $0, h2 \ // clear upper half of h2 + VESRLG $40, h2, h1 \ // h1 now has upper two bits of result + VLEIB $7, $88, h1 \ // for byte shift (11 bytes) + VSLB h1, h2, h2 \ // shift h2 11 bytes to the left + VO h0, h2, h0 \ // combine h0 with 20 bits from limb 1 + VLEIG $0, $0, h1 \ // clear upper half of h1 + +// if h > 2**130-5 then h -= 2**130-5 +// input: h0, h1 +// temp: t0, t1, t2 +// output: h0 +#define MOD(h0, h1, t0, t1, t2) \ + VZERO t0 \ + VLEIG $1, $5, t0 \ + VACCQ h0, t0, t1 \ + VAQ h0, t0, t0 \ + VONE t2 \ + VLEIG $1, $-4, t2 \ + VAQ t2, t1, t1 \ + VACCQ h1, t1, t1 \ + VONE t2 \ + VAQ t2, t1, t1 \ + VN h0, t1, t2 \ + VNC t0, t1, t1 \ + VO t1, t2, h0 \ + +// func poly1305vmsl(out *[16]byte, m *byte, mlen uint64, key *[32]key) +TEXT ·poly1305vmsl(SB), $0-32 + // This code processes 6 + up to 4 blocks (32 bytes) per iteration + // using the algorithm described in: + // NEON crypto, Daniel J. Bernstein & Peter Schwabe + // https://cryptojedi.org/papers/neoncrypto-20120320.pdf + // And as moddified for VMSL as described in + // Accelerating Poly1305 Cryptographic Message Authentication on the z14 + // O'Farrell et al, CASCON 2017, p48-55 + // https://ibm.ent.box.com/s/jf9gedj0e9d2vjctfyh186shaztavnht + + LMG out+0(FP), R1, R4 // R1=out, R2=m, R3=mlen, R4=key + VZERO V0 // c + + // load EX0, EX1 and EX2 + MOVD $·constants<>(SB), R5 + VLM (R5), EX0, EX2 // c + + // setup r + VL (R4), T_0 + MOVD $·keyMask<>(SB), R6 + VL (R6), T_1 + VN T_0, T_1, T_0 + VZERO T_2 // limbs for r + VZERO T_3 + VZERO T_4 + EXPACC2(T_0, T_2, T_3, T_4, T_1, T_5, T_7) + + // T_2, T_3, T_4: [0, r] + + // setup r*20 + VLEIG $0, $0, T_0 + VLEIG $1, $20, T_0 // T_0: [0, 20] + VZERO T_5 + VZERO T_6 + VMSLG T_0, T_3, T_5, T_5 + VMSLG T_0, T_4, T_6, T_6 + + // store r for final block in GR + VLGVG $1, T_2, RSAVE_0 // c + VLGVG $1, T_3, RSAVE_1 // c + VLGVG $1, T_4, RSAVE_2 // c + VLGVG $1, T_5, R5SAVE_1 // c + VLGVG $1, T_6, R5SAVE_2 // c + + // initialize h + VZERO H0_0 + VZERO H1_0 + VZERO H2_0 + VZERO H0_1 + VZERO H1_1 + VZERO H2_1 + + // initialize pointer for reduce constants + MOVD $·reduce<>(SB), R12 + + // calculate r**2 and 20*(r**2) + VZERO R_0 + VZERO R_1 + VZERO R_2 + SQUARE(T_2, T_3, T_4, T_6, R_0, R_1, R_2, T_1, T_5, T_7) + REDUCE2(R_0, R_1, R_2, M0, M1, M2, M3, M4, R5_1, R5_2, M5, T_1) + VZERO R5_1 + VZERO R5_2 + VMSLG T_0, R_1, R5_1, R5_1 + VMSLG T_0, R_2, R5_2, R5_2 + + // skip r**4 calculation if 3 blocks or less + CMPBLE R3, $48, b4 + + // calculate r**4 and 20*(r**4) + VZERO T_8 + VZERO T_9 + VZERO T_10 + SQUARE(R_0, R_1, R_2, R5_2, T_8, T_9, T_10, T_1, T_5, T_7) + REDUCE2(T_8, T_9, T_10, M0, M1, M2, M3, M4, T_2, T_3, M5, T_1) + VZERO T_2 + VZERO T_3 + VMSLG T_0, T_9, T_2, T_2 + VMSLG T_0, T_10, T_3, T_3 + + // put r**2 to the right and r**4 to the left of R_0, R_1, R_2 + VSLDB $8, T_8, T_8, T_8 + VSLDB $8, T_9, T_9, T_9 + VSLDB $8, T_10, T_10, T_10 + VSLDB $8, T_2, T_2, T_2 + VSLDB $8, T_3, T_3, T_3 + + VO T_8, R_0, R_0 + VO T_9, R_1, R_1 + VO T_10, R_2, R_2 + VO T_2, R5_1, R5_1 + VO T_3, R5_2, R5_2 + + CMPBLE R3, $80, load // less than or equal to 5 blocks in message + + // 6(or 5+1) blocks + SUB $81, R3 + VLM (R2), M0, M4 + VLL R3, 80(R2), M5 + ADD $1, R3 + MOVBZ $1, R0 + CMPBGE R3, $16, 2(PC) + VLVGB R3, R0, M5 + MOVD $96(R2), R2 + EXPACC(M0, M1, H0_0, H1_0, H2_0, H0_1, H1_1, H2_1, T_0, T_1, T_2, T_3) + EXPACC(M2, M3, H0_0, H1_0, H2_0, H0_1, H1_1, H2_1, T_0, T_1, T_2, T_3) + VLEIB $2, $1, H2_0 + VLEIB $2, $1, H2_1 + VLEIB $10, $1, H2_0 + VLEIB $10, $1, H2_1 + + VZERO M0 + VZERO M1 + VZERO M2 + VZERO M3 + VZERO T_4 + VZERO T_10 + EXPACC(M4, M5, M0, M1, M2, M3, T_4, T_10, T_0, T_1, T_2, T_3) + VLR T_4, M4 + VLEIB $10, $1, M2 + CMPBLT R3, $16, 2(PC) + VLEIB $10, $1, T_10 + MULTIPLY(H0_0, H1_0, H2_0, H0_1, H1_1, H2_1, R_0, R_1, R_2, R5_1, R5_2, M0, M1, M2, M3, M4, T_10, T_0, T_1, T_2, T_3, T_4, T_5, T_6, T_7, T_8, T_9) + REDUCE(H0_0, H1_0, H2_0, H0_1, H1_1, H2_1, T_10, M0, M1, M2, M3, M4, T_4, T_5, T_2, T_7, T_8, T_9) + VMRHG V0, H0_1, H0_0 + VMRHG V0, H1_1, H1_0 + VMRHG V0, H2_1, H2_0 + VMRLG V0, H0_1, H0_1 + VMRLG V0, H1_1, H1_1 + VMRLG V0, H2_1, H2_1 + + SUB $16, R3 + CMPBLE R3, $0, square + +load: + // load EX0, EX1 and EX2 + MOVD $·c<>(SB), R5 + VLM (R5), EX0, EX2 + +loop: + CMPBLE R3, $64, add // b4 // last 4 or less blocks left + + // next 4 full blocks + VLM (R2), M2, M5 + SUB $64, R3 + MOVD $64(R2), R2 + REDUCE(H0_0, H1_0, H2_0, H0_1, H1_1, H2_1, T_10, M0, M1, T_0, T_1, T_3, T_4, T_5, T_2, T_7, T_8, T_9) + + // expacc in-lined to create [m2, m3] limbs + VGBM $0x3f3f, T_0 // 44 bit clear mask + VGBM $0x1f1f, T_1 // 40 bit clear mask + VPERM M2, M3, EX0, T_3 + VESRLG $4, T_0, T_0 // 44 bit clear mask ready + VPERM M2, M3, EX1, T_4 + VPERM M2, M3, EX2, T_5 + VN T_0, T_3, T_3 + VESRLG $4, T_4, T_4 + VN T_1, T_5, T_5 + VN T_0, T_4, T_4 + VMRHG H0_1, T_3, H0_0 + VMRHG H1_1, T_4, H1_0 + VMRHG H2_1, T_5, H2_0 + VMRLG H0_1, T_3, H0_1 + VMRLG H1_1, T_4, H1_1 + VMRLG H2_1, T_5, H2_1 + VLEIB $10, $1, H2_0 + VLEIB $10, $1, H2_1 + VPERM M4, M5, EX0, T_3 + VPERM M4, M5, EX1, T_4 + VPERM M4, M5, EX2, T_5 + VN T_0, T_3, T_3 + VESRLG $4, T_4, T_4 + VN T_1, T_5, T_5 + VN T_0, T_4, T_4 + VMRHG V0, T_3, M0 + VMRHG V0, T_4, M1 + VMRHG V0, T_5, M2 + VMRLG V0, T_3, M3 + VMRLG V0, T_4, M4 + VMRLG V0, T_5, M5 + VLEIB $10, $1, M2 + VLEIB $10, $1, M5 + + MULTIPLY(H0_0, H1_0, H2_0, H0_1, H1_1, H2_1, R_0, R_1, R_2, R5_1, R5_2, M0, M1, M2, M3, M4, M5, T_0, T_1, T_2, T_3, T_4, T_5, T_6, T_7, T_8, T_9) + CMPBNE R3, $0, loop + REDUCE(H0_0, H1_0, H2_0, H0_1, H1_1, H2_1, T_10, M0, M1, M3, M4, M5, T_4, T_5, T_2, T_7, T_8, T_9) + VMRHG V0, H0_1, H0_0 + VMRHG V0, H1_1, H1_0 + VMRHG V0, H2_1, H2_0 + VMRLG V0, H0_1, H0_1 + VMRLG V0, H1_1, H1_1 + VMRLG V0, H2_1, H2_1 + + // load EX0, EX1, EX2 + MOVD $·constants<>(SB), R5 + VLM (R5), EX0, EX2 + + // sum vectors + VAQ H0_0, H0_1, H0_0 + VAQ H1_0, H1_1, H1_0 + VAQ H2_0, H2_1, H2_0 + + // h may be >= 2*(2**130-5) so we need to reduce it again + // M0...M4 are used as temps here + REDUCE2(H0_0, H1_0, H2_0, M0, M1, M2, M3, M4, T_9, T_10, H0_1, M5) + +next: // carry h1->h2 + VLEIB $7, $0x28, T_1 + VREPIB $4, T_2 + VGBM $0x003F, T_3 + VESRLG $4, T_3 + + // byte shift + VSRLB T_1, H1_0, T_4 + + // bit shift + VSRL T_2, T_4, T_4 + + // clear h1 carry bits + VN T_3, H1_0, H1_0 + + // add carry + VAQ T_4, H2_0, H2_0 + + // h is now < 2*(2**130-5) + // pack h into h1 (hi) and h0 (lo) + PACK(H0_0, H1_0, H2_0) + + // if h > 2**130-5 then h -= 2**130-5 + MOD(H0_0, H1_0, T_0, T_1, T_2) + + // h += s + MOVD $·bswapMask<>(SB), R5 + VL (R5), T_1 + VL 16(R4), T_0 + VPERM T_0, T_0, T_1, T_0 // reverse bytes (to big) + VAQ T_0, H0_0, H0_0 + VPERM H0_0, H0_0, T_1, H0_0 // reverse bytes (to little) + VST H0_0, (R1) + RET + +add: + // load EX0, EX1, EX2 + MOVD $·constants<>(SB), R5 + VLM (R5), EX0, EX2 + + REDUCE(H0_0, H1_0, H2_0, H0_1, H1_1, H2_1, T_10, M0, M1, M3, M4, M5, T_4, T_5, T_2, T_7, T_8, T_9) + VMRHG V0, H0_1, H0_0 + VMRHG V0, H1_1, H1_0 + VMRHG V0, H2_1, H2_0 + VMRLG V0, H0_1, H0_1 + VMRLG V0, H1_1, H1_1 + VMRLG V0, H2_1, H2_1 + CMPBLE R3, $64, b4 + +b4: + CMPBLE R3, $48, b3 // 3 blocks or less + + // 4(3+1) blocks remaining + SUB $49, R3 + VLM (R2), M0, M2 + VLL R3, 48(R2), M3 + ADD $1, R3 + MOVBZ $1, R0 + CMPBEQ R3, $16, 2(PC) + VLVGB R3, R0, M3 + MOVD $64(R2), R2 + EXPACC(M0, M1, H0_0, H1_0, H2_0, H0_1, H1_1, H2_1, T_0, T_1, T_2, T_3) + VLEIB $10, $1, H2_0 + VLEIB $10, $1, H2_1 + VZERO M0 + VZERO M1 + VZERO M4 + VZERO M5 + VZERO T_4 + VZERO T_10 + EXPACC(M2, M3, M0, M1, M4, M5, T_4, T_10, T_0, T_1, T_2, T_3) + VLR T_4, M2 + VLEIB $10, $1, M4 + CMPBNE R3, $16, 2(PC) + VLEIB $10, $1, T_10 + MULTIPLY(H0_0, H1_0, H2_0, H0_1, H1_1, H2_1, R_0, R_1, R_2, R5_1, R5_2, M0, M1, M4, M5, M2, T_10, T_0, T_1, T_2, T_3, T_4, T_5, T_6, T_7, T_8, T_9) + REDUCE(H0_0, H1_0, H2_0, H0_1, H1_1, H2_1, T_10, M0, M1, M3, M4, M5, T_4, T_5, T_2, T_7, T_8, T_9) + VMRHG V0, H0_1, H0_0 + VMRHG V0, H1_1, H1_0 + VMRHG V0, H2_1, H2_0 + VMRLG V0, H0_1, H0_1 + VMRLG V0, H1_1, H1_1 + VMRLG V0, H2_1, H2_1 + SUB $16, R3 + CMPBLE R3, $0, square // this condition must always hold true! + +b3: + CMPBLE R3, $32, b2 + + // 3 blocks remaining + + // setup [r²,r] + VSLDB $8, R_0, R_0, R_0 + VSLDB $8, R_1, R_1, R_1 + VSLDB $8, R_2, R_2, R_2 + VSLDB $8, R5_1, R5_1, R5_1 + VSLDB $8, R5_2, R5_2, R5_2 + + VLVGG $1, RSAVE_0, R_0 + VLVGG $1, RSAVE_1, R_1 + VLVGG $1, RSAVE_2, R_2 + VLVGG $1, R5SAVE_1, R5_1 + VLVGG $1, R5SAVE_2, R5_2 + + // setup [h0, h1] + VSLDB $8, H0_0, H0_0, H0_0 + VSLDB $8, H1_0, H1_0, H1_0 + VSLDB $8, H2_0, H2_0, H2_0 + VO H0_1, H0_0, H0_0 + VO H1_1, H1_0, H1_0 + VO H2_1, H2_0, H2_0 + VZERO H0_1 + VZERO H1_1 + VZERO H2_1 + + VZERO M0 + VZERO M1 + VZERO M2 + VZERO M3 + VZERO M4 + VZERO M5 + + // H*[r**2, r] + MULTIPLY(H0_0, H1_0, H2_0, H0_1, H1_1, H2_1, R_0, R_1, R_2, R5_1, R5_2, M0, M1, M2, M3, M4, M5, T_0, T_1, T_2, T_3, T_4, T_5, T_6, T_7, T_8, T_9) + REDUCE2(H0_0, H1_0, H2_0, M0, M1, M2, M3, M4, H0_1, H1_1, T_10, M5) + + SUB $33, R3 + VLM (R2), M0, M1 + VLL R3, 32(R2), M2 + ADD $1, R3 + MOVBZ $1, R0 + CMPBEQ R3, $16, 2(PC) + VLVGB R3, R0, M2 + + // H += m0 + VZERO T_1 + VZERO T_2 + VZERO T_3 + EXPACC2(M0, T_1, T_2, T_3, T_4, T_5, T_6) + VLEIB $10, $1, T_3 + VAG H0_0, T_1, H0_0 + VAG H1_0, T_2, H1_0 + VAG H2_0, T_3, H2_0 + + VZERO M0 + VZERO M3 + VZERO M4 + VZERO M5 + VZERO T_10 + + // (H+m0)*r + MULTIPLY(H0_0, H1_0, H2_0, H0_1, H1_1, H2_1, R_0, R_1, R_2, R5_1, R5_2, M0, M3, M4, M5, V0, T_10, T_0, T_1, T_2, T_3, T_4, T_5, T_6, T_7, T_8, T_9) + REDUCE2(H0_0, H1_0, H2_0, M0, M3, M4, M5, T_10, H0_1, H1_1, H2_1, T_9) + + // H += m1 + VZERO V0 + VZERO T_1 + VZERO T_2 + VZERO T_3 + EXPACC2(M1, T_1, T_2, T_3, T_4, T_5, T_6) + VLEIB $10, $1, T_3 + VAQ H0_0, T_1, H0_0 + VAQ H1_0, T_2, H1_0 + VAQ H2_0, T_3, H2_0 + REDUCE2(H0_0, H1_0, H2_0, M0, M3, M4, M5, T_9, H0_1, H1_1, H2_1, T_10) + + // [H, m2] * [r**2, r] + EXPACC2(M2, H0_0, H1_0, H2_0, T_1, T_2, T_3) + CMPBNE R3, $16, 2(PC) + VLEIB $10, $1, H2_0 + VZERO M0 + VZERO M1 + VZERO M2 + VZERO M3 + VZERO M4 + VZERO M5 + MULTIPLY(H0_0, H1_0, H2_0, H0_1, H1_1, H2_1, R_0, R_1, R_2, R5_1, R5_2, M0, M1, M2, M3, M4, M5, T_0, T_1, T_2, T_3, T_4, T_5, T_6, T_7, T_8, T_9) + REDUCE2(H0_0, H1_0, H2_0, M0, M1, M2, M3, M4, H0_1, H1_1, M5, T_10) + SUB $16, R3 + CMPBLE R3, $0, next // this condition must always hold true! + +b2: + CMPBLE R3, $16, b1 + + // 2 blocks remaining + + // setup [r²,r] + VSLDB $8, R_0, R_0, R_0 + VSLDB $8, R_1, R_1, R_1 + VSLDB $8, R_2, R_2, R_2 + VSLDB $8, R5_1, R5_1, R5_1 + VSLDB $8, R5_2, R5_2, R5_2 + + VLVGG $1, RSAVE_0, R_0 + VLVGG $1, RSAVE_1, R_1 + VLVGG $1, RSAVE_2, R_2 + VLVGG $1, R5SAVE_1, R5_1 + VLVGG $1, R5SAVE_2, R5_2 + + // setup [h0, h1] + VSLDB $8, H0_0, H0_0, H0_0 + VSLDB $8, H1_0, H1_0, H1_0 + VSLDB $8, H2_0, H2_0, H2_0 + VO H0_1, H0_0, H0_0 + VO H1_1, H1_0, H1_0 + VO H2_1, H2_0, H2_0 + VZERO H0_1 + VZERO H1_1 + VZERO H2_1 + + VZERO M0 + VZERO M1 + VZERO M2 + VZERO M3 + VZERO M4 + VZERO M5 + + // H*[r**2, r] + MULTIPLY(H0_0, H1_0, H2_0, H0_1, H1_1, H2_1, R_0, R_1, R_2, R5_1, R5_2, M0, M1, M2, M3, M4, M5, T_0, T_1, T_2, T_3, T_4, T_5, T_6, T_7, T_8, T_9) + REDUCE(H0_0, H1_0, H2_0, H0_1, H1_1, H2_1, T_10, M0, M1, M2, M3, M4, T_4, T_5, T_2, T_7, T_8, T_9) + VMRHG V0, H0_1, H0_0 + VMRHG V0, H1_1, H1_0 + VMRHG V0, H2_1, H2_0 + VMRLG V0, H0_1, H0_1 + VMRLG V0, H1_1, H1_1 + VMRLG V0, H2_1, H2_1 + + // move h to the left and 0s at the right + VSLDB $8, H0_0, H0_0, H0_0 + VSLDB $8, H1_0, H1_0, H1_0 + VSLDB $8, H2_0, H2_0, H2_0 + + // get message blocks and append 1 to start + SUB $17, R3 + VL (R2), M0 + VLL R3, 16(R2), M1 + ADD $1, R3 + MOVBZ $1, R0 + CMPBEQ R3, $16, 2(PC) + VLVGB R3, R0, M1 + VZERO T_6 + VZERO T_7 + VZERO T_8 + EXPACC2(M0, T_6, T_7, T_8, T_1, T_2, T_3) + EXPACC2(M1, T_6, T_7, T_8, T_1, T_2, T_3) + VLEIB $2, $1, T_8 + CMPBNE R3, $16, 2(PC) + VLEIB $10, $1, T_8 + + // add [m0, m1] to h + VAG H0_0, T_6, H0_0 + VAG H1_0, T_7, H1_0 + VAG H2_0, T_8, H2_0 + + VZERO M2 + VZERO M3 + VZERO M4 + VZERO M5 + VZERO T_10 + VZERO M0 + + // at this point R_0 .. R5_2 look like [r**2, r] + MULTIPLY(H0_0, H1_0, H2_0, H0_1, H1_1, H2_1, R_0, R_1, R_2, R5_1, R5_2, M2, M3, M4, M5, T_10, M0, T_0, T_1, T_2, T_3, T_4, T_5, T_6, T_7, T_8, T_9) + REDUCE2(H0_0, H1_0, H2_0, M2, M3, M4, M5, T_9, H0_1, H1_1, H2_1, T_10) + SUB $16, R3, R3 + CMPBLE R3, $0, next + +b1: + CMPBLE R3, $0, next + + // 1 block remaining + + // setup [r²,r] + VSLDB $8, R_0, R_0, R_0 + VSLDB $8, R_1, R_1, R_1 + VSLDB $8, R_2, R_2, R_2 + VSLDB $8, R5_1, R5_1, R5_1 + VSLDB $8, R5_2, R5_2, R5_2 + + VLVGG $1, RSAVE_0, R_0 + VLVGG $1, RSAVE_1, R_1 + VLVGG $1, RSAVE_2, R_2 + VLVGG $1, R5SAVE_1, R5_1 + VLVGG $1, R5SAVE_2, R5_2 + + // setup [h0, h1] + VSLDB $8, H0_0, H0_0, H0_0 + VSLDB $8, H1_0, H1_0, H1_0 + VSLDB $8, H2_0, H2_0, H2_0 + VO H0_1, H0_0, H0_0 + VO H1_1, H1_0, H1_0 + VO H2_1, H2_0, H2_0 + VZERO H0_1 + VZERO H1_1 + VZERO H2_1 + + VZERO M0 + VZERO M1 + VZERO M2 + VZERO M3 + VZERO M4 + VZERO M5 + + // H*[r**2, r] + MULTIPLY(H0_0, H1_0, H2_0, H0_1, H1_1, H2_1, R_0, R_1, R_2, R5_1, R5_2, M0, M1, M2, M3, M4, M5, T_0, T_1, T_2, T_3, T_4, T_5, T_6, T_7, T_8, T_9) + REDUCE2(H0_0, H1_0, H2_0, M0, M1, M2, M3, M4, T_9, T_10, H0_1, M5) + + // set up [0, m0] limbs + SUB $1, R3 + VLL R3, (R2), M0 + ADD $1, R3 + MOVBZ $1, R0 + CMPBEQ R3, $16, 2(PC) + VLVGB R3, R0, M0 + VZERO T_1 + VZERO T_2 + VZERO T_3 + EXPACC2(M0, T_1, T_2, T_3, T_4, T_5, T_6)// limbs: [0, m] + CMPBNE R3, $16, 2(PC) + VLEIB $10, $1, T_3 + + // h+m0 + VAQ H0_0, T_1, H0_0 + VAQ H1_0, T_2, H1_0 + VAQ H2_0, T_3, H2_0 + + VZERO M0 + VZERO M1 + VZERO M2 + VZERO M3 + VZERO M4 + VZERO M5 + MULTIPLY(H0_0, H1_0, H2_0, H0_1, H1_1, H2_1, R_0, R_1, R_2, R5_1, R5_2, M0, M1, M2, M3, M4, M5, T_0, T_1, T_2, T_3, T_4, T_5, T_6, T_7, T_8, T_9) + REDUCE2(H0_0, H1_0, H2_0, M0, M1, M2, M3, M4, T_9, T_10, H0_1, M5) + + BR next + +square: + // setup [r²,r] + VSLDB $8, R_0, R_0, R_0 + VSLDB $8, R_1, R_1, R_1 + VSLDB $8, R_2, R_2, R_2 + VSLDB $8, R5_1, R5_1, R5_1 + VSLDB $8, R5_2, R5_2, R5_2 + + VLVGG $1, RSAVE_0, R_0 + VLVGG $1, RSAVE_1, R_1 + VLVGG $1, RSAVE_2, R_2 + VLVGG $1, R5SAVE_1, R5_1 + VLVGG $1, R5SAVE_2, R5_2 + + // setup [h0, h1] + VSLDB $8, H0_0, H0_0, H0_0 + VSLDB $8, H1_0, H1_0, H1_0 + VSLDB $8, H2_0, H2_0, H2_0 + VO H0_1, H0_0, H0_0 + VO H1_1, H1_0, H1_0 + VO H2_1, H2_0, H2_0 + VZERO H0_1 + VZERO H1_1 + VZERO H2_1 + + VZERO M0 + VZERO M1 + VZERO M2 + VZERO M3 + VZERO M4 + VZERO M5 + + // (h0*r**2) + (h1*r) + MULTIPLY(H0_0, H1_0, H2_0, H0_1, H1_1, H2_1, R_0, R_1, R_2, R5_1, R5_2, M0, M1, M2, M3, M4, M5, T_0, T_1, T_2, T_3, T_4, T_5, T_6, T_7, T_8, T_9) + REDUCE2(H0_0, H1_0, H2_0, M0, M1, M2, M3, M4, T_9, T_10, H0_1, M5) + BR next diff --git a/vendor/golang.org/x/crypto/ssh/buffer.go b/vendor/golang.org/x/crypto/ssh/buffer.go new file mode 100644 index 00000000000..1ab07d078db --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/buffer.go @@ -0,0 +1,97 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "io" + "sync" +) + +// buffer provides a linked list buffer for data exchange +// between producer and consumer. Theoretically the buffer is +// of unlimited capacity as it does no allocation of its own. +type buffer struct { + // protects concurrent access to head, tail and closed + *sync.Cond + + head *element // the buffer that will be read first + tail *element // the buffer that will be read last + + closed bool +} + +// An element represents a single link in a linked list. +type element struct { + buf []byte + next *element +} + +// newBuffer returns an empty buffer that is not closed. +func newBuffer() *buffer { + e := new(element) + b := &buffer{ + Cond: newCond(), + head: e, + tail: e, + } + return b +} + +// write makes buf available for Read to receive. +// buf must not be modified after the call to write. +func (b *buffer) write(buf []byte) { + b.Cond.L.Lock() + e := &element{buf: buf} + b.tail.next = e + b.tail = e + b.Cond.Signal() + b.Cond.L.Unlock() +} + +// eof closes the buffer. Reads from the buffer once all +// the data has been consumed will receive io.EOF. +func (b *buffer) eof() { + b.Cond.L.Lock() + b.closed = true + b.Cond.Signal() + b.Cond.L.Unlock() +} + +// Read reads data from the internal buffer in buf. Reads will block +// if no data is available, or until the buffer is closed. +func (b *buffer) Read(buf []byte) (n int, err error) { + b.Cond.L.Lock() + defer b.Cond.L.Unlock() + + for len(buf) > 0 { + // if there is data in b.head, copy it + if len(b.head.buf) > 0 { + r := copy(buf, b.head.buf) + buf, b.head.buf = buf[r:], b.head.buf[r:] + n += r + continue + } + // if there is a next buffer, make it the head + if len(b.head.buf) == 0 && b.head != b.tail { + b.head = b.head.next + continue + } + + // if at least one byte has been copied, return + if n > 0 { + break + } + + // if nothing was read, and there is nothing outstanding + // check to see if the buffer is closed. + if b.closed { + err = io.EOF + break + } + // out of buffers, wait for producer + b.Cond.Wait() + } + return +} diff --git a/vendor/golang.org/x/crypto/ssh/certs.go b/vendor/golang.org/x/crypto/ssh/certs.go new file mode 100644 index 00000000000..0f89aec1c7f --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/certs.go @@ -0,0 +1,546 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bytes" + "errors" + "fmt" + "io" + "net" + "sort" + "time" +) + +// These constants from [PROTOCOL.certkeys] represent the algorithm names +// for certificate types supported by this package. +const ( + CertAlgoRSAv01 = "ssh-rsa-cert-v01@openssh.com" + CertAlgoDSAv01 = "ssh-dss-cert-v01@openssh.com" + CertAlgoECDSA256v01 = "ecdsa-sha2-nistp256-cert-v01@openssh.com" + CertAlgoECDSA384v01 = "ecdsa-sha2-nistp384-cert-v01@openssh.com" + CertAlgoECDSA521v01 = "ecdsa-sha2-nistp521-cert-v01@openssh.com" + CertAlgoSKECDSA256v01 = "sk-ecdsa-sha2-nistp256-cert-v01@openssh.com" + CertAlgoED25519v01 = "ssh-ed25519-cert-v01@openssh.com" + CertAlgoSKED25519v01 = "sk-ssh-ed25519-cert-v01@openssh.com" +) + +// Certificate types distinguish between host and user +// certificates. The values can be set in the CertType field of +// Certificate. +const ( + UserCert = 1 + HostCert = 2 +) + +// Signature represents a cryptographic signature. +type Signature struct { + Format string + Blob []byte + Rest []byte `ssh:"rest"` +} + +// CertTimeInfinity can be used for OpenSSHCertV01.ValidBefore to indicate that +// a certificate does not expire. +const CertTimeInfinity = 1<<64 - 1 + +// An Certificate represents an OpenSSH certificate as defined in +// [PROTOCOL.certkeys]?rev=1.8. The Certificate type implements the +// PublicKey interface, so it can be unmarshaled using +// ParsePublicKey. +type Certificate struct { + Nonce []byte + Key PublicKey + Serial uint64 + CertType uint32 + KeyId string + ValidPrincipals []string + ValidAfter uint64 + ValidBefore uint64 + Permissions + Reserved []byte + SignatureKey PublicKey + Signature *Signature +} + +// genericCertData holds the key-independent part of the certificate data. +// Overall, certificates contain an nonce, public key fields and +// key-independent fields. +type genericCertData struct { + Serial uint64 + CertType uint32 + KeyId string + ValidPrincipals []byte + ValidAfter uint64 + ValidBefore uint64 + CriticalOptions []byte + Extensions []byte + Reserved []byte + SignatureKey []byte + Signature []byte +} + +func marshalStringList(namelist []string) []byte { + var to []byte + for _, name := range namelist { + s := struct{ N string }{name} + to = append(to, Marshal(&s)...) + } + return to +} + +type optionsTuple struct { + Key string + Value []byte +} + +type optionsTupleValue struct { + Value string +} + +// serialize a map of critical options or extensions +// issue #10569 - per [PROTOCOL.certkeys] and SSH implementation, +// we need two length prefixes for a non-empty string value +func marshalTuples(tups map[string]string) []byte { + keys := make([]string, 0, len(tups)) + for key := range tups { + keys = append(keys, key) + } + sort.Strings(keys) + + var ret []byte + for _, key := range keys { + s := optionsTuple{Key: key} + if value := tups[key]; len(value) > 0 { + s.Value = Marshal(&optionsTupleValue{value}) + } + ret = append(ret, Marshal(&s)...) + } + return ret +} + +// issue #10569 - per [PROTOCOL.certkeys] and SSH implementation, +// we need two length prefixes for a non-empty option value +func parseTuples(in []byte) (map[string]string, error) { + tups := map[string]string{} + var lastKey string + var haveLastKey bool + + for len(in) > 0 { + var key, val, extra []byte + var ok bool + + if key, in, ok = parseString(in); !ok { + return nil, errShortRead + } + keyStr := string(key) + // according to [PROTOCOL.certkeys], the names must be in + // lexical order. + if haveLastKey && keyStr <= lastKey { + return nil, fmt.Errorf("ssh: certificate options are not in lexical order") + } + lastKey, haveLastKey = keyStr, true + // the next field is a data field, which if non-empty has a string embedded + if val, in, ok = parseString(in); !ok { + return nil, errShortRead + } + if len(val) > 0 { + val, extra, ok = parseString(val) + if !ok { + return nil, errShortRead + } + if len(extra) > 0 { + return nil, fmt.Errorf("ssh: unexpected trailing data after certificate option value") + } + tups[keyStr] = string(val) + } else { + tups[keyStr] = "" + } + } + return tups, nil +} + +func parseCert(in []byte, privAlgo string) (*Certificate, error) { + nonce, rest, ok := parseString(in) + if !ok { + return nil, errShortRead + } + + key, rest, err := parsePubKey(rest, privAlgo) + if err != nil { + return nil, err + } + + var g genericCertData + if err := Unmarshal(rest, &g); err != nil { + return nil, err + } + + c := &Certificate{ + Nonce: nonce, + Key: key, + Serial: g.Serial, + CertType: g.CertType, + KeyId: g.KeyId, + ValidAfter: g.ValidAfter, + ValidBefore: g.ValidBefore, + } + + for principals := g.ValidPrincipals; len(principals) > 0; { + principal, rest, ok := parseString(principals) + if !ok { + return nil, errShortRead + } + c.ValidPrincipals = append(c.ValidPrincipals, string(principal)) + principals = rest + } + + c.CriticalOptions, err = parseTuples(g.CriticalOptions) + if err != nil { + return nil, err + } + c.Extensions, err = parseTuples(g.Extensions) + if err != nil { + return nil, err + } + c.Reserved = g.Reserved + k, err := ParsePublicKey(g.SignatureKey) + if err != nil { + return nil, err + } + + c.SignatureKey = k + c.Signature, rest, ok = parseSignatureBody(g.Signature) + if !ok || len(rest) > 0 { + return nil, errors.New("ssh: signature parse error") + } + + return c, nil +} + +type openSSHCertSigner struct { + pub *Certificate + signer Signer +} + +type algorithmOpenSSHCertSigner struct { + *openSSHCertSigner + algorithmSigner AlgorithmSigner +} + +// NewCertSigner returns a Signer that signs with the given Certificate, whose +// private key is held by signer. It returns an error if the public key in cert +// doesn't match the key used by signer. +func NewCertSigner(cert *Certificate, signer Signer) (Signer, error) { + if bytes.Compare(cert.Key.Marshal(), signer.PublicKey().Marshal()) != 0 { + return nil, errors.New("ssh: signer and cert have different public key") + } + + if algorithmSigner, ok := signer.(AlgorithmSigner); ok { + return &algorithmOpenSSHCertSigner{ + &openSSHCertSigner{cert, signer}, algorithmSigner}, nil + } else { + return &openSSHCertSigner{cert, signer}, nil + } +} + +func (s *openSSHCertSigner) Sign(rand io.Reader, data []byte) (*Signature, error) { + return s.signer.Sign(rand, data) +} + +func (s *openSSHCertSigner) PublicKey() PublicKey { + return s.pub +} + +func (s *algorithmOpenSSHCertSigner) SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*Signature, error) { + return s.algorithmSigner.SignWithAlgorithm(rand, data, algorithm) +} + +const sourceAddressCriticalOption = "source-address" + +// CertChecker does the work of verifying a certificate. Its methods +// can be plugged into ClientConfig.HostKeyCallback and +// ServerConfig.PublicKeyCallback. For the CertChecker to work, +// minimally, the IsAuthority callback should be set. +type CertChecker struct { + // SupportedCriticalOptions lists the CriticalOptions that the + // server application layer understands. These are only used + // for user certificates. + SupportedCriticalOptions []string + + // IsUserAuthority should return true if the key is recognized as an + // authority for the given user certificate. This allows for + // certificates to be signed by other certificates. This must be set + // if this CertChecker will be checking user certificates. + IsUserAuthority func(auth PublicKey) bool + + // IsHostAuthority should report whether the key is recognized as + // an authority for this host. This allows for certificates to be + // signed by other keys, and for those other keys to only be valid + // signers for particular hostnames. This must be set if this + // CertChecker will be checking host certificates. + IsHostAuthority func(auth PublicKey, address string) bool + + // Clock is used for verifying time stamps. If nil, time.Now + // is used. + Clock func() time.Time + + // UserKeyFallback is called when CertChecker.Authenticate encounters a + // public key that is not a certificate. It must implement validation + // of user keys or else, if nil, all such keys are rejected. + UserKeyFallback func(conn ConnMetadata, key PublicKey) (*Permissions, error) + + // HostKeyFallback is called when CertChecker.CheckHostKey encounters a + // public key that is not a certificate. It must implement host key + // validation or else, if nil, all such keys are rejected. + HostKeyFallback HostKeyCallback + + // IsRevoked is called for each certificate so that revocation checking + // can be implemented. It should return true if the given certificate + // is revoked and false otherwise. If nil, no certificates are + // considered to have been revoked. + IsRevoked func(cert *Certificate) bool +} + +// CheckHostKey checks a host key certificate. This method can be +// plugged into ClientConfig.HostKeyCallback. +func (c *CertChecker) CheckHostKey(addr string, remote net.Addr, key PublicKey) error { + cert, ok := key.(*Certificate) + if !ok { + if c.HostKeyFallback != nil { + return c.HostKeyFallback(addr, remote, key) + } + return errors.New("ssh: non-certificate host key") + } + if cert.CertType != HostCert { + return fmt.Errorf("ssh: certificate presented as a host key has type %d", cert.CertType) + } + if !c.IsHostAuthority(cert.SignatureKey, addr) { + return fmt.Errorf("ssh: no authorities for hostname: %v", addr) + } + + hostname, _, err := net.SplitHostPort(addr) + if err != nil { + return err + } + + // Pass hostname only as principal for host certificates (consistent with OpenSSH) + return c.CheckCert(hostname, cert) +} + +// Authenticate checks a user certificate. Authenticate can be used as +// a value for ServerConfig.PublicKeyCallback. +func (c *CertChecker) Authenticate(conn ConnMetadata, pubKey PublicKey) (*Permissions, error) { + cert, ok := pubKey.(*Certificate) + if !ok { + if c.UserKeyFallback != nil { + return c.UserKeyFallback(conn, pubKey) + } + return nil, errors.New("ssh: normal key pairs not accepted") + } + + if cert.CertType != UserCert { + return nil, fmt.Errorf("ssh: cert has type %d", cert.CertType) + } + if !c.IsUserAuthority(cert.SignatureKey) { + return nil, fmt.Errorf("ssh: certificate signed by unrecognized authority") + } + + if err := c.CheckCert(conn.User(), cert); err != nil { + return nil, err + } + + return &cert.Permissions, nil +} + +// CheckCert checks CriticalOptions, ValidPrincipals, revocation, timestamp and +// the signature of the certificate. +func (c *CertChecker) CheckCert(principal string, cert *Certificate) error { + if c.IsRevoked != nil && c.IsRevoked(cert) { + return fmt.Errorf("ssh: certificate serial %d revoked", cert.Serial) + } + + for opt := range cert.CriticalOptions { + // sourceAddressCriticalOption will be enforced by + // serverAuthenticate + if opt == sourceAddressCriticalOption { + continue + } + + found := false + for _, supp := range c.SupportedCriticalOptions { + if supp == opt { + found = true + break + } + } + if !found { + return fmt.Errorf("ssh: unsupported critical option %q in certificate", opt) + } + } + + if len(cert.ValidPrincipals) > 0 { + // By default, certs are valid for all users/hosts. + found := false + for _, p := range cert.ValidPrincipals { + if p == principal { + found = true + break + } + } + if !found { + return fmt.Errorf("ssh: principal %q not in the set of valid principals for given certificate: %q", principal, cert.ValidPrincipals) + } + } + + clock := c.Clock + if clock == nil { + clock = time.Now + } + + unixNow := clock().Unix() + if after := int64(cert.ValidAfter); after < 0 || unixNow < int64(cert.ValidAfter) { + return fmt.Errorf("ssh: cert is not yet valid") + } + if before := int64(cert.ValidBefore); cert.ValidBefore != uint64(CertTimeInfinity) && (unixNow >= before || before < 0) { + return fmt.Errorf("ssh: cert has expired") + } + if err := cert.SignatureKey.Verify(cert.bytesForSigning(), cert.Signature); err != nil { + return fmt.Errorf("ssh: certificate signature does not verify") + } + + return nil +} + +// SignCert sets c.SignatureKey to the authority's public key and stores a +// Signature, by authority, in the certificate. +func (c *Certificate) SignCert(rand io.Reader, authority Signer) error { + c.Nonce = make([]byte, 32) + if _, err := io.ReadFull(rand, c.Nonce); err != nil { + return err + } + c.SignatureKey = authority.PublicKey() + + sig, err := authority.Sign(rand, c.bytesForSigning()) + if err != nil { + return err + } + c.Signature = sig + return nil +} + +var certAlgoNames = map[string]string{ + KeyAlgoRSA: CertAlgoRSAv01, + KeyAlgoDSA: CertAlgoDSAv01, + KeyAlgoECDSA256: CertAlgoECDSA256v01, + KeyAlgoECDSA384: CertAlgoECDSA384v01, + KeyAlgoECDSA521: CertAlgoECDSA521v01, + KeyAlgoSKECDSA256: CertAlgoSKECDSA256v01, + KeyAlgoED25519: CertAlgoED25519v01, + KeyAlgoSKED25519: CertAlgoSKED25519v01, +} + +// certToPrivAlgo returns the underlying algorithm for a certificate algorithm. +// Panics if a non-certificate algorithm is passed. +func certToPrivAlgo(algo string) string { + for privAlgo, pubAlgo := range certAlgoNames { + if pubAlgo == algo { + return privAlgo + } + } + panic("unknown cert algorithm") +} + +func (cert *Certificate) bytesForSigning() []byte { + c2 := *cert + c2.Signature = nil + out := c2.Marshal() + // Drop trailing signature length. + return out[:len(out)-4] +} + +// Marshal serializes c into OpenSSH's wire format. It is part of the +// PublicKey interface. +func (c *Certificate) Marshal() []byte { + generic := genericCertData{ + Serial: c.Serial, + CertType: c.CertType, + KeyId: c.KeyId, + ValidPrincipals: marshalStringList(c.ValidPrincipals), + ValidAfter: uint64(c.ValidAfter), + ValidBefore: uint64(c.ValidBefore), + CriticalOptions: marshalTuples(c.CriticalOptions), + Extensions: marshalTuples(c.Extensions), + Reserved: c.Reserved, + SignatureKey: c.SignatureKey.Marshal(), + } + if c.Signature != nil { + generic.Signature = Marshal(c.Signature) + } + genericBytes := Marshal(&generic) + keyBytes := c.Key.Marshal() + _, keyBytes, _ = parseString(keyBytes) + prefix := Marshal(&struct { + Name string + Nonce []byte + Key []byte `ssh:"rest"` + }{c.Type(), c.Nonce, keyBytes}) + + result := make([]byte, 0, len(prefix)+len(genericBytes)) + result = append(result, prefix...) + result = append(result, genericBytes...) + return result +} + +// Type returns the key name. It is part of the PublicKey interface. +func (c *Certificate) Type() string { + algo, ok := certAlgoNames[c.Key.Type()] + if !ok { + panic("unknown cert key type " + c.Key.Type()) + } + return algo +} + +// Verify verifies a signature against the certificate's public +// key. It is part of the PublicKey interface. +func (c *Certificate) Verify(data []byte, sig *Signature) error { + return c.Key.Verify(data, sig) +} + +func parseSignatureBody(in []byte) (out *Signature, rest []byte, ok bool) { + format, in, ok := parseString(in) + if !ok { + return + } + + out = &Signature{ + Format: string(format), + } + + if out.Blob, in, ok = parseString(in); !ok { + return + } + + switch out.Format { + case KeyAlgoSKECDSA256, CertAlgoSKECDSA256v01, KeyAlgoSKED25519, CertAlgoSKED25519v01: + out.Rest = in + return out, nil, ok + } + + return out, in, ok +} + +func parseSignature(in []byte) (out *Signature, rest []byte, ok bool) { + sigBytes, rest, ok := parseString(in) + if !ok { + return + } + + out, trailing, ok := parseSignatureBody(sigBytes) + if !ok || len(trailing) > 0 { + return nil, nil, false + } + return +} diff --git a/vendor/golang.org/x/crypto/ssh/channel.go b/vendor/golang.org/x/crypto/ssh/channel.go new file mode 100644 index 00000000000..c0834c00dfe --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/channel.go @@ -0,0 +1,633 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "encoding/binary" + "errors" + "fmt" + "io" + "log" + "sync" +) + +const ( + minPacketLength = 9 + // channelMaxPacket contains the maximum number of bytes that will be + // sent in a single packet. As per RFC 4253, section 6.1, 32k is also + // the minimum. + channelMaxPacket = 1 << 15 + // We follow OpenSSH here. + channelWindowSize = 64 * channelMaxPacket +) + +// NewChannel represents an incoming request to a channel. It must either be +// accepted for use by calling Accept, or rejected by calling Reject. +type NewChannel interface { + // Accept accepts the channel creation request. It returns the Channel + // and a Go channel containing SSH requests. The Go channel must be + // serviced otherwise the Channel will hang. + Accept() (Channel, <-chan *Request, error) + + // Reject rejects the channel creation request. After calling + // this, no other methods on the Channel may be called. + Reject(reason RejectionReason, message string) error + + // ChannelType returns the type of the channel, as supplied by the + // client. + ChannelType() string + + // ExtraData returns the arbitrary payload for this channel, as supplied + // by the client. This data is specific to the channel type. + ExtraData() []byte +} + +// A Channel is an ordered, reliable, flow-controlled, duplex stream +// that is multiplexed over an SSH connection. +type Channel interface { + // Read reads up to len(data) bytes from the channel. + Read(data []byte) (int, error) + + // Write writes len(data) bytes to the channel. + Write(data []byte) (int, error) + + // Close signals end of channel use. No data may be sent after this + // call. + Close() error + + // CloseWrite signals the end of sending in-band + // data. Requests may still be sent, and the other side may + // still send data + CloseWrite() error + + // SendRequest sends a channel request. If wantReply is true, + // it will wait for a reply and return the result as a + // boolean, otherwise the return value will be false. Channel + // requests are out-of-band messages so they may be sent even + // if the data stream is closed or blocked by flow control. + // If the channel is closed before a reply is returned, io.EOF + // is returned. + SendRequest(name string, wantReply bool, payload []byte) (bool, error) + + // Stderr returns an io.ReadWriter that writes to this channel + // with the extended data type set to stderr. Stderr may + // safely be read and written from a different goroutine than + // Read and Write respectively. + Stderr() io.ReadWriter +} + +// Request is a request sent outside of the normal stream of +// data. Requests can either be specific to an SSH channel, or they +// can be global. +type Request struct { + Type string + WantReply bool + Payload []byte + + ch *channel + mux *mux +} + +// Reply sends a response to a request. It must be called for all requests +// where WantReply is true and is a no-op otherwise. The payload argument is +// ignored for replies to channel-specific requests. +func (r *Request) Reply(ok bool, payload []byte) error { + if !r.WantReply { + return nil + } + + if r.ch == nil { + return r.mux.ackRequest(ok, payload) + } + + return r.ch.ackRequest(ok) +} + +// RejectionReason is an enumeration used when rejecting channel creation +// requests. See RFC 4254, section 5.1. +type RejectionReason uint32 + +const ( + Prohibited RejectionReason = iota + 1 + ConnectionFailed + UnknownChannelType + ResourceShortage +) + +// String converts the rejection reason to human readable form. +func (r RejectionReason) String() string { + switch r { + case Prohibited: + return "administratively prohibited" + case ConnectionFailed: + return "connect failed" + case UnknownChannelType: + return "unknown channel type" + case ResourceShortage: + return "resource shortage" + } + return fmt.Sprintf("unknown reason %d", int(r)) +} + +func min(a uint32, b int) uint32 { + if a < uint32(b) { + return a + } + return uint32(b) +} + +type channelDirection uint8 + +const ( + channelInbound channelDirection = iota + channelOutbound +) + +// channel is an implementation of the Channel interface that works +// with the mux class. +type channel struct { + // R/O after creation + chanType string + extraData []byte + localId, remoteId uint32 + + // maxIncomingPayload and maxRemotePayload are the maximum + // payload sizes of normal and extended data packets for + // receiving and sending, respectively. The wire packet will + // be 9 or 13 bytes larger (excluding encryption overhead). + maxIncomingPayload uint32 + maxRemotePayload uint32 + + mux *mux + + // decided is set to true if an accept or reject message has been sent + // (for outbound channels) or received (for inbound channels). + decided bool + + // direction contains either channelOutbound, for channels created + // locally, or channelInbound, for channels created by the peer. + direction channelDirection + + // Pending internal channel messages. + msg chan interface{} + + // Since requests have no ID, there can be only one request + // with WantReply=true outstanding. This lock is held by a + // goroutine that has such an outgoing request pending. + sentRequestMu sync.Mutex + + incomingRequests chan *Request + + sentEOF bool + + // thread-safe data + remoteWin window + pending *buffer + extPending *buffer + + // windowMu protects myWindow, the flow-control window. + windowMu sync.Mutex + myWindow uint32 + + // writeMu serializes calls to mux.conn.writePacket() and + // protects sentClose and packetPool. This mutex must be + // different from windowMu, as writePacket can block if there + // is a key exchange pending. + writeMu sync.Mutex + sentClose bool + + // packetPool has a buffer for each extended channel ID to + // save allocations during writes. + packetPool map[uint32][]byte +} + +// writePacket sends a packet. If the packet is a channel close, it updates +// sentClose. This method takes the lock c.writeMu. +func (ch *channel) writePacket(packet []byte) error { + ch.writeMu.Lock() + if ch.sentClose { + ch.writeMu.Unlock() + return io.EOF + } + ch.sentClose = (packet[0] == msgChannelClose) + err := ch.mux.conn.writePacket(packet) + ch.writeMu.Unlock() + return err +} + +func (ch *channel) sendMessage(msg interface{}) error { + if debugMux { + log.Printf("send(%d): %#v", ch.mux.chanList.offset, msg) + } + + p := Marshal(msg) + binary.BigEndian.PutUint32(p[1:], ch.remoteId) + return ch.writePacket(p) +} + +// WriteExtended writes data to a specific extended stream. These streams are +// used, for example, for stderr. +func (ch *channel) WriteExtended(data []byte, extendedCode uint32) (n int, err error) { + if ch.sentEOF { + return 0, io.EOF + } + // 1 byte message type, 4 bytes remoteId, 4 bytes data length + opCode := byte(msgChannelData) + headerLength := uint32(9) + if extendedCode > 0 { + headerLength += 4 + opCode = msgChannelExtendedData + } + + ch.writeMu.Lock() + packet := ch.packetPool[extendedCode] + // We don't remove the buffer from packetPool, so + // WriteExtended calls from different goroutines will be + // flagged as errors by the race detector. + ch.writeMu.Unlock() + + for len(data) > 0 { + space := min(ch.maxRemotePayload, len(data)) + if space, err = ch.remoteWin.reserve(space); err != nil { + return n, err + } + if want := headerLength + space; uint32(cap(packet)) < want { + packet = make([]byte, want) + } else { + packet = packet[:want] + } + + todo := data[:space] + + packet[0] = opCode + binary.BigEndian.PutUint32(packet[1:], ch.remoteId) + if extendedCode > 0 { + binary.BigEndian.PutUint32(packet[5:], uint32(extendedCode)) + } + binary.BigEndian.PutUint32(packet[headerLength-4:], uint32(len(todo))) + copy(packet[headerLength:], todo) + if err = ch.writePacket(packet); err != nil { + return n, err + } + + n += len(todo) + data = data[len(todo):] + } + + ch.writeMu.Lock() + ch.packetPool[extendedCode] = packet + ch.writeMu.Unlock() + + return n, err +} + +func (ch *channel) handleData(packet []byte) error { + headerLen := 9 + isExtendedData := packet[0] == msgChannelExtendedData + if isExtendedData { + headerLen = 13 + } + if len(packet) < headerLen { + // malformed data packet + return parseError(packet[0]) + } + + var extended uint32 + if isExtendedData { + extended = binary.BigEndian.Uint32(packet[5:]) + } + + length := binary.BigEndian.Uint32(packet[headerLen-4 : headerLen]) + if length == 0 { + return nil + } + if length > ch.maxIncomingPayload { + // TODO(hanwen): should send Disconnect? + return errors.New("ssh: incoming packet exceeds maximum payload size") + } + + data := packet[headerLen:] + if length != uint32(len(data)) { + return errors.New("ssh: wrong packet length") + } + + ch.windowMu.Lock() + if ch.myWindow < length { + ch.windowMu.Unlock() + // TODO(hanwen): should send Disconnect with reason? + return errors.New("ssh: remote side wrote too much") + } + ch.myWindow -= length + ch.windowMu.Unlock() + + if extended == 1 { + ch.extPending.write(data) + } else if extended > 0 { + // discard other extended data. + } else { + ch.pending.write(data) + } + return nil +} + +func (c *channel) adjustWindow(n uint32) error { + c.windowMu.Lock() + // Since myWindow is managed on our side, and can never exceed + // the initial window setting, we don't worry about overflow. + c.myWindow += uint32(n) + c.windowMu.Unlock() + return c.sendMessage(windowAdjustMsg{ + AdditionalBytes: uint32(n), + }) +} + +func (c *channel) ReadExtended(data []byte, extended uint32) (n int, err error) { + switch extended { + case 1: + n, err = c.extPending.Read(data) + case 0: + n, err = c.pending.Read(data) + default: + return 0, fmt.Errorf("ssh: extended code %d unimplemented", extended) + } + + if n > 0 { + err = c.adjustWindow(uint32(n)) + // sendWindowAdjust can return io.EOF if the remote + // peer has closed the connection, however we want to + // defer forwarding io.EOF to the caller of Read until + // the buffer has been drained. + if n > 0 && err == io.EOF { + err = nil + } + } + + return n, err +} + +func (c *channel) close() { + c.pending.eof() + c.extPending.eof() + close(c.msg) + close(c.incomingRequests) + c.writeMu.Lock() + // This is not necessary for a normal channel teardown, but if + // there was another error, it is. + c.sentClose = true + c.writeMu.Unlock() + // Unblock writers. + c.remoteWin.close() +} + +// responseMessageReceived is called when a success or failure message is +// received on a channel to check that such a message is reasonable for the +// given channel. +func (ch *channel) responseMessageReceived() error { + if ch.direction == channelInbound { + return errors.New("ssh: channel response message received on inbound channel") + } + if ch.decided { + return errors.New("ssh: duplicate response received for channel") + } + ch.decided = true + return nil +} + +func (ch *channel) handlePacket(packet []byte) error { + switch packet[0] { + case msgChannelData, msgChannelExtendedData: + return ch.handleData(packet) + case msgChannelClose: + ch.sendMessage(channelCloseMsg{PeersID: ch.remoteId}) + ch.mux.chanList.remove(ch.localId) + ch.close() + return nil + case msgChannelEOF: + // RFC 4254 is mute on how EOF affects dataExt messages but + // it is logical to signal EOF at the same time. + ch.extPending.eof() + ch.pending.eof() + return nil + } + + decoded, err := decode(packet) + if err != nil { + return err + } + + switch msg := decoded.(type) { + case *channelOpenFailureMsg: + if err := ch.responseMessageReceived(); err != nil { + return err + } + ch.mux.chanList.remove(msg.PeersID) + ch.msg <- msg + case *channelOpenConfirmMsg: + if err := ch.responseMessageReceived(); err != nil { + return err + } + if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > 1<<31 { + return fmt.Errorf("ssh: invalid MaxPacketSize %d from peer", msg.MaxPacketSize) + } + ch.remoteId = msg.MyID + ch.maxRemotePayload = msg.MaxPacketSize + ch.remoteWin.add(msg.MyWindow) + ch.msg <- msg + case *windowAdjustMsg: + if !ch.remoteWin.add(msg.AdditionalBytes) { + return fmt.Errorf("ssh: invalid window update for %d bytes", msg.AdditionalBytes) + } + case *channelRequestMsg: + req := Request{ + Type: msg.Request, + WantReply: msg.WantReply, + Payload: msg.RequestSpecificData, + ch: ch, + } + + ch.incomingRequests <- &req + default: + ch.msg <- msg + } + return nil +} + +func (m *mux) newChannel(chanType string, direction channelDirection, extraData []byte) *channel { + ch := &channel{ + remoteWin: window{Cond: newCond()}, + myWindow: channelWindowSize, + pending: newBuffer(), + extPending: newBuffer(), + direction: direction, + incomingRequests: make(chan *Request, chanSize), + msg: make(chan interface{}, chanSize), + chanType: chanType, + extraData: extraData, + mux: m, + packetPool: make(map[uint32][]byte), + } + ch.localId = m.chanList.add(ch) + return ch +} + +var errUndecided = errors.New("ssh: must Accept or Reject channel") +var errDecidedAlready = errors.New("ssh: can call Accept or Reject only once") + +type extChannel struct { + code uint32 + ch *channel +} + +func (e *extChannel) Write(data []byte) (n int, err error) { + return e.ch.WriteExtended(data, e.code) +} + +func (e *extChannel) Read(data []byte) (n int, err error) { + return e.ch.ReadExtended(data, e.code) +} + +func (ch *channel) Accept() (Channel, <-chan *Request, error) { + if ch.decided { + return nil, nil, errDecidedAlready + } + ch.maxIncomingPayload = channelMaxPacket + confirm := channelOpenConfirmMsg{ + PeersID: ch.remoteId, + MyID: ch.localId, + MyWindow: ch.myWindow, + MaxPacketSize: ch.maxIncomingPayload, + } + ch.decided = true + if err := ch.sendMessage(confirm); err != nil { + return nil, nil, err + } + + return ch, ch.incomingRequests, nil +} + +func (ch *channel) Reject(reason RejectionReason, message string) error { + if ch.decided { + return errDecidedAlready + } + reject := channelOpenFailureMsg{ + PeersID: ch.remoteId, + Reason: reason, + Message: message, + Language: "en", + } + ch.decided = true + return ch.sendMessage(reject) +} + +func (ch *channel) Read(data []byte) (int, error) { + if !ch.decided { + return 0, errUndecided + } + return ch.ReadExtended(data, 0) +} + +func (ch *channel) Write(data []byte) (int, error) { + if !ch.decided { + return 0, errUndecided + } + return ch.WriteExtended(data, 0) +} + +func (ch *channel) CloseWrite() error { + if !ch.decided { + return errUndecided + } + ch.sentEOF = true + return ch.sendMessage(channelEOFMsg{ + PeersID: ch.remoteId}) +} + +func (ch *channel) Close() error { + if !ch.decided { + return errUndecided + } + + return ch.sendMessage(channelCloseMsg{ + PeersID: ch.remoteId}) +} + +// Extended returns an io.ReadWriter that sends and receives data on the given, +// SSH extended stream. Such streams are used, for example, for stderr. +func (ch *channel) Extended(code uint32) io.ReadWriter { + if !ch.decided { + return nil + } + return &extChannel{code, ch} +} + +func (ch *channel) Stderr() io.ReadWriter { + return ch.Extended(1) +} + +func (ch *channel) SendRequest(name string, wantReply bool, payload []byte) (bool, error) { + if !ch.decided { + return false, errUndecided + } + + if wantReply { + ch.sentRequestMu.Lock() + defer ch.sentRequestMu.Unlock() + } + + msg := channelRequestMsg{ + PeersID: ch.remoteId, + Request: name, + WantReply: wantReply, + RequestSpecificData: payload, + } + + if err := ch.sendMessage(msg); err != nil { + return false, err + } + + if wantReply { + m, ok := (<-ch.msg) + if !ok { + return false, io.EOF + } + switch m.(type) { + case *channelRequestFailureMsg: + return false, nil + case *channelRequestSuccessMsg: + return true, nil + default: + return false, fmt.Errorf("ssh: unexpected response to channel request: %#v", m) + } + } + + return false, nil +} + +// ackRequest either sends an ack or nack to the channel request. +func (ch *channel) ackRequest(ok bool) error { + if !ch.decided { + return errUndecided + } + + var msg interface{} + if !ok { + msg = channelRequestFailureMsg{ + PeersID: ch.remoteId, + } + } else { + msg = channelRequestSuccessMsg{ + PeersID: ch.remoteId, + } + } + return ch.sendMessage(msg) +} + +func (ch *channel) ChannelType() string { + return ch.chanType +} + +func (ch *channel) ExtraData() []byte { + return ch.extraData +} diff --git a/vendor/golang.org/x/crypto/ssh/cipher.go b/vendor/golang.org/x/crypto/ssh/cipher.go new file mode 100644 index 00000000000..b0204ee59f2 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/cipher.go @@ -0,0 +1,781 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/des" + "crypto/rc4" + "crypto/subtle" + "encoding/binary" + "errors" + "fmt" + "hash" + "io" + "io/ioutil" + + "golang.org/x/crypto/chacha20" + "golang.org/x/crypto/poly1305" +) + +const ( + packetSizeMultiple = 16 // TODO(huin) this should be determined by the cipher. + + // RFC 4253 section 6.1 defines a minimum packet size of 32768 that implementations + // MUST be able to process (plus a few more kilobytes for padding and mac). The RFC + // indicates implementations SHOULD be able to handle larger packet sizes, but then + // waffles on about reasonable limits. + // + // OpenSSH caps their maxPacket at 256kB so we choose to do + // the same. maxPacket is also used to ensure that uint32 + // length fields do not overflow, so it should remain well + // below 4G. + maxPacket = 256 * 1024 +) + +// noneCipher implements cipher.Stream and provides no encryption. It is used +// by the transport before the first key-exchange. +type noneCipher struct{} + +func (c noneCipher) XORKeyStream(dst, src []byte) { + copy(dst, src) +} + +func newAESCTR(key, iv []byte) (cipher.Stream, error) { + c, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + return cipher.NewCTR(c, iv), nil +} + +func newRC4(key, iv []byte) (cipher.Stream, error) { + return rc4.NewCipher(key) +} + +type cipherMode struct { + keySize int + ivSize int + create func(key, iv []byte, macKey []byte, algs directionAlgorithms) (packetCipher, error) +} + +func streamCipherMode(skip int, createFunc func(key, iv []byte) (cipher.Stream, error)) func(key, iv []byte, macKey []byte, algs directionAlgorithms) (packetCipher, error) { + return func(key, iv, macKey []byte, algs directionAlgorithms) (packetCipher, error) { + stream, err := createFunc(key, iv) + if err != nil { + return nil, err + } + + var streamDump []byte + if skip > 0 { + streamDump = make([]byte, 512) + } + + for remainingToDump := skip; remainingToDump > 0; { + dumpThisTime := remainingToDump + if dumpThisTime > len(streamDump) { + dumpThisTime = len(streamDump) + } + stream.XORKeyStream(streamDump[:dumpThisTime], streamDump[:dumpThisTime]) + remainingToDump -= dumpThisTime + } + + mac := macModes[algs.MAC].new(macKey) + return &streamPacketCipher{ + mac: mac, + etm: macModes[algs.MAC].etm, + macResult: make([]byte, mac.Size()), + cipher: stream, + }, nil + } +} + +// cipherModes documents properties of supported ciphers. Ciphers not included +// are not supported and will not be negotiated, even if explicitly requested in +// ClientConfig.Crypto.Ciphers. +var cipherModes = map[string]*cipherMode{ + // Ciphers from RFC4344, which introduced many CTR-based ciphers. Algorithms + // are defined in the order specified in the RFC. + "aes128-ctr": {16, aes.BlockSize, streamCipherMode(0, newAESCTR)}, + "aes192-ctr": {24, aes.BlockSize, streamCipherMode(0, newAESCTR)}, + "aes256-ctr": {32, aes.BlockSize, streamCipherMode(0, newAESCTR)}, + + // Ciphers from RFC4345, which introduces security-improved arcfour ciphers. + // They are defined in the order specified in the RFC. + "arcfour128": {16, 0, streamCipherMode(1536, newRC4)}, + "arcfour256": {32, 0, streamCipherMode(1536, newRC4)}, + + // Cipher defined in RFC 4253, which describes SSH Transport Layer Protocol. + // Note that this cipher is not safe, as stated in RFC 4253: "Arcfour (and + // RC4) has problems with weak keys, and should be used with caution." + // RFC4345 introduces improved versions of Arcfour. + "arcfour": {16, 0, streamCipherMode(0, newRC4)}, + + // AEAD ciphers + gcmCipherID: {16, 12, newGCMCipher}, + chacha20Poly1305ID: {64, 0, newChaCha20Cipher}, + + // CBC mode is insecure and so is not included in the default config. + // (See http://www.isg.rhul.ac.uk/~kp/SandPfinal.pdf). If absolutely + // needed, it's possible to specify a custom Config to enable it. + // You should expect that an active attacker can recover plaintext if + // you do. + aes128cbcID: {16, aes.BlockSize, newAESCBCCipher}, + + // 3des-cbc is insecure and is not included in the default + // config. + tripledescbcID: {24, des.BlockSize, newTripleDESCBCCipher}, +} + +// prefixLen is the length of the packet prefix that contains the packet length +// and number of padding bytes. +const prefixLen = 5 + +// streamPacketCipher is a packetCipher using a stream cipher. +type streamPacketCipher struct { + mac hash.Hash + cipher cipher.Stream + etm bool + + // The following members are to avoid per-packet allocations. + prefix [prefixLen]byte + seqNumBytes [4]byte + padding [2 * packetSizeMultiple]byte + packetData []byte + macResult []byte +} + +// readCipherPacket reads and decrypt a single packet from the reader argument. +func (s *streamPacketCipher) readCipherPacket(seqNum uint32, r io.Reader) ([]byte, error) { + if _, err := io.ReadFull(r, s.prefix[:]); err != nil { + return nil, err + } + + var encryptedPaddingLength [1]byte + if s.mac != nil && s.etm { + copy(encryptedPaddingLength[:], s.prefix[4:5]) + s.cipher.XORKeyStream(s.prefix[4:5], s.prefix[4:5]) + } else { + s.cipher.XORKeyStream(s.prefix[:], s.prefix[:]) + } + + length := binary.BigEndian.Uint32(s.prefix[0:4]) + paddingLength := uint32(s.prefix[4]) + + var macSize uint32 + if s.mac != nil { + s.mac.Reset() + binary.BigEndian.PutUint32(s.seqNumBytes[:], seqNum) + s.mac.Write(s.seqNumBytes[:]) + if s.etm { + s.mac.Write(s.prefix[:4]) + s.mac.Write(encryptedPaddingLength[:]) + } else { + s.mac.Write(s.prefix[:]) + } + macSize = uint32(s.mac.Size()) + } + + if length <= paddingLength+1 { + return nil, errors.New("ssh: invalid packet length, packet too small") + } + + if length > maxPacket { + return nil, errors.New("ssh: invalid packet length, packet too large") + } + + // the maxPacket check above ensures that length-1+macSize + // does not overflow. + if uint32(cap(s.packetData)) < length-1+macSize { + s.packetData = make([]byte, length-1+macSize) + } else { + s.packetData = s.packetData[:length-1+macSize] + } + + if _, err := io.ReadFull(r, s.packetData); err != nil { + return nil, err + } + mac := s.packetData[length-1:] + data := s.packetData[:length-1] + + if s.mac != nil && s.etm { + s.mac.Write(data) + } + + s.cipher.XORKeyStream(data, data) + + if s.mac != nil { + if !s.etm { + s.mac.Write(data) + } + s.macResult = s.mac.Sum(s.macResult[:0]) + if subtle.ConstantTimeCompare(s.macResult, mac) != 1 { + return nil, errors.New("ssh: MAC failure") + } + } + + return s.packetData[:length-paddingLength-1], nil +} + +// writeCipherPacket encrypts and sends a packet of data to the writer argument +func (s *streamPacketCipher) writeCipherPacket(seqNum uint32, w io.Writer, rand io.Reader, packet []byte) error { + if len(packet) > maxPacket { + return errors.New("ssh: packet too large") + } + + aadlen := 0 + if s.mac != nil && s.etm { + // packet length is not encrypted for EtM modes + aadlen = 4 + } + + paddingLength := packetSizeMultiple - (prefixLen+len(packet)-aadlen)%packetSizeMultiple + if paddingLength < 4 { + paddingLength += packetSizeMultiple + } + + length := len(packet) + 1 + paddingLength + binary.BigEndian.PutUint32(s.prefix[:], uint32(length)) + s.prefix[4] = byte(paddingLength) + padding := s.padding[:paddingLength] + if _, err := io.ReadFull(rand, padding); err != nil { + return err + } + + if s.mac != nil { + s.mac.Reset() + binary.BigEndian.PutUint32(s.seqNumBytes[:], seqNum) + s.mac.Write(s.seqNumBytes[:]) + + if s.etm { + // For EtM algorithms, the packet length must stay unencrypted, + // but the following data (padding length) must be encrypted + s.cipher.XORKeyStream(s.prefix[4:5], s.prefix[4:5]) + } + + s.mac.Write(s.prefix[:]) + + if !s.etm { + // For non-EtM algorithms, the algorithm is applied on unencrypted data + s.mac.Write(packet) + s.mac.Write(padding) + } + } + + if !(s.mac != nil && s.etm) { + // For EtM algorithms, the padding length has already been encrypted + // and the packet length must remain unencrypted + s.cipher.XORKeyStream(s.prefix[:], s.prefix[:]) + } + + s.cipher.XORKeyStream(packet, packet) + s.cipher.XORKeyStream(padding, padding) + + if s.mac != nil && s.etm { + // For EtM algorithms, packet and padding must be encrypted + s.mac.Write(packet) + s.mac.Write(padding) + } + + if _, err := w.Write(s.prefix[:]); err != nil { + return err + } + if _, err := w.Write(packet); err != nil { + return err + } + if _, err := w.Write(padding); err != nil { + return err + } + + if s.mac != nil { + s.macResult = s.mac.Sum(s.macResult[:0]) + if _, err := w.Write(s.macResult); err != nil { + return err + } + } + + return nil +} + +type gcmCipher struct { + aead cipher.AEAD + prefix [4]byte + iv []byte + buf []byte +} + +func newGCMCipher(key, iv, unusedMacKey []byte, unusedAlgs directionAlgorithms) (packetCipher, error) { + c, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + aead, err := cipher.NewGCM(c) + if err != nil { + return nil, err + } + + return &gcmCipher{ + aead: aead, + iv: iv, + }, nil +} + +const gcmTagSize = 16 + +func (c *gcmCipher) writeCipherPacket(seqNum uint32, w io.Writer, rand io.Reader, packet []byte) error { + // Pad out to multiple of 16 bytes. This is different from the + // stream cipher because that encrypts the length too. + padding := byte(packetSizeMultiple - (1+len(packet))%packetSizeMultiple) + if padding < 4 { + padding += packetSizeMultiple + } + + length := uint32(len(packet) + int(padding) + 1) + binary.BigEndian.PutUint32(c.prefix[:], length) + if _, err := w.Write(c.prefix[:]); err != nil { + return err + } + + if cap(c.buf) < int(length) { + c.buf = make([]byte, length) + } else { + c.buf = c.buf[:length] + } + + c.buf[0] = padding + copy(c.buf[1:], packet) + if _, err := io.ReadFull(rand, c.buf[1+len(packet):]); err != nil { + return err + } + c.buf = c.aead.Seal(c.buf[:0], c.iv, c.buf, c.prefix[:]) + if _, err := w.Write(c.buf); err != nil { + return err + } + c.incIV() + + return nil +} + +func (c *gcmCipher) incIV() { + for i := 4 + 7; i >= 4; i-- { + c.iv[i]++ + if c.iv[i] != 0 { + break + } + } +} + +func (c *gcmCipher) readCipherPacket(seqNum uint32, r io.Reader) ([]byte, error) { + if _, err := io.ReadFull(r, c.prefix[:]); err != nil { + return nil, err + } + length := binary.BigEndian.Uint32(c.prefix[:]) + if length > maxPacket { + return nil, errors.New("ssh: max packet length exceeded") + } + + if cap(c.buf) < int(length+gcmTagSize) { + c.buf = make([]byte, length+gcmTagSize) + } else { + c.buf = c.buf[:length+gcmTagSize] + } + + if _, err := io.ReadFull(r, c.buf); err != nil { + return nil, err + } + + plain, err := c.aead.Open(c.buf[:0], c.iv, c.buf, c.prefix[:]) + if err != nil { + return nil, err + } + c.incIV() + + padding := plain[0] + if padding < 4 { + // padding is a byte, so it automatically satisfies + // the maximum size, which is 255. + return nil, fmt.Errorf("ssh: illegal padding %d", padding) + } + + if int(padding+1) >= len(plain) { + return nil, fmt.Errorf("ssh: padding %d too large", padding) + } + plain = plain[1 : length-uint32(padding)] + return plain, nil +} + +// cbcCipher implements aes128-cbc cipher defined in RFC 4253 section 6.1 +type cbcCipher struct { + mac hash.Hash + macSize uint32 + decrypter cipher.BlockMode + encrypter cipher.BlockMode + + // The following members are to avoid per-packet allocations. + seqNumBytes [4]byte + packetData []byte + macResult []byte + + // Amount of data we should still read to hide which + // verification error triggered. + oracleCamouflage uint32 +} + +func newCBCCipher(c cipher.Block, key, iv, macKey []byte, algs directionAlgorithms) (packetCipher, error) { + cbc := &cbcCipher{ + mac: macModes[algs.MAC].new(macKey), + decrypter: cipher.NewCBCDecrypter(c, iv), + encrypter: cipher.NewCBCEncrypter(c, iv), + packetData: make([]byte, 1024), + } + if cbc.mac != nil { + cbc.macSize = uint32(cbc.mac.Size()) + } + + return cbc, nil +} + +func newAESCBCCipher(key, iv, macKey []byte, algs directionAlgorithms) (packetCipher, error) { + c, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + cbc, err := newCBCCipher(c, key, iv, macKey, algs) + if err != nil { + return nil, err + } + + return cbc, nil +} + +func newTripleDESCBCCipher(key, iv, macKey []byte, algs directionAlgorithms) (packetCipher, error) { + c, err := des.NewTripleDESCipher(key) + if err != nil { + return nil, err + } + + cbc, err := newCBCCipher(c, key, iv, macKey, algs) + if err != nil { + return nil, err + } + + return cbc, nil +} + +func maxUInt32(a, b int) uint32 { + if a > b { + return uint32(a) + } + return uint32(b) +} + +const ( + cbcMinPacketSizeMultiple = 8 + cbcMinPacketSize = 16 + cbcMinPaddingSize = 4 +) + +// cbcError represents a verification error that may leak information. +type cbcError string + +func (e cbcError) Error() string { return string(e) } + +func (c *cbcCipher) readCipherPacket(seqNum uint32, r io.Reader) ([]byte, error) { + p, err := c.readCipherPacketLeaky(seqNum, r) + if err != nil { + if _, ok := err.(cbcError); ok { + // Verification error: read a fixed amount of + // data, to make distinguishing between + // failing MAC and failing length check more + // difficult. + io.CopyN(ioutil.Discard, r, int64(c.oracleCamouflage)) + } + } + return p, err +} + +func (c *cbcCipher) readCipherPacketLeaky(seqNum uint32, r io.Reader) ([]byte, error) { + blockSize := c.decrypter.BlockSize() + + // Read the header, which will include some of the subsequent data in the + // case of block ciphers - this is copied back to the payload later. + // How many bytes of payload/padding will be read with this first read. + firstBlockLength := uint32((prefixLen + blockSize - 1) / blockSize * blockSize) + firstBlock := c.packetData[:firstBlockLength] + if _, err := io.ReadFull(r, firstBlock); err != nil { + return nil, err + } + + c.oracleCamouflage = maxPacket + 4 + c.macSize - firstBlockLength + + c.decrypter.CryptBlocks(firstBlock, firstBlock) + length := binary.BigEndian.Uint32(firstBlock[:4]) + if length > maxPacket { + return nil, cbcError("ssh: packet too large") + } + if length+4 < maxUInt32(cbcMinPacketSize, blockSize) { + // The minimum size of a packet is 16 (or the cipher block size, whichever + // is larger) bytes. + return nil, cbcError("ssh: packet too small") + } + // The length of the packet (including the length field but not the MAC) must + // be a multiple of the block size or 8, whichever is larger. + if (length+4)%maxUInt32(cbcMinPacketSizeMultiple, blockSize) != 0 { + return nil, cbcError("ssh: invalid packet length multiple") + } + + paddingLength := uint32(firstBlock[4]) + if paddingLength < cbcMinPaddingSize || length <= paddingLength+1 { + return nil, cbcError("ssh: invalid packet length") + } + + // Positions within the c.packetData buffer: + macStart := 4 + length + paddingStart := macStart - paddingLength + + // Entire packet size, starting before length, ending at end of mac. + entirePacketSize := macStart + c.macSize + + // Ensure c.packetData is large enough for the entire packet data. + if uint32(cap(c.packetData)) < entirePacketSize { + // Still need to upsize and copy, but this should be rare at runtime, only + // on upsizing the packetData buffer. + c.packetData = make([]byte, entirePacketSize) + copy(c.packetData, firstBlock) + } else { + c.packetData = c.packetData[:entirePacketSize] + } + + n, err := io.ReadFull(r, c.packetData[firstBlockLength:]) + if err != nil { + return nil, err + } + c.oracleCamouflage -= uint32(n) + + remainingCrypted := c.packetData[firstBlockLength:macStart] + c.decrypter.CryptBlocks(remainingCrypted, remainingCrypted) + + mac := c.packetData[macStart:] + if c.mac != nil { + c.mac.Reset() + binary.BigEndian.PutUint32(c.seqNumBytes[:], seqNum) + c.mac.Write(c.seqNumBytes[:]) + c.mac.Write(c.packetData[:macStart]) + c.macResult = c.mac.Sum(c.macResult[:0]) + if subtle.ConstantTimeCompare(c.macResult, mac) != 1 { + return nil, cbcError("ssh: MAC failure") + } + } + + return c.packetData[prefixLen:paddingStart], nil +} + +func (c *cbcCipher) writeCipherPacket(seqNum uint32, w io.Writer, rand io.Reader, packet []byte) error { + effectiveBlockSize := maxUInt32(cbcMinPacketSizeMultiple, c.encrypter.BlockSize()) + + // Length of encrypted portion of the packet (header, payload, padding). + // Enforce minimum padding and packet size. + encLength := maxUInt32(prefixLen+len(packet)+cbcMinPaddingSize, cbcMinPaddingSize) + // Enforce block size. + encLength = (encLength + effectiveBlockSize - 1) / effectiveBlockSize * effectiveBlockSize + + length := encLength - 4 + paddingLength := int(length) - (1 + len(packet)) + + // Overall buffer contains: header, payload, padding, mac. + // Space for the MAC is reserved in the capacity but not the slice length. + bufferSize := encLength + c.macSize + if uint32(cap(c.packetData)) < bufferSize { + c.packetData = make([]byte, encLength, bufferSize) + } else { + c.packetData = c.packetData[:encLength] + } + + p := c.packetData + + // Packet header. + binary.BigEndian.PutUint32(p, length) + p = p[4:] + p[0] = byte(paddingLength) + + // Payload. + p = p[1:] + copy(p, packet) + + // Padding. + p = p[len(packet):] + if _, err := io.ReadFull(rand, p); err != nil { + return err + } + + if c.mac != nil { + c.mac.Reset() + binary.BigEndian.PutUint32(c.seqNumBytes[:], seqNum) + c.mac.Write(c.seqNumBytes[:]) + c.mac.Write(c.packetData) + // The MAC is now appended into the capacity reserved for it earlier. + c.packetData = c.mac.Sum(c.packetData) + } + + c.encrypter.CryptBlocks(c.packetData[:encLength], c.packetData[:encLength]) + + if _, err := w.Write(c.packetData); err != nil { + return err + } + + return nil +} + +const chacha20Poly1305ID = "chacha20-poly1305@openssh.com" + +// chacha20Poly1305Cipher implements the chacha20-poly1305@openssh.com +// AEAD, which is described here: +// +// https://tools.ietf.org/html/draft-josefsson-ssh-chacha20-poly1305-openssh-00 +// +// the methods here also implement padding, which RFC4253 Section 6 +// also requires of stream ciphers. +type chacha20Poly1305Cipher struct { + lengthKey [32]byte + contentKey [32]byte + buf []byte +} + +func newChaCha20Cipher(key, unusedIV, unusedMACKey []byte, unusedAlgs directionAlgorithms) (packetCipher, error) { + if len(key) != 64 { + panic(len(key)) + } + + c := &chacha20Poly1305Cipher{ + buf: make([]byte, 256), + } + + copy(c.contentKey[:], key[:32]) + copy(c.lengthKey[:], key[32:]) + return c, nil +} + +func (c *chacha20Poly1305Cipher) readCipherPacket(seqNum uint32, r io.Reader) ([]byte, error) { + nonce := make([]byte, 12) + binary.BigEndian.PutUint32(nonce[8:], seqNum) + s, err := chacha20.NewUnauthenticatedCipher(c.contentKey[:], nonce) + if err != nil { + return nil, err + } + var polyKey, discardBuf [32]byte + s.XORKeyStream(polyKey[:], polyKey[:]) + s.XORKeyStream(discardBuf[:], discardBuf[:]) // skip the next 32 bytes + + encryptedLength := c.buf[:4] + if _, err := io.ReadFull(r, encryptedLength); err != nil { + return nil, err + } + + var lenBytes [4]byte + ls, err := chacha20.NewUnauthenticatedCipher(c.lengthKey[:], nonce) + if err != nil { + return nil, err + } + ls.XORKeyStream(lenBytes[:], encryptedLength) + + length := binary.BigEndian.Uint32(lenBytes[:]) + if length > maxPacket { + return nil, errors.New("ssh: invalid packet length, packet too large") + } + + contentEnd := 4 + length + packetEnd := contentEnd + poly1305.TagSize + if uint32(cap(c.buf)) < packetEnd { + c.buf = make([]byte, packetEnd) + copy(c.buf[:], encryptedLength) + } else { + c.buf = c.buf[:packetEnd] + } + + if _, err := io.ReadFull(r, c.buf[4:packetEnd]); err != nil { + return nil, err + } + + var mac [poly1305.TagSize]byte + copy(mac[:], c.buf[contentEnd:packetEnd]) + if !poly1305.Verify(&mac, c.buf[:contentEnd], &polyKey) { + return nil, errors.New("ssh: MAC failure") + } + + plain := c.buf[4:contentEnd] + s.XORKeyStream(plain, plain) + + padding := plain[0] + if padding < 4 { + // padding is a byte, so it automatically satisfies + // the maximum size, which is 255. + return nil, fmt.Errorf("ssh: illegal padding %d", padding) + } + + if int(padding)+1 >= len(plain) { + return nil, fmt.Errorf("ssh: padding %d too large", padding) + } + + plain = plain[1 : len(plain)-int(padding)] + + return plain, nil +} + +func (c *chacha20Poly1305Cipher) writeCipherPacket(seqNum uint32, w io.Writer, rand io.Reader, payload []byte) error { + nonce := make([]byte, 12) + binary.BigEndian.PutUint32(nonce[8:], seqNum) + s, err := chacha20.NewUnauthenticatedCipher(c.contentKey[:], nonce) + if err != nil { + return err + } + var polyKey, discardBuf [32]byte + s.XORKeyStream(polyKey[:], polyKey[:]) + s.XORKeyStream(discardBuf[:], discardBuf[:]) // skip the next 32 bytes + + // There is no blocksize, so fall back to multiple of 8 byte + // padding, as described in RFC 4253, Sec 6. + const packetSizeMultiple = 8 + + padding := packetSizeMultiple - (1+len(payload))%packetSizeMultiple + if padding < 4 { + padding += packetSizeMultiple + } + + // size (4 bytes), padding (1), payload, padding, tag. + totalLength := 4 + 1 + len(payload) + padding + poly1305.TagSize + if cap(c.buf) < totalLength { + c.buf = make([]byte, totalLength) + } else { + c.buf = c.buf[:totalLength] + } + + binary.BigEndian.PutUint32(c.buf, uint32(1+len(payload)+padding)) + ls, err := chacha20.NewUnauthenticatedCipher(c.lengthKey[:], nonce) + if err != nil { + return err + } + ls.XORKeyStream(c.buf, c.buf[:4]) + c.buf[4] = byte(padding) + copy(c.buf[5:], payload) + packetEnd := 5 + len(payload) + padding + if _, err := io.ReadFull(rand, c.buf[5+len(payload):packetEnd]); err != nil { + return err + } + + s.XORKeyStream(c.buf[4:], c.buf[4:packetEnd]) + + var mac [poly1305.TagSize]byte + poly1305.Sum(&mac, c.buf[:packetEnd], &polyKey) + + copy(c.buf[packetEnd:], mac[:]) + + if _, err := w.Write(c.buf); err != nil { + return err + } + return nil +} diff --git a/vendor/golang.org/x/crypto/ssh/client.go b/vendor/golang.org/x/crypto/ssh/client.go new file mode 100644 index 00000000000..7b00bff1caa --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/client.go @@ -0,0 +1,278 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bytes" + "errors" + "fmt" + "net" + "os" + "sync" + "time" +) + +// Client implements a traditional SSH client that supports shells, +// subprocesses, TCP port/streamlocal forwarding and tunneled dialing. +type Client struct { + Conn + + handleForwardsOnce sync.Once // guards calling (*Client).handleForwards + + forwards forwardList // forwarded tcpip connections from the remote side + mu sync.Mutex + channelHandlers map[string]chan NewChannel +} + +// HandleChannelOpen returns a channel on which NewChannel requests +// for the given type are sent. If the type already is being handled, +// nil is returned. The channel is closed when the connection is closed. +func (c *Client) HandleChannelOpen(channelType string) <-chan NewChannel { + c.mu.Lock() + defer c.mu.Unlock() + if c.channelHandlers == nil { + // The SSH channel has been closed. + c := make(chan NewChannel) + close(c) + return c + } + + ch := c.channelHandlers[channelType] + if ch != nil { + return nil + } + + ch = make(chan NewChannel, chanSize) + c.channelHandlers[channelType] = ch + return ch +} + +// NewClient creates a Client on top of the given connection. +func NewClient(c Conn, chans <-chan NewChannel, reqs <-chan *Request) *Client { + conn := &Client{ + Conn: c, + channelHandlers: make(map[string]chan NewChannel, 1), + } + + go conn.handleGlobalRequests(reqs) + go conn.handleChannelOpens(chans) + go func() { + conn.Wait() + conn.forwards.closeAll() + }() + return conn +} + +// NewClientConn establishes an authenticated SSH connection using c +// as the underlying transport. The Request and NewChannel channels +// must be serviced or the connection will hang. +func NewClientConn(c net.Conn, addr string, config *ClientConfig) (Conn, <-chan NewChannel, <-chan *Request, error) { + fullConf := *config + fullConf.SetDefaults() + if fullConf.HostKeyCallback == nil { + c.Close() + return nil, nil, nil, errors.New("ssh: must specify HostKeyCallback") + } + + conn := &connection{ + sshConn: sshConn{conn: c}, + } + + if err := conn.clientHandshake(addr, &fullConf); err != nil { + c.Close() + return nil, nil, nil, fmt.Errorf("ssh: handshake failed: %v", err) + } + conn.mux = newMux(conn.transport) + return conn, conn.mux.incomingChannels, conn.mux.incomingRequests, nil +} + +// clientHandshake performs the client side key exchange. See RFC 4253 Section +// 7. +func (c *connection) clientHandshake(dialAddress string, config *ClientConfig) error { + if config.ClientVersion != "" { + c.clientVersion = []byte(config.ClientVersion) + } else { + c.clientVersion = []byte(packageVersion) + } + var err error + c.serverVersion, err = exchangeVersions(c.sshConn.conn, c.clientVersion) + if err != nil { + return err + } + + c.transport = newClientTransport( + newTransport(c.sshConn.conn, config.Rand, true /* is client */), + c.clientVersion, c.serverVersion, config, dialAddress, c.sshConn.RemoteAddr()) + if err := c.transport.waitSession(); err != nil { + return err + } + + c.sessionID = c.transport.getSessionID() + return c.clientAuthenticate(config) +} + +// verifyHostKeySignature verifies the host key obtained in the key +// exchange. +func verifyHostKeySignature(hostKey PublicKey, result *kexResult) error { + sig, rest, ok := parseSignatureBody(result.Signature) + if len(rest) > 0 || !ok { + return errors.New("ssh: signature parse error") + } + + return hostKey.Verify(result.H, sig) +} + +// NewSession opens a new Session for this client. (A session is a remote +// execution of a program.) +func (c *Client) NewSession() (*Session, error) { + ch, in, err := c.OpenChannel("session", nil) + if err != nil { + return nil, err + } + return newSession(ch, in) +} + +func (c *Client) handleGlobalRequests(incoming <-chan *Request) { + for r := range incoming { + // This handles keepalive messages and matches + // the behaviour of OpenSSH. + r.Reply(false, nil) + } +} + +// handleChannelOpens channel open messages from the remote side. +func (c *Client) handleChannelOpens(in <-chan NewChannel) { + for ch := range in { + c.mu.Lock() + handler := c.channelHandlers[ch.ChannelType()] + c.mu.Unlock() + + if handler != nil { + handler <- ch + } else { + ch.Reject(UnknownChannelType, fmt.Sprintf("unknown channel type: %v", ch.ChannelType())) + } + } + + c.mu.Lock() + for _, ch := range c.channelHandlers { + close(ch) + } + c.channelHandlers = nil + c.mu.Unlock() +} + +// Dial starts a client connection to the given SSH server. It is a +// convenience function that connects to the given network address, +// initiates the SSH handshake, and then sets up a Client. For access +// to incoming channels and requests, use net.Dial with NewClientConn +// instead. +func Dial(network, addr string, config *ClientConfig) (*Client, error) { + conn, err := net.DialTimeout(network, addr, config.Timeout) + if err != nil { + return nil, err + } + c, chans, reqs, err := NewClientConn(conn, addr, config) + if err != nil { + return nil, err + } + return NewClient(c, chans, reqs), nil +} + +// HostKeyCallback is the function type used for verifying server +// keys. A HostKeyCallback must return nil if the host key is OK, or +// an error to reject it. It receives the hostname as passed to Dial +// or NewClientConn. The remote address is the RemoteAddr of the +// net.Conn underlying the SSH connection. +type HostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error + +// BannerCallback is the function type used for treat the banner sent by +// the server. A BannerCallback receives the message sent by the remote server. +type BannerCallback func(message string) error + +// A ClientConfig structure is used to configure a Client. It must not be +// modified after having been passed to an SSH function. +type ClientConfig struct { + // Config contains configuration that is shared between clients and + // servers. + Config + + // User contains the username to authenticate as. + User string + + // Auth contains possible authentication methods to use with the + // server. Only the first instance of a particular RFC 4252 method will + // be used during authentication. + Auth []AuthMethod + + // HostKeyCallback is called during the cryptographic + // handshake to validate the server's host key. The client + // configuration must supply this callback for the connection + // to succeed. The functions InsecureIgnoreHostKey or + // FixedHostKey can be used for simplistic host key checks. + HostKeyCallback HostKeyCallback + + // BannerCallback is called during the SSH dance to display a custom + // server's message. The client configuration can supply this callback to + // handle it as wished. The function BannerDisplayStderr can be used for + // simplistic display on Stderr. + BannerCallback BannerCallback + + // ClientVersion contains the version identification string that will + // be used for the connection. If empty, a reasonable default is used. + ClientVersion string + + // HostKeyAlgorithms lists the key types that the client will + // accept from the server as host key, in order of + // preference. If empty, a reasonable default is used. Any + // string returned from PublicKey.Type method may be used, or + // any of the CertAlgoXxxx and KeyAlgoXxxx constants. + HostKeyAlgorithms []string + + // Timeout is the maximum amount of time for the TCP connection to establish. + // + // A Timeout of zero means no timeout. + Timeout time.Duration +} + +// InsecureIgnoreHostKey returns a function that can be used for +// ClientConfig.HostKeyCallback to accept any host key. It should +// not be used for production code. +func InsecureIgnoreHostKey() HostKeyCallback { + return func(hostname string, remote net.Addr, key PublicKey) error { + return nil + } +} + +type fixedHostKey struct { + key PublicKey +} + +func (f *fixedHostKey) check(hostname string, remote net.Addr, key PublicKey) error { + if f.key == nil { + return fmt.Errorf("ssh: required host key was nil") + } + if !bytes.Equal(key.Marshal(), f.key.Marshal()) { + return fmt.Errorf("ssh: host key mismatch") + } + return nil +} + +// FixedHostKey returns a function for use in +// ClientConfig.HostKeyCallback to accept only a specific host key. +func FixedHostKey(key PublicKey) HostKeyCallback { + hk := &fixedHostKey{key} + return hk.check +} + +// BannerDisplayStderr returns a function that can be used for +// ClientConfig.BannerCallback to display banners on os.Stderr. +func BannerDisplayStderr() BannerCallback { + return func(banner string) error { + _, err := os.Stderr.WriteString(banner) + + return err + } +} diff --git a/vendor/golang.org/x/crypto/ssh/client_auth.go b/vendor/golang.org/x/crypto/ssh/client_auth.go new file mode 100644 index 00000000000..0590070e220 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/client_auth.go @@ -0,0 +1,639 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bytes" + "errors" + "fmt" + "io" +) + +type authResult int + +const ( + authFailure authResult = iota + authPartialSuccess + authSuccess +) + +// clientAuthenticate authenticates with the remote server. See RFC 4252. +func (c *connection) clientAuthenticate(config *ClientConfig) error { + // initiate user auth session + if err := c.transport.writePacket(Marshal(&serviceRequestMsg{serviceUserAuth})); err != nil { + return err + } + packet, err := c.transport.readPacket() + if err != nil { + return err + } + var serviceAccept serviceAcceptMsg + if err := Unmarshal(packet, &serviceAccept); err != nil { + return err + } + + // during the authentication phase the client first attempts the "none" method + // then any untried methods suggested by the server. + tried := make(map[string]bool) + var lastMethods []string + + sessionID := c.transport.getSessionID() + for auth := AuthMethod(new(noneAuth)); auth != nil; { + ok, methods, err := auth.auth(sessionID, config.User, c.transport, config.Rand) + if err != nil { + return err + } + if ok == authSuccess { + // success + return nil + } else if ok == authFailure { + tried[auth.method()] = true + } + if methods == nil { + methods = lastMethods + } + lastMethods = methods + + auth = nil + + findNext: + for _, a := range config.Auth { + candidateMethod := a.method() + if tried[candidateMethod] { + continue + } + for _, meth := range methods { + if meth == candidateMethod { + auth = a + break findNext + } + } + } + } + return fmt.Errorf("ssh: unable to authenticate, attempted methods %v, no supported methods remain", keys(tried)) +} + +func keys(m map[string]bool) []string { + s := make([]string, 0, len(m)) + + for key := range m { + s = append(s, key) + } + return s +} + +// An AuthMethod represents an instance of an RFC 4252 authentication method. +type AuthMethod interface { + // auth authenticates user over transport t. + // Returns true if authentication is successful. + // If authentication is not successful, a []string of alternative + // method names is returned. If the slice is nil, it will be ignored + // and the previous set of possible methods will be reused. + auth(session []byte, user string, p packetConn, rand io.Reader) (authResult, []string, error) + + // method returns the RFC 4252 method name. + method() string +} + +// "none" authentication, RFC 4252 section 5.2. +type noneAuth int + +func (n *noneAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) { + if err := c.writePacket(Marshal(&userAuthRequestMsg{ + User: user, + Service: serviceSSH, + Method: "none", + })); err != nil { + return authFailure, nil, err + } + + return handleAuthResponse(c) +} + +func (n *noneAuth) method() string { + return "none" +} + +// passwordCallback is an AuthMethod that fetches the password through +// a function call, e.g. by prompting the user. +type passwordCallback func() (password string, err error) + +func (cb passwordCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) { + type passwordAuthMsg struct { + User string `sshtype:"50"` + Service string + Method string + Reply bool + Password string + } + + pw, err := cb() + // REVIEW NOTE: is there a need to support skipping a password attempt? + // The program may only find out that the user doesn't have a password + // when prompting. + if err != nil { + return authFailure, nil, err + } + + if err := c.writePacket(Marshal(&passwordAuthMsg{ + User: user, + Service: serviceSSH, + Method: cb.method(), + Reply: false, + Password: pw, + })); err != nil { + return authFailure, nil, err + } + + return handleAuthResponse(c) +} + +func (cb passwordCallback) method() string { + return "password" +} + +// Password returns an AuthMethod using the given password. +func Password(secret string) AuthMethod { + return passwordCallback(func() (string, error) { return secret, nil }) +} + +// PasswordCallback returns an AuthMethod that uses a callback for +// fetching a password. +func PasswordCallback(prompt func() (secret string, err error)) AuthMethod { + return passwordCallback(prompt) +} + +type publickeyAuthMsg struct { + User string `sshtype:"50"` + Service string + Method string + // HasSig indicates to the receiver packet that the auth request is signed and + // should be used for authentication of the request. + HasSig bool + Algoname string + PubKey []byte + // Sig is tagged with "rest" so Marshal will exclude it during + // validateKey + Sig []byte `ssh:"rest"` +} + +// publicKeyCallback is an AuthMethod that uses a set of key +// pairs for authentication. +type publicKeyCallback func() ([]Signer, error) + +func (cb publicKeyCallback) method() string { + return "publickey" +} + +func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) { + // Authentication is performed by sending an enquiry to test if a key is + // acceptable to the remote. If the key is acceptable, the client will + // attempt to authenticate with the valid key. If not the client will repeat + // the process with the remaining keys. + + signers, err := cb() + if err != nil { + return authFailure, nil, err + } + var methods []string + for _, signer := range signers { + ok, err := validateKey(signer.PublicKey(), user, c) + if err != nil { + return authFailure, nil, err + } + if !ok { + continue + } + + pub := signer.PublicKey() + pubKey := pub.Marshal() + sign, err := signer.Sign(rand, buildDataSignedForAuth(session, userAuthRequestMsg{ + User: user, + Service: serviceSSH, + Method: cb.method(), + }, []byte(pub.Type()), pubKey)) + if err != nil { + return authFailure, nil, err + } + + // manually wrap the serialized signature in a string + s := Marshal(sign) + sig := make([]byte, stringLength(len(s))) + marshalString(sig, s) + msg := publickeyAuthMsg{ + User: user, + Service: serviceSSH, + Method: cb.method(), + HasSig: true, + Algoname: pub.Type(), + PubKey: pubKey, + Sig: sig, + } + p := Marshal(&msg) + if err := c.writePacket(p); err != nil { + return authFailure, nil, err + } + var success authResult + success, methods, err = handleAuthResponse(c) + if err != nil { + return authFailure, nil, err + } + + // If authentication succeeds or the list of available methods does not + // contain the "publickey" method, do not attempt to authenticate with any + // other keys. According to RFC 4252 Section 7, the latter can occur when + // additional authentication methods are required. + if success == authSuccess || !containsMethod(methods, cb.method()) { + return success, methods, err + } + } + + return authFailure, methods, nil +} + +func containsMethod(methods []string, method string) bool { + for _, m := range methods { + if m == method { + return true + } + } + + return false +} + +// validateKey validates the key provided is acceptable to the server. +func validateKey(key PublicKey, user string, c packetConn) (bool, error) { + pubKey := key.Marshal() + msg := publickeyAuthMsg{ + User: user, + Service: serviceSSH, + Method: "publickey", + HasSig: false, + Algoname: key.Type(), + PubKey: pubKey, + } + if err := c.writePacket(Marshal(&msg)); err != nil { + return false, err + } + + return confirmKeyAck(key, c) +} + +func confirmKeyAck(key PublicKey, c packetConn) (bool, error) { + pubKey := key.Marshal() + algoname := key.Type() + + for { + packet, err := c.readPacket() + if err != nil { + return false, err + } + switch packet[0] { + case msgUserAuthBanner: + if err := handleBannerResponse(c, packet); err != nil { + return false, err + } + case msgUserAuthPubKeyOk: + var msg userAuthPubKeyOkMsg + if err := Unmarshal(packet, &msg); err != nil { + return false, err + } + if msg.Algo != algoname || !bytes.Equal(msg.PubKey, pubKey) { + return false, nil + } + return true, nil + case msgUserAuthFailure: + return false, nil + default: + return false, unexpectedMessageError(msgUserAuthSuccess, packet[0]) + } + } +} + +// PublicKeys returns an AuthMethod that uses the given key +// pairs. +func PublicKeys(signers ...Signer) AuthMethod { + return publicKeyCallback(func() ([]Signer, error) { return signers, nil }) +} + +// PublicKeysCallback returns an AuthMethod that runs the given +// function to obtain a list of key pairs. +func PublicKeysCallback(getSigners func() (signers []Signer, err error)) AuthMethod { + return publicKeyCallback(getSigners) +} + +// handleAuthResponse returns whether the preceding authentication request succeeded +// along with a list of remaining authentication methods to try next and +// an error if an unexpected response was received. +func handleAuthResponse(c packetConn) (authResult, []string, error) { + for { + packet, err := c.readPacket() + if err != nil { + return authFailure, nil, err + } + + switch packet[0] { + case msgUserAuthBanner: + if err := handleBannerResponse(c, packet); err != nil { + return authFailure, nil, err + } + case msgUserAuthFailure: + var msg userAuthFailureMsg + if err := Unmarshal(packet, &msg); err != nil { + return authFailure, nil, err + } + if msg.PartialSuccess { + return authPartialSuccess, msg.Methods, nil + } + return authFailure, msg.Methods, nil + case msgUserAuthSuccess: + return authSuccess, nil, nil + default: + return authFailure, nil, unexpectedMessageError(msgUserAuthSuccess, packet[0]) + } + } +} + +func handleBannerResponse(c packetConn, packet []byte) error { + var msg userAuthBannerMsg + if err := Unmarshal(packet, &msg); err != nil { + return err + } + + transport, ok := c.(*handshakeTransport) + if !ok { + return nil + } + + if transport.bannerCallback != nil { + return transport.bannerCallback(msg.Message) + } + + return nil +} + +// KeyboardInteractiveChallenge should print questions, optionally +// disabling echoing (e.g. for passwords), and return all the answers. +// Challenge may be called multiple times in a single session. After +// successful authentication, the server may send a challenge with no +// questions, for which the user and instruction messages should be +// printed. RFC 4256 section 3.3 details how the UI should behave for +// both CLI and GUI environments. +type KeyboardInteractiveChallenge func(user, instruction string, questions []string, echos []bool) (answers []string, err error) + +// KeyboardInteractive returns an AuthMethod using a prompt/response +// sequence controlled by the server. +func KeyboardInteractive(challenge KeyboardInteractiveChallenge) AuthMethod { + return challenge +} + +func (cb KeyboardInteractiveChallenge) method() string { + return "keyboard-interactive" +} + +func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) { + type initiateMsg struct { + User string `sshtype:"50"` + Service string + Method string + Language string + Submethods string + } + + if err := c.writePacket(Marshal(&initiateMsg{ + User: user, + Service: serviceSSH, + Method: "keyboard-interactive", + })); err != nil { + return authFailure, nil, err + } + + for { + packet, err := c.readPacket() + if err != nil { + return authFailure, nil, err + } + + // like handleAuthResponse, but with less options. + switch packet[0] { + case msgUserAuthBanner: + if err := handleBannerResponse(c, packet); err != nil { + return authFailure, nil, err + } + continue + case msgUserAuthInfoRequest: + // OK + case msgUserAuthFailure: + var msg userAuthFailureMsg + if err := Unmarshal(packet, &msg); err != nil { + return authFailure, nil, err + } + if msg.PartialSuccess { + return authPartialSuccess, msg.Methods, nil + } + return authFailure, msg.Methods, nil + case msgUserAuthSuccess: + return authSuccess, nil, nil + default: + return authFailure, nil, unexpectedMessageError(msgUserAuthInfoRequest, packet[0]) + } + + var msg userAuthInfoRequestMsg + if err := Unmarshal(packet, &msg); err != nil { + return authFailure, nil, err + } + + // Manually unpack the prompt/echo pairs. + rest := msg.Prompts + var prompts []string + var echos []bool + for i := 0; i < int(msg.NumPrompts); i++ { + prompt, r, ok := parseString(rest) + if !ok || len(r) == 0 { + return authFailure, nil, errors.New("ssh: prompt format error") + } + prompts = append(prompts, string(prompt)) + echos = append(echos, r[0] != 0) + rest = r[1:] + } + + if len(rest) != 0 { + return authFailure, nil, errors.New("ssh: extra data following keyboard-interactive pairs") + } + + answers, err := cb(msg.User, msg.Instruction, prompts, echos) + if err != nil { + return authFailure, nil, err + } + + if len(answers) != len(prompts) { + return authFailure, nil, errors.New("ssh: not enough answers from keyboard-interactive callback") + } + responseLength := 1 + 4 + for _, a := range answers { + responseLength += stringLength(len(a)) + } + serialized := make([]byte, responseLength) + p := serialized + p[0] = msgUserAuthInfoResponse + p = p[1:] + p = marshalUint32(p, uint32(len(answers))) + for _, a := range answers { + p = marshalString(p, []byte(a)) + } + + if err := c.writePacket(serialized); err != nil { + return authFailure, nil, err + } + } +} + +type retryableAuthMethod struct { + authMethod AuthMethod + maxTries int +} + +func (r *retryableAuthMethod) auth(session []byte, user string, c packetConn, rand io.Reader) (ok authResult, methods []string, err error) { + for i := 0; r.maxTries <= 0 || i < r.maxTries; i++ { + ok, methods, err = r.authMethod.auth(session, user, c, rand) + if ok != authFailure || err != nil { // either success, partial success or error terminate + return ok, methods, err + } + } + return ok, methods, err +} + +func (r *retryableAuthMethod) method() string { + return r.authMethod.method() +} + +// RetryableAuthMethod is a decorator for other auth methods enabling them to +// be retried up to maxTries before considering that AuthMethod itself failed. +// If maxTries is <= 0, will retry indefinitely +// +// This is useful for interactive clients using challenge/response type +// authentication (e.g. Keyboard-Interactive, Password, etc) where the user +// could mistype their response resulting in the server issuing a +// SSH_MSG_USERAUTH_FAILURE (rfc4252 #8 [password] and rfc4256 #3.4 +// [keyboard-interactive]); Without this decorator, the non-retryable +// AuthMethod would be removed from future consideration, and never tried again +// (and so the user would never be able to retry their entry). +func RetryableAuthMethod(auth AuthMethod, maxTries int) AuthMethod { + return &retryableAuthMethod{authMethod: auth, maxTries: maxTries} +} + +// GSSAPIWithMICAuthMethod is an AuthMethod with "gssapi-with-mic" authentication. +// See RFC 4462 section 3 +// gssAPIClient is implementation of the GSSAPIClient interface, see the definition of the interface for details. +// target is the server host you want to log in to. +func GSSAPIWithMICAuthMethod(gssAPIClient GSSAPIClient, target string) AuthMethod { + if gssAPIClient == nil { + panic("gss-api client must be not nil with enable gssapi-with-mic") + } + return &gssAPIWithMICCallback{gssAPIClient: gssAPIClient, target: target} +} + +type gssAPIWithMICCallback struct { + gssAPIClient GSSAPIClient + target string +} + +func (g *gssAPIWithMICCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) { + m := &userAuthRequestMsg{ + User: user, + Service: serviceSSH, + Method: g.method(), + } + // The GSS-API authentication method is initiated when the client sends an SSH_MSG_USERAUTH_REQUEST. + // See RFC 4462 section 3.2. + m.Payload = appendU32(m.Payload, 1) + m.Payload = appendString(m.Payload, string(krb5OID)) + if err := c.writePacket(Marshal(m)); err != nil { + return authFailure, nil, err + } + // The server responds to the SSH_MSG_USERAUTH_REQUEST with either an + // SSH_MSG_USERAUTH_FAILURE if none of the mechanisms are supported or + // with an SSH_MSG_USERAUTH_GSSAPI_RESPONSE. + // See RFC 4462 section 3.3. + // OpenSSH supports Kerberos V5 mechanism only for GSS-API authentication,so I don't want to check + // selected mech if it is valid. + packet, err := c.readPacket() + if err != nil { + return authFailure, nil, err + } + userAuthGSSAPIResp := &userAuthGSSAPIResponse{} + if err := Unmarshal(packet, userAuthGSSAPIResp); err != nil { + return authFailure, nil, err + } + // Start the loop into the exchange token. + // See RFC 4462 section 3.4. + var token []byte + defer g.gssAPIClient.DeleteSecContext() + for { + // Initiates the establishment of a security context between the application and a remote peer. + nextToken, needContinue, err := g.gssAPIClient.InitSecContext("host@"+g.target, token, false) + if err != nil { + return authFailure, nil, err + } + if len(nextToken) > 0 { + if err := c.writePacket(Marshal(&userAuthGSSAPIToken{ + Token: nextToken, + })); err != nil { + return authFailure, nil, err + } + } + if !needContinue { + break + } + packet, err = c.readPacket() + if err != nil { + return authFailure, nil, err + } + switch packet[0] { + case msgUserAuthFailure: + var msg userAuthFailureMsg + if err := Unmarshal(packet, &msg); err != nil { + return authFailure, nil, err + } + if msg.PartialSuccess { + return authPartialSuccess, msg.Methods, nil + } + return authFailure, msg.Methods, nil + case msgUserAuthGSSAPIError: + userAuthGSSAPIErrorResp := &userAuthGSSAPIError{} + if err := Unmarshal(packet, userAuthGSSAPIErrorResp); err != nil { + return authFailure, nil, err + } + return authFailure, nil, fmt.Errorf("GSS-API Error:\n"+ + "Major Status: %d\n"+ + "Minor Status: %d\n"+ + "Error Message: %s\n", userAuthGSSAPIErrorResp.MajorStatus, userAuthGSSAPIErrorResp.MinorStatus, + userAuthGSSAPIErrorResp.Message) + case msgUserAuthGSSAPIToken: + userAuthGSSAPITokenReq := &userAuthGSSAPIToken{} + if err := Unmarshal(packet, userAuthGSSAPITokenReq); err != nil { + return authFailure, nil, err + } + token = userAuthGSSAPITokenReq.Token + } + } + // Binding Encryption Keys. + // See RFC 4462 section 3.5. + micField := buildMIC(string(session), user, "ssh-connection", "gssapi-with-mic") + micToken, err := g.gssAPIClient.GetMIC(micField) + if err != nil { + return authFailure, nil, err + } + if err := c.writePacket(Marshal(&userAuthGSSAPIMIC{ + MIC: micToken, + })); err != nil { + return authFailure, nil, err + } + return handleAuthResponse(c) +} + +func (g *gssAPIWithMICCallback) method() string { + return "gssapi-with-mic" +} diff --git a/vendor/golang.org/x/crypto/ssh/common.go b/vendor/golang.org/x/crypto/ssh/common.go new file mode 100644 index 00000000000..290382d059e --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/common.go @@ -0,0 +1,404 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "crypto" + "crypto/rand" + "fmt" + "io" + "math" + "sync" + + _ "crypto/sha1" + _ "crypto/sha256" + _ "crypto/sha512" +) + +// These are string constants in the SSH protocol. +const ( + compressionNone = "none" + serviceUserAuth = "ssh-userauth" + serviceSSH = "ssh-connection" +) + +// supportedCiphers lists ciphers we support but might not recommend. +var supportedCiphers = []string{ + "aes128-ctr", "aes192-ctr", "aes256-ctr", + "aes128-gcm@openssh.com", + chacha20Poly1305ID, + "arcfour256", "arcfour128", "arcfour", + aes128cbcID, + tripledescbcID, +} + +// preferredCiphers specifies the default preference for ciphers. +var preferredCiphers = []string{ + "aes128-gcm@openssh.com", + chacha20Poly1305ID, + "aes128-ctr", "aes192-ctr", "aes256-ctr", +} + +// supportedKexAlgos specifies the supported key-exchange algorithms in +// preference order. +var supportedKexAlgos = []string{ + kexAlgoCurve25519SHA256, + // P384 and P521 are not constant-time yet, but since we don't + // reuse ephemeral keys, using them for ECDH should be OK. + kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521, + kexAlgoDH14SHA1, kexAlgoDH1SHA1, +} + +// serverForbiddenKexAlgos contains key exchange algorithms, that are forbidden +// for the server half. +var serverForbiddenKexAlgos = map[string]struct{}{ + kexAlgoDHGEXSHA1: {}, // server half implementation is only minimal to satisfy the automated tests + kexAlgoDHGEXSHA256: {}, // server half implementation is only minimal to satisfy the automated tests +} + +// preferredKexAlgos specifies the default preference for key-exchange algorithms +// in preference order. +var preferredKexAlgos = []string{ + kexAlgoCurve25519SHA256, + kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521, + kexAlgoDH14SHA1, +} + +// supportedHostKeyAlgos specifies the supported host-key algorithms (i.e. methods +// of authenticating servers) in preference order. +var supportedHostKeyAlgos = []string{ + CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, + CertAlgoECDSA384v01, CertAlgoECDSA521v01, CertAlgoED25519v01, + + KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521, + KeyAlgoRSA, KeyAlgoDSA, + + KeyAlgoED25519, +} + +// supportedMACs specifies a default set of MAC algorithms in preference order. +// This is based on RFC 4253, section 6.4, but with hmac-md5 variants removed +// because they have reached the end of their useful life. +var supportedMACs = []string{ + "hmac-sha2-256-etm@openssh.com", "hmac-sha2-256", "hmac-sha1", "hmac-sha1-96", +} + +var supportedCompressions = []string{compressionNone} + +// hashFuncs keeps the mapping of supported algorithms to their respective +// hashes needed for signature verification. +var hashFuncs = map[string]crypto.Hash{ + KeyAlgoRSA: crypto.SHA1, + KeyAlgoDSA: crypto.SHA1, + KeyAlgoECDSA256: crypto.SHA256, + KeyAlgoECDSA384: crypto.SHA384, + KeyAlgoECDSA521: crypto.SHA512, + CertAlgoRSAv01: crypto.SHA1, + CertAlgoDSAv01: crypto.SHA1, + CertAlgoECDSA256v01: crypto.SHA256, + CertAlgoECDSA384v01: crypto.SHA384, + CertAlgoECDSA521v01: crypto.SHA512, +} + +// unexpectedMessageError results when the SSH message that we received didn't +// match what we wanted. +func unexpectedMessageError(expected, got uint8) error { + return fmt.Errorf("ssh: unexpected message type %d (expected %d)", got, expected) +} + +// parseError results from a malformed SSH message. +func parseError(tag uint8) error { + return fmt.Errorf("ssh: parse error in message type %d", tag) +} + +func findCommon(what string, client []string, server []string) (common string, err error) { + for _, c := range client { + for _, s := range server { + if c == s { + return c, nil + } + } + } + return "", fmt.Errorf("ssh: no common algorithm for %s; client offered: %v, server offered: %v", what, client, server) +} + +// directionAlgorithms records algorithm choices in one direction (either read or write) +type directionAlgorithms struct { + Cipher string + MAC string + Compression string +} + +// rekeyBytes returns a rekeying intervals in bytes. +func (a *directionAlgorithms) rekeyBytes() int64 { + // According to RFC4344 block ciphers should rekey after + // 2^(BLOCKSIZE/4) blocks. For all AES flavors BLOCKSIZE is + // 128. + switch a.Cipher { + case "aes128-ctr", "aes192-ctr", "aes256-ctr", gcmCipherID, aes128cbcID: + return 16 * (1 << 32) + + } + + // For others, stick with RFC4253 recommendation to rekey after 1 Gb of data. + return 1 << 30 +} + +type algorithms struct { + kex string + hostKey string + w directionAlgorithms + r directionAlgorithms +} + +func findAgreedAlgorithms(isClient bool, clientKexInit, serverKexInit *kexInitMsg) (algs *algorithms, err error) { + result := &algorithms{} + + result.kex, err = findCommon("key exchange", clientKexInit.KexAlgos, serverKexInit.KexAlgos) + if err != nil { + return + } + + result.hostKey, err = findCommon("host key", clientKexInit.ServerHostKeyAlgos, serverKexInit.ServerHostKeyAlgos) + if err != nil { + return + } + + stoc, ctos := &result.w, &result.r + if isClient { + ctos, stoc = stoc, ctos + } + + ctos.Cipher, err = findCommon("client to server cipher", clientKexInit.CiphersClientServer, serverKexInit.CiphersClientServer) + if err != nil { + return + } + + stoc.Cipher, err = findCommon("server to client cipher", clientKexInit.CiphersServerClient, serverKexInit.CiphersServerClient) + if err != nil { + return + } + + ctos.MAC, err = findCommon("client to server MAC", clientKexInit.MACsClientServer, serverKexInit.MACsClientServer) + if err != nil { + return + } + + stoc.MAC, err = findCommon("server to client MAC", clientKexInit.MACsServerClient, serverKexInit.MACsServerClient) + if err != nil { + return + } + + ctos.Compression, err = findCommon("client to server compression", clientKexInit.CompressionClientServer, serverKexInit.CompressionClientServer) + if err != nil { + return + } + + stoc.Compression, err = findCommon("server to client compression", clientKexInit.CompressionServerClient, serverKexInit.CompressionServerClient) + if err != nil { + return + } + + return result, nil +} + +// If rekeythreshold is too small, we can't make any progress sending +// stuff. +const minRekeyThreshold uint64 = 256 + +// Config contains configuration data common to both ServerConfig and +// ClientConfig. +type Config struct { + // Rand provides the source of entropy for cryptographic + // primitives. If Rand is nil, the cryptographic random reader + // in package crypto/rand will be used. + Rand io.Reader + + // The maximum number of bytes sent or received after which a + // new key is negotiated. It must be at least 256. If + // unspecified, a size suitable for the chosen cipher is used. + RekeyThreshold uint64 + + // The allowed key exchanges algorithms. If unspecified then a + // default set of algorithms is used. + KeyExchanges []string + + // The allowed cipher algorithms. If unspecified then a sensible + // default is used. + Ciphers []string + + // The allowed MAC algorithms. If unspecified then a sensible default + // is used. + MACs []string +} + +// SetDefaults sets sensible values for unset fields in config. This is +// exported for testing: Configs passed to SSH functions are copied and have +// default values set automatically. +func (c *Config) SetDefaults() { + if c.Rand == nil { + c.Rand = rand.Reader + } + if c.Ciphers == nil { + c.Ciphers = preferredCiphers + } + var ciphers []string + for _, c := range c.Ciphers { + if cipherModes[c] != nil { + // reject the cipher if we have no cipherModes definition + ciphers = append(ciphers, c) + } + } + c.Ciphers = ciphers + + if c.KeyExchanges == nil { + c.KeyExchanges = preferredKexAlgos + } + + if c.MACs == nil { + c.MACs = supportedMACs + } + + if c.RekeyThreshold == 0 { + // cipher specific default + } else if c.RekeyThreshold < minRekeyThreshold { + c.RekeyThreshold = minRekeyThreshold + } else if c.RekeyThreshold >= math.MaxInt64 { + // Avoid weirdness if somebody uses -1 as a threshold. + c.RekeyThreshold = math.MaxInt64 + } +} + +// buildDataSignedForAuth returns the data that is signed in order to prove +// possession of a private key. See RFC 4252, section 7. +func buildDataSignedForAuth(sessionID []byte, req userAuthRequestMsg, algo, pubKey []byte) []byte { + data := struct { + Session []byte + Type byte + User string + Service string + Method string + Sign bool + Algo []byte + PubKey []byte + }{ + sessionID, + msgUserAuthRequest, + req.User, + req.Service, + req.Method, + true, + algo, + pubKey, + } + return Marshal(data) +} + +func appendU16(buf []byte, n uint16) []byte { + return append(buf, byte(n>>8), byte(n)) +} + +func appendU32(buf []byte, n uint32) []byte { + return append(buf, byte(n>>24), byte(n>>16), byte(n>>8), byte(n)) +} + +func appendU64(buf []byte, n uint64) []byte { + return append(buf, + byte(n>>56), byte(n>>48), byte(n>>40), byte(n>>32), + byte(n>>24), byte(n>>16), byte(n>>8), byte(n)) +} + +func appendInt(buf []byte, n int) []byte { + return appendU32(buf, uint32(n)) +} + +func appendString(buf []byte, s string) []byte { + buf = appendU32(buf, uint32(len(s))) + buf = append(buf, s...) + return buf +} + +func appendBool(buf []byte, b bool) []byte { + if b { + return append(buf, 1) + } + return append(buf, 0) +} + +// newCond is a helper to hide the fact that there is no usable zero +// value for sync.Cond. +func newCond() *sync.Cond { return sync.NewCond(new(sync.Mutex)) } + +// window represents the buffer available to clients +// wishing to write to a channel. +type window struct { + *sync.Cond + win uint32 // RFC 4254 5.2 says the window size can grow to 2^32-1 + writeWaiters int + closed bool +} + +// add adds win to the amount of window available +// for consumers. +func (w *window) add(win uint32) bool { + // a zero sized window adjust is a noop. + if win == 0 { + return true + } + w.L.Lock() + if w.win+win < win { + w.L.Unlock() + return false + } + w.win += win + // It is unusual that multiple goroutines would be attempting to reserve + // window space, but not guaranteed. Use broadcast to notify all waiters + // that additional window is available. + w.Broadcast() + w.L.Unlock() + return true +} + +// close sets the window to closed, so all reservations fail +// immediately. +func (w *window) close() { + w.L.Lock() + w.closed = true + w.Broadcast() + w.L.Unlock() +} + +// reserve reserves win from the available window capacity. +// If no capacity remains, reserve will block. reserve may +// return less than requested. +func (w *window) reserve(win uint32) (uint32, error) { + var err error + w.L.Lock() + w.writeWaiters++ + w.Broadcast() + for w.win == 0 && !w.closed { + w.Wait() + } + w.writeWaiters-- + if w.win < win { + win = w.win + } + w.win -= win + if w.closed { + err = io.EOF + } + w.L.Unlock() + return win, err +} + +// waitWriterBlocked waits until some goroutine is blocked for further +// writes. It is used in tests only. +func (w *window) waitWriterBlocked() { + w.Cond.L.Lock() + for w.writeWaiters == 0 { + w.Cond.Wait() + } + w.Cond.L.Unlock() +} diff --git a/vendor/golang.org/x/crypto/ssh/connection.go b/vendor/golang.org/x/crypto/ssh/connection.go new file mode 100644 index 00000000000..fd6b0681b51 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/connection.go @@ -0,0 +1,143 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "fmt" + "net" +) + +// OpenChannelError is returned if the other side rejects an +// OpenChannel request. +type OpenChannelError struct { + Reason RejectionReason + Message string +} + +func (e *OpenChannelError) Error() string { + return fmt.Sprintf("ssh: rejected: %s (%s)", e.Reason, e.Message) +} + +// ConnMetadata holds metadata for the connection. +type ConnMetadata interface { + // User returns the user ID for this connection. + User() string + + // SessionID returns the session hash, also denoted by H. + SessionID() []byte + + // ClientVersion returns the client's version string as hashed + // into the session ID. + ClientVersion() []byte + + // ServerVersion returns the server's version string as hashed + // into the session ID. + ServerVersion() []byte + + // RemoteAddr returns the remote address for this connection. + RemoteAddr() net.Addr + + // LocalAddr returns the local address for this connection. + LocalAddr() net.Addr +} + +// Conn represents an SSH connection for both server and client roles. +// Conn is the basis for implementing an application layer, such +// as ClientConn, which implements the traditional shell access for +// clients. +type Conn interface { + ConnMetadata + + // SendRequest sends a global request, and returns the + // reply. If wantReply is true, it returns the response status + // and payload. See also RFC4254, section 4. + SendRequest(name string, wantReply bool, payload []byte) (bool, []byte, error) + + // OpenChannel tries to open an channel. If the request is + // rejected, it returns *OpenChannelError. On success it returns + // the SSH Channel and a Go channel for incoming, out-of-band + // requests. The Go channel must be serviced, or the + // connection will hang. + OpenChannel(name string, data []byte) (Channel, <-chan *Request, error) + + // Close closes the underlying network connection + Close() error + + // Wait blocks until the connection has shut down, and returns the + // error causing the shutdown. + Wait() error + + // TODO(hanwen): consider exposing: + // RequestKeyChange + // Disconnect +} + +// DiscardRequests consumes and rejects all requests from the +// passed-in channel. +func DiscardRequests(in <-chan *Request) { + for req := range in { + if req.WantReply { + req.Reply(false, nil) + } + } +} + +// A connection represents an incoming connection. +type connection struct { + transport *handshakeTransport + sshConn + + // The connection protocol. + *mux +} + +func (c *connection) Close() error { + return c.sshConn.conn.Close() +} + +// sshconn provides net.Conn metadata, but disallows direct reads and +// writes. +type sshConn struct { + conn net.Conn + + user string + sessionID []byte + clientVersion []byte + serverVersion []byte +} + +func dup(src []byte) []byte { + dst := make([]byte, len(src)) + copy(dst, src) + return dst +} + +func (c *sshConn) User() string { + return c.user +} + +func (c *sshConn) RemoteAddr() net.Addr { + return c.conn.RemoteAddr() +} + +func (c *sshConn) Close() error { + return c.conn.Close() +} + +func (c *sshConn) LocalAddr() net.Addr { + return c.conn.LocalAddr() +} + +func (c *sshConn) SessionID() []byte { + return dup(c.sessionID) +} + +func (c *sshConn) ClientVersion() []byte { + return dup(c.clientVersion) +} + +func (c *sshConn) ServerVersion() []byte { + return dup(c.serverVersion) +} diff --git a/vendor/golang.org/x/crypto/ssh/doc.go b/vendor/golang.org/x/crypto/ssh/doc.go new file mode 100644 index 00000000000..67b7322c058 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/doc.go @@ -0,0 +1,21 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Package ssh implements an SSH client and server. + +SSH is a transport security protocol, an authentication protocol and a +family of application protocols. The most typical application level +protocol is a remote shell and this is specifically implemented. However, +the multiplexed nature of SSH is exposed to users that wish to support +others. + +References: + [PROTOCOL.certkeys]: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys?rev=HEAD + [SSH-PARAMETERS]: http://www.iana.org/assignments/ssh-parameters/ssh-parameters.xml#ssh-parameters-1 + +This package does not fall under the stability promise of the Go language itself, +so its API may be changed when pressing needs arise. +*/ +package ssh // import "golang.org/x/crypto/ssh" diff --git a/vendor/golang.org/x/crypto/ssh/handshake.go b/vendor/golang.org/x/crypto/ssh/handshake.go new file mode 100644 index 00000000000..2b10b05a498 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/handshake.go @@ -0,0 +1,647 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "crypto/rand" + "errors" + "fmt" + "io" + "log" + "net" + "sync" +) + +// debugHandshake, if set, prints messages sent and received. Key +// exchange messages are printed as if DH were used, so the debug +// messages are wrong when using ECDH. +const debugHandshake = false + +// chanSize sets the amount of buffering SSH connections. This is +// primarily for testing: setting chanSize=0 uncovers deadlocks more +// quickly. +const chanSize = 16 + +// keyingTransport is a packet based transport that supports key +// changes. It need not be thread-safe. It should pass through +// msgNewKeys in both directions. +type keyingTransport interface { + packetConn + + // prepareKeyChange sets up a key change. The key change for a + // direction will be effected if a msgNewKeys message is sent + // or received. + prepareKeyChange(*algorithms, *kexResult) error +} + +// handshakeTransport implements rekeying on top of a keyingTransport +// and offers a thread-safe writePacket() interface. +type handshakeTransport struct { + conn keyingTransport + config *Config + + serverVersion []byte + clientVersion []byte + + // hostKeys is non-empty if we are the server. In that case, + // it contains all host keys that can be used to sign the + // connection. + hostKeys []Signer + + // hostKeyAlgorithms is non-empty if we are the client. In that case, + // we accept these key types from the server as host key. + hostKeyAlgorithms []string + + // On read error, incoming is closed, and readError is set. + incoming chan []byte + readError error + + mu sync.Mutex + writeError error + sentInitPacket []byte + sentInitMsg *kexInitMsg + pendingPackets [][]byte // Used when a key exchange is in progress. + + // If the read loop wants to schedule a kex, it pings this + // channel, and the write loop will send out a kex + // message. + requestKex chan struct{} + + // If the other side requests or confirms a kex, its kexInit + // packet is sent here for the write loop to find it. + startKex chan *pendingKex + + // data for host key checking + hostKeyCallback HostKeyCallback + dialAddress string + remoteAddr net.Addr + + // bannerCallback is non-empty if we are the client and it has been set in + // ClientConfig. In that case it is called during the user authentication + // dance to handle a custom server's message. + bannerCallback BannerCallback + + // Algorithms agreed in the last key exchange. + algorithms *algorithms + + readPacketsLeft uint32 + readBytesLeft int64 + + writePacketsLeft uint32 + writeBytesLeft int64 + + // The session ID or nil if first kex did not complete yet. + sessionID []byte +} + +type pendingKex struct { + otherInit []byte + done chan error +} + +func newHandshakeTransport(conn keyingTransport, config *Config, clientVersion, serverVersion []byte) *handshakeTransport { + t := &handshakeTransport{ + conn: conn, + serverVersion: serverVersion, + clientVersion: clientVersion, + incoming: make(chan []byte, chanSize), + requestKex: make(chan struct{}, 1), + startKex: make(chan *pendingKex, 1), + + config: config, + } + t.resetReadThresholds() + t.resetWriteThresholds() + + // We always start with a mandatory key exchange. + t.requestKex <- struct{}{} + return t +} + +func newClientTransport(conn keyingTransport, clientVersion, serverVersion []byte, config *ClientConfig, dialAddr string, addr net.Addr) *handshakeTransport { + t := newHandshakeTransport(conn, &config.Config, clientVersion, serverVersion) + t.dialAddress = dialAddr + t.remoteAddr = addr + t.hostKeyCallback = config.HostKeyCallback + t.bannerCallback = config.BannerCallback + if config.HostKeyAlgorithms != nil { + t.hostKeyAlgorithms = config.HostKeyAlgorithms + } else { + t.hostKeyAlgorithms = supportedHostKeyAlgos + } + go t.readLoop() + go t.kexLoop() + return t +} + +func newServerTransport(conn keyingTransport, clientVersion, serverVersion []byte, config *ServerConfig) *handshakeTransport { + t := newHandshakeTransport(conn, &config.Config, clientVersion, serverVersion) + t.hostKeys = config.hostKeys + go t.readLoop() + go t.kexLoop() + return t +} + +func (t *handshakeTransport) getSessionID() []byte { + return t.sessionID +} + +// waitSession waits for the session to be established. This should be +// the first thing to call after instantiating handshakeTransport. +func (t *handshakeTransport) waitSession() error { + p, err := t.readPacket() + if err != nil { + return err + } + if p[0] != msgNewKeys { + return fmt.Errorf("ssh: first packet should be msgNewKeys") + } + + return nil +} + +func (t *handshakeTransport) id() string { + if len(t.hostKeys) > 0 { + return "server" + } + return "client" +} + +func (t *handshakeTransport) printPacket(p []byte, write bool) { + action := "got" + if write { + action = "sent" + } + + if p[0] == msgChannelData || p[0] == msgChannelExtendedData { + log.Printf("%s %s data (packet %d bytes)", t.id(), action, len(p)) + } else { + msg, err := decode(p) + log.Printf("%s %s %T %v (%v)", t.id(), action, msg, msg, err) + } +} + +func (t *handshakeTransport) readPacket() ([]byte, error) { + p, ok := <-t.incoming + if !ok { + return nil, t.readError + } + return p, nil +} + +func (t *handshakeTransport) readLoop() { + first := true + for { + p, err := t.readOnePacket(first) + first = false + if err != nil { + t.readError = err + close(t.incoming) + break + } + if p[0] == msgIgnore || p[0] == msgDebug { + continue + } + t.incoming <- p + } + + // Stop writers too. + t.recordWriteError(t.readError) + + // Unblock the writer should it wait for this. + close(t.startKex) + + // Don't close t.requestKex; it's also written to from writePacket. +} + +func (t *handshakeTransport) pushPacket(p []byte) error { + if debugHandshake { + t.printPacket(p, true) + } + return t.conn.writePacket(p) +} + +func (t *handshakeTransport) getWriteError() error { + t.mu.Lock() + defer t.mu.Unlock() + return t.writeError +} + +func (t *handshakeTransport) recordWriteError(err error) { + t.mu.Lock() + defer t.mu.Unlock() + if t.writeError == nil && err != nil { + t.writeError = err + } +} + +func (t *handshakeTransport) requestKeyExchange() { + select { + case t.requestKex <- struct{}{}: + default: + // something already requested a kex, so do nothing. + } +} + +func (t *handshakeTransport) resetWriteThresholds() { + t.writePacketsLeft = packetRekeyThreshold + if t.config.RekeyThreshold > 0 { + t.writeBytesLeft = int64(t.config.RekeyThreshold) + } else if t.algorithms != nil { + t.writeBytesLeft = t.algorithms.w.rekeyBytes() + } else { + t.writeBytesLeft = 1 << 30 + } +} + +func (t *handshakeTransport) kexLoop() { + +write: + for t.getWriteError() == nil { + var request *pendingKex + var sent bool + + for request == nil || !sent { + var ok bool + select { + case request, ok = <-t.startKex: + if !ok { + break write + } + case <-t.requestKex: + break + } + + if !sent { + if err := t.sendKexInit(); err != nil { + t.recordWriteError(err) + break + } + sent = true + } + } + + if err := t.getWriteError(); err != nil { + if request != nil { + request.done <- err + } + break + } + + // We're not servicing t.requestKex, but that is OK: + // we never block on sending to t.requestKex. + + // We're not servicing t.startKex, but the remote end + // has just sent us a kexInitMsg, so it can't send + // another key change request, until we close the done + // channel on the pendingKex request. + + err := t.enterKeyExchange(request.otherInit) + + t.mu.Lock() + t.writeError = err + t.sentInitPacket = nil + t.sentInitMsg = nil + + t.resetWriteThresholds() + + // we have completed the key exchange. Since the + // reader is still blocked, it is safe to clear out + // the requestKex channel. This avoids the situation + // where: 1) we consumed our own request for the + // initial kex, and 2) the kex from the remote side + // caused another send on the requestKex channel, + clear: + for { + select { + case <-t.requestKex: + // + default: + break clear + } + } + + request.done <- t.writeError + + // kex finished. Push packets that we received while + // the kex was in progress. Don't look at t.startKex + // and don't increment writtenSinceKex: if we trigger + // another kex while we are still busy with the last + // one, things will become very confusing. + for _, p := range t.pendingPackets { + t.writeError = t.pushPacket(p) + if t.writeError != nil { + break + } + } + t.pendingPackets = t.pendingPackets[:0] + t.mu.Unlock() + } + + // drain startKex channel. We don't service t.requestKex + // because nobody does blocking sends there. + go func() { + for init := range t.startKex { + init.done <- t.writeError + } + }() + + // Unblock reader. + t.conn.Close() +} + +// The protocol uses uint32 for packet counters, so we can't let them +// reach 1<<32. We will actually read and write more packets than +// this, though: the other side may send more packets, and after we +// hit this limit on writing we will send a few more packets for the +// key exchange itself. +const packetRekeyThreshold = (1 << 31) + +func (t *handshakeTransport) resetReadThresholds() { + t.readPacketsLeft = packetRekeyThreshold + if t.config.RekeyThreshold > 0 { + t.readBytesLeft = int64(t.config.RekeyThreshold) + } else if t.algorithms != nil { + t.readBytesLeft = t.algorithms.r.rekeyBytes() + } else { + t.readBytesLeft = 1 << 30 + } +} + +func (t *handshakeTransport) readOnePacket(first bool) ([]byte, error) { + p, err := t.conn.readPacket() + if err != nil { + return nil, err + } + + if t.readPacketsLeft > 0 { + t.readPacketsLeft-- + } else { + t.requestKeyExchange() + } + + if t.readBytesLeft > 0 { + t.readBytesLeft -= int64(len(p)) + } else { + t.requestKeyExchange() + } + + if debugHandshake { + t.printPacket(p, false) + } + + if first && p[0] != msgKexInit { + return nil, fmt.Errorf("ssh: first packet should be msgKexInit") + } + + if p[0] != msgKexInit { + return p, nil + } + + firstKex := t.sessionID == nil + + kex := pendingKex{ + done: make(chan error, 1), + otherInit: p, + } + t.startKex <- &kex + err = <-kex.done + + if debugHandshake { + log.Printf("%s exited key exchange (first %v), err %v", t.id(), firstKex, err) + } + + if err != nil { + return nil, err + } + + t.resetReadThresholds() + + // By default, a key exchange is hidden from higher layers by + // translating it into msgIgnore. + successPacket := []byte{msgIgnore} + if firstKex { + // sendKexInit() for the first kex waits for + // msgNewKeys so the authentication process is + // guaranteed to happen over an encrypted transport. + successPacket = []byte{msgNewKeys} + } + + return successPacket, nil +} + +// sendKexInit sends a key change message. +func (t *handshakeTransport) sendKexInit() error { + t.mu.Lock() + defer t.mu.Unlock() + if t.sentInitMsg != nil { + // kexInits may be sent either in response to the other side, + // or because our side wants to initiate a key change, so we + // may have already sent a kexInit. In that case, don't send a + // second kexInit. + return nil + } + + msg := &kexInitMsg{ + KexAlgos: t.config.KeyExchanges, + CiphersClientServer: t.config.Ciphers, + CiphersServerClient: t.config.Ciphers, + MACsClientServer: t.config.MACs, + MACsServerClient: t.config.MACs, + CompressionClientServer: supportedCompressions, + CompressionServerClient: supportedCompressions, + } + io.ReadFull(rand.Reader, msg.Cookie[:]) + + if len(t.hostKeys) > 0 { + for _, k := range t.hostKeys { + msg.ServerHostKeyAlgos = append( + msg.ServerHostKeyAlgos, k.PublicKey().Type()) + } + } else { + msg.ServerHostKeyAlgos = t.hostKeyAlgorithms + } + packet := Marshal(msg) + + // writePacket destroys the contents, so save a copy. + packetCopy := make([]byte, len(packet)) + copy(packetCopy, packet) + + if err := t.pushPacket(packetCopy); err != nil { + return err + } + + t.sentInitMsg = msg + t.sentInitPacket = packet + + return nil +} + +func (t *handshakeTransport) writePacket(p []byte) error { + switch p[0] { + case msgKexInit: + return errors.New("ssh: only handshakeTransport can send kexInit") + case msgNewKeys: + return errors.New("ssh: only handshakeTransport can send newKeys") + } + + t.mu.Lock() + defer t.mu.Unlock() + if t.writeError != nil { + return t.writeError + } + + if t.sentInitMsg != nil { + // Copy the packet so the writer can reuse the buffer. + cp := make([]byte, len(p)) + copy(cp, p) + t.pendingPackets = append(t.pendingPackets, cp) + return nil + } + + if t.writeBytesLeft > 0 { + t.writeBytesLeft -= int64(len(p)) + } else { + t.requestKeyExchange() + } + + if t.writePacketsLeft > 0 { + t.writePacketsLeft-- + } else { + t.requestKeyExchange() + } + + if err := t.pushPacket(p); err != nil { + t.writeError = err + } + + return nil +} + +func (t *handshakeTransport) Close() error { + return t.conn.Close() +} + +func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error { + if debugHandshake { + log.Printf("%s entered key exchange", t.id()) + } + + otherInit := &kexInitMsg{} + if err := Unmarshal(otherInitPacket, otherInit); err != nil { + return err + } + + magics := handshakeMagics{ + clientVersion: t.clientVersion, + serverVersion: t.serverVersion, + clientKexInit: otherInitPacket, + serverKexInit: t.sentInitPacket, + } + + clientInit := otherInit + serverInit := t.sentInitMsg + isClient := len(t.hostKeys) == 0 + if isClient { + clientInit, serverInit = serverInit, clientInit + + magics.clientKexInit = t.sentInitPacket + magics.serverKexInit = otherInitPacket + } + + var err error + t.algorithms, err = findAgreedAlgorithms(isClient, clientInit, serverInit) + if err != nil { + return err + } + + // We don't send FirstKexFollows, but we handle receiving it. + // + // RFC 4253 section 7 defines the kex and the agreement method for + // first_kex_packet_follows. It states that the guessed packet + // should be ignored if the "kex algorithm and/or the host + // key algorithm is guessed wrong (server and client have + // different preferred algorithm), or if any of the other + // algorithms cannot be agreed upon". The other algorithms have + // already been checked above so the kex algorithm and host key + // algorithm are checked here. + if otherInit.FirstKexFollows && (clientInit.KexAlgos[0] != serverInit.KexAlgos[0] || clientInit.ServerHostKeyAlgos[0] != serverInit.ServerHostKeyAlgos[0]) { + // other side sent a kex message for the wrong algorithm, + // which we have to ignore. + if _, err := t.conn.readPacket(); err != nil { + return err + } + } + + kex, ok := kexAlgoMap[t.algorithms.kex] + if !ok { + return fmt.Errorf("ssh: unexpected key exchange algorithm %v", t.algorithms.kex) + } + + var result *kexResult + if len(t.hostKeys) > 0 { + result, err = t.server(kex, t.algorithms, &magics) + } else { + result, err = t.client(kex, t.algorithms, &magics) + } + + if err != nil { + return err + } + + if t.sessionID == nil { + t.sessionID = result.H + } + result.SessionID = t.sessionID + + if err := t.conn.prepareKeyChange(t.algorithms, result); err != nil { + return err + } + if err = t.conn.writePacket([]byte{msgNewKeys}); err != nil { + return err + } + if packet, err := t.conn.readPacket(); err != nil { + return err + } else if packet[0] != msgNewKeys { + return unexpectedMessageError(msgNewKeys, packet[0]) + } + + return nil +} + +func (t *handshakeTransport) server(kex kexAlgorithm, algs *algorithms, magics *handshakeMagics) (*kexResult, error) { + var hostKey Signer + for _, k := range t.hostKeys { + if algs.hostKey == k.PublicKey().Type() { + hostKey = k + } + } + + r, err := kex.Server(t.conn, t.config.Rand, magics, hostKey) + return r, err +} + +func (t *handshakeTransport) client(kex kexAlgorithm, algs *algorithms, magics *handshakeMagics) (*kexResult, error) { + result, err := kex.Client(t.conn, t.config.Rand, magics) + if err != nil { + return nil, err + } + + hostKey, err := ParsePublicKey(result.HostKey) + if err != nil { + return nil, err + } + + if err := verifyHostKeySignature(hostKey, result); err != nil { + return nil, err + } + + err = t.hostKeyCallback(t.dialAddress, t.remoteAddr, hostKey) + if err != nil { + return nil, err + } + + return result, nil +} diff --git a/vendor/golang.org/x/crypto/ssh/internal/bcrypt_pbkdf/bcrypt_pbkdf.go b/vendor/golang.org/x/crypto/ssh/internal/bcrypt_pbkdf/bcrypt_pbkdf.go new file mode 100644 index 00000000000..af81d266546 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/internal/bcrypt_pbkdf/bcrypt_pbkdf.go @@ -0,0 +1,93 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package bcrypt_pbkdf implements bcrypt_pbkdf(3) from OpenBSD. +// +// See https://flak.tedunangst.com/post/bcrypt-pbkdf and +// https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/lib/libutil/bcrypt_pbkdf.c. +package bcrypt_pbkdf + +import ( + "crypto/sha512" + "errors" + "golang.org/x/crypto/blowfish" +) + +const blockSize = 32 + +// Key derives a key from the password, salt and rounds count, returning a +// []byte of length keyLen that can be used as cryptographic key. +func Key(password, salt []byte, rounds, keyLen int) ([]byte, error) { + if rounds < 1 { + return nil, errors.New("bcrypt_pbkdf: number of rounds is too small") + } + if len(password) == 0 { + return nil, errors.New("bcrypt_pbkdf: empty password") + } + if len(salt) == 0 || len(salt) > 1<<20 { + return nil, errors.New("bcrypt_pbkdf: bad salt length") + } + if keyLen > 1024 { + return nil, errors.New("bcrypt_pbkdf: keyLen is too large") + } + + numBlocks := (keyLen + blockSize - 1) / blockSize + key := make([]byte, numBlocks*blockSize) + + h := sha512.New() + h.Write(password) + shapass := h.Sum(nil) + + shasalt := make([]byte, 0, sha512.Size) + cnt, tmp := make([]byte, 4), make([]byte, blockSize) + for block := 1; block <= numBlocks; block++ { + h.Reset() + h.Write(salt) + cnt[0] = byte(block >> 24) + cnt[1] = byte(block >> 16) + cnt[2] = byte(block >> 8) + cnt[3] = byte(block) + h.Write(cnt) + bcryptHash(tmp, shapass, h.Sum(shasalt)) + + out := make([]byte, blockSize) + copy(out, tmp) + for i := 2; i <= rounds; i++ { + h.Reset() + h.Write(tmp) + bcryptHash(tmp, shapass, h.Sum(shasalt)) + for j := 0; j < len(out); j++ { + out[j] ^= tmp[j] + } + } + + for i, v := range out { + key[i*numBlocks+(block-1)] = v + } + } + return key[:keyLen], nil +} + +var magic = []byte("OxychromaticBlowfishSwatDynamite") + +func bcryptHash(out, shapass, shasalt []byte) { + c, err := blowfish.NewSaltedCipher(shapass, shasalt) + if err != nil { + panic(err) + } + for i := 0; i < 64; i++ { + blowfish.ExpandKey(shasalt, c) + blowfish.ExpandKey(shapass, c) + } + copy(out, magic) + for i := 0; i < 32; i += 8 { + for j := 0; j < 64; j++ { + c.Encrypt(out[i:i+8], out[i:i+8]) + } + } + // Swap bytes due to different endianness. + for i := 0; i < 32; i += 4 { + out[i+3], out[i+2], out[i+1], out[i] = out[i], out[i+1], out[i+2], out[i+3] + } +} diff --git a/vendor/golang.org/x/crypto/ssh/kex.go b/vendor/golang.org/x/crypto/ssh/kex.go new file mode 100644 index 00000000000..6c3c648fc95 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/kex.go @@ -0,0 +1,789 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/subtle" + "encoding/binary" + "errors" + "fmt" + "io" + "math/big" + + "golang.org/x/crypto/curve25519" +) + +const ( + kexAlgoDH1SHA1 = "diffie-hellman-group1-sha1" + kexAlgoDH14SHA1 = "diffie-hellman-group14-sha1" + kexAlgoECDH256 = "ecdh-sha2-nistp256" + kexAlgoECDH384 = "ecdh-sha2-nistp384" + kexAlgoECDH521 = "ecdh-sha2-nistp521" + kexAlgoCurve25519SHA256 = "curve25519-sha256@libssh.org" + + // For the following kex only the client half contains a production + // ready implementation. The server half only consists of a minimal + // implementation to satisfy the automated tests. + kexAlgoDHGEXSHA1 = "diffie-hellman-group-exchange-sha1" + kexAlgoDHGEXSHA256 = "diffie-hellman-group-exchange-sha256" +) + +// kexResult captures the outcome of a key exchange. +type kexResult struct { + // Session hash. See also RFC 4253, section 8. + H []byte + + // Shared secret. See also RFC 4253, section 8. + K []byte + + // Host key as hashed into H. + HostKey []byte + + // Signature of H. + Signature []byte + + // A cryptographic hash function that matches the security + // level of the key exchange algorithm. It is used for + // calculating H, and for deriving keys from H and K. + Hash crypto.Hash + + // The session ID, which is the first H computed. This is used + // to derive key material inside the transport. + SessionID []byte +} + +// handshakeMagics contains data that is always included in the +// session hash. +type handshakeMagics struct { + clientVersion, serverVersion []byte + clientKexInit, serverKexInit []byte +} + +func (m *handshakeMagics) write(w io.Writer) { + writeString(w, m.clientVersion) + writeString(w, m.serverVersion) + writeString(w, m.clientKexInit) + writeString(w, m.serverKexInit) +} + +// kexAlgorithm abstracts different key exchange algorithms. +type kexAlgorithm interface { + // Server runs server-side key agreement, signing the result + // with a hostkey. + Server(p packetConn, rand io.Reader, magics *handshakeMagics, s Signer) (*kexResult, error) + + // Client runs the client-side key agreement. Caller is + // responsible for verifying the host key signature. + Client(p packetConn, rand io.Reader, magics *handshakeMagics) (*kexResult, error) +} + +// dhGroup is a multiplicative group suitable for implementing Diffie-Hellman key agreement. +type dhGroup struct { + g, p, pMinus1 *big.Int +} + +func (group *dhGroup) diffieHellman(theirPublic, myPrivate *big.Int) (*big.Int, error) { + if theirPublic.Cmp(bigOne) <= 0 || theirPublic.Cmp(group.pMinus1) >= 0 { + return nil, errors.New("ssh: DH parameter out of bounds") + } + return new(big.Int).Exp(theirPublic, myPrivate, group.p), nil +} + +func (group *dhGroup) Client(c packetConn, randSource io.Reader, magics *handshakeMagics) (*kexResult, error) { + hashFunc := crypto.SHA1 + + var x *big.Int + for { + var err error + if x, err = rand.Int(randSource, group.pMinus1); err != nil { + return nil, err + } + if x.Sign() > 0 { + break + } + } + + X := new(big.Int).Exp(group.g, x, group.p) + kexDHInit := kexDHInitMsg{ + X: X, + } + if err := c.writePacket(Marshal(&kexDHInit)); err != nil { + return nil, err + } + + packet, err := c.readPacket() + if err != nil { + return nil, err + } + + var kexDHReply kexDHReplyMsg + if err = Unmarshal(packet, &kexDHReply); err != nil { + return nil, err + } + + ki, err := group.diffieHellman(kexDHReply.Y, x) + if err != nil { + return nil, err + } + + h := hashFunc.New() + magics.write(h) + writeString(h, kexDHReply.HostKey) + writeInt(h, X) + writeInt(h, kexDHReply.Y) + K := make([]byte, intLength(ki)) + marshalInt(K, ki) + h.Write(K) + + return &kexResult{ + H: h.Sum(nil), + K: K, + HostKey: kexDHReply.HostKey, + Signature: kexDHReply.Signature, + Hash: crypto.SHA1, + }, nil +} + +func (group *dhGroup) Server(c packetConn, randSource io.Reader, magics *handshakeMagics, priv Signer) (result *kexResult, err error) { + hashFunc := crypto.SHA1 + packet, err := c.readPacket() + if err != nil { + return + } + var kexDHInit kexDHInitMsg + if err = Unmarshal(packet, &kexDHInit); err != nil { + return + } + + var y *big.Int + for { + if y, err = rand.Int(randSource, group.pMinus1); err != nil { + return + } + if y.Sign() > 0 { + break + } + } + + Y := new(big.Int).Exp(group.g, y, group.p) + ki, err := group.diffieHellman(kexDHInit.X, y) + if err != nil { + return nil, err + } + + hostKeyBytes := priv.PublicKey().Marshal() + + h := hashFunc.New() + magics.write(h) + writeString(h, hostKeyBytes) + writeInt(h, kexDHInit.X) + writeInt(h, Y) + + K := make([]byte, intLength(ki)) + marshalInt(K, ki) + h.Write(K) + + H := h.Sum(nil) + + // H is already a hash, but the hostkey signing will apply its + // own key-specific hash algorithm. + sig, err := signAndMarshal(priv, randSource, H) + if err != nil { + return nil, err + } + + kexDHReply := kexDHReplyMsg{ + HostKey: hostKeyBytes, + Y: Y, + Signature: sig, + } + packet = Marshal(&kexDHReply) + + err = c.writePacket(packet) + return &kexResult{ + H: H, + K: K, + HostKey: hostKeyBytes, + Signature: sig, + Hash: crypto.SHA1, + }, err +} + +// ecdh performs Elliptic Curve Diffie-Hellman key exchange as +// described in RFC 5656, section 4. +type ecdh struct { + curve elliptic.Curve +} + +func (kex *ecdh) Client(c packetConn, rand io.Reader, magics *handshakeMagics) (*kexResult, error) { + ephKey, err := ecdsa.GenerateKey(kex.curve, rand) + if err != nil { + return nil, err + } + + kexInit := kexECDHInitMsg{ + ClientPubKey: elliptic.Marshal(kex.curve, ephKey.PublicKey.X, ephKey.PublicKey.Y), + } + + serialized := Marshal(&kexInit) + if err := c.writePacket(serialized); err != nil { + return nil, err + } + + packet, err := c.readPacket() + if err != nil { + return nil, err + } + + var reply kexECDHReplyMsg + if err = Unmarshal(packet, &reply); err != nil { + return nil, err + } + + x, y, err := unmarshalECKey(kex.curve, reply.EphemeralPubKey) + if err != nil { + return nil, err + } + + // generate shared secret + secret, _ := kex.curve.ScalarMult(x, y, ephKey.D.Bytes()) + + h := ecHash(kex.curve).New() + magics.write(h) + writeString(h, reply.HostKey) + writeString(h, kexInit.ClientPubKey) + writeString(h, reply.EphemeralPubKey) + K := make([]byte, intLength(secret)) + marshalInt(K, secret) + h.Write(K) + + return &kexResult{ + H: h.Sum(nil), + K: K, + HostKey: reply.HostKey, + Signature: reply.Signature, + Hash: ecHash(kex.curve), + }, nil +} + +// unmarshalECKey parses and checks an EC key. +func unmarshalECKey(curve elliptic.Curve, pubkey []byte) (x, y *big.Int, err error) { + x, y = elliptic.Unmarshal(curve, pubkey) + if x == nil { + return nil, nil, errors.New("ssh: elliptic.Unmarshal failure") + } + if !validateECPublicKey(curve, x, y) { + return nil, nil, errors.New("ssh: public key not on curve") + } + return x, y, nil +} + +// validateECPublicKey checks that the point is a valid public key for +// the given curve. See [SEC1], 3.2.2 +func validateECPublicKey(curve elliptic.Curve, x, y *big.Int) bool { + if x.Sign() == 0 && y.Sign() == 0 { + return false + } + + if x.Cmp(curve.Params().P) >= 0 { + return false + } + + if y.Cmp(curve.Params().P) >= 0 { + return false + } + + if !curve.IsOnCurve(x, y) { + return false + } + + // We don't check if N * PubKey == 0, since + // + // - the NIST curves have cofactor = 1, so this is implicit. + // (We don't foresee an implementation that supports non NIST + // curves) + // + // - for ephemeral keys, we don't need to worry about small + // subgroup attacks. + return true +} + +func (kex *ecdh) Server(c packetConn, rand io.Reader, magics *handshakeMagics, priv Signer) (result *kexResult, err error) { + packet, err := c.readPacket() + if err != nil { + return nil, err + } + + var kexECDHInit kexECDHInitMsg + if err = Unmarshal(packet, &kexECDHInit); err != nil { + return nil, err + } + + clientX, clientY, err := unmarshalECKey(kex.curve, kexECDHInit.ClientPubKey) + if err != nil { + return nil, err + } + + // We could cache this key across multiple users/multiple + // connection attempts, but the benefit is small. OpenSSH + // generates a new key for each incoming connection. + ephKey, err := ecdsa.GenerateKey(kex.curve, rand) + if err != nil { + return nil, err + } + + hostKeyBytes := priv.PublicKey().Marshal() + + serializedEphKey := elliptic.Marshal(kex.curve, ephKey.PublicKey.X, ephKey.PublicKey.Y) + + // generate shared secret + secret, _ := kex.curve.ScalarMult(clientX, clientY, ephKey.D.Bytes()) + + h := ecHash(kex.curve).New() + magics.write(h) + writeString(h, hostKeyBytes) + writeString(h, kexECDHInit.ClientPubKey) + writeString(h, serializedEphKey) + + K := make([]byte, intLength(secret)) + marshalInt(K, secret) + h.Write(K) + + H := h.Sum(nil) + + // H is already a hash, but the hostkey signing will apply its + // own key-specific hash algorithm. + sig, err := signAndMarshal(priv, rand, H) + if err != nil { + return nil, err + } + + reply := kexECDHReplyMsg{ + EphemeralPubKey: serializedEphKey, + HostKey: hostKeyBytes, + Signature: sig, + } + + serialized := Marshal(&reply) + if err := c.writePacket(serialized); err != nil { + return nil, err + } + + return &kexResult{ + H: H, + K: K, + HostKey: reply.HostKey, + Signature: sig, + Hash: ecHash(kex.curve), + }, nil +} + +var kexAlgoMap = map[string]kexAlgorithm{} + +func init() { + // This is the group called diffie-hellman-group1-sha1 in RFC + // 4253 and Oakley Group 2 in RFC 2409. + p, _ := new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF", 16) + kexAlgoMap[kexAlgoDH1SHA1] = &dhGroup{ + g: new(big.Int).SetInt64(2), + p: p, + pMinus1: new(big.Int).Sub(p, bigOne), + } + + // This is the group called diffie-hellman-group14-sha1 in RFC + // 4253 and Oakley Group 14 in RFC 3526. + p, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF", 16) + + kexAlgoMap[kexAlgoDH14SHA1] = &dhGroup{ + g: new(big.Int).SetInt64(2), + p: p, + pMinus1: new(big.Int).Sub(p, bigOne), + } + + kexAlgoMap[kexAlgoECDH521] = &ecdh{elliptic.P521()} + kexAlgoMap[kexAlgoECDH384] = &ecdh{elliptic.P384()} + kexAlgoMap[kexAlgoECDH256] = &ecdh{elliptic.P256()} + kexAlgoMap[kexAlgoCurve25519SHA256] = &curve25519sha256{} + kexAlgoMap[kexAlgoDHGEXSHA1] = &dhGEXSHA{hashFunc: crypto.SHA1} + kexAlgoMap[kexAlgoDHGEXSHA256] = &dhGEXSHA{hashFunc: crypto.SHA256} +} + +// curve25519sha256 implements the curve25519-sha256@libssh.org key +// agreement protocol, as described in +// https://git.libssh.org/projects/libssh.git/tree/doc/curve25519-sha256@libssh.org.txt +type curve25519sha256 struct{} + +type curve25519KeyPair struct { + priv [32]byte + pub [32]byte +} + +func (kp *curve25519KeyPair) generate(rand io.Reader) error { + if _, err := io.ReadFull(rand, kp.priv[:]); err != nil { + return err + } + curve25519.ScalarBaseMult(&kp.pub, &kp.priv) + return nil +} + +// curve25519Zeros is just an array of 32 zero bytes so that we have something +// convenient to compare against in order to reject curve25519 points with the +// wrong order. +var curve25519Zeros [32]byte + +func (kex *curve25519sha256) Client(c packetConn, rand io.Reader, magics *handshakeMagics) (*kexResult, error) { + var kp curve25519KeyPair + if err := kp.generate(rand); err != nil { + return nil, err + } + if err := c.writePacket(Marshal(&kexECDHInitMsg{kp.pub[:]})); err != nil { + return nil, err + } + + packet, err := c.readPacket() + if err != nil { + return nil, err + } + + var reply kexECDHReplyMsg + if err = Unmarshal(packet, &reply); err != nil { + return nil, err + } + if len(reply.EphemeralPubKey) != 32 { + return nil, errors.New("ssh: peer's curve25519 public value has wrong length") + } + + var servPub, secret [32]byte + copy(servPub[:], reply.EphemeralPubKey) + curve25519.ScalarMult(&secret, &kp.priv, &servPub) + if subtle.ConstantTimeCompare(secret[:], curve25519Zeros[:]) == 1 { + return nil, errors.New("ssh: peer's curve25519 public value has wrong order") + } + + h := crypto.SHA256.New() + magics.write(h) + writeString(h, reply.HostKey) + writeString(h, kp.pub[:]) + writeString(h, reply.EphemeralPubKey) + + ki := new(big.Int).SetBytes(secret[:]) + K := make([]byte, intLength(ki)) + marshalInt(K, ki) + h.Write(K) + + return &kexResult{ + H: h.Sum(nil), + K: K, + HostKey: reply.HostKey, + Signature: reply.Signature, + Hash: crypto.SHA256, + }, nil +} + +func (kex *curve25519sha256) Server(c packetConn, rand io.Reader, magics *handshakeMagics, priv Signer) (result *kexResult, err error) { + packet, err := c.readPacket() + if err != nil { + return + } + var kexInit kexECDHInitMsg + if err = Unmarshal(packet, &kexInit); err != nil { + return + } + + if len(kexInit.ClientPubKey) != 32 { + return nil, errors.New("ssh: peer's curve25519 public value has wrong length") + } + + var kp curve25519KeyPair + if err := kp.generate(rand); err != nil { + return nil, err + } + + var clientPub, secret [32]byte + copy(clientPub[:], kexInit.ClientPubKey) + curve25519.ScalarMult(&secret, &kp.priv, &clientPub) + if subtle.ConstantTimeCompare(secret[:], curve25519Zeros[:]) == 1 { + return nil, errors.New("ssh: peer's curve25519 public value has wrong order") + } + + hostKeyBytes := priv.PublicKey().Marshal() + + h := crypto.SHA256.New() + magics.write(h) + writeString(h, hostKeyBytes) + writeString(h, kexInit.ClientPubKey) + writeString(h, kp.pub[:]) + + ki := new(big.Int).SetBytes(secret[:]) + K := make([]byte, intLength(ki)) + marshalInt(K, ki) + h.Write(K) + + H := h.Sum(nil) + + sig, err := signAndMarshal(priv, rand, H) + if err != nil { + return nil, err + } + + reply := kexECDHReplyMsg{ + EphemeralPubKey: kp.pub[:], + HostKey: hostKeyBytes, + Signature: sig, + } + if err := c.writePacket(Marshal(&reply)); err != nil { + return nil, err + } + return &kexResult{ + H: H, + K: K, + HostKey: hostKeyBytes, + Signature: sig, + Hash: crypto.SHA256, + }, nil +} + +// dhGEXSHA implements the diffie-hellman-group-exchange-sha1 and +// diffie-hellman-group-exchange-sha256 key agreement protocols, +// as described in RFC 4419 +type dhGEXSHA struct { + g, p *big.Int + hashFunc crypto.Hash +} + +const numMRTests = 64 + +const ( + dhGroupExchangeMinimumBits = 2048 + dhGroupExchangePreferredBits = 2048 + dhGroupExchangeMaximumBits = 8192 +) + +func (gex *dhGEXSHA) diffieHellman(theirPublic, myPrivate *big.Int) (*big.Int, error) { + if theirPublic.Sign() <= 0 || theirPublic.Cmp(gex.p) >= 0 { + return nil, fmt.Errorf("ssh: DH parameter out of bounds") + } + return new(big.Int).Exp(theirPublic, myPrivate, gex.p), nil +} + +func (gex *dhGEXSHA) Client(c packetConn, randSource io.Reader, magics *handshakeMagics) (*kexResult, error) { + // Send GexRequest + kexDHGexRequest := kexDHGexRequestMsg{ + MinBits: dhGroupExchangeMinimumBits, + PreferedBits: dhGroupExchangePreferredBits, + MaxBits: dhGroupExchangeMaximumBits, + } + if err := c.writePacket(Marshal(&kexDHGexRequest)); err != nil { + return nil, err + } + + // Receive GexGroup + packet, err := c.readPacket() + if err != nil { + return nil, err + } + + var kexDHGexGroup kexDHGexGroupMsg + if err = Unmarshal(packet, &kexDHGexGroup); err != nil { + return nil, err + } + + // reject if p's bit length < dhGroupExchangeMinimumBits or > dhGroupExchangeMaximumBits + if kexDHGexGroup.P.BitLen() < dhGroupExchangeMinimumBits || kexDHGexGroup.P.BitLen() > dhGroupExchangeMaximumBits { + return nil, fmt.Errorf("ssh: server-generated gex p is out of range (%d bits)", kexDHGexGroup.P.BitLen()) + } + + gex.p = kexDHGexGroup.P + gex.g = kexDHGexGroup.G + + // Check if p is safe by verifing that p and (p-1)/2 are primes + one := big.NewInt(1) + var pHalf = &big.Int{} + pHalf.Rsh(gex.p, 1) + if !gex.p.ProbablyPrime(numMRTests) || !pHalf.ProbablyPrime(numMRTests) { + return nil, fmt.Errorf("ssh: server provided gex p is not safe") + } + + // Check if g is safe by verifing that g > 1 and g < p - 1 + var pMinusOne = &big.Int{} + pMinusOne.Sub(gex.p, one) + if gex.g.Cmp(one) != 1 && gex.g.Cmp(pMinusOne) != -1 { + return nil, fmt.Errorf("ssh: server provided gex g is not safe") + } + + // Send GexInit + x, err := rand.Int(randSource, pHalf) + if err != nil { + return nil, err + } + X := new(big.Int).Exp(gex.g, x, gex.p) + kexDHGexInit := kexDHGexInitMsg{ + X: X, + } + if err := c.writePacket(Marshal(&kexDHGexInit)); err != nil { + return nil, err + } + + // Receive GexReply + packet, err = c.readPacket() + if err != nil { + return nil, err + } + + var kexDHGexReply kexDHGexReplyMsg + if err = Unmarshal(packet, &kexDHGexReply); err != nil { + return nil, err + } + + kInt, err := gex.diffieHellman(kexDHGexReply.Y, x) + if err != nil { + return nil, err + } + + // Check if k is safe by verifing that k > 1 and k < p - 1 + if kInt.Cmp(one) != 1 && kInt.Cmp(pMinusOne) != -1 { + return nil, fmt.Errorf("ssh: derived k is not safe") + } + + h := gex.hashFunc.New() + magics.write(h) + writeString(h, kexDHGexReply.HostKey) + binary.Write(h, binary.BigEndian, uint32(dhGroupExchangeMinimumBits)) + binary.Write(h, binary.BigEndian, uint32(dhGroupExchangePreferredBits)) + binary.Write(h, binary.BigEndian, uint32(dhGroupExchangeMaximumBits)) + writeInt(h, gex.p) + writeInt(h, gex.g) + writeInt(h, X) + writeInt(h, kexDHGexReply.Y) + K := make([]byte, intLength(kInt)) + marshalInt(K, kInt) + h.Write(K) + + return &kexResult{ + H: h.Sum(nil), + K: K, + HostKey: kexDHGexReply.HostKey, + Signature: kexDHGexReply.Signature, + Hash: gex.hashFunc, + }, nil +} + +// Server half implementation of the Diffie Hellman Key Exchange with SHA1 and SHA256. +// +// This is a minimal implementation to satisfy the automated tests. +func (gex *dhGEXSHA) Server(c packetConn, randSource io.Reader, magics *handshakeMagics, priv Signer) (result *kexResult, err error) { + // Receive GexRequest + packet, err := c.readPacket() + if err != nil { + return + } + var kexDHGexRequest kexDHGexRequestMsg + if err = Unmarshal(packet, &kexDHGexRequest); err != nil { + return + } + + // smoosh the user's preferred size into our own limits + if kexDHGexRequest.PreferedBits > dhGroupExchangeMaximumBits { + kexDHGexRequest.PreferedBits = dhGroupExchangeMaximumBits + } + if kexDHGexRequest.PreferedBits < dhGroupExchangeMinimumBits { + kexDHGexRequest.PreferedBits = dhGroupExchangeMinimumBits + } + // fix min/max if they're inconsistent. technically, we could just pout + // and hang up, but there's no harm in giving them the benefit of the + // doubt and just picking a bitsize for them. + if kexDHGexRequest.MinBits > kexDHGexRequest.PreferedBits { + kexDHGexRequest.MinBits = kexDHGexRequest.PreferedBits + } + if kexDHGexRequest.MaxBits < kexDHGexRequest.PreferedBits { + kexDHGexRequest.MaxBits = kexDHGexRequest.PreferedBits + } + + // Send GexGroup + // This is the group called diffie-hellman-group14-sha1 in RFC + // 4253 and Oakley Group 14 in RFC 3526. + p, _ := new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF", 16) + gex.p = p + gex.g = big.NewInt(2) + + kexDHGexGroup := kexDHGexGroupMsg{ + P: gex.p, + G: gex.g, + } + if err := c.writePacket(Marshal(&kexDHGexGroup)); err != nil { + return nil, err + } + + // Receive GexInit + packet, err = c.readPacket() + if err != nil { + return + } + var kexDHGexInit kexDHGexInitMsg + if err = Unmarshal(packet, &kexDHGexInit); err != nil { + return + } + + var pHalf = &big.Int{} + pHalf.Rsh(gex.p, 1) + + y, err := rand.Int(randSource, pHalf) + if err != nil { + return + } + + Y := new(big.Int).Exp(gex.g, y, gex.p) + kInt, err := gex.diffieHellman(kexDHGexInit.X, y) + if err != nil { + return nil, err + } + + hostKeyBytes := priv.PublicKey().Marshal() + + h := gex.hashFunc.New() + magics.write(h) + writeString(h, hostKeyBytes) + binary.Write(h, binary.BigEndian, uint32(dhGroupExchangeMinimumBits)) + binary.Write(h, binary.BigEndian, uint32(dhGroupExchangePreferredBits)) + binary.Write(h, binary.BigEndian, uint32(dhGroupExchangeMaximumBits)) + writeInt(h, gex.p) + writeInt(h, gex.g) + writeInt(h, kexDHGexInit.X) + writeInt(h, Y) + + K := make([]byte, intLength(kInt)) + marshalInt(K, kInt) + h.Write(K) + + H := h.Sum(nil) + + // H is already a hash, but the hostkey signing will apply its + // own key-specific hash algorithm. + sig, err := signAndMarshal(priv, randSource, H) + if err != nil { + return nil, err + } + + kexDHGexReply := kexDHGexReplyMsg{ + HostKey: hostKeyBytes, + Y: Y, + Signature: sig, + } + packet = Marshal(&kexDHGexReply) + + err = c.writePacket(packet) + + return &kexResult{ + H: H, + K: K, + HostKey: hostKeyBytes, + Signature: sig, + Hash: gex.hashFunc, + }, err +} diff --git a/vendor/golang.org/x/crypto/ssh/keys.go b/vendor/golang.org/x/crypto/ssh/keys.go new file mode 100644 index 00000000000..5377ec8c3b5 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/keys.go @@ -0,0 +1,1403 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bytes" + "crypto" + "crypto/aes" + "crypto/cipher" + "crypto/dsa" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/md5" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/asn1" + "encoding/base64" + "encoding/hex" + "encoding/pem" + "errors" + "fmt" + "io" + "math/big" + "strings" + + "golang.org/x/crypto/ed25519" + "golang.org/x/crypto/ssh/internal/bcrypt_pbkdf" +) + +// These constants represent the algorithm names for key types supported by this +// package. +const ( + KeyAlgoRSA = "ssh-rsa" + KeyAlgoDSA = "ssh-dss" + KeyAlgoECDSA256 = "ecdsa-sha2-nistp256" + KeyAlgoSKECDSA256 = "sk-ecdsa-sha2-nistp256@openssh.com" + KeyAlgoECDSA384 = "ecdsa-sha2-nistp384" + KeyAlgoECDSA521 = "ecdsa-sha2-nistp521" + KeyAlgoED25519 = "ssh-ed25519" + KeyAlgoSKED25519 = "sk-ssh-ed25519@openssh.com" +) + +// These constants represent non-default signature algorithms that are supported +// as algorithm parameters to AlgorithmSigner.SignWithAlgorithm methods. See +// [PROTOCOL.agent] section 4.5.1 and +// https://tools.ietf.org/html/draft-ietf-curdle-rsa-sha2-10 +const ( + SigAlgoRSA = "ssh-rsa" + SigAlgoRSASHA2256 = "rsa-sha2-256" + SigAlgoRSASHA2512 = "rsa-sha2-512" +) + +// parsePubKey parses a public key of the given algorithm. +// Use ParsePublicKey for keys with prepended algorithm. +func parsePubKey(in []byte, algo string) (pubKey PublicKey, rest []byte, err error) { + switch algo { + case KeyAlgoRSA: + return parseRSA(in) + case KeyAlgoDSA: + return parseDSA(in) + case KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521: + return parseECDSA(in) + case KeyAlgoSKECDSA256: + return parseSKECDSA(in) + case KeyAlgoED25519: + return parseED25519(in) + case KeyAlgoSKED25519: + return parseSKEd25519(in) + case CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01, CertAlgoSKECDSA256v01, CertAlgoED25519v01, CertAlgoSKED25519v01: + cert, err := parseCert(in, certToPrivAlgo(algo)) + if err != nil { + return nil, nil, err + } + return cert, nil, nil + } + return nil, nil, fmt.Errorf("ssh: unknown key algorithm: %v", algo) +} + +// parseAuthorizedKey parses a public key in OpenSSH authorized_keys format +// (see sshd(8) manual page) once the options and key type fields have been +// removed. +func parseAuthorizedKey(in []byte) (out PublicKey, comment string, err error) { + in = bytes.TrimSpace(in) + + i := bytes.IndexAny(in, " \t") + if i == -1 { + i = len(in) + } + base64Key := in[:i] + + key := make([]byte, base64.StdEncoding.DecodedLen(len(base64Key))) + n, err := base64.StdEncoding.Decode(key, base64Key) + if err != nil { + return nil, "", err + } + key = key[:n] + out, err = ParsePublicKey(key) + if err != nil { + return nil, "", err + } + comment = string(bytes.TrimSpace(in[i:])) + return out, comment, nil +} + +// ParseKnownHosts parses an entry in the format of the known_hosts file. +// +// The known_hosts format is documented in the sshd(8) manual page. This +// function will parse a single entry from in. On successful return, marker +// will contain the optional marker value (i.e. "cert-authority" or "revoked") +// or else be empty, hosts will contain the hosts that this entry matches, +// pubKey will contain the public key and comment will contain any trailing +// comment at the end of the line. See the sshd(8) manual page for the various +// forms that a host string can take. +// +// The unparsed remainder of the input will be returned in rest. This function +// can be called repeatedly to parse multiple entries. +// +// If no entries were found in the input then err will be io.EOF. Otherwise a +// non-nil err value indicates a parse error. +func ParseKnownHosts(in []byte) (marker string, hosts []string, pubKey PublicKey, comment string, rest []byte, err error) { + for len(in) > 0 { + end := bytes.IndexByte(in, '\n') + if end != -1 { + rest = in[end+1:] + in = in[:end] + } else { + rest = nil + } + + end = bytes.IndexByte(in, '\r') + if end != -1 { + in = in[:end] + } + + in = bytes.TrimSpace(in) + if len(in) == 0 || in[0] == '#' { + in = rest + continue + } + + i := bytes.IndexAny(in, " \t") + if i == -1 { + in = rest + continue + } + + // Strip out the beginning of the known_host key. + // This is either an optional marker or a (set of) hostname(s). + keyFields := bytes.Fields(in) + if len(keyFields) < 3 || len(keyFields) > 5 { + return "", nil, nil, "", nil, errors.New("ssh: invalid entry in known_hosts data") + } + + // keyFields[0] is either "@cert-authority", "@revoked" or a comma separated + // list of hosts + marker := "" + if keyFields[0][0] == '@' { + marker = string(keyFields[0][1:]) + keyFields = keyFields[1:] + } + + hosts := string(keyFields[0]) + // keyFields[1] contains the key type (e.g. “ssh-rsa”). + // However, that information is duplicated inside the + // base64-encoded key and so is ignored here. + + key := bytes.Join(keyFields[2:], []byte(" ")) + if pubKey, comment, err = parseAuthorizedKey(key); err != nil { + return "", nil, nil, "", nil, err + } + + return marker, strings.Split(hosts, ","), pubKey, comment, rest, nil + } + + return "", nil, nil, "", nil, io.EOF +} + +// ParseAuthorizedKeys parses a public key from an authorized_keys +// file used in OpenSSH according to the sshd(8) manual page. +func ParseAuthorizedKey(in []byte) (out PublicKey, comment string, options []string, rest []byte, err error) { + for len(in) > 0 { + end := bytes.IndexByte(in, '\n') + if end != -1 { + rest = in[end+1:] + in = in[:end] + } else { + rest = nil + } + + end = bytes.IndexByte(in, '\r') + if end != -1 { + in = in[:end] + } + + in = bytes.TrimSpace(in) + if len(in) == 0 || in[0] == '#' { + in = rest + continue + } + + i := bytes.IndexAny(in, " \t") + if i == -1 { + in = rest + continue + } + + if out, comment, err = parseAuthorizedKey(in[i:]); err == nil { + return out, comment, options, rest, nil + } + + // No key type recognised. Maybe there's an options field at + // the beginning. + var b byte + inQuote := false + var candidateOptions []string + optionStart := 0 + for i, b = range in { + isEnd := !inQuote && (b == ' ' || b == '\t') + if (b == ',' && !inQuote) || isEnd { + if i-optionStart > 0 { + candidateOptions = append(candidateOptions, string(in[optionStart:i])) + } + optionStart = i + 1 + } + if isEnd { + break + } + if b == '"' && (i == 0 || (i > 0 && in[i-1] != '\\')) { + inQuote = !inQuote + } + } + for i < len(in) && (in[i] == ' ' || in[i] == '\t') { + i++ + } + if i == len(in) { + // Invalid line: unmatched quote + in = rest + continue + } + + in = in[i:] + i = bytes.IndexAny(in, " \t") + if i == -1 { + in = rest + continue + } + + if out, comment, err = parseAuthorizedKey(in[i:]); err == nil { + options = candidateOptions + return out, comment, options, rest, nil + } + + in = rest + continue + } + + return nil, "", nil, nil, errors.New("ssh: no key found") +} + +// ParsePublicKey parses an SSH public key formatted for use in +// the SSH wire protocol according to RFC 4253, section 6.6. +func ParsePublicKey(in []byte) (out PublicKey, err error) { + algo, in, ok := parseString(in) + if !ok { + return nil, errShortRead + } + var rest []byte + out, rest, err = parsePubKey(in, string(algo)) + if len(rest) > 0 { + return nil, errors.New("ssh: trailing junk in public key") + } + + return out, err +} + +// MarshalAuthorizedKey serializes key for inclusion in an OpenSSH +// authorized_keys file. The return value ends with newline. +func MarshalAuthorizedKey(key PublicKey) []byte { + b := &bytes.Buffer{} + b.WriteString(key.Type()) + b.WriteByte(' ') + e := base64.NewEncoder(base64.StdEncoding, b) + e.Write(key.Marshal()) + e.Close() + b.WriteByte('\n') + return b.Bytes() +} + +// PublicKey is an abstraction of different types of public keys. +type PublicKey interface { + // Type returns the key's type, e.g. "ssh-rsa". + Type() string + + // Marshal returns the serialized key data in SSH wire format, + // with the name prefix. To unmarshal the returned data, use + // the ParsePublicKey function. + Marshal() []byte + + // Verify that sig is a signature on the given data using this + // key. This function will hash the data appropriately first. + Verify(data []byte, sig *Signature) error +} + +// CryptoPublicKey, if implemented by a PublicKey, +// returns the underlying crypto.PublicKey form of the key. +type CryptoPublicKey interface { + CryptoPublicKey() crypto.PublicKey +} + +// A Signer can create signatures that verify against a public key. +type Signer interface { + // PublicKey returns an associated PublicKey instance. + PublicKey() PublicKey + + // Sign returns raw signature for the given data. This method + // will apply the hash specified for the keytype to the data. + Sign(rand io.Reader, data []byte) (*Signature, error) +} + +// A AlgorithmSigner is a Signer that also supports specifying a specific +// algorithm to use for signing. +type AlgorithmSigner interface { + Signer + + // SignWithAlgorithm is like Signer.Sign, but allows specification of a + // non-default signing algorithm. See the SigAlgo* constants in this + // package for signature algorithms supported by this package. Callers may + // pass an empty string for the algorithm in which case the AlgorithmSigner + // will use its default algorithm. + SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*Signature, error) +} + +type rsaPublicKey rsa.PublicKey + +func (r *rsaPublicKey) Type() string { + return "ssh-rsa" +} + +// parseRSA parses an RSA key according to RFC 4253, section 6.6. +func parseRSA(in []byte) (out PublicKey, rest []byte, err error) { + var w struct { + E *big.Int + N *big.Int + Rest []byte `ssh:"rest"` + } + if err := Unmarshal(in, &w); err != nil { + return nil, nil, err + } + + if w.E.BitLen() > 24 { + return nil, nil, errors.New("ssh: exponent too large") + } + e := w.E.Int64() + if e < 3 || e&1 == 0 { + return nil, nil, errors.New("ssh: incorrect exponent") + } + + var key rsa.PublicKey + key.E = int(e) + key.N = w.N + return (*rsaPublicKey)(&key), w.Rest, nil +} + +func (r *rsaPublicKey) Marshal() []byte { + e := new(big.Int).SetInt64(int64(r.E)) + // RSA publickey struct layout should match the struct used by + // parseRSACert in the x/crypto/ssh/agent package. + wirekey := struct { + Name string + E *big.Int + N *big.Int + }{ + KeyAlgoRSA, + e, + r.N, + } + return Marshal(&wirekey) +} + +func (r *rsaPublicKey) Verify(data []byte, sig *Signature) error { + var hash crypto.Hash + switch sig.Format { + case SigAlgoRSA: + hash = crypto.SHA1 + case SigAlgoRSASHA2256: + hash = crypto.SHA256 + case SigAlgoRSASHA2512: + hash = crypto.SHA512 + default: + return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, r.Type()) + } + h := hash.New() + h.Write(data) + digest := h.Sum(nil) + return rsa.VerifyPKCS1v15((*rsa.PublicKey)(r), hash, digest, sig.Blob) +} + +func (r *rsaPublicKey) CryptoPublicKey() crypto.PublicKey { + return (*rsa.PublicKey)(r) +} + +type dsaPublicKey dsa.PublicKey + +func (k *dsaPublicKey) Type() string { + return "ssh-dss" +} + +func checkDSAParams(param *dsa.Parameters) error { + // SSH specifies FIPS 186-2, which only provided a single size + // (1024 bits) DSA key. FIPS 186-3 allows for larger key + // sizes, which would confuse SSH. + if l := param.P.BitLen(); l != 1024 { + return fmt.Errorf("ssh: unsupported DSA key size %d", l) + } + + return nil +} + +// parseDSA parses an DSA key according to RFC 4253, section 6.6. +func parseDSA(in []byte) (out PublicKey, rest []byte, err error) { + var w struct { + P, Q, G, Y *big.Int + Rest []byte `ssh:"rest"` + } + if err := Unmarshal(in, &w); err != nil { + return nil, nil, err + } + + param := dsa.Parameters{ + P: w.P, + Q: w.Q, + G: w.G, + } + if err := checkDSAParams(¶m); err != nil { + return nil, nil, err + } + + key := &dsaPublicKey{ + Parameters: param, + Y: w.Y, + } + return key, w.Rest, nil +} + +func (k *dsaPublicKey) Marshal() []byte { + // DSA publickey struct layout should match the struct used by + // parseDSACert in the x/crypto/ssh/agent package. + w := struct { + Name string + P, Q, G, Y *big.Int + }{ + k.Type(), + k.P, + k.Q, + k.G, + k.Y, + } + + return Marshal(&w) +} + +func (k *dsaPublicKey) Verify(data []byte, sig *Signature) error { + if sig.Format != k.Type() { + return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type()) + } + h := crypto.SHA1.New() + h.Write(data) + digest := h.Sum(nil) + + // Per RFC 4253, section 6.6, + // The value for 'dss_signature_blob' is encoded as a string containing + // r, followed by s (which are 160-bit integers, without lengths or + // padding, unsigned, and in network byte order). + // For DSS purposes, sig.Blob should be exactly 40 bytes in length. + if len(sig.Blob) != 40 { + return errors.New("ssh: DSA signature parse error") + } + r := new(big.Int).SetBytes(sig.Blob[:20]) + s := new(big.Int).SetBytes(sig.Blob[20:]) + if dsa.Verify((*dsa.PublicKey)(k), digest, r, s) { + return nil + } + return errors.New("ssh: signature did not verify") +} + +func (k *dsaPublicKey) CryptoPublicKey() crypto.PublicKey { + return (*dsa.PublicKey)(k) +} + +type dsaPrivateKey struct { + *dsa.PrivateKey +} + +func (k *dsaPrivateKey) PublicKey() PublicKey { + return (*dsaPublicKey)(&k.PrivateKey.PublicKey) +} + +func (k *dsaPrivateKey) Sign(rand io.Reader, data []byte) (*Signature, error) { + return k.SignWithAlgorithm(rand, data, "") +} + +func (k *dsaPrivateKey) SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*Signature, error) { + if algorithm != "" && algorithm != k.PublicKey().Type() { + return nil, fmt.Errorf("ssh: unsupported signature algorithm %s", algorithm) + } + + h := crypto.SHA1.New() + h.Write(data) + digest := h.Sum(nil) + r, s, err := dsa.Sign(rand, k.PrivateKey, digest) + if err != nil { + return nil, err + } + + sig := make([]byte, 40) + rb := r.Bytes() + sb := s.Bytes() + + copy(sig[20-len(rb):20], rb) + copy(sig[40-len(sb):], sb) + + return &Signature{ + Format: k.PublicKey().Type(), + Blob: sig, + }, nil +} + +type ecdsaPublicKey ecdsa.PublicKey + +func (k *ecdsaPublicKey) Type() string { + return "ecdsa-sha2-" + k.nistID() +} + +func (k *ecdsaPublicKey) nistID() string { + switch k.Params().BitSize { + case 256: + return "nistp256" + case 384: + return "nistp384" + case 521: + return "nistp521" + } + panic("ssh: unsupported ecdsa key size") +} + +type ed25519PublicKey ed25519.PublicKey + +func (k ed25519PublicKey) Type() string { + return KeyAlgoED25519 +} + +func parseED25519(in []byte) (out PublicKey, rest []byte, err error) { + var w struct { + KeyBytes []byte + Rest []byte `ssh:"rest"` + } + + if err := Unmarshal(in, &w); err != nil { + return nil, nil, err + } + + key := ed25519.PublicKey(w.KeyBytes) + + return (ed25519PublicKey)(key), w.Rest, nil +} + +func (k ed25519PublicKey) Marshal() []byte { + w := struct { + Name string + KeyBytes []byte + }{ + KeyAlgoED25519, + []byte(k), + } + return Marshal(&w) +} + +func (k ed25519PublicKey) Verify(b []byte, sig *Signature) error { + if sig.Format != k.Type() { + return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type()) + } + + edKey := (ed25519.PublicKey)(k) + if ok := ed25519.Verify(edKey, b, sig.Blob); !ok { + return errors.New("ssh: signature did not verify") + } + + return nil +} + +func (k ed25519PublicKey) CryptoPublicKey() crypto.PublicKey { + return ed25519.PublicKey(k) +} + +func supportedEllipticCurve(curve elliptic.Curve) bool { + return curve == elliptic.P256() || curve == elliptic.P384() || curve == elliptic.P521() +} + +// ecHash returns the hash to match the given elliptic curve, see RFC +// 5656, section 6.2.1 +func ecHash(curve elliptic.Curve) crypto.Hash { + bitSize := curve.Params().BitSize + switch { + case bitSize <= 256: + return crypto.SHA256 + case bitSize <= 384: + return crypto.SHA384 + } + return crypto.SHA512 +} + +// parseECDSA parses an ECDSA key according to RFC 5656, section 3.1. +func parseECDSA(in []byte) (out PublicKey, rest []byte, err error) { + var w struct { + Curve string + KeyBytes []byte + Rest []byte `ssh:"rest"` + } + + if err := Unmarshal(in, &w); err != nil { + return nil, nil, err + } + + key := new(ecdsa.PublicKey) + + switch w.Curve { + case "nistp256": + key.Curve = elliptic.P256() + case "nistp384": + key.Curve = elliptic.P384() + case "nistp521": + key.Curve = elliptic.P521() + default: + return nil, nil, errors.New("ssh: unsupported curve") + } + + key.X, key.Y = elliptic.Unmarshal(key.Curve, w.KeyBytes) + if key.X == nil || key.Y == nil { + return nil, nil, errors.New("ssh: invalid curve point") + } + return (*ecdsaPublicKey)(key), w.Rest, nil +} + +func (k *ecdsaPublicKey) Marshal() []byte { + // See RFC 5656, section 3.1. + keyBytes := elliptic.Marshal(k.Curve, k.X, k.Y) + // ECDSA publickey struct layout should match the struct used by + // parseECDSACert in the x/crypto/ssh/agent package. + w := struct { + Name string + ID string + Key []byte + }{ + k.Type(), + k.nistID(), + keyBytes, + } + + return Marshal(&w) +} + +func (k *ecdsaPublicKey) Verify(data []byte, sig *Signature) error { + if sig.Format != k.Type() { + return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type()) + } + + h := ecHash(k.Curve).New() + h.Write(data) + digest := h.Sum(nil) + + // Per RFC 5656, section 3.1.2, + // The ecdsa_signature_blob value has the following specific encoding: + // mpint r + // mpint s + var ecSig struct { + R *big.Int + S *big.Int + } + + if err := Unmarshal(sig.Blob, &ecSig); err != nil { + return err + } + + if ecdsa.Verify((*ecdsa.PublicKey)(k), digest, ecSig.R, ecSig.S) { + return nil + } + return errors.New("ssh: signature did not verify") +} + +func (k *ecdsaPublicKey) CryptoPublicKey() crypto.PublicKey { + return (*ecdsa.PublicKey)(k) +} + +// skFields holds the additional fields present in U2F/FIDO2 signatures. +// See openssh/PROTOCOL.u2f 'SSH U2F Signatures' for details. +type skFields struct { + // Flags contains U2F/FIDO2 flags such as 'user present' + Flags byte + // Counter is a monotonic signature counter which can be + // used to detect concurrent use of a private key, should + // it be extracted from hardware. + Counter uint32 +} + +type skECDSAPublicKey struct { + // application is a URL-like string, typically "ssh:" for SSH. + // see openssh/PROTOCOL.u2f for details. + application string + ecdsa.PublicKey +} + +func (k *skECDSAPublicKey) Type() string { + return KeyAlgoSKECDSA256 +} + +func (k *skECDSAPublicKey) nistID() string { + return "nistp256" +} + +func parseSKECDSA(in []byte) (out PublicKey, rest []byte, err error) { + var w struct { + Curve string + KeyBytes []byte + Application string + Rest []byte `ssh:"rest"` + } + + if err := Unmarshal(in, &w); err != nil { + return nil, nil, err + } + + key := new(skECDSAPublicKey) + key.application = w.Application + + if w.Curve != "nistp256" { + return nil, nil, errors.New("ssh: unsupported curve") + } + key.Curve = elliptic.P256() + + key.X, key.Y = elliptic.Unmarshal(key.Curve, w.KeyBytes) + if key.X == nil || key.Y == nil { + return nil, nil, errors.New("ssh: invalid curve point") + } + + return key, w.Rest, nil +} + +func (k *skECDSAPublicKey) Marshal() []byte { + // See RFC 5656, section 3.1. + keyBytes := elliptic.Marshal(k.Curve, k.X, k.Y) + w := struct { + Name string + ID string + Key []byte + Application string + }{ + k.Type(), + k.nistID(), + keyBytes, + k.application, + } + + return Marshal(&w) +} + +func (k *skECDSAPublicKey) Verify(data []byte, sig *Signature) error { + if sig.Format != k.Type() { + return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type()) + } + + h := ecHash(k.Curve).New() + h.Write([]byte(k.application)) + appDigest := h.Sum(nil) + + h.Reset() + h.Write(data) + dataDigest := h.Sum(nil) + + var ecSig struct { + R *big.Int + S *big.Int + } + if err := Unmarshal(sig.Blob, &ecSig); err != nil { + return err + } + + var skf skFields + if err := Unmarshal(sig.Rest, &skf); err != nil { + return err + } + + blob := struct { + ApplicationDigest []byte `ssh:"rest"` + Flags byte + Counter uint32 + MessageDigest []byte `ssh:"rest"` + }{ + appDigest, + skf.Flags, + skf.Counter, + dataDigest, + } + + original := Marshal(blob) + + h.Reset() + h.Write(original) + digest := h.Sum(nil) + + if ecdsa.Verify((*ecdsa.PublicKey)(&k.PublicKey), digest, ecSig.R, ecSig.S) { + return nil + } + return errors.New("ssh: signature did not verify") +} + +type skEd25519PublicKey struct { + // application is a URL-like string, typically "ssh:" for SSH. + // see openssh/PROTOCOL.u2f for details. + application string + ed25519.PublicKey +} + +func (k *skEd25519PublicKey) Type() string { + return KeyAlgoSKED25519 +} + +func parseSKEd25519(in []byte) (out PublicKey, rest []byte, err error) { + var w struct { + KeyBytes []byte + Application string + Rest []byte `ssh:"rest"` + } + + if err := Unmarshal(in, &w); err != nil { + return nil, nil, err + } + + key := new(skEd25519PublicKey) + key.application = w.Application + key.PublicKey = ed25519.PublicKey(w.KeyBytes) + + return key, w.Rest, nil +} + +func (k *skEd25519PublicKey) Marshal() []byte { + w := struct { + Name string + KeyBytes []byte + Application string + }{ + KeyAlgoSKED25519, + []byte(k.PublicKey), + k.application, + } + return Marshal(&w) +} + +func (k *skEd25519PublicKey) Verify(data []byte, sig *Signature) error { + if sig.Format != k.Type() { + return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type()) + } + + h := sha256.New() + h.Write([]byte(k.application)) + appDigest := h.Sum(nil) + + h.Reset() + h.Write(data) + dataDigest := h.Sum(nil) + + var edSig struct { + Signature []byte `ssh:"rest"` + } + + if err := Unmarshal(sig.Blob, &edSig); err != nil { + return err + } + + var skf skFields + if err := Unmarshal(sig.Rest, &skf); err != nil { + return err + } + + blob := struct { + ApplicationDigest []byte `ssh:"rest"` + Flags byte + Counter uint32 + MessageDigest []byte `ssh:"rest"` + }{ + appDigest, + skf.Flags, + skf.Counter, + dataDigest, + } + + original := Marshal(blob) + + edKey := (ed25519.PublicKey)(k.PublicKey) + if ok := ed25519.Verify(edKey, original, edSig.Signature); !ok { + return errors.New("ssh: signature did not verify") + } + + return nil +} + +// NewSignerFromKey takes an *rsa.PrivateKey, *dsa.PrivateKey, +// *ecdsa.PrivateKey or any other crypto.Signer and returns a +// corresponding Signer instance. ECDSA keys must use P-256, P-384 or +// P-521. DSA keys must use parameter size L1024N160. +func NewSignerFromKey(key interface{}) (Signer, error) { + switch key := key.(type) { + case crypto.Signer: + return NewSignerFromSigner(key) + case *dsa.PrivateKey: + return newDSAPrivateKey(key) + default: + return nil, fmt.Errorf("ssh: unsupported key type %T", key) + } +} + +func newDSAPrivateKey(key *dsa.PrivateKey) (Signer, error) { + if err := checkDSAParams(&key.PublicKey.Parameters); err != nil { + return nil, err + } + + return &dsaPrivateKey{key}, nil +} + +type wrappedSigner struct { + signer crypto.Signer + pubKey PublicKey +} + +// NewSignerFromSigner takes any crypto.Signer implementation and +// returns a corresponding Signer interface. This can be used, for +// example, with keys kept in hardware modules. +func NewSignerFromSigner(signer crypto.Signer) (Signer, error) { + pubKey, err := NewPublicKey(signer.Public()) + if err != nil { + return nil, err + } + + return &wrappedSigner{signer, pubKey}, nil +} + +func (s *wrappedSigner) PublicKey() PublicKey { + return s.pubKey +} + +func (s *wrappedSigner) Sign(rand io.Reader, data []byte) (*Signature, error) { + return s.SignWithAlgorithm(rand, data, "") +} + +func (s *wrappedSigner) SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*Signature, error) { + var hashFunc crypto.Hash + + if _, ok := s.pubKey.(*rsaPublicKey); ok { + // RSA keys support a few hash functions determined by the requested signature algorithm + switch algorithm { + case "", SigAlgoRSA: + algorithm = SigAlgoRSA + hashFunc = crypto.SHA1 + case SigAlgoRSASHA2256: + hashFunc = crypto.SHA256 + case SigAlgoRSASHA2512: + hashFunc = crypto.SHA512 + default: + return nil, fmt.Errorf("ssh: unsupported signature algorithm %s", algorithm) + } + } else { + // The only supported algorithm for all other key types is the same as the type of the key + if algorithm == "" { + algorithm = s.pubKey.Type() + } else if algorithm != s.pubKey.Type() { + return nil, fmt.Errorf("ssh: unsupported signature algorithm %s", algorithm) + } + + switch key := s.pubKey.(type) { + case *dsaPublicKey: + hashFunc = crypto.SHA1 + case *ecdsaPublicKey: + hashFunc = ecHash(key.Curve) + case ed25519PublicKey: + default: + return nil, fmt.Errorf("ssh: unsupported key type %T", key) + } + } + + var digest []byte + if hashFunc != 0 { + h := hashFunc.New() + h.Write(data) + digest = h.Sum(nil) + } else { + digest = data + } + + signature, err := s.signer.Sign(rand, digest, hashFunc) + if err != nil { + return nil, err + } + + // crypto.Signer.Sign is expected to return an ASN.1-encoded signature + // for ECDSA and DSA, but that's not the encoding expected by SSH, so + // re-encode. + switch s.pubKey.(type) { + case *ecdsaPublicKey, *dsaPublicKey: + type asn1Signature struct { + R, S *big.Int + } + asn1Sig := new(asn1Signature) + _, err := asn1.Unmarshal(signature, asn1Sig) + if err != nil { + return nil, err + } + + switch s.pubKey.(type) { + case *ecdsaPublicKey: + signature = Marshal(asn1Sig) + + case *dsaPublicKey: + signature = make([]byte, 40) + r := asn1Sig.R.Bytes() + s := asn1Sig.S.Bytes() + copy(signature[20-len(r):20], r) + copy(signature[40-len(s):40], s) + } + } + + return &Signature{ + Format: algorithm, + Blob: signature, + }, nil +} + +// NewPublicKey takes an *rsa.PublicKey, *dsa.PublicKey, *ecdsa.PublicKey, +// or ed25519.PublicKey returns a corresponding PublicKey instance. +// ECDSA keys must use P-256, P-384 or P-521. +func NewPublicKey(key interface{}) (PublicKey, error) { + switch key := key.(type) { + case *rsa.PublicKey: + return (*rsaPublicKey)(key), nil + case *ecdsa.PublicKey: + if !supportedEllipticCurve(key.Curve) { + return nil, errors.New("ssh: only P-256, P-384 and P-521 EC keys are supported") + } + return (*ecdsaPublicKey)(key), nil + case *dsa.PublicKey: + return (*dsaPublicKey)(key), nil + case ed25519.PublicKey: + return (ed25519PublicKey)(key), nil + default: + return nil, fmt.Errorf("ssh: unsupported key type %T", key) + } +} + +// ParsePrivateKey returns a Signer from a PEM encoded private key. It supports +// the same keys as ParseRawPrivateKey. If the private key is encrypted, it +// will return a PassphraseMissingError. +func ParsePrivateKey(pemBytes []byte) (Signer, error) { + key, err := ParseRawPrivateKey(pemBytes) + if err != nil { + return nil, err + } + + return NewSignerFromKey(key) +} + +// ParsePrivateKeyWithPassphrase returns a Signer from a PEM encoded private +// key and passphrase. It supports the same keys as +// ParseRawPrivateKeyWithPassphrase. +func ParsePrivateKeyWithPassphrase(pemBytes, passphrase []byte) (Signer, error) { + key, err := ParseRawPrivateKeyWithPassphrase(pemBytes, passphrase) + if err != nil { + return nil, err + } + + return NewSignerFromKey(key) +} + +// encryptedBlock tells whether a private key is +// encrypted by examining its Proc-Type header +// for a mention of ENCRYPTED +// according to RFC 1421 Section 4.6.1.1. +func encryptedBlock(block *pem.Block) bool { + return strings.Contains(block.Headers["Proc-Type"], "ENCRYPTED") +} + +// A PassphraseMissingError indicates that parsing this private key requires a +// passphrase. Use ParsePrivateKeyWithPassphrase. +type PassphraseMissingError struct { + // PublicKey will be set if the private key format includes an unencrypted + // public key along with the encrypted private key. + PublicKey PublicKey +} + +func (*PassphraseMissingError) Error() string { + return "ssh: this private key is passphrase protected" +} + +// ParseRawPrivateKey returns a private key from a PEM encoded private key. It +// supports RSA (PKCS#1), PKCS#8, DSA (OpenSSL), and ECDSA private keys. If the +// private key is encrypted, it will return a PassphraseMissingError. +func ParseRawPrivateKey(pemBytes []byte) (interface{}, error) { + block, _ := pem.Decode(pemBytes) + if block == nil { + return nil, errors.New("ssh: no key found") + } + + if encryptedBlock(block) { + return nil, &PassphraseMissingError{} + } + + switch block.Type { + case "RSA PRIVATE KEY": + return x509.ParsePKCS1PrivateKey(block.Bytes) + // RFC5208 - https://tools.ietf.org/html/rfc5208 + case "PRIVATE KEY": + return x509.ParsePKCS8PrivateKey(block.Bytes) + case "EC PRIVATE KEY": + return x509.ParseECPrivateKey(block.Bytes) + case "DSA PRIVATE KEY": + return ParseDSAPrivateKey(block.Bytes) + case "OPENSSH PRIVATE KEY": + return parseOpenSSHPrivateKey(block.Bytes, unencryptedOpenSSHKey) + default: + return nil, fmt.Errorf("ssh: unsupported key type %q", block.Type) + } +} + +// ParseRawPrivateKeyWithPassphrase returns a private key decrypted with +// passphrase from a PEM encoded private key. If the passphrase is wrong, it +// will return x509.IncorrectPasswordError. +func ParseRawPrivateKeyWithPassphrase(pemBytes, passphrase []byte) (interface{}, error) { + block, _ := pem.Decode(pemBytes) + if block == nil { + return nil, errors.New("ssh: no key found") + } + + if block.Type == "OPENSSH PRIVATE KEY" { + return parseOpenSSHPrivateKey(block.Bytes, passphraseProtectedOpenSSHKey(passphrase)) + } + + if !encryptedBlock(block) || !x509.IsEncryptedPEMBlock(block) { + return nil, errors.New("ssh: not an encrypted key") + } + + buf, err := x509.DecryptPEMBlock(block, passphrase) + if err != nil { + if err == x509.IncorrectPasswordError { + return nil, err + } + return nil, fmt.Errorf("ssh: cannot decode encrypted private keys: %v", err) + } + + switch block.Type { + case "RSA PRIVATE KEY": + return x509.ParsePKCS1PrivateKey(buf) + case "EC PRIVATE KEY": + return x509.ParseECPrivateKey(buf) + case "DSA PRIVATE KEY": + return ParseDSAPrivateKey(buf) + default: + return nil, fmt.Errorf("ssh: unsupported key type %q", block.Type) + } +} + +// ParseDSAPrivateKey returns a DSA private key from its ASN.1 DER encoding, as +// specified by the OpenSSL DSA man page. +func ParseDSAPrivateKey(der []byte) (*dsa.PrivateKey, error) { + var k struct { + Version int + P *big.Int + Q *big.Int + G *big.Int + Pub *big.Int + Priv *big.Int + } + rest, err := asn1.Unmarshal(der, &k) + if err != nil { + return nil, errors.New("ssh: failed to parse DSA key: " + err.Error()) + } + if len(rest) > 0 { + return nil, errors.New("ssh: garbage after DSA key") + } + + return &dsa.PrivateKey{ + PublicKey: dsa.PublicKey{ + Parameters: dsa.Parameters{ + P: k.P, + Q: k.Q, + G: k.G, + }, + Y: k.Pub, + }, + X: k.Priv, + }, nil +} + +func unencryptedOpenSSHKey(cipherName, kdfName, kdfOpts string, privKeyBlock []byte) ([]byte, error) { + if kdfName != "none" || cipherName != "none" { + return nil, &PassphraseMissingError{} + } + if kdfOpts != "" { + return nil, errors.New("ssh: invalid openssh private key") + } + return privKeyBlock, nil +} + +func passphraseProtectedOpenSSHKey(passphrase []byte) openSSHDecryptFunc { + return func(cipherName, kdfName, kdfOpts string, privKeyBlock []byte) ([]byte, error) { + if kdfName == "none" || cipherName == "none" { + return nil, errors.New("ssh: key is not password protected") + } + if kdfName != "bcrypt" { + return nil, fmt.Errorf("ssh: unknown KDF %q, only supports %q", kdfName, "bcrypt") + } + + var opts struct { + Salt string + Rounds uint32 + } + if err := Unmarshal([]byte(kdfOpts), &opts); err != nil { + return nil, err + } + + k, err := bcrypt_pbkdf.Key(passphrase, []byte(opts.Salt), int(opts.Rounds), 32+16) + if err != nil { + return nil, err + } + key, iv := k[:32], k[32:] + + if cipherName != "aes256-ctr" { + return nil, fmt.Errorf("ssh: unknown cipher %q, only supports %q", cipherName, "aes256-ctr") + } + c, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + ctr := cipher.NewCTR(c, iv) + ctr.XORKeyStream(privKeyBlock, privKeyBlock) + + return privKeyBlock, nil + } +} + +type openSSHDecryptFunc func(CipherName, KdfName, KdfOpts string, PrivKeyBlock []byte) ([]byte, error) + +// parseOpenSSHPrivateKey parses an OpenSSH private key, using the decrypt +// function to unwrap the encrypted portion. unencryptedOpenSSHKey can be used +// as the decrypt function to parse an unencrypted private key. See +// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key. +func parseOpenSSHPrivateKey(key []byte, decrypt openSSHDecryptFunc) (crypto.PrivateKey, error) { + const magic = "openssh-key-v1\x00" + if len(key) < len(magic) || string(key[:len(magic)]) != magic { + return nil, errors.New("ssh: invalid openssh private key format") + } + remaining := key[len(magic):] + + var w struct { + CipherName string + KdfName string + KdfOpts string + NumKeys uint32 + PubKey []byte + PrivKeyBlock []byte + } + + if err := Unmarshal(remaining, &w); err != nil { + return nil, err + } + if w.NumKeys != 1 { + // We only support single key files, and so does OpenSSH. + // https://github.com/openssh/openssh-portable/blob/4103a3ec7/sshkey.c#L4171 + return nil, errors.New("ssh: multi-key files are not supported") + } + + privKeyBlock, err := decrypt(w.CipherName, w.KdfName, w.KdfOpts, w.PrivKeyBlock) + if err != nil { + if err, ok := err.(*PassphraseMissingError); ok { + pub, errPub := ParsePublicKey(w.PubKey) + if errPub != nil { + return nil, fmt.Errorf("ssh: failed to parse embedded public key: %v", errPub) + } + err.PublicKey = pub + } + return nil, err + } + + pk1 := struct { + Check1 uint32 + Check2 uint32 + Keytype string + Rest []byte `ssh:"rest"` + }{} + + if err := Unmarshal(privKeyBlock, &pk1); err != nil || pk1.Check1 != pk1.Check2 { + if w.CipherName != "none" { + return nil, x509.IncorrectPasswordError + } + return nil, errors.New("ssh: malformed OpenSSH key") + } + + // we only handle ed25519 and rsa keys currently + switch pk1.Keytype { + case KeyAlgoRSA: + // https://github.com/openssh/openssh-portable/blob/master/sshkey.c#L2760-L2773 + key := struct { + N *big.Int + E *big.Int + D *big.Int + Iqmp *big.Int + P *big.Int + Q *big.Int + Comment string + Pad []byte `ssh:"rest"` + }{} + + if err := Unmarshal(pk1.Rest, &key); err != nil { + return nil, err + } + + if err := checkOpenSSHKeyPadding(key.Pad); err != nil { + return nil, err + } + + pk := &rsa.PrivateKey{ + PublicKey: rsa.PublicKey{ + N: key.N, + E: int(key.E.Int64()), + }, + D: key.D, + Primes: []*big.Int{key.P, key.Q}, + } + + if err := pk.Validate(); err != nil { + return nil, err + } + + pk.Precompute() + + return pk, nil + case KeyAlgoED25519: + key := struct { + Pub []byte + Priv []byte + Comment string + Pad []byte `ssh:"rest"` + }{} + + if err := Unmarshal(pk1.Rest, &key); err != nil { + return nil, err + } + + if len(key.Priv) != ed25519.PrivateKeySize { + return nil, errors.New("ssh: private key unexpected length") + } + + if err := checkOpenSSHKeyPadding(key.Pad); err != nil { + return nil, err + } + + pk := ed25519.PrivateKey(make([]byte, ed25519.PrivateKeySize)) + copy(pk, key.Priv) + return &pk, nil + default: + return nil, errors.New("ssh: unhandled key type") + } +} + +func checkOpenSSHKeyPadding(pad []byte) error { + for i, b := range pad { + if int(b) != i+1 { + return errors.New("ssh: padding not as expected") + } + } + return nil +} + +// FingerprintLegacyMD5 returns the user presentation of the key's +// fingerprint as described by RFC 4716 section 4. +func FingerprintLegacyMD5(pubKey PublicKey) string { + md5sum := md5.Sum(pubKey.Marshal()) + hexarray := make([]string, len(md5sum)) + for i, c := range md5sum { + hexarray[i] = hex.EncodeToString([]byte{c}) + } + return strings.Join(hexarray, ":") +} + +// FingerprintSHA256 returns the user presentation of the key's +// fingerprint as unpadded base64 encoded sha256 hash. +// This format was introduced from OpenSSH 6.8. +// https://www.openssh.com/txt/release-6.8 +// https://tools.ietf.org/html/rfc4648#section-3.2 (unpadded base64 encoding) +func FingerprintSHA256(pubKey PublicKey) string { + sha256sum := sha256.Sum256(pubKey.Marshal()) + hash := base64.RawStdEncoding.EncodeToString(sha256sum[:]) + return "SHA256:" + hash +} diff --git a/vendor/golang.org/x/crypto/ssh/mac.go b/vendor/golang.org/x/crypto/ssh/mac.go new file mode 100644 index 00000000000..c07a06285e6 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/mac.go @@ -0,0 +1,61 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +// Message authentication support + +import ( + "crypto/hmac" + "crypto/sha1" + "crypto/sha256" + "hash" +) + +type macMode struct { + keySize int + etm bool + new func(key []byte) hash.Hash +} + +// truncatingMAC wraps around a hash.Hash and truncates the output digest to +// a given size. +type truncatingMAC struct { + length int + hmac hash.Hash +} + +func (t truncatingMAC) Write(data []byte) (int, error) { + return t.hmac.Write(data) +} + +func (t truncatingMAC) Sum(in []byte) []byte { + out := t.hmac.Sum(in) + return out[:len(in)+t.length] +} + +func (t truncatingMAC) Reset() { + t.hmac.Reset() +} + +func (t truncatingMAC) Size() int { + return t.length +} + +func (t truncatingMAC) BlockSize() int { return t.hmac.BlockSize() } + +var macModes = map[string]*macMode{ + "hmac-sha2-256-etm@openssh.com": {32, true, func(key []byte) hash.Hash { + return hmac.New(sha256.New, key) + }}, + "hmac-sha2-256": {32, false, func(key []byte) hash.Hash { + return hmac.New(sha256.New, key) + }}, + "hmac-sha1": {20, false, func(key []byte) hash.Hash { + return hmac.New(sha1.New, key) + }}, + "hmac-sha1-96": {20, false, func(key []byte) hash.Hash { + return truncatingMAC{12, hmac.New(sha1.New, key)} + }}, +} diff --git a/vendor/golang.org/x/crypto/ssh/messages.go b/vendor/golang.org/x/crypto/ssh/messages.go new file mode 100644 index 00000000000..ac41a4168bf --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/messages.go @@ -0,0 +1,866 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "math/big" + "reflect" + "strconv" + "strings" +) + +// These are SSH message type numbers. They are scattered around several +// documents but many were taken from [SSH-PARAMETERS]. +const ( + msgIgnore = 2 + msgUnimplemented = 3 + msgDebug = 4 + msgNewKeys = 21 +) + +// SSH messages: +// +// These structures mirror the wire format of the corresponding SSH messages. +// They are marshaled using reflection with the marshal and unmarshal functions +// in this file. The only wrinkle is that a final member of type []byte with a +// ssh tag of "rest" receives the remainder of a packet when unmarshaling. + +// See RFC 4253, section 11.1. +const msgDisconnect = 1 + +// disconnectMsg is the message that signals a disconnect. It is also +// the error type returned from mux.Wait() +type disconnectMsg struct { + Reason uint32 `sshtype:"1"` + Message string + Language string +} + +func (d *disconnectMsg) Error() string { + return fmt.Sprintf("ssh: disconnect, reason %d: %s", d.Reason, d.Message) +} + +// See RFC 4253, section 7.1. +const msgKexInit = 20 + +type kexInitMsg struct { + Cookie [16]byte `sshtype:"20"` + KexAlgos []string + ServerHostKeyAlgos []string + CiphersClientServer []string + CiphersServerClient []string + MACsClientServer []string + MACsServerClient []string + CompressionClientServer []string + CompressionServerClient []string + LanguagesClientServer []string + LanguagesServerClient []string + FirstKexFollows bool + Reserved uint32 +} + +// See RFC 4253, section 8. + +// Diffie-Helman +const msgKexDHInit = 30 + +type kexDHInitMsg struct { + X *big.Int `sshtype:"30"` +} + +const msgKexECDHInit = 30 + +type kexECDHInitMsg struct { + ClientPubKey []byte `sshtype:"30"` +} + +const msgKexECDHReply = 31 + +type kexECDHReplyMsg struct { + HostKey []byte `sshtype:"31"` + EphemeralPubKey []byte + Signature []byte +} + +const msgKexDHReply = 31 + +type kexDHReplyMsg struct { + HostKey []byte `sshtype:"31"` + Y *big.Int + Signature []byte +} + +// See RFC 4419, section 5. +const msgKexDHGexGroup = 31 + +type kexDHGexGroupMsg struct { + P *big.Int `sshtype:"31"` + G *big.Int +} + +const msgKexDHGexInit = 32 + +type kexDHGexInitMsg struct { + X *big.Int `sshtype:"32"` +} + +const msgKexDHGexReply = 33 + +type kexDHGexReplyMsg struct { + HostKey []byte `sshtype:"33"` + Y *big.Int + Signature []byte +} + +const msgKexDHGexRequest = 34 + +type kexDHGexRequestMsg struct { + MinBits uint32 `sshtype:"34"` + PreferedBits uint32 + MaxBits uint32 +} + +// See RFC 4253, section 10. +const msgServiceRequest = 5 + +type serviceRequestMsg struct { + Service string `sshtype:"5"` +} + +// See RFC 4253, section 10. +const msgServiceAccept = 6 + +type serviceAcceptMsg struct { + Service string `sshtype:"6"` +} + +// See RFC 4252, section 5. +const msgUserAuthRequest = 50 + +type userAuthRequestMsg struct { + User string `sshtype:"50"` + Service string + Method string + Payload []byte `ssh:"rest"` +} + +// Used for debug printouts of packets. +type userAuthSuccessMsg struct { +} + +// See RFC 4252, section 5.1 +const msgUserAuthFailure = 51 + +type userAuthFailureMsg struct { + Methods []string `sshtype:"51"` + PartialSuccess bool +} + +// See RFC 4252, section 5.1 +const msgUserAuthSuccess = 52 + +// See RFC 4252, section 5.4 +const msgUserAuthBanner = 53 + +type userAuthBannerMsg struct { + Message string `sshtype:"53"` + // unused, but required to allow message parsing + Language string +} + +// See RFC 4256, section 3.2 +const msgUserAuthInfoRequest = 60 +const msgUserAuthInfoResponse = 61 + +type userAuthInfoRequestMsg struct { + User string `sshtype:"60"` + Instruction string + DeprecatedLanguage string + NumPrompts uint32 + Prompts []byte `ssh:"rest"` +} + +// See RFC 4254, section 5.1. +const msgChannelOpen = 90 + +type channelOpenMsg struct { + ChanType string `sshtype:"90"` + PeersID uint32 + PeersWindow uint32 + MaxPacketSize uint32 + TypeSpecificData []byte `ssh:"rest"` +} + +const msgChannelExtendedData = 95 +const msgChannelData = 94 + +// Used for debug print outs of packets. +type channelDataMsg struct { + PeersID uint32 `sshtype:"94"` + Length uint32 + Rest []byte `ssh:"rest"` +} + +// See RFC 4254, section 5.1. +const msgChannelOpenConfirm = 91 + +type channelOpenConfirmMsg struct { + PeersID uint32 `sshtype:"91"` + MyID uint32 + MyWindow uint32 + MaxPacketSize uint32 + TypeSpecificData []byte `ssh:"rest"` +} + +// See RFC 4254, section 5.1. +const msgChannelOpenFailure = 92 + +type channelOpenFailureMsg struct { + PeersID uint32 `sshtype:"92"` + Reason RejectionReason + Message string + Language string +} + +const msgChannelRequest = 98 + +type channelRequestMsg struct { + PeersID uint32 `sshtype:"98"` + Request string + WantReply bool + RequestSpecificData []byte `ssh:"rest"` +} + +// See RFC 4254, section 5.4. +const msgChannelSuccess = 99 + +type channelRequestSuccessMsg struct { + PeersID uint32 `sshtype:"99"` +} + +// See RFC 4254, section 5.4. +const msgChannelFailure = 100 + +type channelRequestFailureMsg struct { + PeersID uint32 `sshtype:"100"` +} + +// See RFC 4254, section 5.3 +const msgChannelClose = 97 + +type channelCloseMsg struct { + PeersID uint32 `sshtype:"97"` +} + +// See RFC 4254, section 5.3 +const msgChannelEOF = 96 + +type channelEOFMsg struct { + PeersID uint32 `sshtype:"96"` +} + +// See RFC 4254, section 4 +const msgGlobalRequest = 80 + +type globalRequestMsg struct { + Type string `sshtype:"80"` + WantReply bool + Data []byte `ssh:"rest"` +} + +// See RFC 4254, section 4 +const msgRequestSuccess = 81 + +type globalRequestSuccessMsg struct { + Data []byte `ssh:"rest" sshtype:"81"` +} + +// See RFC 4254, section 4 +const msgRequestFailure = 82 + +type globalRequestFailureMsg struct { + Data []byte `ssh:"rest" sshtype:"82"` +} + +// See RFC 4254, section 5.2 +const msgChannelWindowAdjust = 93 + +type windowAdjustMsg struct { + PeersID uint32 `sshtype:"93"` + AdditionalBytes uint32 +} + +// See RFC 4252, section 7 +const msgUserAuthPubKeyOk = 60 + +type userAuthPubKeyOkMsg struct { + Algo string `sshtype:"60"` + PubKey []byte +} + +// See RFC 4462, section 3 +const msgUserAuthGSSAPIResponse = 60 + +type userAuthGSSAPIResponse struct { + SupportMech []byte `sshtype:"60"` +} + +const msgUserAuthGSSAPIToken = 61 + +type userAuthGSSAPIToken struct { + Token []byte `sshtype:"61"` +} + +const msgUserAuthGSSAPIMIC = 66 + +type userAuthGSSAPIMIC struct { + MIC []byte `sshtype:"66"` +} + +// See RFC 4462, section 3.9 +const msgUserAuthGSSAPIErrTok = 64 + +type userAuthGSSAPIErrTok struct { + ErrorToken []byte `sshtype:"64"` +} + +// See RFC 4462, section 3.8 +const msgUserAuthGSSAPIError = 65 + +type userAuthGSSAPIError struct { + MajorStatus uint32 `sshtype:"65"` + MinorStatus uint32 + Message string + LanguageTag string +} + +// typeTags returns the possible type bytes for the given reflect.Type, which +// should be a struct. The possible values are separated by a '|' character. +func typeTags(structType reflect.Type) (tags []byte) { + tagStr := structType.Field(0).Tag.Get("sshtype") + + for _, tag := range strings.Split(tagStr, "|") { + i, err := strconv.Atoi(tag) + if err == nil { + tags = append(tags, byte(i)) + } + } + + return tags +} + +func fieldError(t reflect.Type, field int, problem string) error { + if problem != "" { + problem = ": " + problem + } + return fmt.Errorf("ssh: unmarshal error for field %s of type %s%s", t.Field(field).Name, t.Name(), problem) +} + +var errShortRead = errors.New("ssh: short read") + +// Unmarshal parses data in SSH wire format into a structure. The out +// argument should be a pointer to struct. If the first member of the +// struct has the "sshtype" tag set to a '|'-separated set of numbers +// in decimal, the packet must start with one of those numbers. In +// case of error, Unmarshal returns a ParseError or +// UnexpectedMessageError. +func Unmarshal(data []byte, out interface{}) error { + v := reflect.ValueOf(out).Elem() + structType := v.Type() + expectedTypes := typeTags(structType) + + var expectedType byte + if len(expectedTypes) > 0 { + expectedType = expectedTypes[0] + } + + if len(data) == 0 { + return parseError(expectedType) + } + + if len(expectedTypes) > 0 { + goodType := false + for _, e := range expectedTypes { + if e > 0 && data[0] == e { + goodType = true + break + } + } + if !goodType { + return fmt.Errorf("ssh: unexpected message type %d (expected one of %v)", data[0], expectedTypes) + } + data = data[1:] + } + + var ok bool + for i := 0; i < v.NumField(); i++ { + field := v.Field(i) + t := field.Type() + switch t.Kind() { + case reflect.Bool: + if len(data) < 1 { + return errShortRead + } + field.SetBool(data[0] != 0) + data = data[1:] + case reflect.Array: + if t.Elem().Kind() != reflect.Uint8 { + return fieldError(structType, i, "array of unsupported type") + } + if len(data) < t.Len() { + return errShortRead + } + for j, n := 0, t.Len(); j < n; j++ { + field.Index(j).Set(reflect.ValueOf(data[j])) + } + data = data[t.Len():] + case reflect.Uint64: + var u64 uint64 + if u64, data, ok = parseUint64(data); !ok { + return errShortRead + } + field.SetUint(u64) + case reflect.Uint32: + var u32 uint32 + if u32, data, ok = parseUint32(data); !ok { + return errShortRead + } + field.SetUint(uint64(u32)) + case reflect.Uint8: + if len(data) < 1 { + return errShortRead + } + field.SetUint(uint64(data[0])) + data = data[1:] + case reflect.String: + var s []byte + if s, data, ok = parseString(data); !ok { + return fieldError(structType, i, "") + } + field.SetString(string(s)) + case reflect.Slice: + switch t.Elem().Kind() { + case reflect.Uint8: + if structType.Field(i).Tag.Get("ssh") == "rest" { + field.Set(reflect.ValueOf(data)) + data = nil + } else { + var s []byte + if s, data, ok = parseString(data); !ok { + return errShortRead + } + field.Set(reflect.ValueOf(s)) + } + case reflect.String: + var nl []string + if nl, data, ok = parseNameList(data); !ok { + return errShortRead + } + field.Set(reflect.ValueOf(nl)) + default: + return fieldError(structType, i, "slice of unsupported type") + } + case reflect.Ptr: + if t == bigIntType { + var n *big.Int + if n, data, ok = parseInt(data); !ok { + return errShortRead + } + field.Set(reflect.ValueOf(n)) + } else { + return fieldError(structType, i, "pointer to unsupported type") + } + default: + return fieldError(structType, i, fmt.Sprintf("unsupported type: %v", t)) + } + } + + if len(data) != 0 { + return parseError(expectedType) + } + + return nil +} + +// Marshal serializes the message in msg to SSH wire format. The msg +// argument should be a struct or pointer to struct. If the first +// member has the "sshtype" tag set to a number in decimal, that +// number is prepended to the result. If the last of member has the +// "ssh" tag set to "rest", its contents are appended to the output. +func Marshal(msg interface{}) []byte { + out := make([]byte, 0, 64) + return marshalStruct(out, msg) +} + +func marshalStruct(out []byte, msg interface{}) []byte { + v := reflect.Indirect(reflect.ValueOf(msg)) + msgTypes := typeTags(v.Type()) + if len(msgTypes) > 0 { + out = append(out, msgTypes[0]) + } + + for i, n := 0, v.NumField(); i < n; i++ { + field := v.Field(i) + switch t := field.Type(); t.Kind() { + case reflect.Bool: + var v uint8 + if field.Bool() { + v = 1 + } + out = append(out, v) + case reflect.Array: + if t.Elem().Kind() != reflect.Uint8 { + panic(fmt.Sprintf("array of non-uint8 in field %d: %T", i, field.Interface())) + } + for j, l := 0, t.Len(); j < l; j++ { + out = append(out, uint8(field.Index(j).Uint())) + } + case reflect.Uint32: + out = appendU32(out, uint32(field.Uint())) + case reflect.Uint64: + out = appendU64(out, uint64(field.Uint())) + case reflect.Uint8: + out = append(out, uint8(field.Uint())) + case reflect.String: + s := field.String() + out = appendInt(out, len(s)) + out = append(out, s...) + case reflect.Slice: + switch t.Elem().Kind() { + case reflect.Uint8: + if v.Type().Field(i).Tag.Get("ssh") != "rest" { + out = appendInt(out, field.Len()) + } + out = append(out, field.Bytes()...) + case reflect.String: + offset := len(out) + out = appendU32(out, 0) + if n := field.Len(); n > 0 { + for j := 0; j < n; j++ { + f := field.Index(j) + if j != 0 { + out = append(out, ',') + } + out = append(out, f.String()...) + } + // overwrite length value + binary.BigEndian.PutUint32(out[offset:], uint32(len(out)-offset-4)) + } + default: + panic(fmt.Sprintf("slice of unknown type in field %d: %T", i, field.Interface())) + } + case reflect.Ptr: + if t == bigIntType { + var n *big.Int + nValue := reflect.ValueOf(&n) + nValue.Elem().Set(field) + needed := intLength(n) + oldLength := len(out) + + if cap(out)-len(out) < needed { + newOut := make([]byte, len(out), 2*(len(out)+needed)) + copy(newOut, out) + out = newOut + } + out = out[:oldLength+needed] + marshalInt(out[oldLength:], n) + } else { + panic(fmt.Sprintf("pointer to unknown type in field %d: %T", i, field.Interface())) + } + } + } + + return out +} + +var bigOne = big.NewInt(1) + +func parseString(in []byte) (out, rest []byte, ok bool) { + if len(in) < 4 { + return + } + length := binary.BigEndian.Uint32(in) + in = in[4:] + if uint32(len(in)) < length { + return + } + out = in[:length] + rest = in[length:] + ok = true + return +} + +var ( + comma = []byte{','} + emptyNameList = []string{} +) + +func parseNameList(in []byte) (out []string, rest []byte, ok bool) { + contents, rest, ok := parseString(in) + if !ok { + return + } + if len(contents) == 0 { + out = emptyNameList + return + } + parts := bytes.Split(contents, comma) + out = make([]string, len(parts)) + for i, part := range parts { + out[i] = string(part) + } + return +} + +func parseInt(in []byte) (out *big.Int, rest []byte, ok bool) { + contents, rest, ok := parseString(in) + if !ok { + return + } + out = new(big.Int) + + if len(contents) > 0 && contents[0]&0x80 == 0x80 { + // This is a negative number + notBytes := make([]byte, len(contents)) + for i := range notBytes { + notBytes[i] = ^contents[i] + } + out.SetBytes(notBytes) + out.Add(out, bigOne) + out.Neg(out) + } else { + // Positive number + out.SetBytes(contents) + } + ok = true + return +} + +func parseUint32(in []byte) (uint32, []byte, bool) { + if len(in) < 4 { + return 0, nil, false + } + return binary.BigEndian.Uint32(in), in[4:], true +} + +func parseUint64(in []byte) (uint64, []byte, bool) { + if len(in) < 8 { + return 0, nil, false + } + return binary.BigEndian.Uint64(in), in[8:], true +} + +func intLength(n *big.Int) int { + length := 4 /* length bytes */ + if n.Sign() < 0 { + nMinus1 := new(big.Int).Neg(n) + nMinus1.Sub(nMinus1, bigOne) + bitLen := nMinus1.BitLen() + if bitLen%8 == 0 { + // The number will need 0xff padding + length++ + } + length += (bitLen + 7) / 8 + } else if n.Sign() == 0 { + // A zero is the zero length string + } else { + bitLen := n.BitLen() + if bitLen%8 == 0 { + // The number will need 0x00 padding + length++ + } + length += (bitLen + 7) / 8 + } + + return length +} + +func marshalUint32(to []byte, n uint32) []byte { + binary.BigEndian.PutUint32(to, n) + return to[4:] +} + +func marshalUint64(to []byte, n uint64) []byte { + binary.BigEndian.PutUint64(to, n) + return to[8:] +} + +func marshalInt(to []byte, n *big.Int) []byte { + lengthBytes := to + to = to[4:] + length := 0 + + if n.Sign() < 0 { + // A negative number has to be converted to two's-complement + // form. So we'll subtract 1 and invert. If the + // most-significant-bit isn't set then we'll need to pad the + // beginning with 0xff in order to keep the number negative. + nMinus1 := new(big.Int).Neg(n) + nMinus1.Sub(nMinus1, bigOne) + bytes := nMinus1.Bytes() + for i := range bytes { + bytes[i] ^= 0xff + } + if len(bytes) == 0 || bytes[0]&0x80 == 0 { + to[0] = 0xff + to = to[1:] + length++ + } + nBytes := copy(to, bytes) + to = to[nBytes:] + length += nBytes + } else if n.Sign() == 0 { + // A zero is the zero length string + } else { + bytes := n.Bytes() + if len(bytes) > 0 && bytes[0]&0x80 != 0 { + // We'll have to pad this with a 0x00 in order to + // stop it looking like a negative number. + to[0] = 0 + to = to[1:] + length++ + } + nBytes := copy(to, bytes) + to = to[nBytes:] + length += nBytes + } + + lengthBytes[0] = byte(length >> 24) + lengthBytes[1] = byte(length >> 16) + lengthBytes[2] = byte(length >> 8) + lengthBytes[3] = byte(length) + return to +} + +func writeInt(w io.Writer, n *big.Int) { + length := intLength(n) + buf := make([]byte, length) + marshalInt(buf, n) + w.Write(buf) +} + +func writeString(w io.Writer, s []byte) { + var lengthBytes [4]byte + lengthBytes[0] = byte(len(s) >> 24) + lengthBytes[1] = byte(len(s) >> 16) + lengthBytes[2] = byte(len(s) >> 8) + lengthBytes[3] = byte(len(s)) + w.Write(lengthBytes[:]) + w.Write(s) +} + +func stringLength(n int) int { + return 4 + n +} + +func marshalString(to []byte, s []byte) []byte { + to[0] = byte(len(s) >> 24) + to[1] = byte(len(s) >> 16) + to[2] = byte(len(s) >> 8) + to[3] = byte(len(s)) + to = to[4:] + copy(to, s) + return to[len(s):] +} + +var bigIntType = reflect.TypeOf((*big.Int)(nil)) + +// Decode a packet into its corresponding message. +func decode(packet []byte) (interface{}, error) { + var msg interface{} + switch packet[0] { + case msgDisconnect: + msg = new(disconnectMsg) + case msgServiceRequest: + msg = new(serviceRequestMsg) + case msgServiceAccept: + msg = new(serviceAcceptMsg) + case msgKexInit: + msg = new(kexInitMsg) + case msgKexDHInit: + msg = new(kexDHInitMsg) + case msgKexDHReply: + msg = new(kexDHReplyMsg) + case msgUserAuthRequest: + msg = new(userAuthRequestMsg) + case msgUserAuthSuccess: + return new(userAuthSuccessMsg), nil + case msgUserAuthFailure: + msg = new(userAuthFailureMsg) + case msgUserAuthPubKeyOk: + msg = new(userAuthPubKeyOkMsg) + case msgGlobalRequest: + msg = new(globalRequestMsg) + case msgRequestSuccess: + msg = new(globalRequestSuccessMsg) + case msgRequestFailure: + msg = new(globalRequestFailureMsg) + case msgChannelOpen: + msg = new(channelOpenMsg) + case msgChannelData: + msg = new(channelDataMsg) + case msgChannelOpenConfirm: + msg = new(channelOpenConfirmMsg) + case msgChannelOpenFailure: + msg = new(channelOpenFailureMsg) + case msgChannelWindowAdjust: + msg = new(windowAdjustMsg) + case msgChannelEOF: + msg = new(channelEOFMsg) + case msgChannelClose: + msg = new(channelCloseMsg) + case msgChannelRequest: + msg = new(channelRequestMsg) + case msgChannelSuccess: + msg = new(channelRequestSuccessMsg) + case msgChannelFailure: + msg = new(channelRequestFailureMsg) + case msgUserAuthGSSAPIToken: + msg = new(userAuthGSSAPIToken) + case msgUserAuthGSSAPIMIC: + msg = new(userAuthGSSAPIMIC) + case msgUserAuthGSSAPIErrTok: + msg = new(userAuthGSSAPIErrTok) + case msgUserAuthGSSAPIError: + msg = new(userAuthGSSAPIError) + default: + return nil, unexpectedMessageError(0, packet[0]) + } + if err := Unmarshal(packet, msg); err != nil { + return nil, err + } + return msg, nil +} + +var packetTypeNames = map[byte]string{ + msgDisconnect: "disconnectMsg", + msgServiceRequest: "serviceRequestMsg", + msgServiceAccept: "serviceAcceptMsg", + msgKexInit: "kexInitMsg", + msgKexDHInit: "kexDHInitMsg", + msgKexDHReply: "kexDHReplyMsg", + msgUserAuthRequest: "userAuthRequestMsg", + msgUserAuthSuccess: "userAuthSuccessMsg", + msgUserAuthFailure: "userAuthFailureMsg", + msgUserAuthPubKeyOk: "userAuthPubKeyOkMsg", + msgGlobalRequest: "globalRequestMsg", + msgRequestSuccess: "globalRequestSuccessMsg", + msgRequestFailure: "globalRequestFailureMsg", + msgChannelOpen: "channelOpenMsg", + msgChannelData: "channelDataMsg", + msgChannelOpenConfirm: "channelOpenConfirmMsg", + msgChannelOpenFailure: "channelOpenFailureMsg", + msgChannelWindowAdjust: "windowAdjustMsg", + msgChannelEOF: "channelEOFMsg", + msgChannelClose: "channelCloseMsg", + msgChannelRequest: "channelRequestMsg", + msgChannelSuccess: "channelRequestSuccessMsg", + msgChannelFailure: "channelRequestFailureMsg", +} diff --git a/vendor/golang.org/x/crypto/ssh/mux.go b/vendor/golang.org/x/crypto/ssh/mux.go new file mode 100644 index 00000000000..f19016270e8 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/mux.go @@ -0,0 +1,330 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "encoding/binary" + "fmt" + "io" + "log" + "sync" + "sync/atomic" +) + +// debugMux, if set, causes messages in the connection protocol to be +// logged. +const debugMux = false + +// chanList is a thread safe channel list. +type chanList struct { + // protects concurrent access to chans + sync.Mutex + + // chans are indexed by the local id of the channel, which the + // other side should send in the PeersId field. + chans []*channel + + // This is a debugging aid: it offsets all IDs by this + // amount. This helps distinguish otherwise identical + // server/client muxes + offset uint32 +} + +// Assigns a channel ID to the given channel. +func (c *chanList) add(ch *channel) uint32 { + c.Lock() + defer c.Unlock() + for i := range c.chans { + if c.chans[i] == nil { + c.chans[i] = ch + return uint32(i) + c.offset + } + } + c.chans = append(c.chans, ch) + return uint32(len(c.chans)-1) + c.offset +} + +// getChan returns the channel for the given ID. +func (c *chanList) getChan(id uint32) *channel { + id -= c.offset + + c.Lock() + defer c.Unlock() + if id < uint32(len(c.chans)) { + return c.chans[id] + } + return nil +} + +func (c *chanList) remove(id uint32) { + id -= c.offset + c.Lock() + if id < uint32(len(c.chans)) { + c.chans[id] = nil + } + c.Unlock() +} + +// dropAll forgets all channels it knows, returning them in a slice. +func (c *chanList) dropAll() []*channel { + c.Lock() + defer c.Unlock() + var r []*channel + + for _, ch := range c.chans { + if ch == nil { + continue + } + r = append(r, ch) + } + c.chans = nil + return r +} + +// mux represents the state for the SSH connection protocol, which +// multiplexes many channels onto a single packet transport. +type mux struct { + conn packetConn + chanList chanList + + incomingChannels chan NewChannel + + globalSentMu sync.Mutex + globalResponses chan interface{} + incomingRequests chan *Request + + errCond *sync.Cond + err error +} + +// When debugging, each new chanList instantiation has a different +// offset. +var globalOff uint32 + +func (m *mux) Wait() error { + m.errCond.L.Lock() + defer m.errCond.L.Unlock() + for m.err == nil { + m.errCond.Wait() + } + return m.err +} + +// newMux returns a mux that runs over the given connection. +func newMux(p packetConn) *mux { + m := &mux{ + conn: p, + incomingChannels: make(chan NewChannel, chanSize), + globalResponses: make(chan interface{}, 1), + incomingRequests: make(chan *Request, chanSize), + errCond: newCond(), + } + if debugMux { + m.chanList.offset = atomic.AddUint32(&globalOff, 1) + } + + go m.loop() + return m +} + +func (m *mux) sendMessage(msg interface{}) error { + p := Marshal(msg) + if debugMux { + log.Printf("send global(%d): %#v", m.chanList.offset, msg) + } + return m.conn.writePacket(p) +} + +func (m *mux) SendRequest(name string, wantReply bool, payload []byte) (bool, []byte, error) { + if wantReply { + m.globalSentMu.Lock() + defer m.globalSentMu.Unlock() + } + + if err := m.sendMessage(globalRequestMsg{ + Type: name, + WantReply: wantReply, + Data: payload, + }); err != nil { + return false, nil, err + } + + if !wantReply { + return false, nil, nil + } + + msg, ok := <-m.globalResponses + if !ok { + return false, nil, io.EOF + } + switch msg := msg.(type) { + case *globalRequestFailureMsg: + return false, msg.Data, nil + case *globalRequestSuccessMsg: + return true, msg.Data, nil + default: + return false, nil, fmt.Errorf("ssh: unexpected response to request: %#v", msg) + } +} + +// ackRequest must be called after processing a global request that +// has WantReply set. +func (m *mux) ackRequest(ok bool, data []byte) error { + if ok { + return m.sendMessage(globalRequestSuccessMsg{Data: data}) + } + return m.sendMessage(globalRequestFailureMsg{Data: data}) +} + +func (m *mux) Close() error { + return m.conn.Close() +} + +// loop runs the connection machine. It will process packets until an +// error is encountered. To synchronize on loop exit, use mux.Wait. +func (m *mux) loop() { + var err error + for err == nil { + err = m.onePacket() + } + + for _, ch := range m.chanList.dropAll() { + ch.close() + } + + close(m.incomingChannels) + close(m.incomingRequests) + close(m.globalResponses) + + m.conn.Close() + + m.errCond.L.Lock() + m.err = err + m.errCond.Broadcast() + m.errCond.L.Unlock() + + if debugMux { + log.Println("loop exit", err) + } +} + +// onePacket reads and processes one packet. +func (m *mux) onePacket() error { + packet, err := m.conn.readPacket() + if err != nil { + return err + } + + if debugMux { + if packet[0] == msgChannelData || packet[0] == msgChannelExtendedData { + log.Printf("decoding(%d): data packet - %d bytes", m.chanList.offset, len(packet)) + } else { + p, _ := decode(packet) + log.Printf("decoding(%d): %d %#v - %d bytes", m.chanList.offset, packet[0], p, len(packet)) + } + } + + switch packet[0] { + case msgChannelOpen: + return m.handleChannelOpen(packet) + case msgGlobalRequest, msgRequestSuccess, msgRequestFailure: + return m.handleGlobalPacket(packet) + } + + // assume a channel packet. + if len(packet) < 5 { + return parseError(packet[0]) + } + id := binary.BigEndian.Uint32(packet[1:]) + ch := m.chanList.getChan(id) + if ch == nil { + return fmt.Errorf("ssh: invalid channel %d", id) + } + + return ch.handlePacket(packet) +} + +func (m *mux) handleGlobalPacket(packet []byte) error { + msg, err := decode(packet) + if err != nil { + return err + } + + switch msg := msg.(type) { + case *globalRequestMsg: + m.incomingRequests <- &Request{ + Type: msg.Type, + WantReply: msg.WantReply, + Payload: msg.Data, + mux: m, + } + case *globalRequestSuccessMsg, *globalRequestFailureMsg: + m.globalResponses <- msg + default: + panic(fmt.Sprintf("not a global message %#v", msg)) + } + + return nil +} + +// handleChannelOpen schedules a channel to be Accept()ed. +func (m *mux) handleChannelOpen(packet []byte) error { + var msg channelOpenMsg + if err := Unmarshal(packet, &msg); err != nil { + return err + } + + if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > 1<<31 { + failMsg := channelOpenFailureMsg{ + PeersID: msg.PeersID, + Reason: ConnectionFailed, + Message: "invalid request", + Language: "en_US.UTF-8", + } + return m.sendMessage(failMsg) + } + + c := m.newChannel(msg.ChanType, channelInbound, msg.TypeSpecificData) + c.remoteId = msg.PeersID + c.maxRemotePayload = msg.MaxPacketSize + c.remoteWin.add(msg.PeersWindow) + m.incomingChannels <- c + return nil +} + +func (m *mux) OpenChannel(chanType string, extra []byte) (Channel, <-chan *Request, error) { + ch, err := m.openChannel(chanType, extra) + if err != nil { + return nil, nil, err + } + + return ch, ch.incomingRequests, nil +} + +func (m *mux) openChannel(chanType string, extra []byte) (*channel, error) { + ch := m.newChannel(chanType, channelOutbound, extra) + + ch.maxIncomingPayload = channelMaxPacket + + open := channelOpenMsg{ + ChanType: chanType, + PeersWindow: ch.myWindow, + MaxPacketSize: ch.maxIncomingPayload, + TypeSpecificData: extra, + PeersID: ch.localId, + } + if err := m.sendMessage(open); err != nil { + return nil, err + } + + switch msg := (<-ch.msg).(type) { + case *channelOpenConfirmMsg: + return ch, nil + case *channelOpenFailureMsg: + return nil, &OpenChannelError{msg.Reason, msg.Message} + default: + return nil, fmt.Errorf("ssh: unexpected packet in response to channel open: %T", msg) + } +} diff --git a/vendor/golang.org/x/crypto/ssh/server.go b/vendor/golang.org/x/crypto/ssh/server.go new file mode 100644 index 00000000000..7d42a8c88d2 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/server.go @@ -0,0 +1,716 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bytes" + "errors" + "fmt" + "io" + "net" + "strings" +) + +// The Permissions type holds fine-grained permissions that are +// specific to a user or a specific authentication method for a user. +// The Permissions value for a successful authentication attempt is +// available in ServerConn, so it can be used to pass information from +// the user-authentication phase to the application layer. +type Permissions struct { + // CriticalOptions indicate restrictions to the default + // permissions, and are typically used in conjunction with + // user certificates. The standard for SSH certificates + // defines "force-command" (only allow the given command to + // execute) and "source-address" (only allow connections from + // the given address). The SSH package currently only enforces + // the "source-address" critical option. It is up to server + // implementations to enforce other critical options, such as + // "force-command", by checking them after the SSH handshake + // is successful. In general, SSH servers should reject + // connections that specify critical options that are unknown + // or not supported. + CriticalOptions map[string]string + + // Extensions are extra functionality that the server may + // offer on authenticated connections. Lack of support for an + // extension does not preclude authenticating a user. Common + // extensions are "permit-agent-forwarding", + // "permit-X11-forwarding". The Go SSH library currently does + // not act on any extension, and it is up to server + // implementations to honor them. Extensions can be used to + // pass data from the authentication callbacks to the server + // application layer. + Extensions map[string]string +} + +type GSSAPIWithMICConfig struct { + // AllowLogin, must be set, is called when gssapi-with-mic + // authentication is selected (RFC 4462 section 3). The srcName is from the + // results of the GSS-API authentication. The format is username@DOMAIN. + // GSSAPI just guarantees to the server who the user is, but not if they can log in, and with what permissions. + // This callback is called after the user identity is established with GSSAPI to decide if the user can login with + // which permissions. If the user is allowed to login, it should return a nil error. + AllowLogin func(conn ConnMetadata, srcName string) (*Permissions, error) + + // Server must be set. It's the implementation + // of the GSSAPIServer interface. See GSSAPIServer interface for details. + Server GSSAPIServer +} + +// ServerConfig holds server specific configuration data. +type ServerConfig struct { + // Config contains configuration shared between client and server. + Config + + hostKeys []Signer + + // NoClientAuth is true if clients are allowed to connect without + // authenticating. + NoClientAuth bool + + // MaxAuthTries specifies the maximum number of authentication attempts + // permitted per connection. If set to a negative number, the number of + // attempts are unlimited. If set to zero, the number of attempts are limited + // to 6. + MaxAuthTries int + + // PasswordCallback, if non-nil, is called when a user + // attempts to authenticate using a password. + PasswordCallback func(conn ConnMetadata, password []byte) (*Permissions, error) + + // PublicKeyCallback, if non-nil, is called when a client + // offers a public key for authentication. It must return a nil error + // if the given public key can be used to authenticate the + // given user. For example, see CertChecker.Authenticate. A + // call to this function does not guarantee that the key + // offered is in fact used to authenticate. To record any data + // depending on the public key, store it inside a + // Permissions.Extensions entry. + PublicKeyCallback func(conn ConnMetadata, key PublicKey) (*Permissions, error) + + // KeyboardInteractiveCallback, if non-nil, is called when + // keyboard-interactive authentication is selected (RFC + // 4256). The client object's Challenge function should be + // used to query the user. The callback may offer multiple + // Challenge rounds. To avoid information leaks, the client + // should be presented a challenge even if the user is + // unknown. + KeyboardInteractiveCallback func(conn ConnMetadata, client KeyboardInteractiveChallenge) (*Permissions, error) + + // AuthLogCallback, if non-nil, is called to log all authentication + // attempts. + AuthLogCallback func(conn ConnMetadata, method string, err error) + + // ServerVersion is the version identification string to announce in + // the public handshake. + // If empty, a reasonable default is used. + // Note that RFC 4253 section 4.2 requires that this string start with + // "SSH-2.0-". + ServerVersion string + + // BannerCallback, if present, is called and the return string is sent to + // the client after key exchange completed but before authentication. + BannerCallback func(conn ConnMetadata) string + + // GSSAPIWithMICConfig includes gssapi server and callback, which if both non-nil, is used + // when gssapi-with-mic authentication is selected (RFC 4462 section 3). + GSSAPIWithMICConfig *GSSAPIWithMICConfig +} + +// AddHostKey adds a private key as a host key. If an existing host +// key exists with the same algorithm, it is overwritten. Each server +// config must have at least one host key. +func (s *ServerConfig) AddHostKey(key Signer) { + for i, k := range s.hostKeys { + if k.PublicKey().Type() == key.PublicKey().Type() { + s.hostKeys[i] = key + return + } + } + + s.hostKeys = append(s.hostKeys, key) +} + +// cachedPubKey contains the results of querying whether a public key is +// acceptable for a user. +type cachedPubKey struct { + user string + pubKeyData []byte + result error + perms *Permissions +} + +const maxCachedPubKeys = 16 + +// pubKeyCache caches tests for public keys. Since SSH clients +// will query whether a public key is acceptable before attempting to +// authenticate with it, we end up with duplicate queries for public +// key validity. The cache only applies to a single ServerConn. +type pubKeyCache struct { + keys []cachedPubKey +} + +// get returns the result for a given user/algo/key tuple. +func (c *pubKeyCache) get(user string, pubKeyData []byte) (cachedPubKey, bool) { + for _, k := range c.keys { + if k.user == user && bytes.Equal(k.pubKeyData, pubKeyData) { + return k, true + } + } + return cachedPubKey{}, false +} + +// add adds the given tuple to the cache. +func (c *pubKeyCache) add(candidate cachedPubKey) { + if len(c.keys) < maxCachedPubKeys { + c.keys = append(c.keys, candidate) + } +} + +// ServerConn is an authenticated SSH connection, as seen from the +// server +type ServerConn struct { + Conn + + // If the succeeding authentication callback returned a + // non-nil Permissions pointer, it is stored here. + Permissions *Permissions +} + +// NewServerConn starts a new SSH server with c as the underlying +// transport. It starts with a handshake and, if the handshake is +// unsuccessful, it closes the connection and returns an error. The +// Request and NewChannel channels must be serviced, or the connection +// will hang. +// +// The returned error may be of type *ServerAuthError for +// authentication errors. +func NewServerConn(c net.Conn, config *ServerConfig) (*ServerConn, <-chan NewChannel, <-chan *Request, error) { + fullConf := *config + fullConf.SetDefaults() + if fullConf.MaxAuthTries == 0 { + fullConf.MaxAuthTries = 6 + } + // Check if the config contains any unsupported key exchanges + for _, kex := range fullConf.KeyExchanges { + if _, ok := serverForbiddenKexAlgos[kex]; ok { + return nil, nil, nil, fmt.Errorf("ssh: unsupported key exchange %s for server", kex) + } + } + + s := &connection{ + sshConn: sshConn{conn: c}, + } + perms, err := s.serverHandshake(&fullConf) + if err != nil { + c.Close() + return nil, nil, nil, err + } + return &ServerConn{s, perms}, s.mux.incomingChannels, s.mux.incomingRequests, nil +} + +// signAndMarshal signs the data with the appropriate algorithm, +// and serializes the result in SSH wire format. +func signAndMarshal(k Signer, rand io.Reader, data []byte) ([]byte, error) { + sig, err := k.Sign(rand, data) + if err != nil { + return nil, err + } + + return Marshal(sig), nil +} + +// handshake performs key exchange and user authentication. +func (s *connection) serverHandshake(config *ServerConfig) (*Permissions, error) { + if len(config.hostKeys) == 0 { + return nil, errors.New("ssh: server has no host keys") + } + + if !config.NoClientAuth && config.PasswordCallback == nil && config.PublicKeyCallback == nil && + config.KeyboardInteractiveCallback == nil && (config.GSSAPIWithMICConfig == nil || + config.GSSAPIWithMICConfig.AllowLogin == nil || config.GSSAPIWithMICConfig.Server == nil) { + return nil, errors.New("ssh: no authentication methods configured but NoClientAuth is also false") + } + + if config.ServerVersion != "" { + s.serverVersion = []byte(config.ServerVersion) + } else { + s.serverVersion = []byte(packageVersion) + } + var err error + s.clientVersion, err = exchangeVersions(s.sshConn.conn, s.serverVersion) + if err != nil { + return nil, err + } + + tr := newTransport(s.sshConn.conn, config.Rand, false /* not client */) + s.transport = newServerTransport(tr, s.clientVersion, s.serverVersion, config) + + if err := s.transport.waitSession(); err != nil { + return nil, err + } + + // We just did the key change, so the session ID is established. + s.sessionID = s.transport.getSessionID() + + var packet []byte + if packet, err = s.transport.readPacket(); err != nil { + return nil, err + } + + var serviceRequest serviceRequestMsg + if err = Unmarshal(packet, &serviceRequest); err != nil { + return nil, err + } + if serviceRequest.Service != serviceUserAuth { + return nil, errors.New("ssh: requested service '" + serviceRequest.Service + "' before authenticating") + } + serviceAccept := serviceAcceptMsg{ + Service: serviceUserAuth, + } + if err := s.transport.writePacket(Marshal(&serviceAccept)); err != nil { + return nil, err + } + + perms, err := s.serverAuthenticate(config) + if err != nil { + return nil, err + } + s.mux = newMux(s.transport) + return perms, err +} + +func isAcceptableAlgo(algo string) bool { + switch algo { + case KeyAlgoRSA, KeyAlgoDSA, KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521, KeyAlgoSKECDSA256, KeyAlgoED25519, KeyAlgoSKED25519, + CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01, CertAlgoSKECDSA256v01, CertAlgoED25519v01, CertAlgoSKED25519v01: + return true + } + return false +} + +func checkSourceAddress(addr net.Addr, sourceAddrs string) error { + if addr == nil { + return errors.New("ssh: no address known for client, but source-address match required") + } + + tcpAddr, ok := addr.(*net.TCPAddr) + if !ok { + return fmt.Errorf("ssh: remote address %v is not an TCP address when checking source-address match", addr) + } + + for _, sourceAddr := range strings.Split(sourceAddrs, ",") { + if allowedIP := net.ParseIP(sourceAddr); allowedIP != nil { + if allowedIP.Equal(tcpAddr.IP) { + return nil + } + } else { + _, ipNet, err := net.ParseCIDR(sourceAddr) + if err != nil { + return fmt.Errorf("ssh: error parsing source-address restriction %q: %v", sourceAddr, err) + } + + if ipNet.Contains(tcpAddr.IP) { + return nil + } + } + } + + return fmt.Errorf("ssh: remote address %v is not allowed because of source-address restriction", addr) +} + +func gssExchangeToken(gssapiConfig *GSSAPIWithMICConfig, firstToken []byte, s *connection, + sessionID []byte, userAuthReq userAuthRequestMsg) (authErr error, perms *Permissions, err error) { + gssAPIServer := gssapiConfig.Server + defer gssAPIServer.DeleteSecContext() + var srcName string + for { + var ( + outToken []byte + needContinue bool + ) + outToken, srcName, needContinue, err = gssAPIServer.AcceptSecContext(firstToken) + if err != nil { + return err, nil, nil + } + if len(outToken) != 0 { + if err := s.transport.writePacket(Marshal(&userAuthGSSAPIToken{ + Token: outToken, + })); err != nil { + return nil, nil, err + } + } + if !needContinue { + break + } + packet, err := s.transport.readPacket() + if err != nil { + return nil, nil, err + } + userAuthGSSAPITokenReq := &userAuthGSSAPIToken{} + if err := Unmarshal(packet, userAuthGSSAPITokenReq); err != nil { + return nil, nil, err + } + } + packet, err := s.transport.readPacket() + if err != nil { + return nil, nil, err + } + userAuthGSSAPIMICReq := &userAuthGSSAPIMIC{} + if err := Unmarshal(packet, userAuthGSSAPIMICReq); err != nil { + return nil, nil, err + } + mic := buildMIC(string(sessionID), userAuthReq.User, userAuthReq.Service, userAuthReq.Method) + if err := gssAPIServer.VerifyMIC(mic, userAuthGSSAPIMICReq.MIC); err != nil { + return err, nil, nil + } + perms, authErr = gssapiConfig.AllowLogin(s, srcName) + return authErr, perms, nil +} + +// ServerAuthError represents server authentication errors and is +// sometimes returned by NewServerConn. It appends any authentication +// errors that may occur, and is returned if all of the authentication +// methods provided by the user failed to authenticate. +type ServerAuthError struct { + // Errors contains authentication errors returned by the authentication + // callback methods. The first entry is typically ErrNoAuth. + Errors []error +} + +func (l ServerAuthError) Error() string { + var errs []string + for _, err := range l.Errors { + errs = append(errs, err.Error()) + } + return "[" + strings.Join(errs, ", ") + "]" +} + +// ErrNoAuth is the error value returned if no +// authentication method has been passed yet. This happens as a normal +// part of the authentication loop, since the client first tries +// 'none' authentication to discover available methods. +// It is returned in ServerAuthError.Errors from NewServerConn. +var ErrNoAuth = errors.New("ssh: no auth passed yet") + +func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, error) { + sessionID := s.transport.getSessionID() + var cache pubKeyCache + var perms *Permissions + + authFailures := 0 + var authErrs []error + var displayedBanner bool + +userAuthLoop: + for { + if authFailures >= config.MaxAuthTries && config.MaxAuthTries > 0 { + discMsg := &disconnectMsg{ + Reason: 2, + Message: "too many authentication failures", + } + + if err := s.transport.writePacket(Marshal(discMsg)); err != nil { + return nil, err + } + + return nil, discMsg + } + + var userAuthReq userAuthRequestMsg + if packet, err := s.transport.readPacket(); err != nil { + if err == io.EOF { + return nil, &ServerAuthError{Errors: authErrs} + } + return nil, err + } else if err = Unmarshal(packet, &userAuthReq); err != nil { + return nil, err + } + + if userAuthReq.Service != serviceSSH { + return nil, errors.New("ssh: client attempted to negotiate for unknown service: " + userAuthReq.Service) + } + + s.user = userAuthReq.User + + if !displayedBanner && config.BannerCallback != nil { + displayedBanner = true + msg := config.BannerCallback(s) + if msg != "" { + bannerMsg := &userAuthBannerMsg{ + Message: msg, + } + if err := s.transport.writePacket(Marshal(bannerMsg)); err != nil { + return nil, err + } + } + } + + perms = nil + authErr := ErrNoAuth + + switch userAuthReq.Method { + case "none": + if config.NoClientAuth { + authErr = nil + } + + // allow initial attempt of 'none' without penalty + if authFailures == 0 { + authFailures-- + } + case "password": + if config.PasswordCallback == nil { + authErr = errors.New("ssh: password auth not configured") + break + } + payload := userAuthReq.Payload + if len(payload) < 1 || payload[0] != 0 { + return nil, parseError(msgUserAuthRequest) + } + payload = payload[1:] + password, payload, ok := parseString(payload) + if !ok || len(payload) > 0 { + return nil, parseError(msgUserAuthRequest) + } + + perms, authErr = config.PasswordCallback(s, password) + case "keyboard-interactive": + if config.KeyboardInteractiveCallback == nil { + authErr = errors.New("ssh: keyboard-interactive auth not configured") + break + } + + prompter := &sshClientKeyboardInteractive{s} + perms, authErr = config.KeyboardInteractiveCallback(s, prompter.Challenge) + case "publickey": + if config.PublicKeyCallback == nil { + authErr = errors.New("ssh: publickey auth not configured") + break + } + payload := userAuthReq.Payload + if len(payload) < 1 { + return nil, parseError(msgUserAuthRequest) + } + isQuery := payload[0] == 0 + payload = payload[1:] + algoBytes, payload, ok := parseString(payload) + if !ok { + return nil, parseError(msgUserAuthRequest) + } + algo := string(algoBytes) + if !isAcceptableAlgo(algo) { + authErr = fmt.Errorf("ssh: algorithm %q not accepted", algo) + break + } + + pubKeyData, payload, ok := parseString(payload) + if !ok { + return nil, parseError(msgUserAuthRequest) + } + + pubKey, err := ParsePublicKey(pubKeyData) + if err != nil { + return nil, err + } + + candidate, ok := cache.get(s.user, pubKeyData) + if !ok { + candidate.user = s.user + candidate.pubKeyData = pubKeyData + candidate.perms, candidate.result = config.PublicKeyCallback(s, pubKey) + if candidate.result == nil && candidate.perms != nil && candidate.perms.CriticalOptions != nil && candidate.perms.CriticalOptions[sourceAddressCriticalOption] != "" { + candidate.result = checkSourceAddress( + s.RemoteAddr(), + candidate.perms.CriticalOptions[sourceAddressCriticalOption]) + } + cache.add(candidate) + } + + if isQuery { + // The client can query if the given public key + // would be okay. + + if len(payload) > 0 { + return nil, parseError(msgUserAuthRequest) + } + + if candidate.result == nil { + okMsg := userAuthPubKeyOkMsg{ + Algo: algo, + PubKey: pubKeyData, + } + if err = s.transport.writePacket(Marshal(&okMsg)); err != nil { + return nil, err + } + continue userAuthLoop + } + authErr = candidate.result + } else { + sig, payload, ok := parseSignature(payload) + if !ok || len(payload) > 0 { + return nil, parseError(msgUserAuthRequest) + } + // Ensure the public key algo and signature algo + // are supported. Compare the private key + // algorithm name that corresponds to algo with + // sig.Format. This is usually the same, but + // for certs, the names differ. + if !isAcceptableAlgo(sig.Format) { + authErr = fmt.Errorf("ssh: algorithm %q not accepted", sig.Format) + break + } + signedData := buildDataSignedForAuth(sessionID, userAuthReq, algoBytes, pubKeyData) + + if err := pubKey.Verify(signedData, sig); err != nil { + return nil, err + } + + authErr = candidate.result + perms = candidate.perms + } + case "gssapi-with-mic": + gssapiConfig := config.GSSAPIWithMICConfig + userAuthRequestGSSAPI, err := parseGSSAPIPayload(userAuthReq.Payload) + if err != nil { + return nil, parseError(msgUserAuthRequest) + } + // OpenSSH supports Kerberos V5 mechanism only for GSS-API authentication. + if userAuthRequestGSSAPI.N == 0 { + authErr = fmt.Errorf("ssh: Mechanism negotiation is not supported") + break + } + var i uint32 + present := false + for i = 0; i < userAuthRequestGSSAPI.N; i++ { + if userAuthRequestGSSAPI.OIDS[i].Equal(krb5Mesh) { + present = true + break + } + } + if !present { + authErr = fmt.Errorf("ssh: GSSAPI authentication must use the Kerberos V5 mechanism") + break + } + // Initial server response, see RFC 4462 section 3.3. + if err := s.transport.writePacket(Marshal(&userAuthGSSAPIResponse{ + SupportMech: krb5OID, + })); err != nil { + return nil, err + } + // Exchange token, see RFC 4462 section 3.4. + packet, err := s.transport.readPacket() + if err != nil { + return nil, err + } + userAuthGSSAPITokenReq := &userAuthGSSAPIToken{} + if err := Unmarshal(packet, userAuthGSSAPITokenReq); err != nil { + return nil, err + } + authErr, perms, err = gssExchangeToken(gssapiConfig, userAuthGSSAPITokenReq.Token, s, sessionID, + userAuthReq) + if err != nil { + return nil, err + } + default: + authErr = fmt.Errorf("ssh: unknown method %q", userAuthReq.Method) + } + + authErrs = append(authErrs, authErr) + + if config.AuthLogCallback != nil { + config.AuthLogCallback(s, userAuthReq.Method, authErr) + } + + if authErr == nil { + break userAuthLoop + } + + authFailures++ + + var failureMsg userAuthFailureMsg + if config.PasswordCallback != nil { + failureMsg.Methods = append(failureMsg.Methods, "password") + } + if config.PublicKeyCallback != nil { + failureMsg.Methods = append(failureMsg.Methods, "publickey") + } + if config.KeyboardInteractiveCallback != nil { + failureMsg.Methods = append(failureMsg.Methods, "keyboard-interactive") + } + if config.GSSAPIWithMICConfig != nil && config.GSSAPIWithMICConfig.Server != nil && + config.GSSAPIWithMICConfig.AllowLogin != nil { + failureMsg.Methods = append(failureMsg.Methods, "gssapi-with-mic") + } + + if len(failureMsg.Methods) == 0 { + return nil, errors.New("ssh: no authentication methods configured but NoClientAuth is also false") + } + + if err := s.transport.writePacket(Marshal(&failureMsg)); err != nil { + return nil, err + } + } + + if err := s.transport.writePacket([]byte{msgUserAuthSuccess}); err != nil { + return nil, err + } + return perms, nil +} + +// sshClientKeyboardInteractive implements a ClientKeyboardInteractive by +// asking the client on the other side of a ServerConn. +type sshClientKeyboardInteractive struct { + *connection +} + +func (c *sshClientKeyboardInteractive) Challenge(user, instruction string, questions []string, echos []bool) (answers []string, err error) { + if len(questions) != len(echos) { + return nil, errors.New("ssh: echos and questions must have equal length") + } + + var prompts []byte + for i := range questions { + prompts = appendString(prompts, questions[i]) + prompts = appendBool(prompts, echos[i]) + } + + if err := c.transport.writePacket(Marshal(&userAuthInfoRequestMsg{ + Instruction: instruction, + NumPrompts: uint32(len(questions)), + Prompts: prompts, + })); err != nil { + return nil, err + } + + packet, err := c.transport.readPacket() + if err != nil { + return nil, err + } + if packet[0] != msgUserAuthInfoResponse { + return nil, unexpectedMessageError(msgUserAuthInfoResponse, packet[0]) + } + packet = packet[1:] + + n, packet, ok := parseUint32(packet) + if !ok || int(n) != len(questions) { + return nil, parseError(msgUserAuthInfoResponse) + } + + for i := uint32(0); i < n; i++ { + ans, rest, ok := parseString(packet) + if !ok { + return nil, parseError(msgUserAuthInfoResponse) + } + + answers = append(answers, string(ans)) + packet = rest + } + if len(packet) != 0 { + return nil, errors.New("ssh: junk at end of message") + } + + return answers, nil +} diff --git a/vendor/golang.org/x/crypto/ssh/session.go b/vendor/golang.org/x/crypto/ssh/session.go new file mode 100644 index 00000000000..d3321f6b784 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/session.go @@ -0,0 +1,647 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +// Session implements an interactive session described in +// "RFC 4254, section 6". + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "io/ioutil" + "sync" +) + +type Signal string + +// POSIX signals as listed in RFC 4254 Section 6.10. +const ( + SIGABRT Signal = "ABRT" + SIGALRM Signal = "ALRM" + SIGFPE Signal = "FPE" + SIGHUP Signal = "HUP" + SIGILL Signal = "ILL" + SIGINT Signal = "INT" + SIGKILL Signal = "KILL" + SIGPIPE Signal = "PIPE" + SIGQUIT Signal = "QUIT" + SIGSEGV Signal = "SEGV" + SIGTERM Signal = "TERM" + SIGUSR1 Signal = "USR1" + SIGUSR2 Signal = "USR2" +) + +var signals = map[Signal]int{ + SIGABRT: 6, + SIGALRM: 14, + SIGFPE: 8, + SIGHUP: 1, + SIGILL: 4, + SIGINT: 2, + SIGKILL: 9, + SIGPIPE: 13, + SIGQUIT: 3, + SIGSEGV: 11, + SIGTERM: 15, +} + +type TerminalModes map[uint8]uint32 + +// POSIX terminal mode flags as listed in RFC 4254 Section 8. +const ( + tty_OP_END = 0 + VINTR = 1 + VQUIT = 2 + VERASE = 3 + VKILL = 4 + VEOF = 5 + VEOL = 6 + VEOL2 = 7 + VSTART = 8 + VSTOP = 9 + VSUSP = 10 + VDSUSP = 11 + VREPRINT = 12 + VWERASE = 13 + VLNEXT = 14 + VFLUSH = 15 + VSWTCH = 16 + VSTATUS = 17 + VDISCARD = 18 + IGNPAR = 30 + PARMRK = 31 + INPCK = 32 + ISTRIP = 33 + INLCR = 34 + IGNCR = 35 + ICRNL = 36 + IUCLC = 37 + IXON = 38 + IXANY = 39 + IXOFF = 40 + IMAXBEL = 41 + ISIG = 50 + ICANON = 51 + XCASE = 52 + ECHO = 53 + ECHOE = 54 + ECHOK = 55 + ECHONL = 56 + NOFLSH = 57 + TOSTOP = 58 + IEXTEN = 59 + ECHOCTL = 60 + ECHOKE = 61 + PENDIN = 62 + OPOST = 70 + OLCUC = 71 + ONLCR = 72 + OCRNL = 73 + ONOCR = 74 + ONLRET = 75 + CS7 = 90 + CS8 = 91 + PARENB = 92 + PARODD = 93 + TTY_OP_ISPEED = 128 + TTY_OP_OSPEED = 129 +) + +// A Session represents a connection to a remote command or shell. +type Session struct { + // Stdin specifies the remote process's standard input. + // If Stdin is nil, the remote process reads from an empty + // bytes.Buffer. + Stdin io.Reader + + // Stdout and Stderr specify the remote process's standard + // output and error. + // + // If either is nil, Run connects the corresponding file + // descriptor to an instance of ioutil.Discard. There is a + // fixed amount of buffering that is shared for the two streams. + // If either blocks it may eventually cause the remote + // command to block. + Stdout io.Writer + Stderr io.Writer + + ch Channel // the channel backing this session + started bool // true once Start, Run or Shell is invoked. + copyFuncs []func() error + errors chan error // one send per copyFunc + + // true if pipe method is active + stdinpipe, stdoutpipe, stderrpipe bool + + // stdinPipeWriter is non-nil if StdinPipe has not been called + // and Stdin was specified by the user; it is the write end of + // a pipe connecting Session.Stdin to the stdin channel. + stdinPipeWriter io.WriteCloser + + exitStatus chan error +} + +// SendRequest sends an out-of-band channel request on the SSH channel +// underlying the session. +func (s *Session) SendRequest(name string, wantReply bool, payload []byte) (bool, error) { + return s.ch.SendRequest(name, wantReply, payload) +} + +func (s *Session) Close() error { + return s.ch.Close() +} + +// RFC 4254 Section 6.4. +type setenvRequest struct { + Name string + Value string +} + +// Setenv sets an environment variable that will be applied to any +// command executed by Shell or Run. +func (s *Session) Setenv(name, value string) error { + msg := setenvRequest{ + Name: name, + Value: value, + } + ok, err := s.ch.SendRequest("env", true, Marshal(&msg)) + if err == nil && !ok { + err = errors.New("ssh: setenv failed") + } + return err +} + +// RFC 4254 Section 6.2. +type ptyRequestMsg struct { + Term string + Columns uint32 + Rows uint32 + Width uint32 + Height uint32 + Modelist string +} + +// RequestPty requests the association of a pty with the session on the remote host. +func (s *Session) RequestPty(term string, h, w int, termmodes TerminalModes) error { + var tm []byte + for k, v := range termmodes { + kv := struct { + Key byte + Val uint32 + }{k, v} + + tm = append(tm, Marshal(&kv)...) + } + tm = append(tm, tty_OP_END) + req := ptyRequestMsg{ + Term: term, + Columns: uint32(w), + Rows: uint32(h), + Width: uint32(w * 8), + Height: uint32(h * 8), + Modelist: string(tm), + } + ok, err := s.ch.SendRequest("pty-req", true, Marshal(&req)) + if err == nil && !ok { + err = errors.New("ssh: pty-req failed") + } + return err +} + +// RFC 4254 Section 6.5. +type subsystemRequestMsg struct { + Subsystem string +} + +// RequestSubsystem requests the association of a subsystem with the session on the remote host. +// A subsystem is a predefined command that runs in the background when the ssh session is initiated +func (s *Session) RequestSubsystem(subsystem string) error { + msg := subsystemRequestMsg{ + Subsystem: subsystem, + } + ok, err := s.ch.SendRequest("subsystem", true, Marshal(&msg)) + if err == nil && !ok { + err = errors.New("ssh: subsystem request failed") + } + return err +} + +// RFC 4254 Section 6.7. +type ptyWindowChangeMsg struct { + Columns uint32 + Rows uint32 + Width uint32 + Height uint32 +} + +// WindowChange informs the remote host about a terminal window dimension change to h rows and w columns. +func (s *Session) WindowChange(h, w int) error { + req := ptyWindowChangeMsg{ + Columns: uint32(w), + Rows: uint32(h), + Width: uint32(w * 8), + Height: uint32(h * 8), + } + _, err := s.ch.SendRequest("window-change", false, Marshal(&req)) + return err +} + +// RFC 4254 Section 6.9. +type signalMsg struct { + Signal string +} + +// Signal sends the given signal to the remote process. +// sig is one of the SIG* constants. +func (s *Session) Signal(sig Signal) error { + msg := signalMsg{ + Signal: string(sig), + } + + _, err := s.ch.SendRequest("signal", false, Marshal(&msg)) + return err +} + +// RFC 4254 Section 6.5. +type execMsg struct { + Command string +} + +// Start runs cmd on the remote host. Typically, the remote +// server passes cmd to the shell for interpretation. +// A Session only accepts one call to Run, Start or Shell. +func (s *Session) Start(cmd string) error { + if s.started { + return errors.New("ssh: session already started") + } + req := execMsg{ + Command: cmd, + } + + ok, err := s.ch.SendRequest("exec", true, Marshal(&req)) + if err == nil && !ok { + err = fmt.Errorf("ssh: command %v failed", cmd) + } + if err != nil { + return err + } + return s.start() +} + +// Run runs cmd on the remote host. Typically, the remote +// server passes cmd to the shell for interpretation. +// A Session only accepts one call to Run, Start, Shell, Output, +// or CombinedOutput. +// +// The returned error is nil if the command runs, has no problems +// copying stdin, stdout, and stderr, and exits with a zero exit +// status. +// +// If the remote server does not send an exit status, an error of type +// *ExitMissingError is returned. If the command completes +// unsuccessfully or is interrupted by a signal, the error is of type +// *ExitError. Other error types may be returned for I/O problems. +func (s *Session) Run(cmd string) error { + err := s.Start(cmd) + if err != nil { + return err + } + return s.Wait() +} + +// Output runs cmd on the remote host and returns its standard output. +func (s *Session) Output(cmd string) ([]byte, error) { + if s.Stdout != nil { + return nil, errors.New("ssh: Stdout already set") + } + var b bytes.Buffer + s.Stdout = &b + err := s.Run(cmd) + return b.Bytes(), err +} + +type singleWriter struct { + b bytes.Buffer + mu sync.Mutex +} + +func (w *singleWriter) Write(p []byte) (int, error) { + w.mu.Lock() + defer w.mu.Unlock() + return w.b.Write(p) +} + +// CombinedOutput runs cmd on the remote host and returns its combined +// standard output and standard error. +func (s *Session) CombinedOutput(cmd string) ([]byte, error) { + if s.Stdout != nil { + return nil, errors.New("ssh: Stdout already set") + } + if s.Stderr != nil { + return nil, errors.New("ssh: Stderr already set") + } + var b singleWriter + s.Stdout = &b + s.Stderr = &b + err := s.Run(cmd) + return b.b.Bytes(), err +} + +// Shell starts a login shell on the remote host. A Session only +// accepts one call to Run, Start, Shell, Output, or CombinedOutput. +func (s *Session) Shell() error { + if s.started { + return errors.New("ssh: session already started") + } + + ok, err := s.ch.SendRequest("shell", true, nil) + if err == nil && !ok { + return errors.New("ssh: could not start shell") + } + if err != nil { + return err + } + return s.start() +} + +func (s *Session) start() error { + s.started = true + + type F func(*Session) + for _, setupFd := range []F{(*Session).stdin, (*Session).stdout, (*Session).stderr} { + setupFd(s) + } + + s.errors = make(chan error, len(s.copyFuncs)) + for _, fn := range s.copyFuncs { + go func(fn func() error) { + s.errors <- fn() + }(fn) + } + return nil +} + +// Wait waits for the remote command to exit. +// +// The returned error is nil if the command runs, has no problems +// copying stdin, stdout, and stderr, and exits with a zero exit +// status. +// +// If the remote server does not send an exit status, an error of type +// *ExitMissingError is returned. If the command completes +// unsuccessfully or is interrupted by a signal, the error is of type +// *ExitError. Other error types may be returned for I/O problems. +func (s *Session) Wait() error { + if !s.started { + return errors.New("ssh: session not started") + } + waitErr := <-s.exitStatus + + if s.stdinPipeWriter != nil { + s.stdinPipeWriter.Close() + } + var copyError error + for range s.copyFuncs { + if err := <-s.errors; err != nil && copyError == nil { + copyError = err + } + } + if waitErr != nil { + return waitErr + } + return copyError +} + +func (s *Session) wait(reqs <-chan *Request) error { + wm := Waitmsg{status: -1} + // Wait for msg channel to be closed before returning. + for msg := range reqs { + switch msg.Type { + case "exit-status": + wm.status = int(binary.BigEndian.Uint32(msg.Payload)) + case "exit-signal": + var sigval struct { + Signal string + CoreDumped bool + Error string + Lang string + } + if err := Unmarshal(msg.Payload, &sigval); err != nil { + return err + } + + // Must sanitize strings? + wm.signal = sigval.Signal + wm.msg = sigval.Error + wm.lang = sigval.Lang + default: + // This handles keepalives and matches + // OpenSSH's behaviour. + if msg.WantReply { + msg.Reply(false, nil) + } + } + } + if wm.status == 0 { + return nil + } + if wm.status == -1 { + // exit-status was never sent from server + if wm.signal == "" { + // signal was not sent either. RFC 4254 + // section 6.10 recommends against this + // behavior, but it is allowed, so we let + // clients handle it. + return &ExitMissingError{} + } + wm.status = 128 + if _, ok := signals[Signal(wm.signal)]; ok { + wm.status += signals[Signal(wm.signal)] + } + } + + return &ExitError{wm} +} + +// ExitMissingError is returned if a session is torn down cleanly, but +// the server sends no confirmation of the exit status. +type ExitMissingError struct{} + +func (e *ExitMissingError) Error() string { + return "wait: remote command exited without exit status or exit signal" +} + +func (s *Session) stdin() { + if s.stdinpipe { + return + } + var stdin io.Reader + if s.Stdin == nil { + stdin = new(bytes.Buffer) + } else { + r, w := io.Pipe() + go func() { + _, err := io.Copy(w, s.Stdin) + w.CloseWithError(err) + }() + stdin, s.stdinPipeWriter = r, w + } + s.copyFuncs = append(s.copyFuncs, func() error { + _, err := io.Copy(s.ch, stdin) + if err1 := s.ch.CloseWrite(); err == nil && err1 != io.EOF { + err = err1 + } + return err + }) +} + +func (s *Session) stdout() { + if s.stdoutpipe { + return + } + if s.Stdout == nil { + s.Stdout = ioutil.Discard + } + s.copyFuncs = append(s.copyFuncs, func() error { + _, err := io.Copy(s.Stdout, s.ch) + return err + }) +} + +func (s *Session) stderr() { + if s.stderrpipe { + return + } + if s.Stderr == nil { + s.Stderr = ioutil.Discard + } + s.copyFuncs = append(s.copyFuncs, func() error { + _, err := io.Copy(s.Stderr, s.ch.Stderr()) + return err + }) +} + +// sessionStdin reroutes Close to CloseWrite. +type sessionStdin struct { + io.Writer + ch Channel +} + +func (s *sessionStdin) Close() error { + return s.ch.CloseWrite() +} + +// StdinPipe returns a pipe that will be connected to the +// remote command's standard input when the command starts. +func (s *Session) StdinPipe() (io.WriteCloser, error) { + if s.Stdin != nil { + return nil, errors.New("ssh: Stdin already set") + } + if s.started { + return nil, errors.New("ssh: StdinPipe after process started") + } + s.stdinpipe = true + return &sessionStdin{s.ch, s.ch}, nil +} + +// StdoutPipe returns a pipe that will be connected to the +// remote command's standard output when the command starts. +// There is a fixed amount of buffering that is shared between +// stdout and stderr streams. If the StdoutPipe reader is +// not serviced fast enough it may eventually cause the +// remote command to block. +func (s *Session) StdoutPipe() (io.Reader, error) { + if s.Stdout != nil { + return nil, errors.New("ssh: Stdout already set") + } + if s.started { + return nil, errors.New("ssh: StdoutPipe after process started") + } + s.stdoutpipe = true + return s.ch, nil +} + +// StderrPipe returns a pipe that will be connected to the +// remote command's standard error when the command starts. +// There is a fixed amount of buffering that is shared between +// stdout and stderr streams. If the StderrPipe reader is +// not serviced fast enough it may eventually cause the +// remote command to block. +func (s *Session) StderrPipe() (io.Reader, error) { + if s.Stderr != nil { + return nil, errors.New("ssh: Stderr already set") + } + if s.started { + return nil, errors.New("ssh: StderrPipe after process started") + } + s.stderrpipe = true + return s.ch.Stderr(), nil +} + +// newSession returns a new interactive session on the remote host. +func newSession(ch Channel, reqs <-chan *Request) (*Session, error) { + s := &Session{ + ch: ch, + } + s.exitStatus = make(chan error, 1) + go func() { + s.exitStatus <- s.wait(reqs) + }() + + return s, nil +} + +// An ExitError reports unsuccessful completion of a remote command. +type ExitError struct { + Waitmsg +} + +func (e *ExitError) Error() string { + return e.Waitmsg.String() +} + +// Waitmsg stores the information about an exited remote command +// as reported by Wait. +type Waitmsg struct { + status int + signal string + msg string + lang string +} + +// ExitStatus returns the exit status of the remote command. +func (w Waitmsg) ExitStatus() int { + return w.status +} + +// Signal returns the exit signal of the remote command if +// it was terminated violently. +func (w Waitmsg) Signal() string { + return w.signal +} + +// Msg returns the exit message given by the remote command +func (w Waitmsg) Msg() string { + return w.msg +} + +// Lang returns the language tag. See RFC 3066 +func (w Waitmsg) Lang() string { + return w.lang +} + +func (w Waitmsg) String() string { + str := fmt.Sprintf("Process exited with status %v", w.status) + if w.signal != "" { + str += fmt.Sprintf(" from signal %v", w.signal) + } + if w.msg != "" { + str += fmt.Sprintf(". Reason was: %v", w.msg) + } + return str +} diff --git a/vendor/golang.org/x/crypto/ssh/ssh_gss.go b/vendor/golang.org/x/crypto/ssh/ssh_gss.go new file mode 100644 index 00000000000..24bd7c8e830 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/ssh_gss.go @@ -0,0 +1,139 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "encoding/asn1" + "errors" +) + +var krb5OID []byte + +func init() { + krb5OID, _ = asn1.Marshal(krb5Mesh) +} + +// GSSAPIClient provides the API to plug-in GSSAPI authentication for client logins. +type GSSAPIClient interface { + // InitSecContext initiates the establishment of a security context for GSS-API between the + // ssh client and ssh server. Initially the token parameter should be specified as nil. + // The routine may return a outputToken which should be transferred to + // the ssh server, where the ssh server will present it to + // AcceptSecContext. If no token need be sent, InitSecContext will indicate this by setting + // needContinue to false. To complete the context + // establishment, one or more reply tokens may be required from the ssh + // server;if so, InitSecContext will return a needContinue which is true. + // In this case, InitSecContext should be called again when the + // reply token is received from the ssh server, passing the reply + // token to InitSecContext via the token parameters. + // See RFC 2743 section 2.2.1 and RFC 4462 section 3.4. + InitSecContext(target string, token []byte, isGSSDelegCreds bool) (outputToken []byte, needContinue bool, err error) + // GetMIC generates a cryptographic MIC for the SSH2 message, and places + // the MIC in a token for transfer to the ssh server. + // The contents of the MIC field are obtained by calling GSS_GetMIC() + // over the following, using the GSS-API context that was just + // established: + // string session identifier + // byte SSH_MSG_USERAUTH_REQUEST + // string user name + // string service + // string "gssapi-with-mic" + // See RFC 2743 section 2.3.1 and RFC 4462 3.5. + GetMIC(micFiled []byte) ([]byte, error) + // Whenever possible, it should be possible for + // DeleteSecContext() calls to be successfully processed even + // if other calls cannot succeed, thereby enabling context-related + // resources to be released. + // In addition to deleting established security contexts, + // gss_delete_sec_context must also be able to delete "half-built" + // security contexts resulting from an incomplete sequence of + // InitSecContext()/AcceptSecContext() calls. + // See RFC 2743 section 2.2.3. + DeleteSecContext() error +} + +// GSSAPIServer provides the API to plug in GSSAPI authentication for server logins. +type GSSAPIServer interface { + // AcceptSecContext allows a remotely initiated security context between the application + // and a remote peer to be established by the ssh client. The routine may return a + // outputToken which should be transferred to the ssh client, + // where the ssh client will present it to InitSecContext. + // If no token need be sent, AcceptSecContext will indicate this + // by setting the needContinue to false. To + // complete the context establishment, one or more reply tokens may be + // required from the ssh client. if so, AcceptSecContext + // will return a needContinue which is true, in which case it + // should be called again when the reply token is received from the ssh + // client, passing the token to AcceptSecContext via the + // token parameters. + // The srcName return value is the authenticated username. + // See RFC 2743 section 2.2.2 and RFC 4462 section 3.4. + AcceptSecContext(token []byte) (outputToken []byte, srcName string, needContinue bool, err error) + // VerifyMIC verifies that a cryptographic MIC, contained in the token parameter, + // fits the supplied message is received from the ssh client. + // See RFC 2743 section 2.3.2. + VerifyMIC(micField []byte, micToken []byte) error + // Whenever possible, it should be possible for + // DeleteSecContext() calls to be successfully processed even + // if other calls cannot succeed, thereby enabling context-related + // resources to be released. + // In addition to deleting established security contexts, + // gss_delete_sec_context must also be able to delete "half-built" + // security contexts resulting from an incomplete sequence of + // InitSecContext()/AcceptSecContext() calls. + // See RFC 2743 section 2.2.3. + DeleteSecContext() error +} + +var ( + // OpenSSH supports Kerberos V5 mechanism only for GSS-API authentication, + // so we also support the krb5 mechanism only. + // See RFC 1964 section 1. + krb5Mesh = asn1.ObjectIdentifier{1, 2, 840, 113554, 1, 2, 2} +) + +// The GSS-API authentication method is initiated when the client sends an SSH_MSG_USERAUTH_REQUEST +// See RFC 4462 section 3.2. +type userAuthRequestGSSAPI struct { + N uint32 + OIDS []asn1.ObjectIdentifier +} + +func parseGSSAPIPayload(payload []byte) (*userAuthRequestGSSAPI, error) { + n, rest, ok := parseUint32(payload) + if !ok { + return nil, errors.New("parse uint32 failed") + } + s := &userAuthRequestGSSAPI{ + N: n, + OIDS: make([]asn1.ObjectIdentifier, n), + } + for i := 0; i < int(n); i++ { + var ( + desiredMech []byte + err error + ) + desiredMech, rest, ok = parseString(rest) + if !ok { + return nil, errors.New("parse string failed") + } + if rest, err = asn1.Unmarshal(desiredMech, &s.OIDS[i]); err != nil { + return nil, err + } + + } + return s, nil +} + +// See RFC 4462 section 3.6. +func buildMIC(sessionID string, username string, service string, authMethod string) []byte { + out := make([]byte, 0, 0) + out = appendString(out, sessionID) + out = append(out, msgUserAuthRequest) + out = appendString(out, username) + out = appendString(out, service) + out = appendString(out, authMethod) + return out +} diff --git a/vendor/golang.org/x/crypto/ssh/streamlocal.go b/vendor/golang.org/x/crypto/ssh/streamlocal.go new file mode 100644 index 00000000000..b171b330bc3 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/streamlocal.go @@ -0,0 +1,116 @@ +package ssh + +import ( + "errors" + "io" + "net" +) + +// streamLocalChannelOpenDirectMsg is a struct used for SSH_MSG_CHANNEL_OPEN message +// with "direct-streamlocal@openssh.com" string. +// +// See openssh-portable/PROTOCOL, section 2.4. connection: Unix domain socket forwarding +// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL#L235 +type streamLocalChannelOpenDirectMsg struct { + socketPath string + reserved0 string + reserved1 uint32 +} + +// forwardedStreamLocalPayload is a struct used for SSH_MSG_CHANNEL_OPEN message +// with "forwarded-streamlocal@openssh.com" string. +type forwardedStreamLocalPayload struct { + SocketPath string + Reserved0 string +} + +// streamLocalChannelForwardMsg is a struct used for SSH2_MSG_GLOBAL_REQUEST message +// with "streamlocal-forward@openssh.com"/"cancel-streamlocal-forward@openssh.com" string. +type streamLocalChannelForwardMsg struct { + socketPath string +} + +// ListenUnix is similar to ListenTCP but uses a Unix domain socket. +func (c *Client) ListenUnix(socketPath string) (net.Listener, error) { + c.handleForwardsOnce.Do(c.handleForwards) + m := streamLocalChannelForwardMsg{ + socketPath, + } + // send message + ok, _, err := c.SendRequest("streamlocal-forward@openssh.com", true, Marshal(&m)) + if err != nil { + return nil, err + } + if !ok { + return nil, errors.New("ssh: streamlocal-forward@openssh.com request denied by peer") + } + ch := c.forwards.add(&net.UnixAddr{Name: socketPath, Net: "unix"}) + + return &unixListener{socketPath, c, ch}, nil +} + +func (c *Client) dialStreamLocal(socketPath string) (Channel, error) { + msg := streamLocalChannelOpenDirectMsg{ + socketPath: socketPath, + } + ch, in, err := c.OpenChannel("direct-streamlocal@openssh.com", Marshal(&msg)) + if err != nil { + return nil, err + } + go DiscardRequests(in) + return ch, err +} + +type unixListener struct { + socketPath string + + conn *Client + in <-chan forward +} + +// Accept waits for and returns the next connection to the listener. +func (l *unixListener) Accept() (net.Conn, error) { + s, ok := <-l.in + if !ok { + return nil, io.EOF + } + ch, incoming, err := s.newCh.Accept() + if err != nil { + return nil, err + } + go DiscardRequests(incoming) + + return &chanConn{ + Channel: ch, + laddr: &net.UnixAddr{ + Name: l.socketPath, + Net: "unix", + }, + raddr: &net.UnixAddr{ + Name: "@", + Net: "unix", + }, + }, nil +} + +// Close closes the listener. +func (l *unixListener) Close() error { + // this also closes the listener. + l.conn.forwards.remove(&net.UnixAddr{Name: l.socketPath, Net: "unix"}) + m := streamLocalChannelForwardMsg{ + l.socketPath, + } + ok, _, err := l.conn.SendRequest("cancel-streamlocal-forward@openssh.com", true, Marshal(&m)) + if err == nil && !ok { + err = errors.New("ssh: cancel-streamlocal-forward@openssh.com failed") + } + return err +} + +// Addr returns the listener's network address. +func (l *unixListener) Addr() net.Addr { + return &net.UnixAddr{ + Name: l.socketPath, + Net: "unix", + } +} diff --git a/vendor/golang.org/x/crypto/ssh/tcpip.go b/vendor/golang.org/x/crypto/ssh/tcpip.go new file mode 100644 index 00000000000..80d35f5ec18 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/tcpip.go @@ -0,0 +1,474 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "errors" + "fmt" + "io" + "math/rand" + "net" + "strconv" + "strings" + "sync" + "time" +) + +// Listen requests the remote peer open a listening socket on +// addr. Incoming connections will be available by calling Accept on +// the returned net.Listener. The listener must be serviced, or the +// SSH connection may hang. +// N must be "tcp", "tcp4", "tcp6", or "unix". +func (c *Client) Listen(n, addr string) (net.Listener, error) { + switch n { + case "tcp", "tcp4", "tcp6": + laddr, err := net.ResolveTCPAddr(n, addr) + if err != nil { + return nil, err + } + return c.ListenTCP(laddr) + case "unix": + return c.ListenUnix(addr) + default: + return nil, fmt.Errorf("ssh: unsupported protocol: %s", n) + } +} + +// Automatic port allocation is broken with OpenSSH before 6.0. See +// also https://bugzilla.mindrot.org/show_bug.cgi?id=2017. In +// particular, OpenSSH 5.9 sends a channelOpenMsg with port number 0, +// rather than the actual port number. This means you can never open +// two different listeners with auto allocated ports. We work around +// this by trying explicit ports until we succeed. + +const openSSHPrefix = "OpenSSH_" + +var portRandomizer = rand.New(rand.NewSource(time.Now().UnixNano())) + +// isBrokenOpenSSHVersion returns true if the given version string +// specifies a version of OpenSSH that is known to have a bug in port +// forwarding. +func isBrokenOpenSSHVersion(versionStr string) bool { + i := strings.Index(versionStr, openSSHPrefix) + if i < 0 { + return false + } + i += len(openSSHPrefix) + j := i + for ; j < len(versionStr); j++ { + if versionStr[j] < '0' || versionStr[j] > '9' { + break + } + } + version, _ := strconv.Atoi(versionStr[i:j]) + return version < 6 +} + +// autoPortListenWorkaround simulates automatic port allocation by +// trying random ports repeatedly. +func (c *Client) autoPortListenWorkaround(laddr *net.TCPAddr) (net.Listener, error) { + var sshListener net.Listener + var err error + const tries = 10 + for i := 0; i < tries; i++ { + addr := *laddr + addr.Port = 1024 + portRandomizer.Intn(60000) + sshListener, err = c.ListenTCP(&addr) + if err == nil { + laddr.Port = addr.Port + return sshListener, err + } + } + return nil, fmt.Errorf("ssh: listen on random port failed after %d tries: %v", tries, err) +} + +// RFC 4254 7.1 +type channelForwardMsg struct { + addr string + rport uint32 +} + +// handleForwards starts goroutines handling forwarded connections. +// It's called on first use by (*Client).ListenTCP to not launch +// goroutines until needed. +func (c *Client) handleForwards() { + go c.forwards.handleChannels(c.HandleChannelOpen("forwarded-tcpip")) + go c.forwards.handleChannels(c.HandleChannelOpen("forwarded-streamlocal@openssh.com")) +} + +// ListenTCP requests the remote peer open a listening socket +// on laddr. Incoming connections will be available by calling +// Accept on the returned net.Listener. +func (c *Client) ListenTCP(laddr *net.TCPAddr) (net.Listener, error) { + c.handleForwardsOnce.Do(c.handleForwards) + if laddr.Port == 0 && isBrokenOpenSSHVersion(string(c.ServerVersion())) { + return c.autoPortListenWorkaround(laddr) + } + + m := channelForwardMsg{ + laddr.IP.String(), + uint32(laddr.Port), + } + // send message + ok, resp, err := c.SendRequest("tcpip-forward", true, Marshal(&m)) + if err != nil { + return nil, err + } + if !ok { + return nil, errors.New("ssh: tcpip-forward request denied by peer") + } + + // If the original port was 0, then the remote side will + // supply a real port number in the response. + if laddr.Port == 0 { + var p struct { + Port uint32 + } + if err := Unmarshal(resp, &p); err != nil { + return nil, err + } + laddr.Port = int(p.Port) + } + + // Register this forward, using the port number we obtained. + ch := c.forwards.add(laddr) + + return &tcpListener{laddr, c, ch}, nil +} + +// forwardList stores a mapping between remote +// forward requests and the tcpListeners. +type forwardList struct { + sync.Mutex + entries []forwardEntry +} + +// forwardEntry represents an established mapping of a laddr on a +// remote ssh server to a channel connected to a tcpListener. +type forwardEntry struct { + laddr net.Addr + c chan forward +} + +// forward represents an incoming forwarded tcpip connection. The +// arguments to add/remove/lookup should be address as specified in +// the original forward-request. +type forward struct { + newCh NewChannel // the ssh client channel underlying this forward + raddr net.Addr // the raddr of the incoming connection +} + +func (l *forwardList) add(addr net.Addr) chan forward { + l.Lock() + defer l.Unlock() + f := forwardEntry{ + laddr: addr, + c: make(chan forward, 1), + } + l.entries = append(l.entries, f) + return f.c +} + +// See RFC 4254, section 7.2 +type forwardedTCPPayload struct { + Addr string + Port uint32 + OriginAddr string + OriginPort uint32 +} + +// parseTCPAddr parses the originating address from the remote into a *net.TCPAddr. +func parseTCPAddr(addr string, port uint32) (*net.TCPAddr, error) { + if port == 0 || port > 65535 { + return nil, fmt.Errorf("ssh: port number out of range: %d", port) + } + ip := net.ParseIP(string(addr)) + if ip == nil { + return nil, fmt.Errorf("ssh: cannot parse IP address %q", addr) + } + return &net.TCPAddr{IP: ip, Port: int(port)}, nil +} + +func (l *forwardList) handleChannels(in <-chan NewChannel) { + for ch := range in { + var ( + laddr net.Addr + raddr net.Addr + err error + ) + switch channelType := ch.ChannelType(); channelType { + case "forwarded-tcpip": + var payload forwardedTCPPayload + if err = Unmarshal(ch.ExtraData(), &payload); err != nil { + ch.Reject(ConnectionFailed, "could not parse forwarded-tcpip payload: "+err.Error()) + continue + } + + // RFC 4254 section 7.2 specifies that incoming + // addresses should list the address, in string + // format. It is implied that this should be an IP + // address, as it would be impossible to connect to it + // otherwise. + laddr, err = parseTCPAddr(payload.Addr, payload.Port) + if err != nil { + ch.Reject(ConnectionFailed, err.Error()) + continue + } + raddr, err = parseTCPAddr(payload.OriginAddr, payload.OriginPort) + if err != nil { + ch.Reject(ConnectionFailed, err.Error()) + continue + } + + case "forwarded-streamlocal@openssh.com": + var payload forwardedStreamLocalPayload + if err = Unmarshal(ch.ExtraData(), &payload); err != nil { + ch.Reject(ConnectionFailed, "could not parse forwarded-streamlocal@openssh.com payload: "+err.Error()) + continue + } + laddr = &net.UnixAddr{ + Name: payload.SocketPath, + Net: "unix", + } + raddr = &net.UnixAddr{ + Name: "@", + Net: "unix", + } + default: + panic(fmt.Errorf("ssh: unknown channel type %s", channelType)) + } + if ok := l.forward(laddr, raddr, ch); !ok { + // Section 7.2, implementations MUST reject spurious incoming + // connections. + ch.Reject(Prohibited, "no forward for address") + continue + } + + } +} + +// remove removes the forward entry, and the channel feeding its +// listener. +func (l *forwardList) remove(addr net.Addr) { + l.Lock() + defer l.Unlock() + for i, f := range l.entries { + if addr.Network() == f.laddr.Network() && addr.String() == f.laddr.String() { + l.entries = append(l.entries[:i], l.entries[i+1:]...) + close(f.c) + return + } + } +} + +// closeAll closes and clears all forwards. +func (l *forwardList) closeAll() { + l.Lock() + defer l.Unlock() + for _, f := range l.entries { + close(f.c) + } + l.entries = nil +} + +func (l *forwardList) forward(laddr, raddr net.Addr, ch NewChannel) bool { + l.Lock() + defer l.Unlock() + for _, f := range l.entries { + if laddr.Network() == f.laddr.Network() && laddr.String() == f.laddr.String() { + f.c <- forward{newCh: ch, raddr: raddr} + return true + } + } + return false +} + +type tcpListener struct { + laddr *net.TCPAddr + + conn *Client + in <-chan forward +} + +// Accept waits for and returns the next connection to the listener. +func (l *tcpListener) Accept() (net.Conn, error) { + s, ok := <-l.in + if !ok { + return nil, io.EOF + } + ch, incoming, err := s.newCh.Accept() + if err != nil { + return nil, err + } + go DiscardRequests(incoming) + + return &chanConn{ + Channel: ch, + laddr: l.laddr, + raddr: s.raddr, + }, nil +} + +// Close closes the listener. +func (l *tcpListener) Close() error { + m := channelForwardMsg{ + l.laddr.IP.String(), + uint32(l.laddr.Port), + } + + // this also closes the listener. + l.conn.forwards.remove(l.laddr) + ok, _, err := l.conn.SendRequest("cancel-tcpip-forward", true, Marshal(&m)) + if err == nil && !ok { + err = errors.New("ssh: cancel-tcpip-forward failed") + } + return err +} + +// Addr returns the listener's network address. +func (l *tcpListener) Addr() net.Addr { + return l.laddr +} + +// Dial initiates a connection to the addr from the remote host. +// The resulting connection has a zero LocalAddr() and RemoteAddr(). +func (c *Client) Dial(n, addr string) (net.Conn, error) { + var ch Channel + switch n { + case "tcp", "tcp4", "tcp6": + // Parse the address into host and numeric port. + host, portString, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } + port, err := strconv.ParseUint(portString, 10, 16) + if err != nil { + return nil, err + } + ch, err = c.dial(net.IPv4zero.String(), 0, host, int(port)) + if err != nil { + return nil, err + } + // Use a zero address for local and remote address. + zeroAddr := &net.TCPAddr{ + IP: net.IPv4zero, + Port: 0, + } + return &chanConn{ + Channel: ch, + laddr: zeroAddr, + raddr: zeroAddr, + }, nil + case "unix": + var err error + ch, err = c.dialStreamLocal(addr) + if err != nil { + return nil, err + } + return &chanConn{ + Channel: ch, + laddr: &net.UnixAddr{ + Name: "@", + Net: "unix", + }, + raddr: &net.UnixAddr{ + Name: addr, + Net: "unix", + }, + }, nil + default: + return nil, fmt.Errorf("ssh: unsupported protocol: %s", n) + } +} + +// DialTCP connects to the remote address raddr on the network net, +// which must be "tcp", "tcp4", or "tcp6". If laddr is not nil, it is used +// as the local address for the connection. +func (c *Client) DialTCP(n string, laddr, raddr *net.TCPAddr) (net.Conn, error) { + if laddr == nil { + laddr = &net.TCPAddr{ + IP: net.IPv4zero, + Port: 0, + } + } + ch, err := c.dial(laddr.IP.String(), laddr.Port, raddr.IP.String(), raddr.Port) + if err != nil { + return nil, err + } + return &chanConn{ + Channel: ch, + laddr: laddr, + raddr: raddr, + }, nil +} + +// RFC 4254 7.2 +type channelOpenDirectMsg struct { + raddr string + rport uint32 + laddr string + lport uint32 +} + +func (c *Client) dial(laddr string, lport int, raddr string, rport int) (Channel, error) { + msg := channelOpenDirectMsg{ + raddr: raddr, + rport: uint32(rport), + laddr: laddr, + lport: uint32(lport), + } + ch, in, err := c.OpenChannel("direct-tcpip", Marshal(&msg)) + if err != nil { + return nil, err + } + go DiscardRequests(in) + return ch, err +} + +type tcpChan struct { + Channel // the backing channel +} + +// chanConn fulfills the net.Conn interface without +// the tcpChan having to hold laddr or raddr directly. +type chanConn struct { + Channel + laddr, raddr net.Addr +} + +// LocalAddr returns the local network address. +func (t *chanConn) LocalAddr() net.Addr { + return t.laddr +} + +// RemoteAddr returns the remote network address. +func (t *chanConn) RemoteAddr() net.Addr { + return t.raddr +} + +// SetDeadline sets the read and write deadlines associated +// with the connection. +func (t *chanConn) SetDeadline(deadline time.Time) error { + if err := t.SetReadDeadline(deadline); err != nil { + return err + } + return t.SetWriteDeadline(deadline) +} + +// SetReadDeadline sets the read deadline. +// A zero value for t means Read will not time out. +// After the deadline, the error from Read will implement net.Error +// with Timeout() == true. +func (t *chanConn) SetReadDeadline(deadline time.Time) error { + // for compatibility with previous version, + // the error message contains "tcpChan" + return errors.New("ssh: tcpChan: deadline not supported") +} + +// SetWriteDeadline exists to satisfy the net.Conn interface +// but is not implemented by this type. It always returns an error. +func (t *chanConn) SetWriteDeadline(deadline time.Time) error { + return errors.New("ssh: tcpChan: deadline not supported") +} diff --git a/vendor/golang.org/x/crypto/ssh/transport.go b/vendor/golang.org/x/crypto/ssh/transport.go new file mode 100644 index 00000000000..49ddc2e7de4 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/transport.go @@ -0,0 +1,353 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bufio" + "bytes" + "errors" + "io" + "log" +) + +// debugTransport if set, will print packet types as they go over the +// wire. No message decoding is done, to minimize the impact on timing. +const debugTransport = false + +const ( + gcmCipherID = "aes128-gcm@openssh.com" + aes128cbcID = "aes128-cbc" + tripledescbcID = "3des-cbc" +) + +// packetConn represents a transport that implements packet based +// operations. +type packetConn interface { + // Encrypt and send a packet of data to the remote peer. + writePacket(packet []byte) error + + // Read a packet from the connection. The read is blocking, + // i.e. if error is nil, then the returned byte slice is + // always non-empty. + readPacket() ([]byte, error) + + // Close closes the write-side of the connection. + Close() error +} + +// transport is the keyingTransport that implements the SSH packet +// protocol. +type transport struct { + reader connectionState + writer connectionState + + bufReader *bufio.Reader + bufWriter *bufio.Writer + rand io.Reader + isClient bool + io.Closer +} + +// packetCipher represents a combination of SSH encryption/MAC +// protocol. A single instance should be used for one direction only. +type packetCipher interface { + // writeCipherPacket encrypts the packet and writes it to w. The + // contents of the packet are generally scrambled. + writeCipherPacket(seqnum uint32, w io.Writer, rand io.Reader, packet []byte) error + + // readCipherPacket reads and decrypts a packet of data. The + // returned packet may be overwritten by future calls of + // readPacket. + readCipherPacket(seqnum uint32, r io.Reader) ([]byte, error) +} + +// connectionState represents one side (read or write) of the +// connection. This is necessary because each direction has its own +// keys, and can even have its own algorithms +type connectionState struct { + packetCipher + seqNum uint32 + dir direction + pendingKeyChange chan packetCipher +} + +// prepareKeyChange sets up key material for a keychange. The key changes in +// both directions are triggered by reading and writing a msgNewKey packet +// respectively. +func (t *transport) prepareKeyChange(algs *algorithms, kexResult *kexResult) error { + ciph, err := newPacketCipher(t.reader.dir, algs.r, kexResult) + if err != nil { + return err + } + t.reader.pendingKeyChange <- ciph + + ciph, err = newPacketCipher(t.writer.dir, algs.w, kexResult) + if err != nil { + return err + } + t.writer.pendingKeyChange <- ciph + + return nil +} + +func (t *transport) printPacket(p []byte, write bool) { + if len(p) == 0 { + return + } + who := "server" + if t.isClient { + who = "client" + } + what := "read" + if write { + what = "write" + } + + log.Println(what, who, p[0]) +} + +// Read and decrypt next packet. +func (t *transport) readPacket() (p []byte, err error) { + for { + p, err = t.reader.readPacket(t.bufReader) + if err != nil { + break + } + if len(p) == 0 || (p[0] != msgIgnore && p[0] != msgDebug) { + break + } + } + if debugTransport { + t.printPacket(p, false) + } + + return p, err +} + +func (s *connectionState) readPacket(r *bufio.Reader) ([]byte, error) { + packet, err := s.packetCipher.readCipherPacket(s.seqNum, r) + s.seqNum++ + if err == nil && len(packet) == 0 { + err = errors.New("ssh: zero length packet") + } + + if len(packet) > 0 { + switch packet[0] { + case msgNewKeys: + select { + case cipher := <-s.pendingKeyChange: + s.packetCipher = cipher + default: + return nil, errors.New("ssh: got bogus newkeys message") + } + + case msgDisconnect: + // Transform a disconnect message into an + // error. Since this is lowest level at which + // we interpret message types, doing it here + // ensures that we don't have to handle it + // elsewhere. + var msg disconnectMsg + if err := Unmarshal(packet, &msg); err != nil { + return nil, err + } + return nil, &msg + } + } + + // The packet may point to an internal buffer, so copy the + // packet out here. + fresh := make([]byte, len(packet)) + copy(fresh, packet) + + return fresh, err +} + +func (t *transport) writePacket(packet []byte) error { + if debugTransport { + t.printPacket(packet, true) + } + return t.writer.writePacket(t.bufWriter, t.rand, packet) +} + +func (s *connectionState) writePacket(w *bufio.Writer, rand io.Reader, packet []byte) error { + changeKeys := len(packet) > 0 && packet[0] == msgNewKeys + + err := s.packetCipher.writeCipherPacket(s.seqNum, w, rand, packet) + if err != nil { + return err + } + if err = w.Flush(); err != nil { + return err + } + s.seqNum++ + if changeKeys { + select { + case cipher := <-s.pendingKeyChange: + s.packetCipher = cipher + default: + panic("ssh: no key material for msgNewKeys") + } + } + return err +} + +func newTransport(rwc io.ReadWriteCloser, rand io.Reader, isClient bool) *transport { + t := &transport{ + bufReader: bufio.NewReader(rwc), + bufWriter: bufio.NewWriter(rwc), + rand: rand, + reader: connectionState{ + packetCipher: &streamPacketCipher{cipher: noneCipher{}}, + pendingKeyChange: make(chan packetCipher, 1), + }, + writer: connectionState{ + packetCipher: &streamPacketCipher{cipher: noneCipher{}}, + pendingKeyChange: make(chan packetCipher, 1), + }, + Closer: rwc, + } + t.isClient = isClient + + if isClient { + t.reader.dir = serverKeys + t.writer.dir = clientKeys + } else { + t.reader.dir = clientKeys + t.writer.dir = serverKeys + } + + return t +} + +type direction struct { + ivTag []byte + keyTag []byte + macKeyTag []byte +} + +var ( + serverKeys = direction{[]byte{'B'}, []byte{'D'}, []byte{'F'}} + clientKeys = direction{[]byte{'A'}, []byte{'C'}, []byte{'E'}} +) + +// setupKeys sets the cipher and MAC keys from kex.K, kex.H and sessionId, as +// described in RFC 4253, section 6.4. direction should either be serverKeys +// (to setup server->client keys) or clientKeys (for client->server keys). +func newPacketCipher(d direction, algs directionAlgorithms, kex *kexResult) (packetCipher, error) { + cipherMode := cipherModes[algs.Cipher] + macMode := macModes[algs.MAC] + + iv := make([]byte, cipherMode.ivSize) + key := make([]byte, cipherMode.keySize) + macKey := make([]byte, macMode.keySize) + + generateKeyMaterial(iv, d.ivTag, kex) + generateKeyMaterial(key, d.keyTag, kex) + generateKeyMaterial(macKey, d.macKeyTag, kex) + + return cipherModes[algs.Cipher].create(key, iv, macKey, algs) +} + +// generateKeyMaterial fills out with key material generated from tag, K, H +// and sessionId, as specified in RFC 4253, section 7.2. +func generateKeyMaterial(out, tag []byte, r *kexResult) { + var digestsSoFar []byte + + h := r.Hash.New() + for len(out) > 0 { + h.Reset() + h.Write(r.K) + h.Write(r.H) + + if len(digestsSoFar) == 0 { + h.Write(tag) + h.Write(r.SessionID) + } else { + h.Write(digestsSoFar) + } + + digest := h.Sum(nil) + n := copy(out, digest) + out = out[n:] + if len(out) > 0 { + digestsSoFar = append(digestsSoFar, digest...) + } + } +} + +const packageVersion = "SSH-2.0-Go" + +// Sends and receives a version line. The versionLine string should +// be US ASCII, start with "SSH-2.0-", and should not include a +// newline. exchangeVersions returns the other side's version line. +func exchangeVersions(rw io.ReadWriter, versionLine []byte) (them []byte, err error) { + // Contrary to the RFC, we do not ignore lines that don't + // start with "SSH-2.0-" to make the library usable with + // nonconforming servers. + for _, c := range versionLine { + // The spec disallows non US-ASCII chars, and + // specifically forbids null chars. + if c < 32 { + return nil, errors.New("ssh: junk character in version line") + } + } + if _, err = rw.Write(append(versionLine, '\r', '\n')); err != nil { + return + } + + them, err = readVersion(rw) + return them, err +} + +// maxVersionStringBytes is the maximum number of bytes that we'll +// accept as a version string. RFC 4253 section 4.2 limits this at 255 +// chars +const maxVersionStringBytes = 255 + +// Read version string as specified by RFC 4253, section 4.2. +func readVersion(r io.Reader) ([]byte, error) { + versionString := make([]byte, 0, 64) + var ok bool + var buf [1]byte + + for length := 0; length < maxVersionStringBytes; length++ { + _, err := io.ReadFull(r, buf[:]) + if err != nil { + return nil, err + } + // The RFC says that the version should be terminated with \r\n + // but several SSH servers actually only send a \n. + if buf[0] == '\n' { + if !bytes.HasPrefix(versionString, []byte("SSH-")) { + // RFC 4253 says we need to ignore all version string lines + // except the one containing the SSH version (provided that + // all the lines do not exceed 255 bytes in total). + versionString = versionString[:0] + continue + } + ok = true + break + } + + // non ASCII chars are disallowed, but we are lenient, + // since Go doesn't use null-terminated strings. + + // The RFC allows a comment after a space, however, + // all of it (version and comments) goes into the + // session hash. + versionString = append(versionString, buf[0]) + } + + if !ok { + return nil, errors.New("ssh: overflow reading version string") + } + + // There might be a '\r' on the end which we should remove. + if len(versionString) > 0 && versionString[len(versionString)-1] == '\r' { + versionString = versionString[:len(versionString)-1] + } + return versionString, nil +} diff --git a/vendor/k8s.io/apimachinery/pkg/util/httpstream/doc.go b/vendor/k8s.io/apimachinery/pkg/util/httpstream/doc.go new file mode 100644 index 00000000000..5893df5bd26 --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/util/httpstream/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2015 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package httpstream adds multiplexed streaming support to HTTP requests and +// responses via connection upgrades. +package httpstream // import "k8s.io/apimachinery/pkg/util/httpstream" diff --git a/vendor/k8s.io/apimachinery/pkg/util/httpstream/httpstream.go b/vendor/k8s.io/apimachinery/pkg/util/httpstream/httpstream.go new file mode 100644 index 00000000000..50d9a366f36 --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/util/httpstream/httpstream.go @@ -0,0 +1,149 @@ +/* +Copyright 2015 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package httpstream + +import ( + "fmt" + "io" + "net/http" + "strings" + "time" +) + +const ( + HeaderConnection = "Connection" + HeaderUpgrade = "Upgrade" + HeaderProtocolVersion = "X-Stream-Protocol-Version" + HeaderAcceptedProtocolVersions = "X-Accepted-Stream-Protocol-Versions" +) + +// NewStreamHandler defines a function that is called when a new Stream is +// received. If no error is returned, the Stream is accepted; otherwise, +// the stream is rejected. After the reply frame has been sent, replySent is closed. +type NewStreamHandler func(stream Stream, replySent <-chan struct{}) error + +// NoOpNewStreamHandler is a stream handler that accepts a new stream and +// performs no other logic. +func NoOpNewStreamHandler(stream Stream, replySent <-chan struct{}) error { return nil } + +// Dialer knows how to open a streaming connection to a server. +type Dialer interface { + + // Dial opens a streaming connection to a server using one of the protocols + // specified (in order of most preferred to least preferred). + Dial(protocols ...string) (Connection, string, error) +} + +// UpgradeRoundTripper is a type of http.RoundTripper that is able to upgrade +// HTTP requests to support multiplexed bidirectional streams. After RoundTrip() +// is invoked, if the upgrade is successful, clients may retrieve the upgraded +// connection by calling UpgradeRoundTripper.Connection(). +type UpgradeRoundTripper interface { + http.RoundTripper + // NewConnection validates the response and creates a new Connection. + NewConnection(resp *http.Response) (Connection, error) +} + +// ResponseUpgrader knows how to upgrade HTTP requests and responses to +// add streaming support to them. +type ResponseUpgrader interface { + // UpgradeResponse upgrades an HTTP response to one that supports multiplexed + // streams. newStreamHandler will be called asynchronously whenever the + // other end of the upgraded connection creates a new stream. + UpgradeResponse(w http.ResponseWriter, req *http.Request, newStreamHandler NewStreamHandler) Connection +} + +// Connection represents an upgraded HTTP connection. +type Connection interface { + // CreateStream creates a new Stream with the supplied headers. + CreateStream(headers http.Header) (Stream, error) + // Close resets all streams and closes the connection. + Close() error + // CloseChan returns a channel that is closed when the underlying connection is closed. + CloseChan() <-chan bool + // SetIdleTimeout sets the amount of time the connection may remain idle before + // it is automatically closed. + SetIdleTimeout(timeout time.Duration) +} + +// Stream represents a bidirectional communications channel that is part of an +// upgraded connection. +type Stream interface { + io.ReadWriteCloser + // Reset closes both directions of the stream, indicating that neither client + // or server can use it any more. + Reset() error + // Headers returns the headers used to create the stream. + Headers() http.Header + // Identifier returns the stream's ID. + Identifier() uint32 +} + +// IsUpgradeRequest returns true if the given request is a connection upgrade request +func IsUpgradeRequest(req *http.Request) bool { + for _, h := range req.Header[http.CanonicalHeaderKey(HeaderConnection)] { + if strings.Contains(strings.ToLower(h), strings.ToLower(HeaderUpgrade)) { + return true + } + } + return false +} + +func negotiateProtocol(clientProtocols, serverProtocols []string) string { + for i := range clientProtocols { + for j := range serverProtocols { + if clientProtocols[i] == serverProtocols[j] { + return clientProtocols[i] + } + } + } + return "" +} + +// Handshake performs a subprotocol negotiation. If the client did request a +// subprotocol, Handshake will select the first common value found in +// serverProtocols. If a match is found, Handshake adds a response header +// indicating the chosen subprotocol. If no match is found, HTTP forbidden is +// returned, along with a response header containing the list of protocols the +// server can accept. +func Handshake(req *http.Request, w http.ResponseWriter, serverProtocols []string) (string, error) { + clientProtocols := req.Header[http.CanonicalHeaderKey(HeaderProtocolVersion)] + if len(clientProtocols) == 0 { + // Kube 1.0 clients didn't support subprotocol negotiation. + // TODO require clientProtocols once Kube 1.0 is no longer supported + return "", nil + } + + if len(serverProtocols) == 0 { + // Kube 1.0 servers didn't support subprotocol negotiation. This is mainly for testing. + // TODO require serverProtocols once Kube 1.0 is no longer supported + return "", nil + } + + negotiatedProtocol := negotiateProtocol(clientProtocols, serverProtocols) + if len(negotiatedProtocol) == 0 { + for i := range serverProtocols { + w.Header().Add(HeaderAcceptedProtocolVersions, serverProtocols[i]) + } + err := fmt.Errorf("unable to upgrade: unable to negotiate protocol: client supports %v, server accepts %v", clientProtocols, serverProtocols) + http.Error(w, err.Error(), http.StatusForbidden) + return "", err + } + + w.Header().Add(HeaderProtocolVersion, negotiatedProtocol) + return negotiatedProtocol, nil +} diff --git a/vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/connection.go b/vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/connection.go new file mode 100644 index 00000000000..9d222faa898 --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/connection.go @@ -0,0 +1,145 @@ +/* +Copyright 2015 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package spdy + +import ( + "net" + "net/http" + "sync" + "time" + + "github.com/docker/spdystream" + "k8s.io/apimachinery/pkg/util/httpstream" + "k8s.io/klog" +) + +// connection maintains state about a spdystream.Connection and its associated +// streams. +type connection struct { + conn *spdystream.Connection + streams []httpstream.Stream + streamLock sync.Mutex + newStreamHandler httpstream.NewStreamHandler +} + +// NewClientConnection creates a new SPDY client connection. +func NewClientConnection(conn net.Conn) (httpstream.Connection, error) { + spdyConn, err := spdystream.NewConnection(conn, false) + if err != nil { + defer conn.Close() + return nil, err + } + + return newConnection(spdyConn, httpstream.NoOpNewStreamHandler), nil +} + +// NewServerConnection creates a new SPDY server connection. newStreamHandler +// will be invoked when the server receives a newly created stream from the +// client. +func NewServerConnection(conn net.Conn, newStreamHandler httpstream.NewStreamHandler) (httpstream.Connection, error) { + spdyConn, err := spdystream.NewConnection(conn, true) + if err != nil { + defer conn.Close() + return nil, err + } + + return newConnection(spdyConn, newStreamHandler), nil +} + +// newConnection returns a new connection wrapping conn. newStreamHandler +// will be invoked when the server receives a newly created stream from the +// client. +func newConnection(conn *spdystream.Connection, newStreamHandler httpstream.NewStreamHandler) httpstream.Connection { + c := &connection{conn: conn, newStreamHandler: newStreamHandler} + go conn.Serve(c.newSpdyStream) + return c +} + +// createStreamResponseTimeout indicates how long to wait for the other side to +// acknowledge the new stream before timing out. +const createStreamResponseTimeout = 30 * time.Second + +// Close first sends a reset for all of the connection's streams, and then +// closes the underlying spdystream.Connection. +func (c *connection) Close() error { + c.streamLock.Lock() + for _, s := range c.streams { + // calling Reset instead of Close ensures that all streams are fully torn down + s.Reset() + } + c.streams = make([]httpstream.Stream, 0) + c.streamLock.Unlock() + + // now that all streams are fully torn down, it's safe to call close on the underlying connection, + // which should be able to terminate immediately at this point, instead of waiting for any + // remaining graceful stream termination. + return c.conn.Close() +} + +// CreateStream creates a new stream with the specified headers and registers +// it with the connection. +func (c *connection) CreateStream(headers http.Header) (httpstream.Stream, error) { + stream, err := c.conn.CreateStream(headers, nil, false) + if err != nil { + return nil, err + } + if err = stream.WaitTimeout(createStreamResponseTimeout); err != nil { + return nil, err + } + + c.registerStream(stream) + return stream, nil +} + +// registerStream adds the stream s to the connection's list of streams that +// it owns. +func (c *connection) registerStream(s httpstream.Stream) { + c.streamLock.Lock() + c.streams = append(c.streams, s) + c.streamLock.Unlock() +} + +// CloseChan returns a channel that, when closed, indicates that the underlying +// spdystream.Connection has been closed. +func (c *connection) CloseChan() <-chan bool { + return c.conn.CloseChan() +} + +// newSpdyStream is the internal new stream handler used by spdystream.Connection.Serve. +// It calls connection's newStreamHandler, giving it the opportunity to accept or reject +// the stream. If newStreamHandler returns an error, the stream is rejected. If not, the +// stream is accepted and registered with the connection. +func (c *connection) newSpdyStream(stream *spdystream.Stream) { + replySent := make(chan struct{}) + err := c.newStreamHandler(stream, replySent) + rejectStream := (err != nil) + if rejectStream { + klog.Warningf("Stream rejected: %v", err) + stream.Reset() + return + } + + c.registerStream(stream) + stream.SendReply(http.Header{}, rejectStream) + close(replySent) +} + +// SetIdleTimeout sets the amount of time the connection may remain idle before +// it is automatically closed. +func (c *connection) SetIdleTimeout(timeout time.Duration) { + c.conn.SetIdleTimeout(timeout) +} diff --git a/vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/roundtripper.go b/vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/roundtripper.go new file mode 100644 index 00000000000..2699597e7a5 --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/roundtripper.go @@ -0,0 +1,335 @@ +/* +Copyright 2015 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package spdy + +import ( + "bufio" + "bytes" + "context" + "crypto/tls" + "encoding/base64" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "net/http/httputil" + "net/url" + "strings" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apimachinery/pkg/util/httpstream" + utilnet "k8s.io/apimachinery/pkg/util/net" + "k8s.io/apimachinery/third_party/forked/golang/netutil" +) + +// SpdyRoundTripper knows how to upgrade an HTTP request to one that supports +// multiplexed streams. After RoundTrip() is invoked, Conn will be set +// and usable. SpdyRoundTripper implements the UpgradeRoundTripper interface. +type SpdyRoundTripper struct { + //tlsConfig holds the TLS configuration settings to use when connecting + //to the remote server. + tlsConfig *tls.Config + + /* TODO according to http://golang.org/pkg/net/http/#RoundTripper, a RoundTripper + must be safe for use by multiple concurrent goroutines. If this is absolutely + necessary, we could keep a map from http.Request to net.Conn. In practice, + a client will create an http.Client, set the transport to a new insteace of + SpdyRoundTripper, and use it a single time, so this hopefully won't be an issue. + */ + // conn is the underlying network connection to the remote server. + conn net.Conn + + // Dialer is the dialer used to connect. Used if non-nil. + Dialer *net.Dialer + + // proxier knows which proxy to use given a request, defaults to http.ProxyFromEnvironment + // Used primarily for mocking the proxy discovery in tests. + proxier func(req *http.Request) (*url.URL, error) + + // followRedirects indicates if the round tripper should examine responses for redirects and + // follow them. + followRedirects bool + // requireSameHostRedirects restricts redirect following to only follow redirects to the same host + // as the original request. + requireSameHostRedirects bool +} + +var _ utilnet.TLSClientConfigHolder = &SpdyRoundTripper{} +var _ httpstream.UpgradeRoundTripper = &SpdyRoundTripper{} +var _ utilnet.Dialer = &SpdyRoundTripper{} + +// NewRoundTripper creates a new SpdyRoundTripper that will use +// the specified tlsConfig. +func NewRoundTripper(tlsConfig *tls.Config, followRedirects, requireSameHostRedirects bool) httpstream.UpgradeRoundTripper { + return NewSpdyRoundTripper(tlsConfig, followRedirects, requireSameHostRedirects) +} + +// NewSpdyRoundTripper creates a new SpdyRoundTripper that will use +// the specified tlsConfig. This function is mostly meant for unit tests. +func NewSpdyRoundTripper(tlsConfig *tls.Config, followRedirects, requireSameHostRedirects bool) *SpdyRoundTripper { + return &SpdyRoundTripper{ + tlsConfig: tlsConfig, + followRedirects: followRedirects, + requireSameHostRedirects: requireSameHostRedirects, + } +} + +// TLSClientConfig implements pkg/util/net.TLSClientConfigHolder for proper TLS checking during +// proxying with a spdy roundtripper. +func (s *SpdyRoundTripper) TLSClientConfig() *tls.Config { + return s.tlsConfig +} + +// Dial implements k8s.io/apimachinery/pkg/util/net.Dialer. +func (s *SpdyRoundTripper) Dial(req *http.Request) (net.Conn, error) { + conn, err := s.dial(req) + if err != nil { + return nil, err + } + + if err := req.Write(conn); err != nil { + conn.Close() + return nil, err + } + + return conn, nil +} + +// dial dials the host specified by req, using TLS if appropriate, optionally +// using a proxy server if one is configured via environment variables. +func (s *SpdyRoundTripper) dial(req *http.Request) (net.Conn, error) { + proxier := s.proxier + if proxier == nil { + proxier = utilnet.NewProxierWithNoProxyCIDR(http.ProxyFromEnvironment) + } + proxyURL, err := proxier(req) + if err != nil { + return nil, err + } + + if proxyURL == nil { + return s.dialWithoutProxy(req.Context(), req.URL) + } + + // ensure we use a canonical host with proxyReq + targetHost := netutil.CanonicalAddr(req.URL) + + // proxying logic adapted from http://blog.h6t.eu/post/74098062923/golang-websocket-with-http-proxy-support + proxyReq := http.Request{ + Method: "CONNECT", + URL: &url.URL{}, + Host: targetHost, + } + + if pa := s.proxyAuth(proxyURL); pa != "" { + proxyReq.Header = http.Header{} + proxyReq.Header.Set("Proxy-Authorization", pa) + } + + proxyDialConn, err := s.dialWithoutProxy(req.Context(), proxyURL) + if err != nil { + return nil, err + } + + proxyClientConn := httputil.NewProxyClientConn(proxyDialConn, nil) + _, err = proxyClientConn.Do(&proxyReq) + if err != nil && err != httputil.ErrPersistEOF { + return nil, err + } + + rwc, _ := proxyClientConn.Hijack() + + if req.URL.Scheme != "https" { + return rwc, nil + } + + host, _, err := net.SplitHostPort(targetHost) + if err != nil { + return nil, err + } + + tlsConfig := s.tlsConfig + switch { + case tlsConfig == nil: + tlsConfig = &tls.Config{ServerName: host} + case len(tlsConfig.ServerName) == 0: + tlsConfig = tlsConfig.Clone() + tlsConfig.ServerName = host + } + + tlsConn := tls.Client(rwc, tlsConfig) + + // need to manually call Handshake() so we can call VerifyHostname() below + if err := tlsConn.Handshake(); err != nil { + return nil, err + } + + // Return if we were configured to skip validation + if tlsConfig.InsecureSkipVerify { + return tlsConn, nil + } + + if err := tlsConn.VerifyHostname(tlsConfig.ServerName); err != nil { + return nil, err + } + + return tlsConn, nil +} + +// dialWithoutProxy dials the host specified by url, using TLS if appropriate. +func (s *SpdyRoundTripper) dialWithoutProxy(ctx context.Context, url *url.URL) (net.Conn, error) { + dialAddr := netutil.CanonicalAddr(url) + + if url.Scheme == "http" { + if s.Dialer == nil { + var d net.Dialer + return d.DialContext(ctx, "tcp", dialAddr) + } else { + return s.Dialer.DialContext(ctx, "tcp", dialAddr) + } + } + + // TODO validate the TLSClientConfig is set up? + var conn *tls.Conn + var err error + if s.Dialer == nil { + conn, err = tls.Dial("tcp", dialAddr, s.tlsConfig) + } else { + conn, err = tls.DialWithDialer(s.Dialer, "tcp", dialAddr, s.tlsConfig) + } + if err != nil { + return nil, err + } + + // Return if we were configured to skip validation + if s.tlsConfig != nil && s.tlsConfig.InsecureSkipVerify { + return conn, nil + } + + host, _, err := net.SplitHostPort(dialAddr) + if err != nil { + return nil, err + } + if s.tlsConfig != nil && len(s.tlsConfig.ServerName) > 0 { + host = s.tlsConfig.ServerName + } + err = conn.VerifyHostname(host) + if err != nil { + return nil, err + } + + return conn, nil +} + +// proxyAuth returns, for a given proxy URL, the value to be used for the Proxy-Authorization header +func (s *SpdyRoundTripper) proxyAuth(proxyURL *url.URL) string { + if proxyURL == nil || proxyURL.User == nil { + return "" + } + credentials := proxyURL.User.String() + encodedAuth := base64.StdEncoding.EncodeToString([]byte(credentials)) + return fmt.Sprintf("Basic %s", encodedAuth) +} + +// RoundTrip executes the Request and upgrades it. After a successful upgrade, +// clients may call SpdyRoundTripper.Connection() to retrieve the upgraded +// connection. +func (s *SpdyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + header := utilnet.CloneHeader(req.Header) + header.Add(httpstream.HeaderConnection, httpstream.HeaderUpgrade) + header.Add(httpstream.HeaderUpgrade, HeaderSpdy31) + + var ( + conn net.Conn + rawResponse []byte + err error + ) + + if s.followRedirects { + conn, rawResponse, err = utilnet.ConnectWithRedirects(req.Method, req.URL, header, req.Body, s, s.requireSameHostRedirects) + } else { + clone := utilnet.CloneRequest(req) + clone.Header = header + conn, err = s.Dial(clone) + } + if err != nil { + return nil, err + } + + responseReader := bufio.NewReader( + io.MultiReader( + bytes.NewBuffer(rawResponse), + conn, + ), + ) + + resp, err := http.ReadResponse(responseReader, nil) + if err != nil { + if conn != nil { + conn.Close() + } + return nil, err + } + + s.conn = conn + + return resp, nil +} + +// NewConnection validates the upgrade response, creating and returning a new +// httpstream.Connection if there were no errors. +func (s *SpdyRoundTripper) NewConnection(resp *http.Response) (httpstream.Connection, error) { + connectionHeader := strings.ToLower(resp.Header.Get(httpstream.HeaderConnection)) + upgradeHeader := strings.ToLower(resp.Header.Get(httpstream.HeaderUpgrade)) + if (resp.StatusCode != http.StatusSwitchingProtocols) || !strings.Contains(connectionHeader, strings.ToLower(httpstream.HeaderUpgrade)) || !strings.Contains(upgradeHeader, strings.ToLower(HeaderSpdy31)) { + defer resp.Body.Close() + responseError := "" + responseErrorBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + responseError = "unable to read error from server response" + } else { + // TODO: I don't belong here, I should be abstracted from this class + if obj, _, err := statusCodecs.UniversalDecoder().Decode(responseErrorBytes, nil, &metav1.Status{}); err == nil { + if status, ok := obj.(*metav1.Status); ok { + return nil, &apierrors.StatusError{ErrStatus: *status} + } + } + responseError = string(responseErrorBytes) + responseError = strings.TrimSpace(responseError) + } + + return nil, fmt.Errorf("unable to upgrade connection: %s", responseError) + } + + return NewClientConnection(s.conn) +} + +// statusScheme is private scheme for the decoding here until someone fixes the TODO in NewConnection +var statusScheme = runtime.NewScheme() + +// ParameterCodec knows about query parameters used with the meta v1 API spec. +var statusCodecs = serializer.NewCodecFactory(statusScheme) + +func init() { + statusScheme.AddUnversionedTypes(metav1.SchemeGroupVersion, + &metav1.Status{}, + ) +} diff --git a/vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/upgrade.go b/vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/upgrade.go new file mode 100644 index 00000000000..045d214d2b7 --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/upgrade.go @@ -0,0 +1,107 @@ +/* +Copyright 2015 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package spdy + +import ( + "bufio" + "fmt" + "io" + "net" + "net/http" + "strings" + "sync/atomic" + + "k8s.io/apimachinery/pkg/util/httpstream" + "k8s.io/apimachinery/pkg/util/runtime" +) + +const HeaderSpdy31 = "SPDY/3.1" + +// responseUpgrader knows how to upgrade HTTP responses. It +// implements the httpstream.ResponseUpgrader interface. +type responseUpgrader struct { +} + +// connWrapper is used to wrap a hijacked connection and its bufio.Reader. All +// calls will be handled directly by the underlying net.Conn with the exception +// of Read and Close calls, which will consider data in the bufio.Reader. This +// ensures that data already inside the used bufio.Reader instance is also +// read. +type connWrapper struct { + net.Conn + closed int32 + bufReader *bufio.Reader +} + +func (w *connWrapper) Read(b []byte) (n int, err error) { + if atomic.LoadInt32(&w.closed) == 1 { + return 0, io.EOF + } + return w.bufReader.Read(b) +} + +func (w *connWrapper) Close() error { + err := w.Conn.Close() + atomic.StoreInt32(&w.closed, 1) + return err +} + +// NewResponseUpgrader returns a new httpstream.ResponseUpgrader that is +// capable of upgrading HTTP responses using SPDY/3.1 via the +// spdystream package. +func NewResponseUpgrader() httpstream.ResponseUpgrader { + return responseUpgrader{} +} + +// UpgradeResponse upgrades an HTTP response to one that supports multiplexed +// streams. newStreamHandler will be called synchronously whenever the +// other end of the upgraded connection creates a new stream. +func (u responseUpgrader) UpgradeResponse(w http.ResponseWriter, req *http.Request, newStreamHandler httpstream.NewStreamHandler) httpstream.Connection { + connectionHeader := strings.ToLower(req.Header.Get(httpstream.HeaderConnection)) + upgradeHeader := strings.ToLower(req.Header.Get(httpstream.HeaderUpgrade)) + if !strings.Contains(connectionHeader, strings.ToLower(httpstream.HeaderUpgrade)) || !strings.Contains(upgradeHeader, strings.ToLower(HeaderSpdy31)) { + errorMsg := fmt.Sprintf("unable to upgrade: missing upgrade headers in request: %#v", req.Header) + http.Error(w, errorMsg, http.StatusBadRequest) + return nil + } + + hijacker, ok := w.(http.Hijacker) + if !ok { + errorMsg := fmt.Sprintf("unable to upgrade: unable to hijack response") + http.Error(w, errorMsg, http.StatusInternalServerError) + return nil + } + + w.Header().Add(httpstream.HeaderConnection, httpstream.HeaderUpgrade) + w.Header().Add(httpstream.HeaderUpgrade, HeaderSpdy31) + w.WriteHeader(http.StatusSwitchingProtocols) + + conn, bufrw, err := hijacker.Hijack() + if err != nil { + runtime.HandleError(fmt.Errorf("unable to upgrade: error hijacking response: %v", err)) + return nil + } + + connWithBuf := &connWrapper{Conn: conn, bufReader: bufrw.Reader} + spdyConn, err := NewServerConnection(connWithBuf, newStreamHandler) + if err != nil { + runtime.HandleError(fmt.Errorf("unable to upgrade: error creating SPDY server connection: %v", err)) + return nil + } + + return spdyConn +} diff --git a/vendor/k8s.io/apimachinery/third_party/forked/golang/netutil/addr.go b/vendor/k8s.io/apimachinery/third_party/forked/golang/netutil/addr.go new file mode 100644 index 00000000000..c70f431c272 --- /dev/null +++ b/vendor/k8s.io/apimachinery/third_party/forked/golang/netutil/addr.go @@ -0,0 +1,27 @@ +package netutil + +import ( + "net/url" + "strings" +) + +// FROM: http://golang.org/src/net/http/client.go +// Given a string of the form "host", "host:port", or "[ipv6::address]:port", +// return true if the string includes a port. +func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") } + +// FROM: http://golang.org/src/net/http/transport.go +var portMap = map[string]string{ + "http": "80", + "https": "443", +} + +// FROM: http://golang.org/src/net/http/transport.go +// canonicalAddr returns url.Host but always with a ":port" suffix +func CanonicalAddr(url *url.URL) string { + addr := url.Host + if !hasPort(addr) { + return addr + ":" + portMap[url.Scheme] + } + return addr +} diff --git a/vendor/k8s.io/client-go/tools/portforward/doc.go b/vendor/k8s.io/client-go/tools/portforward/doc.go new file mode 100644 index 00000000000..2f53406344f --- /dev/null +++ b/vendor/k8s.io/client-go/tools/portforward/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2015 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package portforward adds support for SSH-like port forwarding from the client's +// local host to remote containers. +package portforward // import "k8s.io/client-go/tools/portforward" diff --git a/vendor/k8s.io/client-go/tools/portforward/portforward.go b/vendor/k8s.io/client-go/tools/portforward/portforward.go new file mode 100644 index 00000000000..4ab72bb4f3c --- /dev/null +++ b/vendor/k8s.io/client-go/tools/portforward/portforward.go @@ -0,0 +1,429 @@ +/* +Copyright 2015 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package portforward + +import ( + "errors" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "sort" + "strconv" + "strings" + "sync" + + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/httpstream" + "k8s.io/apimachinery/pkg/util/runtime" +) + +// PortForwardProtocolV1Name is the subprotocol used for port forwarding. +// TODO move to API machinery and re-unify with kubelet/server/portfoward +const PortForwardProtocolV1Name = "portforward.k8s.io" + +// PortForwarder knows how to listen for local connections and forward them to +// a remote pod via an upgraded HTTP request. +type PortForwarder struct { + addresses []listenAddress + ports []ForwardedPort + stopChan <-chan struct{} + + dialer httpstream.Dialer + streamConn httpstream.Connection + listeners []io.Closer + Ready chan struct{} + requestIDLock sync.Mutex + requestID int + out io.Writer + errOut io.Writer +} + +// ForwardedPort contains a Local:Remote port pairing. +type ForwardedPort struct { + Local uint16 + Remote uint16 +} + +/* + valid port specifications: + + 5000 + - forwards from localhost:5000 to pod:5000 + + 8888:5000 + - forwards from localhost:8888 to pod:5000 + + 0:5000 + :5000 + - selects a random available local port, + forwards from localhost: to pod:5000 +*/ +func parsePorts(ports []string) ([]ForwardedPort, error) { + var forwards []ForwardedPort + for _, portString := range ports { + parts := strings.Split(portString, ":") + var localString, remoteString string + if len(parts) == 1 { + localString = parts[0] + remoteString = parts[0] + } else if len(parts) == 2 { + localString = parts[0] + if localString == "" { + // support :5000 + localString = "0" + } + remoteString = parts[1] + } else { + return nil, fmt.Errorf("Invalid port format '%s'", portString) + } + + localPort, err := strconv.ParseUint(localString, 10, 16) + if err != nil { + return nil, fmt.Errorf("Error parsing local port '%s': %s", localString, err) + } + + remotePort, err := strconv.ParseUint(remoteString, 10, 16) + if err != nil { + return nil, fmt.Errorf("Error parsing remote port '%s': %s", remoteString, err) + } + if remotePort == 0 { + return nil, fmt.Errorf("Remote port must be > 0") + } + + forwards = append(forwards, ForwardedPort{uint16(localPort), uint16(remotePort)}) + } + + return forwards, nil +} + +type listenAddress struct { + address string + protocol string + failureMode string +} + +func parseAddresses(addressesToParse []string) ([]listenAddress, error) { + var addresses []listenAddress + parsed := make(map[string]listenAddress) + for _, address := range addressesToParse { + if address == "localhost" { + if _, exists := parsed["127.0.0.1"]; !exists { + ip := listenAddress{address: "127.0.0.1", protocol: "tcp4", failureMode: "all"} + parsed[ip.address] = ip + } + if _, exists := parsed["::1"]; !exists { + ip := listenAddress{address: "::1", protocol: "tcp6", failureMode: "all"} + parsed[ip.address] = ip + } + } else if net.ParseIP(address).To4() != nil { + parsed[address] = listenAddress{address: address, protocol: "tcp4", failureMode: "any"} + } else if net.ParseIP(address) != nil { + parsed[address] = listenAddress{address: address, protocol: "tcp6", failureMode: "any"} + } else { + return nil, fmt.Errorf("%s is not a valid IP", address) + } + } + addresses = make([]listenAddress, len(parsed)) + id := 0 + for _, v := range parsed { + addresses[id] = v + id++ + } + // Sort addresses before returning to get a stable order + sort.Slice(addresses, func(i, j int) bool { return addresses[i].address < addresses[j].address }) + + return addresses, nil +} + +// New creates a new PortForwarder with localhost listen addresses. +func New(dialer httpstream.Dialer, ports []string, stopChan <-chan struct{}, readyChan chan struct{}, out, errOut io.Writer) (*PortForwarder, error) { + return NewOnAddresses(dialer, []string{"localhost"}, ports, stopChan, readyChan, out, errOut) +} + +// NewOnAddresses creates a new PortForwarder with custom listen addresses. +func NewOnAddresses(dialer httpstream.Dialer, addresses []string, ports []string, stopChan <-chan struct{}, readyChan chan struct{}, out, errOut io.Writer) (*PortForwarder, error) { + if len(addresses) == 0 { + return nil, errors.New("You must specify at least 1 address") + } + parsedAddresses, err := parseAddresses(addresses) + if err != nil { + return nil, err + } + if len(ports) == 0 { + return nil, errors.New("You must specify at least 1 port") + } + parsedPorts, err := parsePorts(ports) + if err != nil { + return nil, err + } + return &PortForwarder{ + dialer: dialer, + addresses: parsedAddresses, + ports: parsedPorts, + stopChan: stopChan, + Ready: readyChan, + out: out, + errOut: errOut, + }, nil +} + +// ForwardPorts formats and executes a port forwarding request. The connection will remain +// open until stopChan is closed. +func (pf *PortForwarder) ForwardPorts() error { + defer pf.Close() + + var err error + pf.streamConn, _, err = pf.dialer.Dial(PortForwardProtocolV1Name) + if err != nil { + return fmt.Errorf("error upgrading connection: %s", err) + } + defer pf.streamConn.Close() + + return pf.forward() +} + +// forward dials the remote host specific in req, upgrades the request, starts +// listeners for each port specified in ports, and forwards local connections +// to the remote host via streams. +func (pf *PortForwarder) forward() error { + var err error + + listenSuccess := false + for i := range pf.ports { + port := &pf.ports[i] + err = pf.listenOnPort(port) + switch { + case err == nil: + listenSuccess = true + default: + if pf.errOut != nil { + fmt.Fprintf(pf.errOut, "Unable to listen on port %d: %v\n", port.Local, err) + } + } + } + + if !listenSuccess { + return fmt.Errorf("Unable to listen on any of the requested ports: %v", pf.ports) + } + + if pf.Ready != nil { + close(pf.Ready) + } + + // wait for interrupt or conn closure + select { + case <-pf.stopChan: + case <-pf.streamConn.CloseChan(): + runtime.HandleError(errors.New("lost connection to pod")) + } + + return nil +} + +// listenOnPort delegates listener creation and waits for connections on requested bind addresses. +// An error is raised based on address groups (default and localhost) and their failure modes +func (pf *PortForwarder) listenOnPort(port *ForwardedPort) error { + var errors []error + failCounters := make(map[string]int, 2) + successCounters := make(map[string]int, 2) + for _, addr := range pf.addresses { + err := pf.listenOnPortAndAddress(port, addr.protocol, addr.address) + if err != nil { + errors = append(errors, err) + failCounters[addr.failureMode]++ + } else { + successCounters[addr.failureMode]++ + } + } + if successCounters["all"] == 0 && failCounters["all"] > 0 { + return fmt.Errorf("%s: %v", "Listeners failed to create with the following errors", errors) + } + if failCounters["any"] > 0 { + return fmt.Errorf("%s: %v", "Listeners failed to create with the following errors", errors) + } + return nil +} + +// listenOnPortAndAddress delegates listener creation and waits for new connections +// in the background f +func (pf *PortForwarder) listenOnPortAndAddress(port *ForwardedPort, protocol string, address string) error { + listener, err := pf.getListener(protocol, address, port) + if err != nil { + return err + } + pf.listeners = append(pf.listeners, listener) + go pf.waitForConnection(listener, *port) + return nil +} + +// getListener creates a listener on the interface targeted by the given hostname on the given port with +// the given protocol. protocol is in net.Listen style which basically admits values like tcp, tcp4, tcp6 +func (pf *PortForwarder) getListener(protocol string, hostname string, port *ForwardedPort) (net.Listener, error) { + listener, err := net.Listen(protocol, net.JoinHostPort(hostname, strconv.Itoa(int(port.Local)))) + if err != nil { + return nil, fmt.Errorf("Unable to create listener: Error %s", err) + } + listenerAddress := listener.Addr().String() + host, localPort, _ := net.SplitHostPort(listenerAddress) + localPortUInt, err := strconv.ParseUint(localPort, 10, 16) + + if err != nil { + fmt.Fprintf(pf.out, "Failed to forward from %s:%d -> %d\n", hostname, localPortUInt, port.Remote) + return nil, fmt.Errorf("Error parsing local port: %s from %s (%s)", err, listenerAddress, host) + } + port.Local = uint16(localPortUInt) + if pf.out != nil { + fmt.Fprintf(pf.out, "Forwarding from %s -> %d\n", net.JoinHostPort(hostname, strconv.Itoa(int(localPortUInt))), port.Remote) + } + + return listener, nil +} + +// waitForConnection waits for new connections to listener and handles them in +// the background. +func (pf *PortForwarder) waitForConnection(listener net.Listener, port ForwardedPort) { + for { + conn, err := listener.Accept() + if err != nil { + // TODO consider using something like https://github.com/hydrogen18/stoppableListener? + if !strings.Contains(strings.ToLower(err.Error()), "use of closed network connection") { + runtime.HandleError(fmt.Errorf("Error accepting connection on port %d: %v", port.Local, err)) + } + return + } + go pf.handleConnection(conn, port) + } +} + +func (pf *PortForwarder) nextRequestID() int { + pf.requestIDLock.Lock() + defer pf.requestIDLock.Unlock() + id := pf.requestID + pf.requestID++ + return id +} + +// handleConnection copies data between the local connection and the stream to +// the remote server. +func (pf *PortForwarder) handleConnection(conn net.Conn, port ForwardedPort) { + defer conn.Close() + + if pf.out != nil { + fmt.Fprintf(pf.out, "Handling connection for %d\n", port.Local) + } + + requestID := pf.nextRequestID() + + // create error stream + headers := http.Header{} + headers.Set(v1.StreamType, v1.StreamTypeError) + headers.Set(v1.PortHeader, fmt.Sprintf("%d", port.Remote)) + headers.Set(v1.PortForwardRequestIDHeader, strconv.Itoa(requestID)) + errorStream, err := pf.streamConn.CreateStream(headers) + if err != nil { + runtime.HandleError(fmt.Errorf("error creating error stream for port %d -> %d: %v", port.Local, port.Remote, err)) + return + } + // we're not writing to this stream + errorStream.Close() + + errorChan := make(chan error) + go func() { + message, err := ioutil.ReadAll(errorStream) + switch { + case err != nil: + errorChan <- fmt.Errorf("error reading from error stream for port %d -> %d: %v", port.Local, port.Remote, err) + case len(message) > 0: + errorChan <- fmt.Errorf("an error occurred forwarding %d -> %d: %v", port.Local, port.Remote, string(message)) + } + close(errorChan) + }() + + // create data stream + headers.Set(v1.StreamType, v1.StreamTypeData) + dataStream, err := pf.streamConn.CreateStream(headers) + if err != nil { + runtime.HandleError(fmt.Errorf("error creating forwarding stream for port %d -> %d: %v", port.Local, port.Remote, err)) + return + } + + localError := make(chan struct{}) + remoteDone := make(chan struct{}) + + go func() { + // Copy from the remote side to the local port. + if _, err := io.Copy(conn, dataStream); err != nil && !strings.Contains(err.Error(), "use of closed network connection") { + runtime.HandleError(fmt.Errorf("error copying from remote stream to local connection: %v", err)) + } + + // inform the select below that the remote copy is done + close(remoteDone) + }() + + go func() { + // inform server we're not sending any more data after copy unblocks + defer dataStream.Close() + + // Copy from the local port to the remote side. + if _, err := io.Copy(dataStream, conn); err != nil && !strings.Contains(err.Error(), "use of closed network connection") { + runtime.HandleError(fmt.Errorf("error copying from local connection to remote stream: %v", err)) + // break out of the select below without waiting for the other copy to finish + close(localError) + } + }() + + // wait for either a local->remote error or for copying from remote->local to finish + select { + case <-remoteDone: + case <-localError: + } + + // always expect something on errorChan (it may be nil) + err = <-errorChan + if err != nil { + runtime.HandleError(err) + } +} + +// Close stops all listeners of PortForwarder. +func (pf *PortForwarder) Close() { + // stop all listeners + for _, l := range pf.listeners { + if err := l.Close(); err != nil { + runtime.HandleError(fmt.Errorf("error closing listener: %v", err)) + } + } +} + +// GetPorts will return the ports that were forwarded; this can be used to +// retrieve the locally-bound port in cases where the input was port 0. This +// function will signal an error if the Ready channel is nil or if the +// listeners are not ready yet; this function will succeed after the Ready +// channel has been closed. +func (pf *PortForwarder) GetPorts() ([]ForwardedPort, error) { + if pf.Ready == nil { + return nil, fmt.Errorf("no Ready channel provided") + } + select { + case <-pf.Ready: + return pf.ports, nil + default: + return nil, fmt.Errorf("listeners not ready") + } +} diff --git a/vendor/k8s.io/client-go/tools/watch/informerwatcher.go b/vendor/k8s.io/client-go/tools/watch/informerwatcher.go new file mode 100644 index 00000000000..4e0a400bb55 --- /dev/null +++ b/vendor/k8s.io/client-go/tools/watch/informerwatcher.go @@ -0,0 +1,150 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package watch + +import ( + "sync" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/tools/cache" +) + +func newEventProcessor(out chan<- watch.Event) *eventProcessor { + return &eventProcessor{ + out: out, + cond: sync.NewCond(&sync.Mutex{}), + done: make(chan struct{}), + } +} + +// eventProcessor buffers events and writes them to an out chan when a reader +// is waiting. Because of the requirement to buffer events, it synchronizes +// input with a condition, and synchronizes output with a channels. It needs to +// be able to yield while both waiting on an input condition and while blocked +// on writing to the output channel. +type eventProcessor struct { + out chan<- watch.Event + + cond *sync.Cond + buff []watch.Event + + done chan struct{} +} + +func (e *eventProcessor) run() { + for { + batch := e.takeBatch() + e.writeBatch(batch) + if e.stopped() { + return + } + } +} + +func (e *eventProcessor) takeBatch() []watch.Event { + e.cond.L.Lock() + defer e.cond.L.Unlock() + + for len(e.buff) == 0 && !e.stopped() { + e.cond.Wait() + } + + batch := e.buff + e.buff = nil + return batch +} + +func (e *eventProcessor) writeBatch(events []watch.Event) { + for _, event := range events { + select { + case e.out <- event: + case <-e.done: + return + } + } +} + +func (e *eventProcessor) push(event watch.Event) { + e.cond.L.Lock() + defer e.cond.L.Unlock() + defer e.cond.Signal() + e.buff = append(e.buff, event) +} + +func (e *eventProcessor) stopped() bool { + select { + case <-e.done: + return true + default: + return false + } +} + +func (e *eventProcessor) stop() { + close(e.done) + e.cond.Signal() +} + +// NewIndexerInformerWatcher will create an IndexerInformer and wrap it into watch.Interface +// so you can use it anywhere where you'd have used a regular Watcher returned from Watch method. +// it also returns a channel you can use to wait for the informers to fully shutdown. +func NewIndexerInformerWatcher(lw cache.ListerWatcher, objType runtime.Object) (cache.Indexer, cache.Controller, watch.Interface, <-chan struct{}) { + ch := make(chan watch.Event) + w := watch.NewProxyWatcher(ch) + e := newEventProcessor(ch) + + indexer, informer := cache.NewIndexerInformer(lw, objType, 0, cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + e.push(watch.Event{ + Type: watch.Added, + Object: obj.(runtime.Object), + }) + }, + UpdateFunc: func(old, new interface{}) { + e.push(watch.Event{ + Type: watch.Modified, + Object: new.(runtime.Object), + }) + }, + DeleteFunc: func(obj interface{}) { + staleObj, stale := obj.(cache.DeletedFinalStateUnknown) + if stale { + // We have no means of passing the additional information down using + // watch API based on watch.Event but the caller can filter such + // objects by checking if metadata.deletionTimestamp is set + obj = staleObj + } + + e.push(watch.Event{ + Type: watch.Deleted, + Object: obj.(runtime.Object), + }) + }, + }, cache.Indexers{}) + + go e.run() + + doneCh := make(chan struct{}) + go func() { + defer close(doneCh) + defer e.stop() + informer.Run(w.StopChan()) + }() + + return indexer, informer, w, doneCh +} diff --git a/vendor/k8s.io/client-go/tools/watch/retrywatcher.go b/vendor/k8s.io/client-go/tools/watch/retrywatcher.go new file mode 100644 index 00000000000..47ae9df4afd --- /dev/null +++ b/vendor/k8s.io/client-go/tools/watch/retrywatcher.go @@ -0,0 +1,287 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package watch + +import ( + "context" + "errors" + "fmt" + "io" + "net/http" + "time" + + "github.com/davecgh/go-spew/spew" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/net" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/tools/cache" + "k8s.io/klog" +) + +// resourceVersionGetter is an interface used to get resource version from events. +// We can't reuse an interface from meta otherwise it would be a cyclic dependency and we need just this one method +type resourceVersionGetter interface { + GetResourceVersion() string +} + +// RetryWatcher will make sure that in case the underlying watcher is closed (e.g. due to API timeout or etcd timeout) +// it will get restarted from the last point without the consumer even knowing about it. +// RetryWatcher does that by inspecting events and keeping track of resourceVersion. +// Especially useful when using watch.UntilWithoutRetry where premature termination is causing issues and flakes. +// Please note that this is not resilient to etcd cache not having the resource version anymore - you would need to +// use Informers for that. +type RetryWatcher struct { + lastResourceVersion string + watcherClient cache.Watcher + resultChan chan watch.Event + stopChan chan struct{} + doneChan chan struct{} + minRestartDelay time.Duration +} + +// NewRetryWatcher creates a new RetryWatcher. +// It will make sure that watches gets restarted in case of recoverable errors. +// The initialResourceVersion will be given to watch method when first called. +func NewRetryWatcher(initialResourceVersion string, watcherClient cache.Watcher) (*RetryWatcher, error) { + return newRetryWatcher(initialResourceVersion, watcherClient, 1*time.Second) +} + +func newRetryWatcher(initialResourceVersion string, watcherClient cache.Watcher, minRestartDelay time.Duration) (*RetryWatcher, error) { + switch initialResourceVersion { + case "", "0": + // TODO: revisit this if we ever get WATCH v2 where it means start "now" + // without doing the synthetic list of objects at the beginning (see #74022) + return nil, fmt.Errorf("initial RV %q is not supported due to issues with underlying WATCH", initialResourceVersion) + default: + break + } + + rw := &RetryWatcher{ + lastResourceVersion: initialResourceVersion, + watcherClient: watcherClient, + stopChan: make(chan struct{}), + doneChan: make(chan struct{}), + resultChan: make(chan watch.Event, 0), + minRestartDelay: minRestartDelay, + } + + go rw.receive() + return rw, nil +} + +func (rw *RetryWatcher) send(event watch.Event) bool { + // Writing to an unbuffered channel is blocking operation + // and we need to check if stop wasn't requested while doing so. + select { + case rw.resultChan <- event: + return true + case <-rw.stopChan: + return false + } +} + +// doReceive returns true when it is done, false otherwise. +// If it is not done the second return value holds the time to wait before calling it again. +func (rw *RetryWatcher) doReceive() (bool, time.Duration) { + watcher, err := rw.watcherClient.Watch(metav1.ListOptions{ + ResourceVersion: rw.lastResourceVersion, + }) + // We are very unlikely to hit EOF here since we are just establishing the call, + // but it may happen that the apiserver is just shutting down (e.g. being restarted) + // This is consistent with how it is handled for informers + switch err { + case nil: + break + + case io.EOF: + // watch closed normally + return false, 0 + + case io.ErrUnexpectedEOF: + klog.V(1).Infof("Watch closed with unexpected EOF: %v", err) + return false, 0 + + default: + msg := "Watch failed: %v" + if net.IsProbableEOF(err) { + klog.V(5).Infof(msg, err) + // Retry + return false, 0 + } + + klog.Errorf(msg, err) + // Retry + return false, 0 + } + + if watcher == nil { + klog.Error("Watch returned nil watcher") + // Retry + return false, 0 + } + + ch := watcher.ResultChan() + defer watcher.Stop() + + for { + select { + case <-rw.stopChan: + klog.V(4).Info("Stopping RetryWatcher.") + return true, 0 + case event, ok := <-ch: + if !ok { + klog.V(4).Infof("Failed to get event! Re-creating the watcher. Last RV: %s", rw.lastResourceVersion) + return false, 0 + } + + // We need to inspect the event and get ResourceVersion out of it + switch event.Type { + case watch.Added, watch.Modified, watch.Deleted, watch.Bookmark: + metaObject, ok := event.Object.(resourceVersionGetter) + if !ok { + _ = rw.send(watch.Event{ + Type: watch.Error, + Object: &apierrors.NewInternalError(errors.New("retryWatcher: doesn't support resourceVersion")).ErrStatus, + }) + // We have to abort here because this might cause lastResourceVersion inconsistency by skipping a potential RV with valid data! + return true, 0 + } + + resourceVersion := metaObject.GetResourceVersion() + if resourceVersion == "" { + _ = rw.send(watch.Event{ + Type: watch.Error, + Object: &apierrors.NewInternalError(fmt.Errorf("retryWatcher: object %#v doesn't support resourceVersion", event.Object)).ErrStatus, + }) + // We have to abort here because this might cause lastResourceVersion inconsistency by skipping a potential RV with valid data! + return true, 0 + } + + // All is fine; send the event and update lastResourceVersion + ok = rw.send(event) + if !ok { + return true, 0 + } + rw.lastResourceVersion = resourceVersion + + continue + + case watch.Error: + // This round trip allows us to handle unstructured status + errObject := apierrors.FromObject(event.Object) + statusErr, ok := errObject.(*apierrors.StatusError) + if !ok { + klog.Error(spew.Sprintf("Received an error which is not *metav1.Status but %#+v", event.Object)) + // Retry unknown errors + return false, 0 + } + + status := statusErr.ErrStatus + + statusDelay := time.Duration(0) + if status.Details != nil { + statusDelay = time.Duration(status.Details.RetryAfterSeconds) * time.Second + } + + switch status.Code { + case http.StatusGone: + // Never retry RV too old errors + _ = rw.send(event) + return true, 0 + + case http.StatusGatewayTimeout, http.StatusInternalServerError: + // Retry + return false, statusDelay + + default: + // We retry by default. RetryWatcher is meant to proceed unless it is certain + // that it can't. If we are not certain, we proceed with retry and leave it + // up to the user to timeout if needed. + + // Log here so we have a record of hitting the unexpected error + // and we can whitelist some error codes if we missed any that are expected. + klog.V(5).Info(spew.Sprintf("Retrying after unexpected error: %#+v", event.Object)) + + // Retry + return false, statusDelay + } + + default: + klog.Errorf("Failed to recognize Event type %q", event.Type) + _ = rw.send(watch.Event{ + Type: watch.Error, + Object: &apierrors.NewInternalError(fmt.Errorf("retryWatcher failed to recognize Event type %q", event.Type)).ErrStatus, + }) + // We are unable to restart the watch and have to stop the loop or this might cause lastResourceVersion inconsistency by skipping a potential RV with valid data! + return true, 0 + } + } + } +} + +// receive reads the result from a watcher, restarting it if necessary. +func (rw *RetryWatcher) receive() { + defer close(rw.doneChan) + defer close(rw.resultChan) + + klog.V(4).Info("Starting RetryWatcher.") + defer klog.V(4).Info("Stopping RetryWatcher.") + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go func() { + select { + case <-rw.stopChan: + cancel() + return + case <-ctx.Done(): + return + } + }() + + // We use non sliding until so we don't introduce delays on happy path when WATCH call + // timeouts or gets closed and we need to reestablish it while also avoiding hot loops. + wait.NonSlidingUntilWithContext(ctx, func(ctx context.Context) { + done, retryAfter := rw.doReceive() + if done { + cancel() + return + } + + time.Sleep(retryAfter) + + klog.V(4).Infof("Restarting RetryWatcher at RV=%q", rw.lastResourceVersion) + }, rw.minRestartDelay) +} + +// ResultChan implements Interface. +func (rw *RetryWatcher) ResultChan() <-chan watch.Event { + return rw.resultChan +} + +// Stop implements Interface. +func (rw *RetryWatcher) Stop() { + close(rw.stopChan) +} + +// Done allows the caller to be notified when Retry watcher stops. +func (rw *RetryWatcher) Done() <-chan struct{} { + return rw.doneChan +} diff --git a/vendor/k8s.io/client-go/tools/watch/until.go b/vendor/k8s.io/client-go/tools/watch/until.go new file mode 100644 index 00000000000..e12d82aca48 --- /dev/null +++ b/vendor/k8s.io/client-go/tools/watch/until.go @@ -0,0 +1,236 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package watch + +import ( + "context" + "errors" + "fmt" + "time" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/tools/cache" + "k8s.io/klog" +) + +// PreconditionFunc returns true if the condition has been reached, false if it has not been reached yet, +// or an error if the condition failed or detected an error state. +type PreconditionFunc func(store cache.Store) (bool, error) + +// ConditionFunc returns true if the condition has been reached, false if it has not been reached yet, +// or an error if the condition cannot be checked and should terminate. In general, it is better to define +// level driven conditions over edge driven conditions (pod has ready=true, vs pod modified and ready changed +// from false to true). +type ConditionFunc func(event watch.Event) (bool, error) + +// ErrWatchClosed is returned when the watch channel is closed before timeout in UntilWithoutRetry. +var ErrWatchClosed = errors.New("watch closed before UntilWithoutRetry timeout") + +// UntilWithoutRetry reads items from the watch until each provided condition succeeds, and then returns the last watch +// encountered. The first condition that returns an error terminates the watch (and the event is also returned). +// If no event has been received, the returned event will be nil. +// Conditions are satisfied sequentially so as to provide a useful primitive for higher level composition. +// Waits until context deadline or until context is canceled. +// +// Warning: Unless you have a very specific use case (probably a special Watcher) don't use this function!!! +// Warning: This will fail e.g. on API timeouts and/or 'too old resource version' error. +// Warning: You are most probably looking for a function *Until* or *UntilWithSync* below, +// Warning: solving such issues. +// TODO: Consider making this function private to prevent misuse when the other occurrences in our codebase are gone. +func UntilWithoutRetry(ctx context.Context, watcher watch.Interface, conditions ...ConditionFunc) (*watch.Event, error) { + ch := watcher.ResultChan() + defer watcher.Stop() + var lastEvent *watch.Event + for _, condition := range conditions { + // check the next condition against the previous event and short circuit waiting for the next watch + if lastEvent != nil { + done, err := condition(*lastEvent) + if err != nil { + return lastEvent, err + } + if done { + continue + } + } + ConditionSucceeded: + for { + select { + case event, ok := <-ch: + if !ok { + return lastEvent, ErrWatchClosed + } + lastEvent = &event + + done, err := condition(event) + if err != nil { + return lastEvent, err + } + if done { + break ConditionSucceeded + } + + case <-ctx.Done(): + return lastEvent, wait.ErrWaitTimeout + } + } + } + return lastEvent, nil +} + +// Until wraps the watcherClient's watch function with RetryWatcher making sure that watcher gets restarted in case of errors. +// The initialResourceVersion will be given to watch method when first called. It shall not be "" or "0" +// given the underlying WATCH call issues (#74022). If you want the initial list ("", "0") done for you use ListWatchUntil instead. +// Remaining behaviour is identical to function UntilWithoutRetry. (See above.) +// Until can deal with API timeouts and lost connections. +// It guarantees you to see all events and in the order they happened. +// Due to this guarantee there is no way it can deal with 'Resource version too old error'. It will fail in this case. +// (See `UntilWithSync` if you'd prefer to recover from all the errors including RV too old by re-listing +// those items. In normal code you should care about being level driven so you'd not care about not seeing all the edges.) +// The most frequent usage for Until would be a test where you want to verify exact order of events ("edges"). +func Until(ctx context.Context, initialResourceVersion string, watcherClient cache.Watcher, conditions ...ConditionFunc) (*watch.Event, error) { + w, err := NewRetryWatcher(initialResourceVersion, watcherClient) + if err != nil { + return nil, err + } + + return UntilWithoutRetry(ctx, w, conditions...) +} + +// UntilWithSync creates an informer from lw, optionally checks precondition when the store is synced, +// and watches the output until each provided condition succeeds, in a way that is identical +// to function UntilWithoutRetry. (See above.) +// UntilWithSync can deal with all errors like API timeout, lost connections and 'Resource version too old'. +// It is the only function that can recover from 'Resource version too old', Until and UntilWithoutRetry will +// just fail in that case. On the other hand it can't provide you with guarantees as strong as using simple +// Watch method with Until. It can skip some intermediate events in case of watch function failing but it will +// re-list to recover and you always get an event, if there has been a change, after recovery. +// Also with the current implementation based on DeltaFIFO, order of the events you receive is guaranteed only for +// particular object, not between more of them even it's the same resource. +// The most frequent usage would be a command that needs to watch the "state of the world" and should't fail, like: +// waiting for object reaching a state, "small" controllers, ... +func UntilWithSync(ctx context.Context, lw cache.ListerWatcher, objType runtime.Object, precondition PreconditionFunc, conditions ...ConditionFunc) (*watch.Event, error) { + indexer, informer, watcher, done := NewIndexerInformerWatcher(lw, objType) + // We need to wait for the internal informers to fully stop so it's easier to reason about + // and it works with non-thread safe clients. + defer func() { <-done }() + // Proxy watcher can be stopped multiple times so it's fine to use defer here to cover alternative branches and + // let UntilWithoutRetry to stop it + defer watcher.Stop() + + if precondition != nil { + if !cache.WaitForCacheSync(ctx.Done(), informer.HasSynced) { + return nil, fmt.Errorf("UntilWithSync: unable to sync caches: %v", ctx.Err()) + } + + done, err := precondition(indexer) + if err != nil { + return nil, err + } + + if done { + return nil, nil + } + } + + return UntilWithoutRetry(ctx, watcher, conditions...) +} + +// ContextWithOptionalTimeout wraps context.WithTimeout and handles infinite timeouts expressed as 0 duration. +func ContextWithOptionalTimeout(parent context.Context, timeout time.Duration) (context.Context, context.CancelFunc) { + if timeout < 0 { + // This should be handled in validation + klog.Errorf("Timeout for context shall not be negative!") + timeout = 0 + } + + if timeout == 0 { + return context.WithCancel(parent) + } + + return context.WithTimeout(parent, timeout) +} + +// ListWatchUntil first lists objects, converts them into synthetic ADDED events +// and checks conditions for those synthetic events. If the conditions have not been reached so far +// it continues by calling Until which establishes a watch from resourceVersion of the list call +// to evaluate those conditions based on new events. +// ListWatchUntil provides the same guarantees as Until and replaces the old WATCH from RV "" (or "0") +// which was mixing list and watch calls internally and having severe design issues. (see #74022) +// There is no resourceVersion order guarantee for the initial list and those synthetic events. +func ListWatchUntil(ctx context.Context, lw cache.ListerWatcher, conditions ...ConditionFunc) (*watch.Event, error) { + if len(conditions) == 0 { + return nil, nil + } + + list, err := lw.List(metav1.ListOptions{}) + if err != nil { + return nil, err + } + initialItems, err := meta.ExtractList(list) + if err != nil { + return nil, err + } + + // use the initial items as simulated "adds" + var lastEvent *watch.Event + currIndex := 0 + passedConditions := 0 + for _, condition := range conditions { + // check the next condition against the previous event and short circuit waiting for the next watch + if lastEvent != nil { + done, err := condition(*lastEvent) + if err != nil { + return lastEvent, err + } + if done { + passedConditions = passedConditions + 1 + continue + } + } + + ConditionSucceeded: + for currIndex < len(initialItems) { + lastEvent = &watch.Event{Type: watch.Added, Object: initialItems[currIndex]} + currIndex++ + + done, err := condition(*lastEvent) + if err != nil { + return lastEvent, err + } + if done { + passedConditions = passedConditions + 1 + break ConditionSucceeded + } + } + } + if passedConditions == len(conditions) { + return lastEvent, nil + } + remainingConditions := conditions[passedConditions:] + + metaObj, err := meta.ListAccessor(list) + if err != nil { + return nil, err + } + currResourceVersion := metaObj.GetResourceVersion() + + return Until(ctx, currResourceVersion, lw, remainingConditions...) +} diff --git a/vendor/k8s.io/client-go/transport/spdy/spdy.go b/vendor/k8s.io/client-go/transport/spdy/spdy.go new file mode 100644 index 00000000000..53cc7ee18c5 --- /dev/null +++ b/vendor/k8s.io/client-go/transport/spdy/spdy.go @@ -0,0 +1,94 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package spdy + +import ( + "fmt" + "net/http" + "net/url" + + "k8s.io/apimachinery/pkg/util/httpstream" + "k8s.io/apimachinery/pkg/util/httpstream/spdy" + restclient "k8s.io/client-go/rest" +) + +// Upgrader validates a response from the server after a SPDY upgrade. +type Upgrader interface { + // NewConnection validates the response and creates a new Connection. + NewConnection(resp *http.Response) (httpstream.Connection, error) +} + +// RoundTripperFor returns a round tripper and upgrader to use with SPDY. +func RoundTripperFor(config *restclient.Config) (http.RoundTripper, Upgrader, error) { + tlsConfig, err := restclient.TLSConfigFor(config) + if err != nil { + return nil, nil, err + } + upgradeRoundTripper := spdy.NewRoundTripper(tlsConfig, true, false) + wrapper, err := restclient.HTTPWrappersForConfig(config, upgradeRoundTripper) + if err != nil { + return nil, nil, err + } + return wrapper, upgradeRoundTripper, nil +} + +// dialer implements the httpstream.Dialer interface. +type dialer struct { + client *http.Client + upgrader Upgrader + method string + url *url.URL +} + +var _ httpstream.Dialer = &dialer{} + +// NewDialer will create a dialer that connects to the provided URL and upgrades the connection to SPDY. +func NewDialer(upgrader Upgrader, client *http.Client, method string, url *url.URL) httpstream.Dialer { + return &dialer{ + client: client, + upgrader: upgrader, + method: method, + url: url, + } +} + +func (d *dialer) Dial(protocols ...string) (httpstream.Connection, string, error) { + req, err := http.NewRequest(d.method, d.url.String(), nil) + if err != nil { + return nil, "", fmt.Errorf("error creating request: %v", err) + } + return Negotiate(d.upgrader, d.client, req, protocols...) +} + +// Negotiate opens a connection to a remote server and attempts to negotiate +// a SPDY connection. Upon success, it returns the connection and the protocol selected by +// the server. The client transport must use the upgradeRoundTripper - see RoundTripperFor. +func Negotiate(upgrader Upgrader, client *http.Client, req *http.Request, protocols ...string) (httpstream.Connection, string, error) { + for i := range protocols { + req.Header.Add(httpstream.HeaderProtocolVersion, protocols[i]) + } + resp, err := client.Do(req) + if err != nil { + return nil, "", fmt.Errorf("error sending request: %v", err) + } + defer resp.Body.Close() + conn, err := upgrader.NewConnection(resp) + if err != nil { + return nil, "", err + } + return conn, resp.Header.Get(httpstream.HeaderProtocolVersion), nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index f1901f98bca..9be44254b45 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -387,6 +387,9 @@ github.com/docker/go-metrics github.com/docker/go-plugins-helpers/sdk # github.com/docker/go-units v0.4.0 github.com/docker/go-units +# github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 +github.com/docker/spdystream +github.com/docker/spdystream/spdy # github.com/dop251/goja v0.0.0-00010101000000-000000000000 => github.com/andrewkroh/goja v0.0.0-20190128172624-dd2ac4456e20 github.com/dop251/goja github.com/dop251/goja/ast @@ -840,9 +843,13 @@ go.uber.org/zap/zapcore go.uber.org/zap/zaptest/observer # golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72 golang.org/x/crypto/blake2b +golang.org/x/crypto/blowfish golang.org/x/crypto/cast5 +golang.org/x/crypto/chacha20 +golang.org/x/crypto/curve25519 golang.org/x/crypto/ed25519 golang.org/x/crypto/ed25519/internal/edwards25519 +golang.org/x/crypto/internal/subtle golang.org/x/crypto/md4 golang.org/x/crypto/openpgp golang.org/x/crypto/openpgp/armor @@ -853,7 +860,10 @@ golang.org/x/crypto/openpgp/s2k golang.org/x/crypto/pbkdf2 golang.org/x/crypto/pkcs12 golang.org/x/crypto/pkcs12/internal/rc2 +golang.org/x/crypto/poly1305 golang.org/x/crypto/sha3 +golang.org/x/crypto/ssh +golang.org/x/crypto/ssh/internal/bcrypt_pbkdf golang.org/x/crypto/ssh/terminal # golang.org/x/exp v0.0.0-20191227195350-da58074b4299 golang.org/x/exp/apidiff @@ -1190,6 +1200,8 @@ k8s.io/apimachinery/pkg/util/clock k8s.io/apimachinery/pkg/util/diff k8s.io/apimachinery/pkg/util/errors k8s.io/apimachinery/pkg/util/framer +k8s.io/apimachinery/pkg/util/httpstream +k8s.io/apimachinery/pkg/util/httpstream/spdy k8s.io/apimachinery/pkg/util/intstr k8s.io/apimachinery/pkg/util/json k8s.io/apimachinery/pkg/util/naming @@ -1202,6 +1214,7 @@ k8s.io/apimachinery/pkg/util/wait k8s.io/apimachinery/pkg/util/yaml k8s.io/apimachinery/pkg/version k8s.io/apimachinery/pkg/watch +k8s.io/apimachinery/third_party/forked/golang/netutil k8s.io/apimachinery/third_party/forked/golang/reflect # k8s.io/client-go v0.0.0-20190620085101-78d2af792bab k8s.io/client-go/discovery @@ -1258,8 +1271,11 @@ k8s.io/client-go/tools/clientcmd/api/latest k8s.io/client-go/tools/clientcmd/api/v1 k8s.io/client-go/tools/metrics k8s.io/client-go/tools/pager +k8s.io/client-go/tools/portforward k8s.io/client-go/tools/reference +k8s.io/client-go/tools/watch k8s.io/client-go/transport +k8s.io/client-go/transport/spdy k8s.io/client-go/util/cert k8s.io/client-go/util/connrotation k8s.io/client-go/util/flowcontrol diff --git a/x-pack/filebeat/input/googlepubsub/pubsub_test.go b/x-pack/filebeat/input/googlepubsub/pubsub_test.go index 17863862c8a..58d4db9331c 100644 --- a/x-pack/filebeat/input/googlepubsub/pubsub_test.go +++ b/x-pack/filebeat/input/googlepubsub/pubsub_test.go @@ -208,7 +208,7 @@ func defaultTestConfig() *common.Config { } func isInDockerIntegTestEnv() bool { - return os.Getenv("BEATS_DOCKER_INTEGRATION_TEST_ENV") != "" + return os.Getenv("BEATS_INSIDE_INTEGRATION_TEST_ENV") != "" } func runTest(t *testing.T, cfg *common.Config, run func(client *pubsub.Client, input *pubsubInput, out *stubOutleter, t *testing.T)) { diff --git a/x-pack/filebeat/magefile.go b/x-pack/filebeat/magefile.go index f157b8b1a06..66d90e26e80 100644 --- a/x-pack/filebeat/magefile.go +++ b/x-pack/filebeat/magefile.go @@ -148,8 +148,6 @@ func includeList() error { // IntegTest executes integration tests (it uses Docker to run the tests). func IntegTest() { - devtools.AddIntegTestUsage() - defer devtools.StopIntegTestEnv() mg.SerialDeps(GoIntegTest, PythonIntegTest) } @@ -157,7 +155,11 @@ func IntegTest() { // Use TEST_COVERAGE=true to enable code coverage profiling. // Use RACE_DETECTOR=true to enable the race detector. func GoIntegTest(ctx context.Context) error { - return devtools.RunIntegTest("goIntegTest", func() error { + runner, err := devtools.NewDockerIntegrationRunner() + if err != nil { + return err + } + return runner.Test("goIntegTest", func() error { return devtools.GoTest(ctx, devtools.DefaultGoTestIntegrationArgs()) }) } @@ -170,10 +172,14 @@ func PythonIntegTest(ctx context.Context) error { if !devtools.IsInIntegTestEnv() { mg.Deps(Fields) } - return devtools.RunIntegTest("pythonIntegTest", func() error { + runner, err := devtools.NewDockerIntegrationRunner(append(devtools.ListMatchingEnvVars("TESTING_FILEBEAT_", "NOSE_"), "GENERATE")...) + if err != nil { + return err + } + return runner.Test("pythonIntegTest", func() error { mg.Deps(devtools.BuildSystemTestBinary) args := devtools.DefaultPythonTestIntegrationArgs() args.Env["MODULES_PATH"] = devtools.CWD("module") return devtools.PythonNoseTest(args) - }, append(devtools.ListMatchingEnvVars("TESTING_FILEBEAT_", "NOSE_"), "GENERATE")...) + }) } diff --git a/x-pack/metricbeat/magefile.go b/x-pack/metricbeat/magefile.go index 992e4d6d1fa..ee5ecc9e0f0 100644 --- a/x-pack/metricbeat/magefile.go +++ b/x-pack/metricbeat/magefile.go @@ -106,7 +106,7 @@ func moduleFieldsGo() error { return devtools.GenerateModuleFieldsGo("module") } -// fieldsYML generates a fields.yml based on filebeat + x-pack/filebeat/modules. +// fieldsYML generates a fields.yml based on metricbeat + x-pack/metricbeat/modules. func fieldsYML() error { return devtools.GenerateFieldsYAML(devtools.OSSBeatDir("module"), "module") } @@ -134,8 +134,6 @@ func Update() { // IntegTest executes integration tests (it uses Docker to run the tests). func IntegTest() { - devtools.AddIntegTestUsage() - defer devtools.StopIntegTestEnv() mg.SerialDeps(GoIntegTest, PythonIntegTest) } @@ -160,8 +158,12 @@ func PythonIntegTest(ctx context.Context) error { if !devtools.IsInIntegTestEnv() { mg.SerialDeps(Fields, Dashboards) } - return devtools.RunIntegTest("pythonIntegTest", func() error { + runner, err := devtools.NewDockerIntegrationRunner(devtools.ListMatchingEnvVars("NOSE_")...) + if err != nil { + return err + } + return runner.Test("pythonIntegTest", func() error { mg.Deps(devtools.BuildSystemTestBinary) return devtools.PythonNoseTest(devtools.DefaultPythonTestIntegrationArgs()) - }, devtools.ListMatchingEnvVars("NOSE_")...) + }) } From 0de9b67c48891d032033de52c5ddaec3e110b692 Mon Sep 17 00:00:00 2001 From: Lee Hinman <57081003+leehinman@users.noreply.github.com> Date: Wed, 29 Apr 2020 16:15:26 -0500 Subject: [PATCH 059/116] Document recent changes to nginx fileset as breaking changes (#18026) PR #17844 introduced the following changes: - http.request.method is now lowercase - http.request.referrer is only set when nginx provides a value This PR updates the CHANGELOG to report this as a breaking change. --- CHANGELOG.next.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 56a49e52ecd..5d81c48f7e1 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -21,6 +21,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d *Filebeat* - Improve ECS field mappings in panw module. event.outcome now only contains success/failure per ECS specification. {issue}16025[16025] {pull}17910[17910] +- Improve ECS categorization field mappings for nginx module. http.request.referrer is now lowercase & http.request.referrer only populated when nginx sets a value {issue}16174[16174] {pull}17844[17844] *Heartbeat* @@ -276,7 +277,6 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Enhance `elasticsearch/slowlog` fileset to handle ECS-compatible logs emitted by Elasticsearch. {issue}17715[17715] {pull}17729[17729] - Improve ECS categorization field mappings in misp module. {issue}16026[16026] {pull}17344[17344] - Added Unix stream socket support as an input source and a syslog input source. {pull}17492[17492] -- Improve ECS categorization field mappings for nginx module. {issue}16174[16174] {pull}17844[17844] - Improve ECS categorization field mappings in postgresql module. {issue}16177[16177] {pull}17914[17914] - Improve ECS categorization field mappings in rabbitmq module. {issue}16178[16178] {pull}17916[17916] - Improve ECS categorization field mappings in redis module. {issue}16179[16179] {pull}17918[17918] From 81dfe6109976ab99161f6bb592afd19828e0ff80 Mon Sep 17 00:00:00 2001 From: Lee Hinman <57081003+leehinman@users.noreply.github.com> Date: Wed, 29 Apr 2020 16:35:27 -0500 Subject: [PATCH 060/116] [Filebeat] Improve ECS field mappings in santa module (#17982) * Improve ECS field mappings in santa module - move certificate.common_name to santa.certificate.common_name (breaking change) - move certificate.sha256 to santa.certificate.sha256 (breaking change) - move hash.sha256 to process.hash.sha256 (breaking change) - event.action - event.category - event.kind - event.type - event.outcome - log.level - add full path to executable to process.args - related.hash - related.user - Add new default file path Closes #16180 --- CHANGELOG.next.asciidoc | 1 + filebeat/docs/fields.asciidoc | 4 +- filebeat/module/santa/_meta/fields.yml | 12 +- filebeat/module/santa/fields.go | 2 +- .../module/santa/log/ingest/pipeline.json | 71 ------ filebeat/module/santa/log/ingest/pipeline.yml | 91 ++++++++ filebeat/module/santa/log/manifest.yml | 3 +- .../santa/log/test/santa.log-expected.json | 216 ++++++++++++++++-- 8 files changed, 294 insertions(+), 106 deletions(-) delete mode 100644 filebeat/module/santa/log/ingest/pipeline.json create mode 100644 filebeat/module/santa/log/ingest/pipeline.yml diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 5d81c48f7e1..6080145eb21 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -22,6 +22,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d *Filebeat* - Improve ECS field mappings in panw module. event.outcome now only contains success/failure per ECS specification. {issue}16025[16025] {pull}17910[17910] - Improve ECS categorization field mappings for nginx module. http.request.referrer is now lowercase & http.request.referrer only populated when nginx sets a value {issue}16174[16174] {pull}17844[17844] +- Improve ECS field mappings in santa module. move hash.sha256 to process.hash.sha256 & move certificate fields to santa.certificate . {issue}16180[16180] {pull}17982[17982] *Heartbeat* diff --git a/filebeat/docs/fields.asciidoc b/filebeat/docs/fields.asciidoc index 488cab967ff..05f4677f138 100644 --- a/filebeat/docs/fields.asciidoc +++ b/filebeat/docs/fields.asciidoc @@ -28708,7 +28708,7 @@ The disk volume path. -- -*`certificate.common_name`*:: +*`santa.certificate.common_name`*:: + -- Common name from code signing certificate. @@ -28717,7 +28717,7 @@ type: keyword -- -*`certificate.sha256`*:: +*`santa.certificate.sha256`*:: + -- SHA256 hash of code signing certificate. diff --git a/filebeat/module/santa/_meta/fields.yml b/filebeat/module/santa/_meta/fields.yml index fea0b03a78c..57255dd76c8 100644 --- a/filebeat/module/santa/_meta/fields.yml +++ b/filebeat/module/santa/_meta/fields.yml @@ -56,10 +56,10 @@ - name: mount description: The disk volume path. - - name: certificate.common_name - type: keyword - description: Common name from code signing certificate. + - name: certificate.common_name + type: keyword + description: Common name from code signing certificate. - - name: certificate.sha256 - type: keyword - description: SHA256 hash of code signing certificate. + - name: certificate.sha256 + type: keyword + description: SHA256 hash of code signing certificate. diff --git a/filebeat/module/santa/fields.go b/filebeat/module/santa/fields.go index 06b53e41d84..cd3f44d3647 100644 --- a/filebeat/module/santa/fields.go +++ b/filebeat/module/santa/fields.go @@ -32,5 +32,5 @@ func init() { // AssetSanta returns asset data. // This is the base64 encoded gzipped contents of module/santa. func AssetSanta() string { - return "eJyUk82O2jAQgO88xWhP7WFRl4o95FAphfRHBS0iK7W3yhtPiJXEE9lOW96+spMFk8QtcCJj+/s8npl7KPEYgWbSsBmAEabCCO4+Ex0qhNSG72YAHHWmRGMEyQg+zACgW4Mt8bbCGUAusOI6ckv3IFmNZ6r9mWODERwUtU0fmWCeMf3nmcUyu/EUfgWWePxNintx/MPqxiaR/EhWXvxCF3e0kYVjJvSNnnizefoeEq17IJiCme5BOBiicj6WK2T6NvUq2T+HzHtHg5wUmAJtZtrdZEJcE8dbtNuQ86lBxYyQB4cEyrsumVByocuR0u+OEfuT6wyXz/pr+i3e7ZJ437eFnnun/E4can9R1dZ4sTTQPBfY73JHvKv7nJdW/w9iM7T7oFFkKKMqgNKoBKuuonVbQbb1C6rQzTS3fwa8U+ks50G/v0r3MV3/6w1siYf3Pg/FbrdJIE3XkG7fLR8Wm6uMDhnQ5cMXP7lYM1qbxvd1LYXk8CYXFeqjNli77nsbzLKV5hZ6w0zRs14ZGSojcpExg/OM6prkT69GU+N2YVi5I44FuaIaMjtcWhyknTQfHtbqgi2Wj9ca0y/xYvkIBdOFHeKw728AAAD//5mnu+4=" + return "eJyUk82O2jAQgO88xWhP7WFRl4o9cKiUQvqjghaRldpb5Y0nxErsiWynLW9f2UkhJPGWcCJj+/tm7Jl7KPC0AsOUZTMAK2yJK7j7THQsERIXvpsBcDSpFpUVpFbwYQYAzRrsiNclzgAygSU3K790D4pJvFDdz54qXMFRU121kRHmBdN+XlgsdRvP4X/AAk+/SfNOHP8wWbki4h/xuhO/0kUNbWDhmAoz0RNtt0/fQ6JNCwSbM9tcCAdLVMyHco3MTFOv48NzyHzwNMhIg83RVWZ8JiNiSRynaHch51OFmlmhjh4JlDVdMqLkwhQDZbc7BuxPvjN8PZuvybdov4+jQ9sWZt451e3EvvYXlbXEq6We5jnHdpc/0km9y3mpzf8grkK3DypNllIqAyiDWrDyJlqzFVQtX1CHMjPc/enxzk/nOA/m/U26j8nmtTtwT9zP+zIU+/02hiTZQLJ7t3xYbG8yemRAl/Vv/Oxi1WBtHN++ayEUhzeZKNGcjEXpu+9tsMpa2Sn0itl8pOFT1FZkImUW5ylJSepn751CY3dlW/ujngmZJgmpGzQjjspNXVfyegomZ4vl41R78iVaLB8hZyZ3wx12/w0AAP//xDi+7g==" } diff --git a/filebeat/module/santa/log/ingest/pipeline.json b/filebeat/module/santa/log/ingest/pipeline.json deleted file mode 100644 index 4eaddc753a6..00000000000 --- a/filebeat/module/santa/log/ingest/pipeline.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "description": "Pipeline for parsing Google Santa logs.", - "processors": [ - { - "grok": { - "field": "message", - "patterns": [ - "\\[%{TIMESTAMP_ISO8601:process.start}\\] I santad: action=%{NOT_SEPARATOR:santa.action}\\|decision=%{NOT_SEPARATOR:santa.decision}\\|reason=%{NOT_SEPARATOR:santa.reason}\\|sha256=%{NOT_SEPARATOR:hash.sha256}\\|path=%{NOT_SEPARATOR:process.executable}(\\|args=%{NOT_SEPARATOR:process.args})?(\\|cert_sha256=%{NOT_SEPARATOR:certificate.sha256})?(\\|cert_cn=%{NOT_SEPARATOR:certificate.common_name})?\\|pid=%{NUMBER:process.pid:long}\\|ppid=%{NUMBER:process.ppid:long}\\|uid=%{NUMBER:user.id}\\|user=%{NOT_SEPARATOR:user.name}\\|gid=%{NUMBER:group.id}\\|group=%{NOT_SEPARATOR:group.name}\\|mode=%{WORD:santa.mode}", - "\\[%{TIMESTAMP_ISO8601:timestamp}\\] I santad: action=%{NOT_SEPARATOR:santa.action}\\|mount=%{NOT_SEPARATOR:santa.disk.mount}\\|volume=%{NOT_SEPARATOR:santa.disk.volume}\\|bsdname=%{NOT_SEPARATOR:santa.disk.bsdname}\\|fs=%{NOT_SEPARATOR:santa.disk.fs}\\|model=%{NOT_SEPARATOR:santa.disk.model}\\|serial=%{NOT_SEPARATOR:santa.disk.serial}\\|bus=%{NOT_SEPARATOR:santa.disk.bus}\\|dmgpath=%{NOT_SEPARATOR:santa.disk.dmgpath}?" - ], - "pattern_definitions": { - "NOT_SEPARATOR": "[^\\|]+" - } - } - }, - { - "rename": { - "field": "message", - "target_field": "log.original" - } - }, - { - "date": { - "field": "process.start", - "target_field": "process.start", - "formats": [ - "ISO8601" - ], - "ignore_failure": true - } - }, - { - "set": { - "field": "@timestamp", - "value": "{{ process.start }}", - "ignore_failure": true - } - }, - { - "split": { - "field": "process.args", - "separator": " ", - "ignore_failure": true - } - }, - { - "date": { - "field": "timestamp", - "target_field": "@timestamp", - "formats": [ - "ISO8601" - ], - "ignore_failure": true - } - }, - { - "remove": { - "field": "timestamp", - "ignore_missing": true - } - } - ], - "on_failure": [ - { - "set": { - "field": "error.message", - "value": "{{ _ingest.on_failure_message }}" - } - } - ] -} diff --git a/filebeat/module/santa/log/ingest/pipeline.yml b/filebeat/module/santa/log/ingest/pipeline.yml new file mode 100644 index 00000000000..11ad4cead6c --- /dev/null +++ b/filebeat/module/santa/log/ingest/pipeline.yml @@ -0,0 +1,91 @@ +description: Pipeline for parsing Google Santa logs. +processors: +- grok: + field: message + patterns: + - '\[%{TIMESTAMP_ISO8601:process.start}\] %{NOT_SEPARATOR:log.level} santad: action=%{NOT_SEPARATOR:santa.action}\|decision=%{NOT_SEPARATOR:santa.decision}\|reason=%{NOT_SEPARATOR:santa.reason}\|sha256=%{NOT_SEPARATOR:process.hash.sha256}\|path=%{NOT_SEPARATOR:process.executable}(\|args=%{NOT_SEPARATOR:santa.args})?(\|cert_sha256=%{NOT_SEPARATOR:santa.certificate.sha256})?(\|cert_cn=%{NOT_SEPARATOR:santa.certificate.common_name})?\|pid=%{NUMBER:process.pid:long}\|ppid=%{NUMBER:process.ppid:long}\|uid=%{NUMBER:user.id}\|user=%{NOT_SEPARATOR:user.name}\|gid=%{NUMBER:group.id}\|group=%{NOT_SEPARATOR:group.name}\|mode=%{WORD:santa.mode}' + - '\[%{TIMESTAMP_ISO8601:timestamp}\] %{NOT_SEPARATOR:log.level} santad: action=%{NOT_SEPARATOR:santa.action}\|mount=%{NOT_SEPARATOR:santa.disk.mount}\|volume=%{NOT_SEPARATOR:santa.disk.volume}\|bsdname=%{NOT_SEPARATOR:santa.disk.bsdname}\|fs=%{NOT_SEPARATOR:santa.disk.fs}\|model=%{NOT_SEPARATOR:santa.disk.model}\|serial=%{NOT_SEPARATOR:santa.disk.serial}\|bus=%{NOT_SEPARATOR:santa.disk.bus}\|dmgpath=%{NOT_SEPARATOR:santa.disk.dmgpath}?' + pattern_definitions: + NOT_SEPARATOR: '[^\|]+' +- rename: + field: message + target_field: log.original +- date: + field: process.start + target_field: process.start + formats: + - ISO8601 + ignore_failure: true +- set: + field: '@timestamp' + value: '{{ process.start }}' + ignore_failure: true +- split: + field: santa.args + separator: ' ' + ignore_failure: true +- date: + field: timestamp + target_field: '@timestamp' + formats: + - ISO8601 + ignore_failure: true +- remove: + field: timestamp + ignore_missing: true +- append: + field: process.args + value: "{{process.executable}}" + if: "ctx?.process?.executable != null" +- foreach: + field: santa.args + processor: + append: + field: process.args + value: "{{_ingest._value}}" + ignore_missing: true +- remove: + field: santa.args + ignore_missing: true +- set: + field: event.kind + value: event +- append: + field: event.category + value: process + if: "ctx?.santa?.action == 'EXEC'" +- append: + field: event.type + value: start + if: "ctx?.santa?.action == 'EXEC'" +- set: + field: event.outcome + value: success + if: "ctx?.santa?.decision == 'ALLOW'" +- set: + field: event.outcome + value: failure + if: "ctx?.santa?.decision == 'DENY'" +- set: + field: event.action + value: "{{santa.action}}" + if: "ctx?.santa?.action != null" +- lowercase: + field: event.action + ignore_missing: true +- append: + field: related.user + value: "{{user.name}}" + if: "ctx?.user?.name != null" +- append: + field: related.hash + value: "{{santa.certificate.sha256}}" + if: "ctx?.santa?.certificate?.sha256 != null" +- append: + field: related.hash + value: "{{process.hash.sha256}}" + if: "ctx?.process?.hash != null" +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/filebeat/module/santa/log/manifest.yml b/filebeat/module/santa/log/manifest.yml index d0369930490..43cad6e1934 100644 --- a/filebeat/module/santa/log/manifest.yml +++ b/filebeat/module/santa/log/manifest.yml @@ -4,8 +4,9 @@ var: - name: paths default: - /var/log/santa.log + - /var/db/santa/santa.log - name: input default: file -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/{{.input}}.yml diff --git a/filebeat/module/santa/log/test/santa.log-expected.json b/filebeat/module/santa/log/test/santa.log-expected.json index ab94261c13a..6c1fbe81184 100644 --- a/filebeat/module/santa/log/test/santa.log-expected.json +++ b/filebeat/module/santa/log/test/santa.log-expected.json @@ -1,25 +1,43 @@ [ { "@timestamp": "2018-12-10T06:45:16.802Z", - "certificate.common_name": "Software Signing", - "certificate.sha256": "2aa4b9973b7ba07add447ee4da8b5337c3ee2c3a991911e80e7282e8a751fc32", + "event.action": "exec", + "event.category": [ + "process" + ], "event.dataset": "santa.log", + "event.kind": "event", "event.module": "santa", + "event.outcome": "success", + "event.type": [ + "start" + ], "fileset.name": "log", "group.id": "0", "group.name": "wheel", - "hash.sha256": "c4bc09fd2f248534552f517acf3edb9a635aba2b02e46f49df683ea9b778e5b4", "input.type": "log", + "log.level": "I", "log.offset": 0, "log.original": "[2018-12-10T06:45:16.802Z] I santad: action=EXEC|decision=ALLOW|reason=CERT|sha256=c4bc09fd2f248534552f517acf3edb9a635aba2b02e46f49df683ea9b778e5b4|path=/usr/libexec/xpcproxy|args=/usr/sbin/newsyslog|cert_sha256=2aa4b9973b7ba07add447ee4da8b5337c3ee2c3a991911e80e7282e8a751fc32|cert_cn=Software Signing|pid=29678|ppid=1|uid=0|user=root|gid=0|group=wheel|mode=M", "process.args": [ + "/usr/libexec/xpcproxy", "/usr/sbin/newsyslog" ], "process.executable": "/usr/libexec/xpcproxy", + "process.hash.sha256": "c4bc09fd2f248534552f517acf3edb9a635aba2b02e46f49df683ea9b778e5b4", "process.pid": 29678, "process.ppid": 1, "process.start": "2018-12-10T06:45:16.802Z", + "related.hash": [ + "2aa4b9973b7ba07add447ee4da8b5337c3ee2c3a991911e80e7282e8a751fc32", + "c4bc09fd2f248534552f517acf3edb9a635aba2b02e46f49df683ea9b778e5b4" + ], + "related.user": [ + "root" + ], "santa.action": "EXEC", + "santa.certificate.common_name": "Software Signing", + "santa.certificate.sha256": "2aa4b9973b7ba07add447ee4da8b5337c3ee2c3a991911e80e7282e8a751fc32", "santa.decision": "ALLOW", "santa.mode": "M", "santa.reason": "CERT", @@ -29,26 +47,44 @@ }, { "@timestamp": "2018-12-10T06:45:16.802Z", - "certificate.common_name": "Software Signing", - "certificate.sha256": "2aa4b9973b7ba07add447ee4da8b5337c3ee2c3a991911e80e7282e8a751fc32", + "event.action": "exec", + "event.category": [ + "process" + ], "event.dataset": "santa.log", + "event.kind": "event", "event.module": "santa", + "event.outcome": "success", + "event.type": [ + "start" + ], "fileset.name": "log", "group.id": "0", "group.name": "wheel", - "hash.sha256": "c4bc09fd2f248534552f517acf3edb9a635aba2b02e46f49df683ea9b778e5b4", "input.type": "log", + "log.level": "I", "log.offset": 360, "log.original": "[2018-12-10T06:45:16.802Z] I santad: action=EXEC|decision=ALLOW|reason=CERT|sha256=c4bc09fd2f248534552f517acf3edb9a635aba2b02e46f49df683ea9b778e5b4|path=/usr/libexec/xpcproxy|args=xpcproxy com.apple.systemstats.daily|cert_sha256=2aa4b9973b7ba07add447ee4da8b5337c3ee2c3a991911e80e7282e8a751fc32|cert_cn=Software Signing|pid=29679|ppid=1|uid=0|user=root|gid=0|group=wheel|mode=M", "process.args": [ + "/usr/libexec/xpcproxy", "xpcproxy", "com.apple.systemstats.daily" ], "process.executable": "/usr/libexec/xpcproxy", + "process.hash.sha256": "c4bc09fd2f248534552f517acf3edb9a635aba2b02e46f49df683ea9b778e5b4", "process.pid": 29679, "process.ppid": 1, "process.start": "2018-12-10T06:45:16.802Z", + "related.hash": [ + "2aa4b9973b7ba07add447ee4da8b5337c3ee2c3a991911e80e7282e8a751fc32", + "c4bc09fd2f248534552f517acf3edb9a635aba2b02e46f49df683ea9b778e5b4" + ], + "related.user": [ + "root" + ], "santa.action": "EXEC", + "santa.certificate.common_name": "Software Signing", + "santa.certificate.sha256": "2aa4b9973b7ba07add447ee4da8b5337c3ee2c3a991911e80e7282e8a751fc32", "santa.decision": "ALLOW", "santa.mode": "M", "santa.reason": "CERT", @@ -58,25 +94,43 @@ }, { "@timestamp": "2018-12-10T06:45:16.851Z", - "certificate.common_name": "Software Signing", - "certificate.sha256": "2aa4b9973b7ba07add447ee4da8b5337c3ee2c3a991911e80e7282e8a751fc32", + "event.action": "exec", + "event.category": [ + "process" + ], "event.dataset": "santa.log", + "event.kind": "event", "event.module": "santa", + "event.outcome": "success", + "event.type": [ + "start" + ], "fileset.name": "log", "group.id": "0", "group.name": "wheel", - "hash.sha256": "746f0dbafb7e675d5ce67131e5544772ee612b894e8ab51d3ce2d21f7cb7332d", "input.type": "log", + "log.level": "I", "log.offset": 737, "log.original": "[2018-12-10T06:45:16.851Z] I santad: action=EXEC|decision=ALLOW|reason=CERT|sha256=746f0dbafb7e675d5ce67131e5544772ee612b894e8ab51d3ce2d21f7cb7332d|path=/usr/sbin/newsyslog|args=/usr/sbin/newsyslog|cert_sha256=2aa4b9973b7ba07add447ee4da8b5337c3ee2c3a991911e80e7282e8a751fc32|cert_cn=Software Signing|pid=29678|ppid=1|uid=0|user=root|gid=0|group=wheel|mode=M", "process.args": [ + "/usr/sbin/newsyslog", "/usr/sbin/newsyslog" ], "process.executable": "/usr/sbin/newsyslog", + "process.hash.sha256": "746f0dbafb7e675d5ce67131e5544772ee612b894e8ab51d3ce2d21f7cb7332d", "process.pid": 29678, "process.ppid": 1, "process.start": "2018-12-10T06:45:16.851Z", + "related.hash": [ + "2aa4b9973b7ba07add447ee4da8b5337c3ee2c3a991911e80e7282e8a751fc32", + "746f0dbafb7e675d5ce67131e5544772ee612b894e8ab51d3ce2d21f7cb7332d" + ], + "related.user": [ + "root" + ], "santa.action": "EXEC", + "santa.certificate.common_name": "Software Signing", + "santa.certificate.sha256": "2aa4b9973b7ba07add447ee4da8b5337c3ee2c3a991911e80e7282e8a751fc32", "santa.decision": "ALLOW", "santa.mode": "M", "santa.reason": "CERT", @@ -86,26 +140,44 @@ }, { "@timestamp": "2018-12-10T06:45:16.859Z", - "certificate.common_name": "Software Signing", - "certificate.sha256": "2aa4b9973b7ba07add447ee4da8b5337c3ee2c3a991911e80e7282e8a751fc32", + "event.action": "exec", + "event.category": [ + "process" + ], "event.dataset": "santa.log", + "event.kind": "event", "event.module": "santa", + "event.outcome": "success", + "event.type": [ + "start" + ], "fileset.name": "log", "group.id": "0", "group.name": "wheel", - "hash.sha256": "d6be9bfbd777ac5dcd30488014acc787a2df5ce840f1fe4d5742d323ee00392f", "input.type": "log", + "log.level": "I", "log.offset": 1095, "log.original": "[2018-12-10T06:45:16.859Z] I santad: action=EXEC|decision=ALLOW|reason=CERT|sha256=d6be9bfbd777ac5dcd30488014acc787a2df5ce840f1fe4d5742d323ee00392f|path=/usr/sbin/systemstats|args=/usr/sbin/systemstats --daily|cert_sha256=2aa4b9973b7ba07add447ee4da8b5337c3ee2c3a991911e80e7282e8a751fc32|cert_cn=Software Signing|pid=29679|ppid=1|uid=0|user=root|gid=0|group=wheel|mode=M", "process.args": [ + "/usr/sbin/systemstats", "/usr/sbin/systemstats", "--daily" ], "process.executable": "/usr/sbin/systemstats", + "process.hash.sha256": "d6be9bfbd777ac5dcd30488014acc787a2df5ce840f1fe4d5742d323ee00392f", "process.pid": 29679, "process.ppid": 1, "process.start": "2018-12-10T06:45:16.859Z", + "related.hash": [ + "2aa4b9973b7ba07add447ee4da8b5337c3ee2c3a991911e80e7282e8a751fc32", + "d6be9bfbd777ac5dcd30488014acc787a2df5ce840f1fe4d5742d323ee00392f" + ], + "related.user": [ + "root" + ], "santa.action": "EXEC", + "santa.certificate.common_name": "Software Signing", + "santa.certificate.sha256": "2aa4b9973b7ba07add447ee4da8b5337c3ee2c3a991911e80e7282e8a751fc32", "santa.decision": "ALLOW", "santa.mode": "M", "santa.reason": "CERT", @@ -115,25 +187,43 @@ }, { "@timestamp": "2018-12-10T08:45:27.810Z", - "certificate.common_name": "Software Signing", - "certificate.sha256": "2aa4b9973b7ba07add447ee4da8b5337c3ee2c3a991911e80e7282e8a751fc32", + "event.action": "exec", + "event.category": [ + "process" + ], "event.dataset": "santa.log", + "event.kind": "event", "event.module": "santa", + "event.outcome": "success", + "event.type": [ + "start" + ], "fileset.name": "log", "group.id": "0", "group.name": "wheel", - "hash.sha256": "c4bc09fd2f248534552f517acf3edb9a635aba2b02e46f49df683ea9b778e5b4", "input.type": "log", + "log.level": "I", "log.offset": 1465, "log.original": "[2018-12-10T08:45:27.810Z] I santad: action=EXEC|decision=ALLOW|reason=CERT|sha256=c4bc09fd2f248534552f517acf3edb9a635aba2b02e46f49df683ea9b778e5b4|path=/usr/libexec/xpcproxy|args=/usr/sbin/newsyslog|cert_sha256=2aa4b9973b7ba07add447ee4da8b5337c3ee2c3a991911e80e7282e8a751fc32|cert_cn=Software Signing|pid=29681|ppid=1|uid=0|user=root|gid=0|group=wheel|mode=M", "process.args": [ + "/usr/libexec/xpcproxy", "/usr/sbin/newsyslog" ], "process.executable": "/usr/libexec/xpcproxy", + "process.hash.sha256": "c4bc09fd2f248534552f517acf3edb9a635aba2b02e46f49df683ea9b778e5b4", "process.pid": 29681, "process.ppid": 1, "process.start": "2018-12-10T08:45:27.810Z", + "related.hash": [ + "2aa4b9973b7ba07add447ee4da8b5337c3ee2c3a991911e80e7282e8a751fc32", + "c4bc09fd2f248534552f517acf3edb9a635aba2b02e46f49df683ea9b778e5b4" + ], + "related.user": [ + "root" + ], "santa.action": "EXEC", + "santa.certificate.common_name": "Software Signing", + "santa.certificate.sha256": "2aa4b9973b7ba07add447ee4da8b5337c3ee2c3a991911e80e7282e8a751fc32", "santa.decision": "ALLOW", "santa.mode": "M", "santa.reason": "CERT", @@ -143,26 +233,44 @@ }, { "@timestamp": "2018-12-10T08:45:27.810Z", - "certificate.common_name": "Software Signing", - "certificate.sha256": "2aa4b9973b7ba07add447ee4da8b5337c3ee2c3a991911e80e7282e8a751fc32", + "event.action": "exec", + "event.category": [ + "process" + ], "event.dataset": "santa.log", + "event.kind": "event", "event.module": "santa", + "event.outcome": "success", + "event.type": [ + "start" + ], "fileset.name": "log", "group.id": "0", "group.name": "wheel", - "hash.sha256": "c4bc09fd2f248534552f517acf3edb9a635aba2b02e46f49df683ea9b778e5b4", "input.type": "log", + "log.level": "I", "log.offset": 1825, "log.original": "[2018-12-10T08:45:27.810Z] I santad: action=EXEC|decision=ALLOW|reason=CERT|sha256=c4bc09fd2f248534552f517acf3edb9a635aba2b02e46f49df683ea9b778e5b4|path=/usr/libexec/xpcproxy|args=xpcproxy com.adobe.AAM.Scheduler-1.0|cert_sha256=2aa4b9973b7ba07add447ee4da8b5337c3ee2c3a991911e80e7282e8a751fc32|cert_cn=Software Signing|pid=29680|ppid=1|uid=0|user=root|gid=0|group=wheel|mode=M", "process.args": [ + "/usr/libexec/xpcproxy", "xpcproxy", "com.adobe.AAM.Scheduler-1.0" ], "process.executable": "/usr/libexec/xpcproxy", + "process.hash.sha256": "c4bc09fd2f248534552f517acf3edb9a635aba2b02e46f49df683ea9b778e5b4", "process.pid": 29680, "process.ppid": 1, "process.start": "2018-12-10T08:45:27.810Z", + "related.hash": [ + "2aa4b9973b7ba07add447ee4da8b5337c3ee2c3a991911e80e7282e8a751fc32", + "c4bc09fd2f248534552f517acf3edb9a635aba2b02e46f49df683ea9b778e5b4" + ], + "related.user": [ + "root" + ], "santa.action": "EXEC", + "santa.certificate.common_name": "Software Signing", + "santa.certificate.sha256": "2aa4b9973b7ba07add447ee4da8b5337c3ee2c3a991911e80e7282e8a751fc32", "santa.decision": "ALLOW", "santa.mode": "M", "santa.reason": "CERT", @@ -172,24 +280,41 @@ }, { "@timestamp": "2018-12-10T21:37:27.247Z", + "event.action": "exec", + "event.category": [ + "process" + ], "event.dataset": "santa.log", + "event.kind": "event", "event.module": "santa", + "event.outcome": "success", + "event.type": [ + "start" + ], "fileset.name": "log", "group.id": "0", "group.name": "wheel", - "hash.sha256": "08bd61582657cd6d78c9e071d34d79a32bb59e7210077a44919d2c5477e988a1", "input.type": "log", + "log.level": "I", "log.offset": 2202, "log.original": "[2018-12-10T21:37:27.247Z] I santad: action=EXEC|decision=ALLOW|reason=UNKNOWN|sha256=08bd61582657cd6d78c9e071d34d79a32bb59e7210077a44919d2c5477e988a1|path=/usr/local/Cellar/osquery/3.3.0_1/bin/osqueryd|args=/usr/local/bin/osqueryd --flagfile=/private/var/osquery/osquery.flags --logger_min_stderr=1|pid=45084|ppid=1|uid=0|user=root|gid=0|group=wheel|mode=M", "process.args": [ + "/usr/local/Cellar/osquery/3.3.0_1/bin/osqueryd", "/usr/local/bin/osqueryd", "--flagfile=/private/var/osquery/osquery.flags", "--logger_min_stderr=1" ], "process.executable": "/usr/local/Cellar/osquery/3.3.0_1/bin/osqueryd", + "process.hash.sha256": "08bd61582657cd6d78c9e071d34d79a32bb59e7210077a44919d2c5477e988a1", "process.pid": 45084, "process.ppid": 1, "process.start": "2018-12-10T21:37:27.247Z", + "related.hash": [ + "08bd61582657cd6d78c9e071d34d79a32bb59e7210077a44919d2c5477e988a1" + ], + "related.user": [ + "root" + ], "santa.action": "EXEC", "santa.decision": "ALLOW", "santa.mode": "M", @@ -200,22 +325,42 @@ }, { "@timestamp": "2018-12-10T16:24:43.992Z", - "certificate.common_name": "Software Signing", - "certificate.sha256": "2aa4b9973b7ba07add447ee4da8b5337c3ee2c3a991911e80e7282e8a751fc32", + "event.action": "exec", + "event.category": [ + "process" + ], "event.dataset": "santa.log", + "event.kind": "event", "event.module": "santa", + "event.outcome": "success", + "event.type": [ + "start" + ], "fileset.name": "log", "group.id": "20", "group.name": "staff", - "hash.sha256": "63b6a54848d7b4adf726d68f11409a4ac05b43926cb0f2792f7d41dc0221c106", "input.type": "log", + "log.level": "I", "log.offset": 2560, "log.original": "[2018-12-10T16:24:43.992Z] I santad: action=EXEC|decision=ALLOW|reason=CERT|sha256=63b6a54848d7b4adf726d68f11409a4ac05b43926cb0f2792f7d41dc0221c106|path=/usr/bin/basename|cert_sha256=2aa4b9973b7ba07add447ee4da8b5337c3ee2c3a991911e80e7282e8a751fc32|cert_cn=Software Signing|pid=40757|ppid=40756|uid=501|user=akroh|gid=20|group=staff|mode=M", + "process.args": [ + "/usr/bin/basename" + ], "process.executable": "/usr/bin/basename", + "process.hash.sha256": "63b6a54848d7b4adf726d68f11409a4ac05b43926cb0f2792f7d41dc0221c106", "process.pid": 40757, "process.ppid": 40756, "process.start": "2018-12-10T16:24:43.992Z", + "related.hash": [ + "2aa4b9973b7ba07add447ee4da8b5337c3ee2c3a991911e80e7282e8a751fc32", + "63b6a54848d7b4adf726d68f11409a4ac05b43926cb0f2792f7d41dc0221c106" + ], + "related.user": [ + "akroh" + ], "santa.action": "EXEC", + "santa.certificate.common_name": "Software Signing", + "santa.certificate.sha256": "2aa4b9973b7ba07add447ee4da8b5337c3ee2c3a991911e80e7282e8a751fc32", "santa.decision": "ALLOW", "santa.mode": "M", "santa.reason": "CERT", @@ -225,18 +370,26 @@ }, { "@timestamp": "2018-12-14T05:35:38.313Z", - "certificate.common_name": "Developer ID Application: Google, Inc. (EQHXZ8M8AV)", - "certificate.sha256": "345a8e098bd04794aaeefda8c9ef56a0bf3d3706d67d35bc0e23f11bb3bffce5", + "event.action": "exec", + "event.category": [ + "process" + ], "event.dataset": "santa.log", + "event.kind": "event", "event.module": "santa", + "event.outcome": "success", + "event.type": [ + "start" + ], "fileset.name": "log", "group.id": "20", "group.name": "staff", - "hash.sha256": "a8defc1b24c45f6dabeb8298af5f8e1daf39e1504e16f878345f15ac94ae96d7", "input.type": "log", + "log.level": "I", "log.offset": 2899, "log.original": "[2018-12-14T05:35:38.313Z] I santad: action=EXEC|decision=ALLOW|reason=UNKNOWN|sha256=a8defc1b24c45f6dabeb8298af5f8e1daf39e1504e16f878345f15ac94ae96d7|path=/Applications/Google Chrome.app/Contents/Versions/70.0.3538.110/Google Chrome Helper.app/Contents/MacOS/Google Chrome Helper|args=/Applications/Google Chrome.app/Contents/Versions/70.0.3538.110/Google Chrome Helper.app/Contents/MacOS/Google Chrome Helper --type=utility --field-trial-handle=120122713615061869,9401617251746517350,131072 --lang=en-US --service-sandbox-type=utility --service-request-channel-token=10458143409865682077 --seatbelt-client=262|cert_sha256=345a8e098bd04794aaeefda8c9ef56a0bf3d3706d67d35bc0e23f11bb3bffce5|cert_cn=Developer ID Application: Google, Inc. (EQHXZ8M8AV)|pid=89238|ppid=704|uid=501|user=akroh|gid=20|group=staff|mode=M", "process.args": [ + "/Applications/Google Chrome.app/Contents/Versions/70.0.3538.110/Google Chrome Helper.app/Contents/MacOS/Google Chrome Helper", "/Applications/Google", "Chrome.app/Contents/Versions/70.0.3538.110/Google", "Chrome", @@ -251,10 +404,20 @@ "--seatbelt-client=262" ], "process.executable": "/Applications/Google Chrome.app/Contents/Versions/70.0.3538.110/Google Chrome Helper.app/Contents/MacOS/Google Chrome Helper", + "process.hash.sha256": "a8defc1b24c45f6dabeb8298af5f8e1daf39e1504e16f878345f15ac94ae96d7", "process.pid": 89238, "process.ppid": 704, "process.start": "2018-12-14T05:35:38.313Z", + "related.hash": [ + "345a8e098bd04794aaeefda8c9ef56a0bf3d3706d67d35bc0e23f11bb3bffce5", + "a8defc1b24c45f6dabeb8298af5f8e1daf39e1504e16f878345f15ac94ae96d7" + ], + "related.user": [ + "akroh" + ], "santa.action": "EXEC", + "santa.certificate.common_name": "Developer ID Application: Google, Inc. (EQHXZ8M8AV)", + "santa.certificate.sha256": "345a8e098bd04794aaeefda8c9ef56a0bf3d3706d67d35bc0e23f11bb3bffce5", "santa.decision": "ALLOW", "santa.mode": "M", "santa.reason": "UNKNOWN", @@ -264,10 +427,13 @@ }, { "@timestamp": "2018-12-17T03:03:52.337Z", + "event.action": "diskappear", "event.dataset": "santa.log", + "event.kind": "event", "event.module": "santa", "fileset.name": "log", "input.type": "log", + "log.level": "I", "log.offset": 3712, "log.original": "[2018-12-17T03:03:52.337Z] I santad: action=DISKAPPEAR|mount=/Volumes/Recovery|volume=Recovery|bsdname=disk1s3|fs=apfs|model=APPLE SSD SM0512L|serial=C026495006UHCHH1Q|bus=PCI-Express|dmgpath=", "santa.action": "DISKAPPEAR", From cde17b13d048edd434004f2bdfbd221dbb1cd129 Mon Sep 17 00:00:00 2001 From: Andrew Cholakian Date: Wed, 29 Apr 2020 17:11:20 -0500 Subject: [PATCH 061/116] Use non-wildcard field for text (#18066) --- heartbeat/docs/fields.asciidoc | 4 ++-- heartbeat/include/fields.go | 2 +- heartbeat/monitors/active/dialchain/_meta/fields.yml | 6 ++++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/heartbeat/docs/fields.asciidoc b/heartbeat/docs/fields.asciidoc index f52d1205662..05507f95dc5 100644 --- a/heartbeat/docs/fields.asciidoc +++ b/heartbeat/docs/fields.asciidoc @@ -7915,7 +7915,7 @@ example: DigiCert SHA2 High Assurance Server CA *`tls.server.x509.issuer.common_name.text`*:: + -- -type: wildcard +type: text -- @@ -8033,7 +8033,7 @@ example: r2.shared.global.fastly.net *`tls.server.x509.subject.subject.common_name.text`*:: + -- -type: wildcard +type: text -- diff --git a/heartbeat/include/fields.go b/heartbeat/include/fields.go index 601fcb201b9..a449cc5fe7e 100644 --- a/heartbeat/include/fields.go +++ b/heartbeat/include/fields.go @@ -32,5 +32,5 @@ func init() { // AssetFieldsYml returns asset data. // This is the base64 encoded gzipped contents of fields.yml. func AssetFieldsYml() string { - return "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" + return "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" } diff --git a/heartbeat/monitors/active/dialchain/_meta/fields.yml b/heartbeat/monitors/active/dialchain/_meta/fields.yml index a234429103a..288fe9edca7 100644 --- a/heartbeat/monitors/active/dialchain/_meta/fields.yml +++ b/heartbeat/monitors/active/dialchain/_meta/fields.yml @@ -80,7 +80,8 @@ default_field: false multi_fields: - name: text - type: wildcard + type: text + analyzer: simple - name: distinguished_name type: keyword ignore_above: 1024 @@ -147,7 +148,8 @@ default_field: false multi_fields: - name: text - type: wildcard + type: text + analyzer: simple - name: subject.distinguished_name type: keyword ignore_above: 1024 From ac39111312dedb2500ab7fd6b205d124570a90d4 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Thu, 30 Apr 2020 08:05:16 +0100 Subject: [PATCH 062/116] ci: support runbld to aggregate data (#18037) --- Jenkinsfile | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index f15ded84e83..44b88922ee1 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -2,6 +2,13 @@ @Library('apm@current') _ +import groovy.transform.Field + +/** + This is required to store the stashed id with the test results to be digested with runbld +*/ +@Field def stashedTestReports = [:] + pipeline { agent { label 'ubuntu && immutable' } environment { @@ -11,6 +18,7 @@ pipeline { PIPELINE_LOG_LEVEL = "INFO" DOCKERELASTIC_SECRET = 'secret/observability-team/ci/docker-registry/prod' DOCKER_REGISTRY = 'docker.elastic.co' + RUNBLD_DISABLE_NOTIFICATIONS = 'true' } options { timeout(time: 2, unit: 'HOURS') @@ -594,6 +602,9 @@ pipeline { } } post { + always { + runbld() + } cleanup { notifyBuildResult(prComment: true) } @@ -682,7 +693,7 @@ def withBeatsEnv(boolean archive, Closure body) { } finally { if (archive) { catchError(buildResult: 'SUCCESS', stageResult: 'UNSTABLE') { - junit(allowEmptyResults: true, keepLongStdio: true, testResults: "**/build/TEST*.xml") + junitAndStore(allowEmptyResults: true, keepLongStdio: true, testResults: "**/build/TEST*.xml") archiveArtifacts(allowEmptyArchive: true, artifacts: '**/build/TEST*.out') } } @@ -716,7 +727,7 @@ def withBeatsEnvWin(Closure body) { } } finally { catchError(buildResult: 'SUCCESS', stageResult: 'UNSTABLE') { - junit(allowEmptyResults: true, keepLongStdio: true, testResults: "**\\build\\TEST*.xml") + junitAndStore(allowEmptyResults: true, keepLongStdio: true, testResults: "**\\build\\TEST*.xml") archiveArtifacts(allowEmptyArchive: true, artifacts: '**\\build\\TEST*.out') } } @@ -980,3 +991,35 @@ def setGitConfig(){ def isDockerInstalled(){ return sh(label: 'check for Docker', script: 'command -v docker', returnStatus: true) } + +def junitAndStore(Map params = [:]){ + junit(params) + // STAGE_NAME env variable could be null in some cases, so let's use the currentmilliseconds + def stageName = env.STAGE_NAME ? env.STAGE_NAME.replaceAll("[\\W]|_",'-') : "uncategorized-${new java.util.Date().getTime()}" + stash(includes: params.testResults, allowEmpty: true, name: stageName, useDefaultExcludes: true) + stashedTestReports[stageName] = stageName +} + +def runbld() { + catchError(buildResult: 'SUCCESS', message: 'runbld post build action failed.') { + if (stashedTestReports) { + dir("${env.BASE_DIR}") { + sh(label: 'Prepare workspace context', + script: 'find . -type f -name "TEST*.xml" -path "*/build/*" -delete') + // Unstash the test reports + stashedTestReports.each { k, v -> + dir(k) { + unstash v + } + } + sh(label: 'Process JUnit reports with runbld', + script: '''\ + cat >./runbld-script < Date: Thu, 30 Apr 2020 08:52:01 +0100 Subject: [PATCH 063/116] ci: retry if download issues (#18088) --- Jenkinsfile | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 44b88922ee1..f55ce7c46f8 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -680,9 +680,7 @@ def withBeatsEnv(boolean archive, Closure body) { dockerLogin(secret: "${DOCKERELASTIC_SECRET}", registry: "${DOCKER_REGISTRY}") } dir("${env.BASE_DIR}") { - sh(label: "Install Go ${GO_VERSION}", script: ".ci/scripts/install-go.sh") - sh(label: "Install docker-compose ${DOCKER_COMPOSE_VERSION}", script: ".ci/scripts/install-docker-compose.sh") - sh(label: "Install Mage", script: "make mage") + installTools() // TODO (2020-04-07): This is a work-around to fix the Beat generator tests. // See https://github.com/elastic/beats/issues/17787. setGitConfig() @@ -720,7 +718,7 @@ def withBeatsEnvWin(Closure body) { deleteDir() unstash 'source' dir("${env.BASE_DIR}"){ - bat(label: "Install Go/Mage/Python ${GO_VERSION}", script: ".ci/scripts/install-tools.bat") + installTools() try { if(!params.dry_run){ body() @@ -735,6 +733,17 @@ def withBeatsEnvWin(Closure body) { } } +def installTools() { + def i = 2 // Number of retries + if(isUnix()) { + retry(i) { sh(label: "Install Go ${GO_VERSION}", script: ".ci/scripts/install-go.sh") } + retry(i) { sh(label: "Install docker-compose ${DOCKER_COMPOSE_VERSION}", script: ".ci/scripts/install-docker-compose.sh") } + retry(i) { sh(label: "Install Mage", script: "make mage") } + } else { + retry(i) { bat(label: "Install Go/Mage/Python ${GO_VERSION}", script: ".ci/scripts/install-tools.bat") } + } +} + def goos(){ def labels = env.NODE_LABELS @@ -891,7 +900,7 @@ def loadConfigEnvVars(){ env.GO_VERSION = readFile(".go-version").trim() withEnv(["HOME=${env.WORKSPACE}"]) { - sh(label: "Install Go ${env.GO_VERSION}", script: ".ci/scripts/install-go.sh") + retry(2) { sh(label: "Install Go ${env.GO_VERSION}", script: ".ci/scripts/install-go.sh") } } // Libbeat is the core framework of Beats. It has no additional dependencies From cd53df24cbe47c0e6a91ccd64fbda41a2efad585 Mon Sep 17 00:00:00 2001 From: Peter Deng Date: Thu, 30 Apr 2020 17:42:35 +0800 Subject: [PATCH 064/116] Support Fixed length extraction (#17191) Signed-off-by: Peter Deng --- CHANGELOG.next.asciidoc | 1 + libbeat/processors/dissect/const.go | 12 +++- libbeat/processors/dissect/dissect.go | 34 +++++++++-- .../processors/dissect/docs/dissect.asciidoc | 2 +- libbeat/processors/dissect/field.go | 56 +++++++++++------ libbeat/processors/dissect/parser.go | 6 ++ .../dissect/testdata/dissect_tests.json | 60 ++++++++++++++++++- libbeat/processors/dissect/validate_test.go | 8 +-- 8 files changed, 146 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 6080145eb21..551562ebc93 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -209,6 +209,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Add support for AWS IAM `role_arn` in credentials config. {pull}17658[17658] {issue}12464[12464] - Add keystore support for autodiscover static configurations. {pull]16306[16306] - Add Kerberos support to Elasticsearch output. {pull}17927[17927] +- Add support for fixed length extraction in `dissect` processor. {pull}17191[17191] *Auditbeat* diff --git a/libbeat/processors/dissect/const.go b/libbeat/processors/dissect/const.go index 610f27ec50b..aa0349cf82d 100644 --- a/libbeat/processors/dissect/const.go +++ b/libbeat/processors/dissect/const.go @@ -28,8 +28,8 @@ var ( // ` %{key}, %{key/2}` // into: // [["", "key" ], [", ", "key/2"]] - delimiterRE = regexp.MustCompile("(?s)(.*?)%\\{([^}]*?)}") - suffixRE = regexp.MustCompile("(.+?)(/(\\d{1,2}))?(->)?$") + ordinalIndicator = "/" + fixedLengthIndicator = "#" skipFieldPrefix = "?" appendFieldPrefix = "+" @@ -39,6 +39,14 @@ var ( greedySuffix = "->" pointerFieldPrefix = "*" + numberRE = "\\d{1,2}" + + delimiterRE = regexp.MustCompile("(?s)(.*?)%\\{([^}]*?)}") + suffixRE = regexp.MustCompile("(.+?)" + // group 1 for key name + "(" + ordinalIndicator + "(" + numberRE + ")" + ")?" + // group 2, 3 for ordinal + "(" + fixedLengthIndicator + "(" + numberRE + ")" + ")?" + // group 4, 5 for fixed length + "(" + greedySuffix + ")?$") // group 6 for greedy + defaultJoinString = " " errParsingFailure = errors.New("parsing failure") diff --git a/libbeat/processors/dissect/dissect.go b/libbeat/processors/dissect/dissect.go index c9093c476f8..406027adfa3 100644 --- a/libbeat/processors/dissect/dissect.go +++ b/libbeat/processors/dissect/dissect.go @@ -89,12 +89,27 @@ func (d *Dissector) extract(s string) (positions, error) { // move through all the other delimiters, until we have consumed all of them. for dl.Next() != nil { start = offset - end = dl.Next().IndexOf(s, offset) - if end == -1 { - return nil, fmt.Errorf( - "could not find delimiter: `%s` in remaining: `%s`, (offset: %d)", - dl.Delimiter(), s[offset:], offset, - ) + + // corresponding field of the delimiter + field := d.parser.fields[d.parser.fieldsIdMap[i]] + + // for fixed-length field, just step the same size of its length + if field.IsFixedLength() { + end = offset + field.Length() + if end > len(s) { + return nil, fmt.Errorf( + "field length is grater than string length: remaining: `%s`, (offset: %d), field: %s", + s[offset:], offset, field, + ) + } + } else { + end = dl.Next().IndexOf(s, offset) + if end == -1 { + return nil, fmt.Errorf( + "could not find delimiter: `%s` in remaining: `%s`, (offset: %d)", + dl.Delimiter(), s[offset:], offset, + ) + } } offset = end @@ -118,6 +133,13 @@ func (d *Dissector) extract(s string) (positions, error) { dl = dl.Next() } + field := d.parser.fields[d.parser.fieldsIdMap[i]] + + if field.IsFixedLength() && offset+field.Length() != len(s) { + return nil, fmt.Errorf("last fixed length key `%s` (length: %d) does not fit into remaining: `%s`, (offset: %d)", + field, field.Length(), s, offset, + ) + } // If we have remaining contents and have not captured all the requested fields if offset < len(s) && i < len(d.parser.fields) { positions[i] = position{start: offset, end: len(s)} diff --git a/libbeat/processors/dissect/docs/dissect.asciidoc b/libbeat/processors/dissect/docs/dissect.asciidoc index c5f1e566793..e11d8ed50b9 100644 --- a/libbeat/processors/dissect/docs/dissect.asciidoc +++ b/libbeat/processors/dissect/docs/dissect.asciidoc @@ -30,7 +30,7 @@ an error; you need to either drop or rename the key before using dissect. For tokenization to be successful, all keys must be found and extracted, if one of them cannot be found an error will be logged and no modification is done on the original event. -NOTE: A key can contain any characters except reserved suffix or prefix modifiers: `/`,`&`, `+` +NOTE: A key can contain any characters except reserved suffix or prefix modifiers: `/`,`&`, `+`, `#` and `?`. See <> for a list of supported conditions. diff --git a/libbeat/processors/dissect/field.go b/libbeat/processors/dissect/field.go index eae6ba7cdf7..bb92db0c18f 100644 --- a/libbeat/processors/dissect/field.go +++ b/libbeat/processors/dissect/field.go @@ -27,17 +27,20 @@ type field interface { MarkGreedy() IsGreedy() bool Ordinal() int + Length() int Key() string ID() int Apply(b string, m Map) String() string IsSaveable() bool + IsFixedLength() bool } type baseField struct { id int key string ordinal int + length int greedy bool } @@ -53,6 +56,10 @@ func (f baseField) Ordinal() int { return f.ordinal } +func (f baseField) Length() int { + return f.length +} + func (f baseField) Key() string { return f.key } @@ -65,6 +72,10 @@ func (f baseField) IsSaveable() bool { return true } +func (f baseField) IsFixedLength() bool { + return f.length > 0 +} + func (f baseField) String() string { return fmt.Sprintf("field: %s, ordinal: %d, greedy: %v", f.key, f.ordinal, f.IsGreedy()) } @@ -193,7 +204,7 @@ func newField(id int, rawKey string, previous delimiter) (field, error) { return newSkipField(id), nil } - key, ordinal, greedy := extractKeyParts(rawKey) + key, ordinal, length, greedy := extractKeyParts(rawKey) // Conflicting prefix used. if strings.HasPrefix(key, appendIndirectPrefix) { @@ -205,81 +216,88 @@ func newField(id int, rawKey string, previous delimiter) (field, error) { } if strings.HasPrefix(key, skipFieldPrefix) { - return newNamedSkipField(id, key[1:]), nil + return newNamedSkipField(id, key[1:], length), nil } if strings.HasPrefix(key, pointerFieldPrefix) { - return newPointerField(id, key[1:]), nil + return newPointerField(id, key[1:], length), nil } if strings.HasPrefix(key, appendFieldPrefix) { - return newAppendField(id, key[1:], ordinal, greedy, previous), nil + return newAppendField(id, key[1:], ordinal, length, greedy, previous), nil } if strings.HasPrefix(key, indirectFieldPrefix) { - return newIndirectField(id, key[1:]), nil + return newIndirectField(id, key[1:], length), nil } - - return newNormalField(id, key, ordinal, greedy), nil + return newNormalField(id, key, ordinal, length, greedy), nil } func newSkipField(id int) skipField { return skipField{baseField{id: id}} } -func newNamedSkipField(id int, key string) namedSkipField { +func newNamedSkipField(id int, key string, length int) namedSkipField { return namedSkipField{ - baseField{id: id, key: key}, + baseField{id: id, key: key, length: length}, } } -func newPointerField(id int, key string) pointerField { +func newPointerField(id int, key string, length int) pointerField { return pointerField{ - baseField{id: id, key: key}, + baseField{id: id, key: key, length: length}, } } -func newAppendField(id int, key string, ordinal int, greedy bool, previous delimiter) appendField { +func newAppendField(id int, key string, ordinal int, length int, greedy bool, previous delimiter) appendField { return appendField{ baseField: baseField{ id: id, key: key, ordinal: ordinal, + length: length, greedy: greedy, }, previous: previous, } } -func newIndirectField(id int, key string) indirectField { +func newIndirectField(id int, key string, length int) indirectField { return indirectField{ baseField{ - id: id, - key: key, + id: id, + key: key, + length: length, }, } } -func newNormalField(id int, key string, ordinal int, greedy bool) normalField { +func newNormalField(id int, key string, ordinal int, length int, greedy bool) normalField { return normalField{ baseField{ id: id, key: key, ordinal: ordinal, + length: length, greedy: greedy, }, } } -func extractKeyParts(rawKey string) (key string, ordinal int, greedy bool) { +func extractKeyParts(rawKey string) (key string, ordinal int, length int, greedy bool) { m := suffixRE.FindAllStringSubmatch(rawKey, -1) if m[0][3] != "" { ordinal, _ = strconv.Atoi(m[0][3]) } - if strings.EqualFold(greedySuffix, m[0][4]) { + if m[0][5] != "" { + length, _ = strconv.Atoi(m[0][5]) + } + + if strings.EqualFold(greedySuffix, m[0][6]) { greedy = true } - return m[0][1], ordinal, greedy + + return m[0][1], ordinal, length, greedy } diff --git a/libbeat/processors/dissect/parser.go b/libbeat/processors/dissect/parser.go index 35c4c48028c..73f42917f7f 100644 --- a/libbeat/processors/dissect/parser.go +++ b/libbeat/processors/dissect/parser.go @@ -26,6 +26,7 @@ import ( type parser struct { delimiters []delimiter fields []field + fieldsIdMap map[int]int referenceFields []field } @@ -81,6 +82,10 @@ func newParser(tokenizer string) (*parser, error) { sort.Slice(fields, func(i, j int) bool { return fields[i].Ordinal() < fields[j].Ordinal() }) + fieldsIdMap := make(map[int]int) + for i, f := range fields { + fieldsIdMap[f.ID()] = i + } // List of fields needed for indirection but don't need to appear in the final event. var referenceFields []field @@ -93,6 +98,7 @@ func newParser(tokenizer string) (*parser, error) { return &parser{ delimiters: delimiters, fields: fields, + fieldsIdMap: fieldsIdMap, referenceFields: referenceFields, }, nil } diff --git a/libbeat/processors/dissect/testdata/dissect_tests.json b/libbeat/processors/dissect/testdata/dissect_tests.json index 35b7ad61a33..6c2b642f969 100644 --- a/libbeat/processors/dissect/testdata/dissect_tests.json +++ b/libbeat/processors/dissect/testdata/dissect_tests.json @@ -230,5 +230,63 @@ }, "skip": false, "fail": false + }, + { + "name": "simple fixed length", + "tok": "%{class#1}%{month#2}%{day#2}", + "msg": "A0118", + "expected": { + "class": "A", + "month": "01", + "day": "18" + }, + "skip": false, + "fail": false + }, + { + "name": "simple ordered and fixed length field", + "tok": "%{+key/3#1}%{+key/1#1} %{+key/2}", + "msg": "12 3", + "expected": { + "key": "2 3 1" + }, + "skip": false, + "fail": false + }, + { + "name": "simple padding and fixed length field", + "tok": "%{+key/3#1}%{+key/1#1->} %{+key/2}", + "msg": "12 3", + "expected": { + "key": "2 3 1" + }, + "skip": false, + "fail": false + }, + { + "name": "mixed pointer and indirect and fixed length", + "tok": "%{*key#5}%{\u0026key#5}", + "msg": "helloworld", + "expected": { + "hello": "world" + }, + "skip": false, + "fail": false + }, + { + "name": "fails when there is remaining string after the fixed-length key", + "tok": "%{class#1}%{month#2}%{day#2}", + "msg": "A0118 ", + "expected": null, + "skip": false, + "fail": true + }, + { + "name": "fails when there is no enough string for the fixed-length key", + "tok": "%{key#10}", + "msg": "foobar", + "expected": null, + "skip": false, + "fail": true } -] +] \ No newline at end of file diff --git a/libbeat/processors/dissect/validate_test.go b/libbeat/processors/dissect/validate_test.go index 8d575a655f6..dd19b688355 100644 --- a/libbeat/processors/dissect/validate_test.go +++ b/libbeat/processors/dissect/validate_test.go @@ -32,16 +32,16 @@ func TestValidate(t *testing.T) { { name: "when we find reference field for all indirect field", p: &parser{ - fields: []field{newIndirectField(1, "hello"), newNormalField(0, "hola", 1, false)}, - referenceFields: []field{newPointerField(2, "hello")}, + fields: []field{newIndirectField(1, "hello", 0), newNormalField(0, "hola", 1, 0, false)}, + referenceFields: []field{newPointerField(2, "hello", 0)}, }, expectError: false, }, { name: "when we cannot find all the reference field for all indirect field", p: &parser{ - fields: []field{newIndirectField(1, "hello"), newNormalField(0, "hola", 1, false)}, - referenceFields: []field{newPointerField(2, "okhello")}, + fields: []field{newIndirectField(1, "hello", 0), newNormalField(0, "hola", 1, 0, false)}, + referenceFields: []field{newPointerField(2, "okhello", 0)}, }, expectError: true, }, From 2c89cb0f9a0663371bf09840f4fee2e5864b6f5b Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Thu, 30 Apr 2020 11:09:42 +0100 Subject: [PATCH 065/116] ci: support GitHub PR approval to run in the CI (#18073) --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index f55ce7c46f8..5ac85c2d6e7 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -47,7 +47,7 @@ pipeline { options { skipDefaultCheckout() } steps { deleteDir() - gitCheckout(basedir: "${BASE_DIR}") + gitCheckout(basedir: "${BASE_DIR}", githubNotifyFirstTimeContributor: true) stash allowEmpty: true, name: 'source', useDefaultExcludes: false dir("${BASE_DIR}"){ loadConfigEnvVars() From 8d978f6239ada71c8ccdab26398397e170c65d77 Mon Sep 17 00:00:00 2001 From: Mariana Dima Date: Thu, 30 Apr 2020 12:38:39 +0200 Subject: [PATCH 066/116] Add dashboard to the azure database account metricset (#17901) * dashboard * add image * update changelog * update dashboard --- CHANGELOG.next.asciidoc | 1 + ...icbeat-azure-database-account-overview.png | Bin 0 -> 242548 bytes ...cbeat-azure-database-account-overview.json | 974 ++++++++++++++++++ .../azure/database_account/_meta/data.json | 4 +- .../database_account/_meta/docs.asciidoc | 4 +- .../azure/database_account/manifest.yml | 48 +- 6 files changed, 1020 insertions(+), 11 deletions(-) create mode 100644 metricbeat/docs/images/metricbeat-azure-database-account-overview.png create mode 100644 x-pack/metricbeat/module/azure/_meta/kibana/7/dashboard/Metricbeat-azure-database-account-overview.json diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 551562ebc93..de7ff112396 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -356,6 +356,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Reference kubernetes manifests mount data directory from the host when running metricbeat as daemonset, so data persist between executions in the same node. {pull}17429[17429] - Add more detailed error messages, system tests and small refactoring to the service metricset in windows. {pull}17725[17725] - Stack Monitoring modules now auto-configure required metricsets when `xpack.enabled: true` is set. {issue}16471[[16471] {pull}17609[17609] +- Add dashboard for the azure database account metricset. {pull}17901[17901] - Allow partial region and zone name in googlecloud module config. {pull}17913[17913] - Add aggregation aligner as a config parameter for googlecloud stackdriver metricset. {issue}17141[[17141] {pull}17719[17719] - Move the perfmon metricset to GA. {issue}16608[16608] {pull}17879[17879] diff --git a/metricbeat/docs/images/metricbeat-azure-database-account-overview.png b/metricbeat/docs/images/metricbeat-azure-database-account-overview.png new file mode 100644 index 0000000000000000000000000000000000000000..040f4d9d8c20f4c7112afe75e6f153b09d68083d GIT binary patch literal 242548 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

r`%HLt813%`-oSNT7!K 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 literal 0 HcmV?d00001 diff --git a/x-pack/metricbeat/module/azure/_meta/kibana/7/dashboard/Metricbeat-azure-database-account-overview.json b/x-pack/metricbeat/module/azure/_meta/kibana/7/dashboard/Metricbeat-azure-database-account-overview.json new file mode 100644 index 00000000000..32baee889f8 --- /dev/null +++ b/x-pack/metricbeat/module/azure/_meta/kibana/7/dashboard/Metricbeat-azure-database-account-overview.json @@ -0,0 +1,974 @@ +{ + "objects": [ + { + "attributes": { + "description": "This Azure Database Account dashboard visualizes the most important database account metrics.", + "hits": 0, + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "optionsJSON": { + "hidePanelTitles": false, + "useMargins": true + }, + "panelsJSON": [ + { + "embeddableConfig": { + "title": "" + }, + "gridData": { + "h": 4, + "i": "fe2125b1-526c-4293-b488-86d8c15ea3fb", + "w": 9, + "x": 0, + "y": 0 + }, + "panelIndex": "fe2125b1-526c-4293-b488-86d8c15ea3fb", + "panelRefName": "panel_0", + "version": "7.6.0" + }, + { + "embeddableConfig": { + "title": "Data Usage" + }, + "gridData": { + "h": 18, + "i": "db895503-5a84-4b26-b889-c3c7ca964643", + "w": 18, + "x": 9, + "y": 0 + }, + "panelIndex": "db895503-5a84-4b26-b889-c3c7ca964643", + "panelRefName": "panel_1", + "title": "Data Usage", + "version": "7.6.0" + }, + { + "embeddableConfig": { + "title": "Index Usage" + }, + "gridData": { + "h": 18, + "i": "0b986285-a5d0-4a31-bdb8-0f29cd50c2eb", + "w": 21, + "x": 27, + "y": 0 + }, + "panelIndex": "0b986285-a5d0-4a31-bdb8-0f29cd50c2eb", + "panelRefName": "panel_2", + "title": "Index Usage", + "version": "7.6.0" + }, + { + "embeddableConfig": { + "title": "" + }, + "gridData": { + "h": 14, + "i": "7d7b71ea-eb6b-43bf-be61-80235d1d4529", + "w": 9, + "x": 0, + "y": 4 + }, + "panelIndex": "7d7b71ea-eb6b-43bf-be61-80235d1d4529", + "panelRefName": "panel_3", + "version": "7.6.0" + }, + { + "embeddableConfig": { + "title": "Total Requests" + }, + "gridData": { + "h": 15, + "i": "f6ce8b48-a876-4031-a43f-3ca3704a4ad6", + "w": 24, + "x": 0, + "y": 18 + }, + "panelIndex": "f6ce8b48-a876-4031-a43f-3ca3704a4ad6", + "panelRefName": "panel_4", + "title": "Total Requests", + "version": "7.6.0" + }, + { + "embeddableConfig": { + "title": "Document Count" + }, + "gridData": { + "h": 15, + "i": "c604014a-647f-4a1d-b2e9-0304fdacc363", + "w": 24, + "x": 24, + "y": 18 + }, + "panelIndex": "c604014a-647f-4a1d-b2e9-0304fdacc363", + "panelRefName": "panel_5", + "title": "Document Count", + "version": "7.6.0" + }, + { + "embeddableConfig": { + "title": "Available Storage" + }, + "gridData": { + "h": 15, + "i": "b0cbeaf7-8a12-4efa-b63b-5479c2cb39a9", + "w": 24, + "x": 0, + "y": 33 + }, + "panelIndex": "b0cbeaf7-8a12-4efa-b63b-5479c2cb39a9", + "panelRefName": "panel_6", + "title": "Available Storage", + "version": "7.6.0" + }, + { + "embeddableConfig": { + "title": "Requests Per Status Code" + }, + "gridData": { + "h": 15, + "i": "5e755432-3e55-405c-91cf-3348d5067a3a", + "w": 24, + "x": 24, + "y": 33 + }, + "panelIndex": "5e755432-3e55-405c-91cf-3348d5067a3a", + "panelRefName": "panel_7", + "title": "Requests Per Status Code", + "version": "7.6.0" + } + ], + "timeRestore": false, + "title": "[Metricbeat Azure] Database Account Overview", + "version": 1 + }, + "id": "b232c220-8481-11ea-b181-4b1a9e0110f9", + "migrationVersion": { + "dashboard": "7.3.0" + }, + "references": [ + { + "id": "4177aab0-83cc-11ea-be84-f5d4d6b9a792", + "name": "panel_0", + "type": "visualization" + }, + { + "id": "a49b4e20-8490-11ea-b181-4b1a9e0110f9", + "name": "panel_1", + "type": "visualization" + }, + { + "id": "d2801d70-8490-11ea-b181-4b1a9e0110f9", + "name": "panel_2", + "type": "visualization" + }, + { + "id": "674c1d70-83cc-11ea-be84-f5d4d6b9a792", + "name": "panel_3", + "type": "visualization" + }, + { + "id": "a16b5900-8492-11ea-b181-4b1a9e0110f9", + "name": "panel_4", + "type": "visualization" + }, + { + "id": "d3ac7d90-8492-11ea-b181-4b1a9e0110f9", + "name": "panel_5", + "type": "visualization" + }, + { + "id": "81f16b40-32ea-11ea-a83e-25b8612d00cc", + "name": "panel_6", + "type": "visualization" + }, + { + "id": "037382e0-856e-11ea-91bc-ab084c7ec0e7", + "name": "panel_7", + "type": "visualization" + } + ], + "type": "dashboard", + "updated_at": "2020-04-23T14:26:12.702Z", + "version": "WzczNTAsMTNd" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": {} + }, + "title": "Navigation Database Account Overview [Metricbeat Azure]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "fontSize": 12, + "markdown": "### Azure Database Accounts\n", + "openLinksInNewTab": false + }, + "title": "Navigation Database Account Overview [Metricbeat Azure]", + "type": "markdown" + } + }, + "id": "4177aab0-83cc-11ea-be84-f5d4d6b9a792", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-21T12:33:08.312Z", + "version": "WzY4OTcsMTFd" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Database Account Data Usage [Metricbeat Azure]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "filter": { + "language": "kuery", + "query": "azure.resource.type : \"Microsoft.DocumentDb/databaseAccounts\" " + }, + "id": "e9a40230-32e9-11ea-bda2-69435df36a5c", + "index_pattern": "metricbeat-*", + "interval": "\u003e=5m", + "isModelInvalid": false, + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "#3185FC", + "fill": "0", + "filter": { + "language": "kuery", + "query": "" + }, + "formatter": "bytes", + "id": "e9a40231-32e9-11ea-bda2-69435df36a5c", + "label": "Data Usage", + "line_width": "2", + "metrics": [ + { + "field": "azure.database_account.data_usage.total", + "id": "e9a40232-32e9-11ea-bda2-69435df36a5c", + "type": "avg" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "rainbow", + "split_mode": "terms", + "stacked": "none", + "terms_exclude": "\"\u003cempty\u003e\"", + "terms_field": "azure.dimensions.database_name", + "terms_order_by": "_count", + "type": "timeseries", + "value_template": "{{value}}" + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "type": "timeseries" + }, + "title": "Database Account Data Usage [Metricbeat Azure]", + "type": "metrics" + } + }, + "id": "a49b4e20-8490-11ea-b181-4b1a9e0110f9", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-23T11:58:07.195Z", + "version": "WzcxODEsMTNd" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Database Account Index Usage [Metricbeat Azure]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "filter": { + "language": "kuery", + "query": "azure.resource.type : \"Microsoft.DocumentDb/databaseAccounts\" " + }, + "id": "e9a40230-32e9-11ea-bda2-69435df36a5c", + "index_pattern": "metricbeat-*", + "interval": "\u003e=5m", + "isModelInvalid": false, + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(176,188,0,1)", + "fill": "0", + "filter": { + "language": "kuery", + "query": "" + }, + "formatter": "bytes", + "id": "e9a40231-32e9-11ea-bda2-69435df36a5c", + "label": "Index Usage", + "line_width": "2", + "metrics": [ + { + "field": "azure.database_account.index_usage.total", + "id": "e9a40232-32e9-11ea-bda2-69435df36a5c", + "type": "avg" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "rainbow", + "split_mode": "terms", + "stacked": "none", + "terms_exclude": "\"\u003cempty\u003e\"", + "terms_field": "azure.dimensions.database_name", + "terms_order_by": "_count", + "type": "timeseries", + "value_template": "{{value}}" + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "type": "timeseries" + }, + "title": "Database Account Index Usage [Metricbeat Azure]", + "type": "metrics" + } + }, + "id": "d2801d70-8490-11ea-b181-4b1a9e0110f9", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-23T11:59:31.862Z", + "version": "WzcyMDEsMTNd" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": {} + }, + "title": "Database Account Filters [Metricbeat Azure]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "controls": [ + { + "fieldName": "azure.subscription_id", + "id": "1584710440054", + "indexPatternRefName": "control_0_index_pattern", + "label": "Subscription", + "options": { + "dynamicOptions": true, + "multiselect": false, + "order": "desc", + "size": 5, + "type": "terms" + }, + "parent": "", + "type": "list" + }, + { + "fieldName": "azure.resource.group", + "id": "1584710497045", + "indexPatternRefName": "control_1_index_pattern", + "label": "Resource Group", + "options": { + "dynamicOptions": true, + "multiselect": false, + "order": "desc", + "size": 5, + "type": "terms" + }, + "parent": "", + "type": "list" + }, + { + "fieldName": "cloud.instance.name", + "id": "1584710535722", + "indexPatternRefName": "control_2_index_pattern", + "label": "Resource", + "options": { + "dynamicOptions": true, + "multiselect": false, + "order": "desc", + "size": 5, + "type": "terms" + }, + "parent": "", + "type": "list" + }, + { + "fieldName": "azure.dimensions.database_name", + "id": "1587643606086", + "indexPatternRefName": "control_3_index_pattern", + "label": "Database", + "options": { + "dynamicOptions": true, + "multiselect": true, + "order": "desc", + "size": 5, + "type": "terms" + }, + "parent": "1584710535722", + "type": "list" + } + ], + "pinFilters": false, + "updateFiltersOnChange": true, + "useTimeFilter": false + }, + "title": "Database Account Filters [Metricbeat Azure]", + "type": "input_control_vis" + } + }, + "id": "674c1d70-83cc-11ea-be84-f5d4d6b9a792", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [ + { + "id": "metricbeat-*", + "name": "control_0_index_pattern", + "type": "index-pattern" + }, + { + "id": "metricbeat-*", + "name": "control_1_index_pattern", + "type": "index-pattern" + }, + { + "id": "metricbeat-*", + "name": "control_2_index_pattern", + "type": "index-pattern" + }, + { + "id": "metricbeat-*", + "name": "control_3_index_pattern", + "type": "index-pattern" + } + ], + "type": "visualization", + "updated_at": "2020-04-23T12:08:07.587Z", + "version": "WzcyNDAsMTNd" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Database Account Total Requests [Metricbeat Azure]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "filter": { + "language": "kuery", + "query": "azure.resource.type : \"Microsoft.DocumentDb/databaseAccounts\" " + }, + "id": "e9a40230-32e9-11ea-bda2-69435df36a5c", + "index_pattern": "metricbeat-*", + "interval": "\u003e=5m", + "isModelInvalid": false, + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(211,49,21,1)", + "fill": "0", + "filter": { + "language": "kuery", + "query": "" + }, + "formatter": "number", + "id": "e9a40231-32e9-11ea-bda2-69435df36a5c", + "label": "Total Requests", + "line_width": "2", + "metrics": [ + { + "field": "azure.database_account.total_requests.count", + "id": "e9a40232-32e9-11ea-bda2-69435df36a5c", + "type": "avg" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "rainbow", + "split_mode": "terms", + "stacked": "none", + "terms_exclude": "\"\u003cempty\u003e\"", + "terms_field": "azure.dimensions.database_name", + "terms_order_by": "_count", + "type": "timeseries", + "value_template": "{{value}}" + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "type": "timeseries" + }, + "title": "Database Account Total Requests [Metricbeat Azure]", + "type": "metrics" + } + }, + "id": "a16b5900-8492-11ea-b181-4b1a9e0110f9", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-23T12:00:24.232Z", + "version": "WzcyMTEsMTNd" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Database Account Document Count [Metricbeat Azure]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "filter": { + "language": "kuery", + "query": "azure.resource.type : \"Microsoft.DocumentDb/databaseAccounts\" " + }, + "id": "e9a40230-32e9-11ea-bda2-69435df36a5c", + "index_pattern": "metricbeat-*", + "interval": "\u003e=5m", + "isModelInvalid": false, + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(102,102,102,1)", + "fill": "0", + "filter": { + "language": "kuery", + "query": "" + }, + "formatter": "number", + "id": "e9a40231-32e9-11ea-bda2-69435df36a5c", + "label": "Document Count", + "line_width": "2", + "metrics": [ + { + "field": "azure.database_account.document_count.total", + "id": "e9a40232-32e9-11ea-bda2-69435df36a5c", + "type": "avg" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "rainbow", + "split_mode": "terms", + "stacked": "none", + "terms_exclude": "\"\u003cempty\u003e\"", + "terms_field": "azure.dimensions.database_name", + "terms_order_by": "_count", + "type": "timeseries", + "value_template": "{{value}} " + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "type": "timeseries" + }, + "title": "Database Account Document Count [Metricbeat Azure]", + "type": "metrics" + } + }, + "id": "d3ac7d90-8492-11ea-b181-4b1a9e0110f9", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-23T12:05:03.644Z", + "version": "WzcyMjEsMTNd" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Database Account Available Storage [Metricbeat Azure]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "filter": { + "language": "kuery", + "query": "azure.resource.type : \"Microsoft.DocumentDb/databaseAccounts\" " + }, + "id": "e9a40230-32e9-11ea-bda2-69435df36a5c", + "index_pattern": "metricbeat-*", + "interval": "\u003e=5m", + "isModelInvalid": false, + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(101,50,148,1)", + "fill": "0", + "formatter": "bytes", + "id": "e9a40231-32e9-11ea-bda2-69435df36a5c", + "label": "Available storage", + "line_width": "2", + "metrics": [ + { + "field": "azure.database_account.available_storage.total", + "id": "e9a40232-32e9-11ea-bda2-69435df36a5c", + "type": "avg" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "rainbow", + "split_mode": "terms", + "stacked": "none", + "terms_exclude": "\"\u003cempty\u003e\"", + "terms_field": "azure.dimensions.database_name", + "terms_order_by": "e9a40232-32e9-11ea-bda2-69435df36a5c", + "type": "timeseries", + "value_template": "{{value}}" + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "type": "timeseries" + }, + "title": "Database Account Available Storage [Metricbeat Azure]", + "type": "metrics" + } + }, + "id": "81f16b40-32ea-11ea-a83e-25b8612d00cc", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-23T12:05:58.477Z", + "version": "WzcyMzMsMTNd" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "indexRefName": "kibanaSavedObjectMeta.searchSourceJSON.index" + } + }, + "title": " Database Account Requests By Status Code [Metricbeat Azure]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [ + { + "enabled": true, + "id": "1", + "params": { + "customLabel": "Total Requests", + "field": "azure.database_account.total_requests.count" + }, + "schema": "metric", + "type": "avg" + }, + { + "enabled": true, + "id": "2", + "params": { + "customLabel": "Status Codes", + "field": "azure.dimensions.status_code", + "missingBucket": false, + "missingBucketLabel": "Missing", + "order": "desc", + "orderBy": "_key", + "otherBucket": false, + "otherBucketLabel": "Other", + "size": 5 + }, + "schema": "group", + "type": "terms" + }, + { + "enabled": true, + "id": "3", + "params": { + "customLabel": "Database", + "field": "azure.dimensions.database_name", + "missingBucket": false, + "missingBucketLabel": "Missing", + "order": "desc", + "orderBy": "_key", + "otherBucket": false, + "otherBucketLabel": "Other", + "row": false, + "size": 5 + }, + "schema": "split", + "type": "terms" + } + ], + "params": { + "addLegend": true, + "addTimeMarker": false, + "addTooltip": false, + "categoryAxes": [ + { + "id": "CategoryAxis-1", + "labels": { + "filter": true, + "show": true, + "truncate": 100 + }, + "position": "bottom", + "scale": { + "type": "linear" + }, + "show": false, + "style": {}, + "title": {}, + "type": "category" + } + ], + "dimensions": { + "series": [ + { + "accessor": 0, + "aggType": "terms", + "format": { + "id": "terms", + "params": { + "id": "string", + "missingBucketLabel": "Missing", + "otherBucketLabel": "Other", + "parsedUrl": { + "basePath": "", + "origin": "http://localhost:5601", + "pathname": "/app/kibana" + } + } + }, + "label": "Status Codes", + "params": {} + } + ], + "splitColumn": [ + { + "accessor": 1, + "aggType": "terms", + "format": { + "id": "terms", + "params": { + "id": "string", + "missingBucketLabel": "Missing", + "otherBucketLabel": "Other", + "parsedUrl": { + "basePath": "", + "origin": "http://localhost:5601", + "pathname": "/app/kibana" + } + } + }, + "label": "Database", + "params": {} + } + ], + "x": null, + "y": [ + { + "accessor": 2, + "aggType": "avg", + "format": { + "id": "number", + "params": { + "parsedUrl": { + "basePath": "", + "origin": "http://localhost:5601", + "pathname": "/app/kibana" + } + } + }, + "label": "Total Requests", + "params": {} + } + ] + }, + "grid": { + "categoryLines": false + }, + "labels": { + "show": false + }, + "legendPosition": "right", + "seriesParams": [ + { + "data": { + "id": "1", + "label": "Total Requests" + }, + "drawLinesBetweenPoints": true, + "lineWidth": 2, + "mode": "stacked", + "show": true, + "showCircles": true, + "type": "histogram", + "valueAxis": "ValueAxis-1" + } + ], + "thresholdLine": { + "color": "#E7664C", + "show": false, + "style": "full", + "value": 10, + "width": 1 + }, + "times": [], + "type": "histogram", + "valueAxes": [ + { + "id": "ValueAxis-1", + "labels": { + "filter": false, + "rotate": 0, + "show": true, + "truncate": 100 + }, + "name": "LeftAxis-1", + "position": "left", + "scale": { + "mode": "normal", + "type": "linear" + }, + "show": true, + "style": {}, + "title": { + "text": "Total Requests" + }, + "type": "value" + } + ] + }, + "title": " Database Account Requests By Status Code [Metricbeat Azure]", + "type": "histogram" + } + }, + "id": "037382e0-856e-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [ + { + "id": "metricbeat-*", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern" + } + ], + "type": "visualization", + "updated_at": "2020-04-23T14:23:33.900Z", + "version": "WzczNDMsMTNd" + } + ], + "version": "7.6.0" +} diff --git a/x-pack/metricbeat/module/azure/database_account/_meta/data.json b/x-pack/metricbeat/module/azure/database_account/_meta/data.json index 6749feeab71..68dbb92043c 100644 --- a/x-pack/metricbeat/module/azure/database_account/_meta/data.json +++ b/x-pack/metricbeat/module/azure/database_account/_meta/data.json @@ -16,10 +16,10 @@ "azure" : { "timegrain" : "PT5M", "dimensions" : { - "databasename" : "testdb" + "database_name" : "testdb" }, "database_account" : { - "provisionedthroughput" : { + "provisioned_throughput" : { "max" : 400 } }, diff --git a/x-pack/metricbeat/module/azure/database_account/_meta/docs.asciidoc b/x-pack/metricbeat/module/azure/database_account/_meta/docs.asciidoc index ef085175dac..182fea6b41b 100644 --- a/x-pack/metricbeat/module/azure/database_account/_meta/docs.asciidoc +++ b/x-pack/metricbeat/module/azure/database_account/_meta/docs.asciidoc @@ -1,6 +1,8 @@ This is the database_account metricset of the module azure. -This metricset allows users to retrieve all metrics from specified database accounts. +This metricset allows users to retrieve relevant metrics from specified database accounts and comes with a predefined dashboard: + +image::./images/metricbeat-azure-database-account-overview.png[] include::../../_meta/shared-azure.asciidoc[] diff --git a/x-pack/metricbeat/module/azure/database_account/manifest.yml b/x-pack/metricbeat/module/azure/database_account/manifest.yml index d714fccb089..39086f6ff66 100644 --- a/x-pack/metricbeat/module/azure/database_account/manifest.yml +++ b/x-pack/metricbeat/module/azure/database_account/manifest.yml @@ -9,39 +9,71 @@ input: resource_type: "Microsoft.DocumentDb/databaseAccounts" metrics: - name: ["AddRegion", "RemoveRegion", "UpdateAccountReplicationSettings", "UpdateAccountNetworkSettings", "UpdateAccountKeys", "ServiceAvailability", "ReplicationLatency", - "RegionFailover", "DeleteAccount", "CreateAccount", "CassandraConnectionClosures"] + "RegionFailover", "DeleteAccount", "CreateAccount", "CassandraConnectionClosures", "UpdateDiagnosticsSettings"] namespace: "Microsoft.DocumentDb/databaseAccounts" - - name: ["AvailableStorage", "DataUsage","DocumentCount", "DocumentQuota", "IndexUsage", "MetadataRequests", - "MongoRequestCharge", "MongoRequests", "MongoRequestsCount", "MongoRequestsDelete","MongoRequestsDelete", "MongoRequestsQuery", "MongoRequestsUpdate", - "ProvisionedThroughput", "TotalRequestUnits", "TotalRequests"] + - name: ["AvailableStorage", "DataUsage","DocumentCount", "DocumentQuota", "IndexUsage", "MetadataRequests", "MongoRequestCharge", "MongoRequests", "MongoRequestsCount", + "MongoRequestsInsert", "MongoRequestsDelete", "MongoRequestsQuery", "MongoRequestsUpdate","ProvisionedThroughput", "NormalizedRUConsumption"] namespace: "Microsoft.DocumentDb/databaseAccounts" timegrain: "PT5M" dimensions: - name: "DatabaseName" value: "*" + - name: ["TotalRequestUnits", "TotalRequests"] + namespace: "Microsoft.DocumentDb/databaseAccounts" + timegrain: "PT5M" + dimensions: + - name: "DatabaseName" + value: "*" + - name: "StatusCode" + value: "*" - name: ["CassandraRequestCharges", "CassandraRequests"] namespace: "Microsoft.DocumentDb/databaseAccounts" timegrain: "PT1M" dimensions: - name: "DatabaseName" value: "*" + - name: [ "GremlinDatabaseDelete", "GremlinDatabaseThroughputUpdate", "GremlinDatabaseUpdate", "GremlinGraphDelete","GremlinGraphThroughputUpdate", "GremlinGraphUpdate", + "MongoCollectionDelete", "MongoCollectionThroughputUpdate", "MongoCollectionUpdate", "MongoDBDatabaseUpdate", "MongoDatabaseDelete", "MongoDatabaseThroughputUpdate", + "CassandraKeyspaceDelete", "CassandraKeyspaceThroughputUpdate", "CassandraKeyspaceUpdate","CassandraTableDelete", "CassandraTableThroughputUpdate", "CassandraTableUpdate", + "SqlContainerDelete", "SqlContainerThroughputUpdate", "SqlContainerUpdate", "SqlDatabaseDelete", "SqlDatabaseThroughputUpdate", "SqlDatabaseUpdate", "TableTableDelete", + "TableTableThroughputUpdate","TableTableUpdate"] + namespace: "Microsoft.DocumentDb/databaseAccounts" + dimensions: + - name: "ResourceName" + value: "*" - resource_id: "" metrics: - name: ["AddRegion", "RemoveRegion", "UpdateAccountReplicationSettings", "UpdateAccountNetworkSettings", "UpdateAccountKeys", "ServiceAvailability", "ReplicationLatency", - "RegionFailover", "DeleteAccount", "CreateAccount", "CassandraConnectionClosures"] + "RegionFailover", "DeleteAccount", "CreateAccount", "CassandraConnectionClosures", "UpdateDiagnosticsSettings"] namespace: "Microsoft.DocumentDb/databaseAccounts" - - name: ["AvailableStorage", "DataUsage","DocumentCount", "DocumentQuota", "IndexUsage", "MetadataRequests", - "MongoRequestCharge", "MongoRequests", "MongoRequestsCount", "MongoRequestsDelete","MongoRequestsDelete", "MongoRequestsQuery", "MongoRequestsUpdate", - "ProvisionedThroughput", "TotalRequestUnits", "TotalRequests"] + - name: ["AvailableStorage", "DataUsage","DocumentCount", "DocumentQuota", "IndexUsage", "MetadataRequests", "MongoRequestCharge", "MongoRequests", "MongoRequestsCount", + "MongoRequestsInsert", "MongoRequestsDelete", "MongoRequestsQuery", "MongoRequestsUpdate","ProvisionedThroughput", "NormalizedRUConsumption"] namespace: "Microsoft.DocumentDb/databaseAccounts" timegrain: "PT5M" dimensions: - name: "DatabaseName" value: "*" + - name: ["TotalRequestUnits", "TotalRequests"] + namespace: "Microsoft.DocumentDb/databaseAccounts" + timegrain: "PT5M" + dimensions: + - name: "DatabaseName" + value: "*" + - name: "StatusCode" + value: "*" - name: ["CassandraRequestCharges", "CassandraRequests"] namespace: "Microsoft.DocumentDb/databaseAccounts" timegrain: "PT1M" dimensions: - name: "DatabaseName" value: "*" + - name: [ "GremlinDatabaseDelete", "GremlinDatabaseThroughputUpdate", "GremlinDatabaseUpdate", "GremlinGraphDelete","GremlinGraphThroughputUpdate", "GremlinGraphUpdate", + "MongoCollectionDelete", "MongoCollectionThroughputUpdate", "MongoCollectionUpdate", "MongoDBDatabaseUpdate", "MongoDatabaseDelete", "MongoDatabaseThroughputUpdate", + "CassandraKeyspaceDelete", "CassandraKeyspaceThroughputUpdate", "CassandraKeyspaceUpdate","CassandraTableDelete", "CassandraTableThroughputUpdate", "CassandraTableUpdate", + "SqlContainerDelete", "SqlContainerThroughputUpdate", "SqlContainerUpdate", "SqlDatabaseDelete", "SqlDatabaseThroughputUpdate", "SqlDatabaseUpdate", "TableTableDelete", + "TableTableThroughputUpdate","TableTableUpdate"] + namespace: "Microsoft.DocumentDb/databaseAccounts" + dimensions: + - name: "ResourceName" + value: "*" From e9e587b0bdbcdca947a386bc454055f78863450f Mon Sep 17 00:00:00 2001 From: Michal Pristas Date: Thu, 30 Apr 2020 15:13:09 +0200 Subject: [PATCH 067/116] [Elastic-Agent] Use /tmp for default monitoring endpoint location for libbeat (#18131) * default tmp socket * changelog * Update x-pack/elastic-agent/CHANGELOG.asciidoc Co-authored-by: Pier-Hugues Pellerin Co-authored-by: Pier-Hugues Pellerin --- x-pack/elastic-agent/CHANGELOG.asciidoc | 1 + .../pkg/core/plugin/app/monitoring/beats/monitoring.go | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/x-pack/elastic-agent/CHANGELOG.asciidoc b/x-pack/elastic-agent/CHANGELOG.asciidoc index bb57c61f03e..124695218bf 100644 --- a/x-pack/elastic-agent/CHANGELOG.asciidoc +++ b/x-pack/elastic-agent/CHANGELOG.asciidoc @@ -27,6 +27,7 @@ - Moved stream.* fields to top of event {pull}17858[17858] - Fix an issue where the checkin_frequency, jitter, and backoff options where not configurable. {pull}17843[17843] - Use default output by default {pull}18091[18091] +- Use /tmp for default monitoring endpoint location for libbeat {pull}18131[18131] ==== New features diff --git a/x-pack/elastic-agent/pkg/core/plugin/app/monitoring/beats/monitoring.go b/x-pack/elastic-agent/pkg/core/plugin/app/monitoring/beats/monitoring.go index 7e6b820611c..265ea4cda82 100644 --- a/x-pack/elastic-agent/pkg/core/plugin/app/monitoring/beats/monitoring.go +++ b/x-pack/elastic-agent/pkg/core/plugin/app/monitoring/beats/monitoring.go @@ -17,7 +17,7 @@ const ( logFileFormatWin = "%s\\logs\\%s\\%s" // args: pipeline name, application name - mbEndpointFileFormat = "unix://%s/run/%s/%s/%s.sock" + mbEndpointFileFormat = "unix:///tmp/elastic-agent/%s/%s/%s.sock" // args: pipeline name, application name mbEndpointFileFormatWin = `npipe:///%s-%s` ) @@ -27,7 +27,7 @@ func getMonitoringEndpoint(program, operatingSystem, pipelineID string) string { return fmt.Sprintf(mbEndpointFileFormatWin, pipelineID, program) } - return fmt.Sprintf(mbEndpointFileFormat, paths.Data(), pipelineID, program, program) + return fmt.Sprintf(mbEndpointFileFormat, pipelineID, program, program) } func getLoggingFile(program, operatingSystem, installPath, pipelineID string) string { From bee8ef3ecf2730d7e56a1735f84871708dc278fc Mon Sep 17 00:00:00 2001 From: Chris Mark Date: Thu, 30 Apr 2020 16:23:20 +0300 Subject: [PATCH 068/116] Fix autodiscover docs (#18097) --- libbeat/docs/shared-autodiscover.asciidoc | 6 ------ 1 file changed, 6 deletions(-) diff --git a/libbeat/docs/shared-autodiscover.asciidoc b/libbeat/docs/shared-autodiscover.asciidoc index 22a72a9b52e..5279b6f3ea0 100644 --- a/libbeat/docs/shared-autodiscover.asciidoc +++ b/libbeat/docs/shared-autodiscover.asciidoc @@ -18,7 +18,6 @@ to set conditions that, when met, launch specific configurations. On start, {beatname_uc} will scan existing containers and launch the proper configs for them. Then it will watch for new start/stop events. This ensures you don't need to worry about state, but only define your desired configs. -ifdef::autodiscoverDocker[] [float] ===== Docker @@ -125,10 +124,7 @@ running configuration for a container, 60s by default. ======================================= endif::[] -endif::autodiscoverDocker[] - -ifdef::autodiscoverKubernetes[] [float] ===== Kubernetes @@ -246,9 +242,7 @@ running configuration for a container, 60s by default. include_labels: ["nodelabel2"] ------------------------------------------------------------------------------------- - include::../../{beatname_lc}/docs/autodiscover-kubernetes-config.asciidoc[] -endif::autodiscoverKubernetes[] [float] ===== Manually Defining Ports with Kubernetes From cce180906e2e327a8126fdd536c5b69e32248d8c Mon Sep 17 00:00:00 2001 From: Michal Pristas Date: Thu, 30 Apr 2020 16:37:23 +0200 Subject: [PATCH 069/116] [Elastic-Agent] ECS compliant Elastic agent metadata sent to fleet (#18006) * use meta object * changelog * agent.* => elastic.agent.* --- x-pack/elastic-agent/CHANGELOG.asciidoc | 1 + .../application/filters/constraints_filter.go | 2 +- .../pkg/agent/application/fleet_gateway.go | 8 +- .../agent/application/info/agent_metadata.go | 99 +++++++++- .../pkg/agent/application/local_meta.go | 2 +- .../elastic-agent/pkg/fleetapi/checkin_cmd.go | 5 +- .../pkg/fleetapi/checkin_cmd_test.go | 176 +++++++++--------- .../elastic-agent/pkg/fleetapi/enroll_cmd.go | 3 +- .../pkg/fleetapi/enroll_cmd_test.go | 23 ++- 9 files changed, 202 insertions(+), 117 deletions(-) diff --git a/x-pack/elastic-agent/CHANGELOG.asciidoc b/x-pack/elastic-agent/CHANGELOG.asciidoc index 124695218bf..8b635c85ae4 100644 --- a/x-pack/elastic-agent/CHANGELOG.asciidoc +++ b/x-pack/elastic-agent/CHANGELOG.asciidoc @@ -26,6 +26,7 @@ - Make sure that the Elastic Agent connect over TLS in cloud. {pull}17843[17843] - Moved stream.* fields to top of event {pull}17858[17858] - Fix an issue where the checkin_frequency, jitter, and backoff options where not configurable. {pull}17843[17843] +- ECS compliant Elastic agent metadata sent to fleet {pull}18006[18006] - Use default output by default {pull}18091[18091] - Use /tmp for default monitoring endpoint location for libbeat {pull}18131[18131] diff --git a/x-pack/elastic-agent/pkg/agent/application/filters/constraints_filter.go b/x-pack/elastic-agent/pkg/agent/application/filters/constraints_filter.go index 2cb92cffd97..9159199e794 100644 --- a/x-pack/elastic-agent/pkg/agent/application/filters/constraints_filter.go +++ b/x-pack/elastic-agent/pkg/agent/application/filters/constraints_filter.go @@ -229,7 +229,7 @@ func initVarStore(store *constraintVarStore) error { return err } - meta, err := agentInfo.ECSMetadata() + meta, err := agentInfo.ECSMetadataFlatMap() if err != nil { return errors.New(err, "failed to gather host metadata") } diff --git a/x-pack/elastic-agent/pkg/agent/application/fleet_gateway.go b/x-pack/elastic-agent/pkg/agent/application/fleet_gateway.go index d21e2392f5b..51d103e4d65 100644 --- a/x-pack/elastic-agent/pkg/agent/application/fleet_gateway.go +++ b/x-pack/elastic-agent/pkg/agent/application/fleet_gateway.go @@ -196,10 +196,8 @@ func (f *fleetGateway) execute(ctx context.Context) (*fleetapi.CheckinResponse, // get events ee, ack := f.reporter.Events() - var metaData map[string]interface{} - if m, err := metadata(); err == nil { - metaData = m - } else { + ecsMeta, err := metadata() + if err != nil { f.log.Error(errors.New("failed to load metadata", err)) } @@ -207,7 +205,7 @@ func (f *fleetGateway) execute(ctx context.Context) (*fleetapi.CheckinResponse, cmd := fleetapi.NewCheckinCmd(f.agentInfo, f.client) req := &fleetapi.CheckinRequest{ Events: ee, - Metadata: metaData, + Metadata: ecsMeta, } resp, err := cmd.Execute(ctx, req) diff --git a/x-pack/elastic-agent/pkg/agent/application/info/agent_metadata.go b/x-pack/elastic-agent/pkg/agent/application/info/agent_metadata.go index 79371e76600..a061f6f4bef 100644 --- a/x-pack/elastic-agent/pkg/agent/application/info/agent_metadata.go +++ b/x-pack/elastic-agent/pkg/agent/application/info/agent_metadata.go @@ -15,6 +15,57 @@ import ( "github.com/elastic/go-sysinfo/types" ) +// ECSMeta is a collection of agent related metadata in ECS compliant object form. +type ECSMeta struct { + Agent *AgentECSMeta `json:"elastic.agent"` + Host *HostECSMeta `json:"host"` + OS *SystemECSMeta `json:"os"` +} + +// AgentECSMeta is a collection of agent metadata in ECS compliant object form. +type AgentECSMeta struct { + // ID is a generated (in standalone) or assigned (in fleet) agent identifier. + ID string `json:"id"` + // Version specifies current version of an agent. + Version string `json:"version"` +} + +// SystemECSMeta is a collection of operating system metadata in ECS compliant object form. +type SystemECSMeta struct { + // Family defines a family of underlying operating system (e.g. redhat, debian, freebsd, windows). + Family string `json:"family"` + // Kernel specifies current version of a kernel in a semver format. + Kernel string `json:"kernel"` + // Platform specifies platform agent is running on (e.g. centos, ubuntu, windows). + Platform string `json:"platform"` + // Version specifies version of underlying operating system (e.g. 10.12.6). + Version string `json:"version"` + // Name is a operating system name. + // Currently we just normalize the name (i.e. macOS, Windows, Linux). See https://www.elastic.co/guide/en/ecs/current/ecs-html + Name string `json:"name"` + // Full is an operating system name, including the version or code name. + FullName string `json:"full"` +} + +// HostECSMeta is a collection of host metadata in ECS compliant object form. +type HostECSMeta struct { + // Arch defines architecture of a host (e.g. x86_64, arm, ppc, mips). + Arch string `json:"architecture"` + // Hostname specifies hostname of the host. + Hostname string `json:"hostname"` + // Name specifies hostname of the host. + Name string `json:"name"` + // ID is a Unique host id. + // As hostname is not always unique, use values that are meaningful in your environment. + ID string `json:"id"` + // IP is Host ip addresses. + // Note: this field should contain an array of values. + IP []string `json:"ip"` + // Mac is Host mac addresses. + // Note: this field should contain an array of values. + MAC []string `json:"mac"` +} + // List of variables available to be used in constraint definitions. const ( // `agent.id` is a generated (in standalone) or assigned (in fleet) agent identifier. @@ -54,19 +105,55 @@ const ( ) // ECSMetadata returns an agent ECS compliant metadata. -func (i *AgentInfo) ECSMetadata() (map[string]interface{}, error) { +func (i *AgentInfo) ECSMetadata() (*ECSMeta, error) { hostname, err := os.Hostname() if err != nil { return nil, err } - // TODO: remove these values when kibana migrates to ECS - meta := map[string]interface{}{ - "platform": runtime.GOOS, - "version": release.Version(), - "host": hostname, + sysInfo, err := sysinfo.Host() + if err != nil { + return nil, err } + info := sysInfo.Info() + + return &ECSMeta{ + Agent: &AgentECSMeta{ + ID: i.agentID, + Version: release.Version(), + }, + Host: &HostECSMeta{ + Arch: info.Architecture, + Hostname: hostname, + Name: hostname, + ID: info.UniqueID, + IP: info.IPs, + MAC: info.MACs, + }, + + // Operating system + OS: &SystemECSMeta{ + Family: runtime.GOOS, + Kernel: info.KernelVersion, + Platform: info.OS.Family, + Version: info.OS.Version, + Name: info.OS.Name, + FullName: getFullOSName(info), + }, + }, nil +} + +// ECSMetadataFlatMap returns an agent ECS compliant metadata in a form of flattened map. +func (i *AgentInfo) ECSMetadataFlatMap() (map[string]interface{}, error) { + hostname, err := os.Hostname() + if err != nil { + return nil, err + } + + // TODO: remove these values when kibana migrates to ECS + meta := make(map[string]interface{}) + sysInfo, err := sysinfo.Host() if err != nil { return nil, err diff --git a/x-pack/elastic-agent/pkg/agent/application/local_meta.go b/x-pack/elastic-agent/pkg/agent/application/local_meta.go index 3456075baa9..540f74ad924 100644 --- a/x-pack/elastic-agent/pkg/agent/application/local_meta.go +++ b/x-pack/elastic-agent/pkg/agent/application/local_meta.go @@ -9,7 +9,7 @@ import ( "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/errors" ) -func metadata() (map[string]interface{}, error) { +func metadata() (*info.ECSMeta, error) { agentInfo, err := info.NewAgentInfo() if err != nil { return nil, err diff --git a/x-pack/elastic-agent/pkg/fleetapi/checkin_cmd.go b/x-pack/elastic-agent/pkg/fleetapi/checkin_cmd.go index 80ce76d5b55..7f6eb3d91a2 100644 --- a/x-pack/elastic-agent/pkg/fleetapi/checkin_cmd.go +++ b/x-pack/elastic-agent/pkg/fleetapi/checkin_cmd.go @@ -13,6 +13,7 @@ import ( "net/http" "time" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application/info" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/errors" ) @@ -20,8 +21,8 @@ const checkingPath = "/api/ingest_manager/fleet/agents/%s/checkin" // CheckinRequest consists of multiple events reported to fleet ui. type CheckinRequest struct { - Events []SerializableEvent `json:"events"` - Metadata map[string]interface{} `json:"local_metadata,omitempty"` + Events []SerializableEvent `json:"events"` + Metadata *info.ECSMeta `json:"local_metadata,omitempty"` } // SerializableEvent is a representation of the event to be send to the Fleet API via the checkin diff --git a/x-pack/elastic-agent/pkg/fleetapi/checkin_cmd_test.go b/x-pack/elastic-agent/pkg/fleetapi/checkin_cmd_test.go index b9eb9f3aa81..43501c7fac7 100644 --- a/x-pack/elastic-agent/pkg/fleetapi/checkin_cmd_test.go +++ b/x-pack/elastic-agent/pkg/fleetapi/checkin_cmd_test.go @@ -14,6 +14,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application/info" ) type agentinfo struct{} @@ -28,9 +30,9 @@ func TestCheckin(t *testing.T) { t.Run("Propagate any errors from the server", withServerWithAuthClient( func(t *testing.T) *http.ServeMux { raw := ` -Something went wrong -} -` + Something went wrong + } + ` mux := http.NewServeMux() path := fmt.Sprintf("/api/ingest_manager/fleet/agents/%s/checkin", agentInfo.AgentID()) mux.HandleFunc(path, authHandler(func(w http.ResponseWriter, r *http.Request) { @@ -52,35 +54,35 @@ Something went wrong t.Run("Checkin receives a PolicyChange", withServerWithAuthClient( func(t *testing.T) *http.ServeMux { raw := ` -{ - "actions": [{ - "type": "CONFIG_CHANGE", - "id": "id1", - "data": { - "config": { - "id": "policy-id", - "outputs": { - "default": { - "hosts": "https://localhost:9200" - } - }, - "datasources": [{ - "id": "string", - "enabled": true, - "use_output": "default", - "inputs": [{ - "type": "logs", - "streams": [{ - "paths": ["/var/log/hello.log"] + { + "actions": [{ + "type": "CONFIG_CHANGE", + "id": "id1", + "data": { + "config": { + "id": "policy-id", + "outputs": { + "default": { + "hosts": "https://localhost:9200" + } + }, + "datasources": [{ + "id": "string", + "enabled": true, + "use_output": "default", + "inputs": [{ + "type": "logs", + "streams": [{ + "paths": ["/var/log/hello.log"] + }] }] }] - }] + } } - } - }], - "success": true -} -` + }], + "success": true + } + ` mux := http.NewServeMux() path := fmt.Sprintf("/api/ingest_manager/fleet/agents/%s/checkin", agentInfo.AgentID()) mux.HandleFunc(path, authHandler(func(w http.ResponseWriter, r *http.Request) { @@ -109,41 +111,41 @@ Something went wrong t.Run("Checkin receives known and unknown action type", withServerWithAuthClient( func(t *testing.T) *http.ServeMux { raw := ` -{ - "actions": [ - { - "type": "CONFIG_CHANGE", - "id": "id1", - "data": { - "config": { - "id": "policy-id", - "outputs": { - "default": { - "hosts": "https://localhost:9200" - } - }, - "datasources": [{ - "id": "string", - "enabled": true, - "use_output": "default", - "inputs": [{ - "type": "logs", - "streams": [{ - "paths": ["/var/log/hello.log"] + { + "actions": [ + { + "type": "CONFIG_CHANGE", + "id": "id1", + "data": { + "config": { + "id": "policy-id", + "outputs": { + "default": { + "hosts": "https://localhost:9200" + } + }, + "datasources": [{ + "id": "string", + "enabled": true, + "use_output": "default", + "inputs": [{ + "type": "logs", + "streams": [{ + "paths": ["/var/log/hello.log"] + }] }] }] - }] - } - } - }, - { - "type": "WHAT_TO_DO_WITH_IT", - "id": "id2" - } - ], - "success": true -} -` + } + } + }, + { + "type": "WHAT_TO_DO_WITH_IT", + "id": "id2" + } + ], + "success": true + } + ` mux := http.NewServeMux() path := fmt.Sprintf("/api/ingest_manager/fleet/agents/%s/checkin", agentInfo.AgentID()) mux.HandleFunc(path, authHandler(func(w http.ResponseWriter, r *http.Request) { @@ -177,11 +179,11 @@ Something went wrong t.Run("When we receive no action", withServerWithAuthClient( func(t *testing.T) *http.ServeMux { raw := ` -{ - "actions": [], - "success": true -} -` + { + "actions": [], + "success": true + } + ` mux := http.NewServeMux() path := fmt.Sprintf("/api/ingest_manager/fleet/agents/%s/checkin", agentInfo.AgentID()) mux.HandleFunc(path, authHandler(func(w http.ResponseWriter, r *http.Request) { @@ -215,21 +217,15 @@ Something went wrong path := fmt.Sprintf("/api/ingest_manager/fleet/agents/%s/checkin", agentInfo.AgentID()) mux.HandleFunc(path, authHandler(func(w http.ResponseWriter, r *http.Request) { type Request struct { - Metadata map[string]interface{} `json:"local_metadata"` + Metadata *info.ECSMeta `json:"local_metadata"` } - req := &Request{} + + var req *Request content, err := ioutil.ReadAll(r.Body) assert.NoError(t, err) assert.NoError(t, json.Unmarshal(content, &req)) - - assert.Equal(t, 1, len(req.Metadata)) - v, found := req.Metadata["key"] - assert.True(t, found) - - intV, ok := v.(string) - assert.True(t, ok) - assert.Equal(t, "value", intV) + assert.Equal(t, "linux", req.Metadata.OS.Name) w.WriteHeader(http.StatusOK) fmt.Fprintf(w, raw) @@ -237,13 +233,9 @@ Something went wrong return mux }, withAPIKey, func(t *testing.T, client clienter) { - meta := map[string]interface{}{ - "key": "value", - } - cmd := NewCheckinCmd(agentInfo, client) - request := CheckinRequest{Metadata: meta} + request := CheckinRequest{Metadata: testMetadata()} r, err := cmd.Execute(ctx, &request) require.NoError(t, err) @@ -256,22 +248,24 @@ Something went wrong t.Run("No meta are sent when not provided", withServerWithAuthClient( func(t *testing.T) *http.ServeMux { raw := ` -{ - "actions": [], - "success": true -} -` + { + "actions": [], + "success": true + } + ` mux := http.NewServeMux() path := fmt.Sprintf("/api/ingest_manager/fleet/agents/%s/checkin", agentInfo.AgentID()) mux.HandleFunc(path, authHandler(func(w http.ResponseWriter, r *http.Request) { - req := make(map[string]interface{}) + type Request struct { + Metadata *info.ECSMeta `json:"local_metadata"` + } + + var req *Request content, err := ioutil.ReadAll(r.Body) assert.NoError(t, err) assert.NoError(t, json.Unmarshal(content, &req)) - - _, found := req["key"] - assert.False(t, found) + assert.Nil(t, req.Metadata) w.WriteHeader(http.StatusOK) fmt.Fprintf(w, raw) diff --git a/x-pack/elastic-agent/pkg/fleetapi/enroll_cmd.go b/x-pack/elastic-agent/pkg/fleetapi/enroll_cmd.go index c7410baa0e7..0d2784ef741 100644 --- a/x-pack/elastic-agent/pkg/fleetapi/enroll_cmd.go +++ b/x-pack/elastic-agent/pkg/fleetapi/enroll_cmd.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/go-multierror" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application/info" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/errors" ) @@ -84,7 +85,7 @@ type EnrollRequest struct { // Metadata is a all the metadata send or received from the elastic-agent. type Metadata struct { - Local map[string]interface{} `json:"local"` + Local *info.ECSMeta `json:"local"` UserProvided map[string]interface{} `json:"user_provided"` } diff --git a/x-pack/elastic-agent/pkg/fleetapi/enroll_cmd_test.go b/x-pack/elastic-agent/pkg/fleetapi/enroll_cmd_test.go index c4f79c7cb38..df341b0110e 100644 --- a/x-pack/elastic-agent/pkg/fleetapi/enroll_cmd_test.go +++ b/x-pack/elastic-agent/pkg/fleetapi/enroll_cmd_test.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application/info" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/config" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/kibana" ) @@ -38,10 +39,8 @@ func TestEnroll(t *testing.T) { require.Equal(t, PermanentEnroll, req.Type) require.Equal(t, "im-a-beat", req.SharedID) - require.Equal(t, Metadata{ - Local: map[string]interface{}{"os": "linux"}, - UserProvided: make(map[string]interface{}), - }, req.Metadata) + require.Equal(t, make(map[string]interface{}), req.Metadata.UserProvided) + require.Equal(t, "linux", req.Metadata.Local.OS.Name) response := &EnrollResponse{ Action: "created", @@ -77,9 +76,7 @@ func TestEnroll(t *testing.T) { EnrollAPIKey: "my-enrollment-api-key", SharedID: "im-a-beat", Metadata: Metadata{ - Local: map[string]interface{}{ - "os": "linux", - }, + Local: testMetadata(), UserProvided: make(map[string]interface{}), }, } @@ -116,9 +113,7 @@ func TestEnroll(t *testing.T) { EnrollAPIKey: "my-enrollment-api-key", SharedID: "im-a-beat", Metadata: Metadata{ - Local: map[string]interface{}{ - "os": "linux", - }, + Local: testMetadata(), UserProvided: make(map[string]interface{}), }, } @@ -132,3 +127,11 @@ func TestEnroll(t *testing.T) { }, )) } + +func testMetadata() *info.ECSMeta { + return &info.ECSMeta{ + OS: &info.SystemECSMeta{ + Name: "linux", + }, + } +} From 36d136c33f7c3d8eac8b1a059d4fe50ca41ebad3 Mon Sep 17 00:00:00 2001 From: Ivan Fernandez Calvo Date: Thu, 30 Apr 2020 17:59:15 +0200 Subject: [PATCH 070/116] fix: Mark generator stages as unstable temporally (#18133) * fix: Mark generator stages as unstable temporally * docs: comment pointing to the issue --- Jenkinsfile | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 5ac85c2d6e7..a0382877db6 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -547,14 +547,20 @@ pipeline { stages { stage('Generators Metricbeat Linux'){ steps { - makeTarget("Generators Metricbeat Linux", "-C generator/_templates/metricbeat test") - makeTarget("Generators Metricbeat Linux", "-C generator/_templates/metricbeat test-package") + // FIXME see https://github.com/elastic/beats/issues/18132 + catchError(buildResult: 'SUCCESS', message: 'Ignore error temporally', stageResult: 'UNSTABLE') { + makeTarget("Generators Metricbeat Linux", "-C generator/_templates/metricbeat test") + makeTarget("Generators Metricbeat Linux", "-C generator/_templates/metricbeat test-package") + } } } stage('Generators Beat Linux'){ steps { - makeTarget("Generators Beat Linux", "-C generator/_templates/beat test") - makeTarget("Generators Beat Linux", "-C generator/_templates/beat test-package") + // FIXME see https://github.com/elastic/beats/issues/18132 + catchError(buildResult: 'SUCCESS', message: 'Ignore error temporally', stageResult: 'UNSTABLE') { + makeTarget("Generators Beat Linux", "-C generator/_templates/beat test") + makeTarget("Generators Beat Linux", "-C generator/_templates/beat test-package") + } } } stage('Generators Metricbeat Mac OS X'){ @@ -567,7 +573,10 @@ pipeline { } } steps { - makeTarget("Generators Metricbeat Mac OS X", "-C generator/_templates/metricbeat test") + // FIXME see https://github.com/elastic/beats/issues/18132 + catchError(buildResult: 'SUCCESS', message: 'Ignore error temporally', stageResult: 'UNSTABLE') { + makeTarget("Generators Metricbeat Mac OS X", "-C generator/_templates/metricbeat test") + } } } stage('Generators Beat Mac OS X'){ @@ -580,7 +589,10 @@ pipeline { } } steps { - makeTarget("Generators Beat Mac OS X", "-C generator/_templates/beat test") + // FIXME see https://github.com/elastic/beats/issues/18132 + catchError(buildResult: 'SUCCESS', message: 'Ignore error temporally', stageResult: 'UNSTABLE') { + makeTarget("Generators Beat Mac OS X", "-C generator/_templates/beat test") + } } } } From 93b59980339c51626c5bd9a0f7b6257e46d5cfb4 Mon Sep 17 00:00:00 2001 From: Jaime Soriano Pastor Date: Thu, 30 Apr 2020 18:28:43 +0200 Subject: [PATCH 071/116] Fix fields validation in non utf-8 environments (#18130) There was a non-ascii character in fields documentation that was making fields validation in some tests to fail. Fix fields validation to don't fail on this case, and replace the non-ascii character. --- libbeat/tests/system/beat/beat.py | 2 +- metricbeat/docs/fields.asciidoc | 2 +- x-pack/metricbeat/module/aws/fields.go | 2 +- x-pack/metricbeat/module/aws/sns/_meta/fields.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libbeat/tests/system/beat/beat.py b/libbeat/tests/system/beat/beat.py index fa8e0ad3cb4..9d0bf471635 100644 --- a/libbeat/tests/system/beat/beat.py +++ b/libbeat/tests/system/beat/beat.py @@ -586,7 +586,7 @@ def extract_fields(doc_list, name): # TODO: Make fields_doc path more generic to work with beat-generator. If it can't find file # "fields.yml" you should run "make update" on metricbeat folder - with open(fields_doc, "r") as f: + with open(fields_doc, "r", encoding="utf_8") as f: path = os.path.abspath(os.path.dirname(__file__) + "../../../../fields.yml") if not os.path.isfile(path): path = os.path.abspath(os.path.dirname(__file__) + "../../../../_meta/fields.common.yml") diff --git a/metricbeat/docs/fields.asciidoc b/metricbeat/docs/fields.asciidoc index 263124a748a..82bdce6ea1e 100644 --- a/metricbeat/docs/fields.asciidoc +++ b/metricbeat/docs/fields.asciidoc @@ -4168,7 +4168,7 @@ type: long *`aws.sns.metrics.NumberOfNotificationsFilteredOut-InvalidAttributes.sum`*:: + -- -The number of messages that were rejected by subscription filter policies because the messages' attributes are invalid – for example, because the attribute JSON is incorrectly formatted. +The number of messages that were rejected by subscription filter policies because the messages' attributes are invalid - for example, because the attribute JSON is incorrectly formatted. type: long diff --git a/x-pack/metricbeat/module/aws/fields.go b/x-pack/metricbeat/module/aws/fields.go index f3bcf9a8eb6..cf96de84a03 100644 --- a/x-pack/metricbeat/module/aws/fields.go +++ b/x-pack/metricbeat/module/aws/fields.go @@ -19,5 +19,5 @@ func init() { // AssetAws returns asset data. // This is the base64 encoded gzipped contents of module/aws. func AssetAws() string { - return "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" + return "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" } diff --git a/x-pack/metricbeat/module/aws/sns/_meta/fields.yml b/x-pack/metricbeat/module/aws/sns/_meta/fields.yml index 5c34d717373..df9c3f2c551 100644 --- a/x-pack/metricbeat/module/aws/sns/_meta/fields.yml +++ b/x-pack/metricbeat/module/aws/sns/_meta/fields.yml @@ -27,7 +27,7 @@ description: The number of messages that were rejected by subscription filter policies. - name: NumberOfNotificationsFilteredOut-InvalidAttributes.sum type: long - description: The number of messages that were rejected by subscription filter policies because the messages' attributes are invalid – for example, because the attribute JSON is incorrectly formatted. + description: The number of messages that were rejected by subscription filter policies because the messages' attributes are invalid - for example, because the attribute JSON is incorrectly formatted. - name: NumberOfNotificationsFilteredOut-NoMessageAttributes.sum type: long description: The number of messages that were rejected by subscription filter policies because the messages have no attributes. From bff78c9d3a0ed1db90d4324e1b6f05e01e79efd0 Mon Sep 17 00:00:00 2001 From: kaiyan-sheng Date: Thu, 30 Apr 2020 10:31:43 -0600 Subject: [PATCH 072/116] Add config.epr.yml to Metricbeat aws module (#18109) * Add config.epr.yml to Metricbeat aws module --- .../module/aws/_meta/config.epr.yml | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 x-pack/metricbeat/module/aws/_meta/config.epr.yml diff --git a/x-pack/metricbeat/module/aws/_meta/config.epr.yml b/x-pack/metricbeat/module/aws/_meta/config.epr.yml new file mode 100644 index 00000000000..9cc299673b2 --- /dev/null +++ b/x-pack/metricbeat/module/aws/_meta/config.epr.yml @@ -0,0 +1,89 @@ +- module: aws + period: 12h + aws_access_key_id: "" + aws_secret_access_key: "" + aws_session_token: "" + credential_profile_name: "" + role_arn: "" + metricsets: + - billing +- module: aws + period: 1m + aws_access_key_id: "" + aws_secret_access_key: "" + aws_session_token: "" + credential_profile_name: "" + role_arn: "" + metricsets: + - natgateway + - transitgateway + - usage + - vpn +- module: aws + period: 5m + aws_access_key_id: "" + aws_secret_access_key: "" + aws_session_token: "" + credential_profile_name: "" + role_arn: "" + metricsets: + - sqs +- module: aws + period: 24h + aws_access_key_id: "" + aws_secret_access_key: "" + aws_session_token: "" + credential_profile_name: "" + role_arn: "" + metricsets: + - s3_daily_storage + - s3_request +- module: aws + period: 1m + aws_access_key_id: "" + aws_secret_access_key: "" + aws_session_token: "" + credential_profile_name: "" + role_arn: "" + metricsets: + - elb + - rds + tags_filter: + - key: "" + value: "" +- module: aws + period: 5m + aws_access_key_id: "" + aws_secret_access_key: "" + aws_session_token: "" + credential_profile_name: "" + role_arn: "" + metricsets: + - dynamodb + - ebs + - ec2 + - lambda + - sns + tags_filter: + - key: "" + value: "" +- module: aws + period: 300s + aws_access_key_id: "" + aws_secret_access_key: "" + aws_session_token: "" + credential_profile_name: "" + role_arn: "" + metricsets: + - cloudwatch + metrics: + - namespace: AWS/EC2 + name: [""] + tags.resource_type_filter: "" + dimensions: + - name: "" + value: "" + statistic: [""] + tags: + - key: "" + value: "" From b6ca76b21751279354efe5dcf0cac16c33d157f0 Mon Sep 17 00:00:00 2001 From: Jaime Soriano Pastor Date: Thu, 30 Apr 2020 20:50:03 +0200 Subject: [PATCH 073/116] Keep running integration tests of modules without environment (#18125) Modules that don't define a local environment with docker compose or kubernetes (kind) are not being executed. Fall back to the old default of docker in that case. --- dev-tools/mage/integtest.go | 64 ++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/dev-tools/mage/integtest.go b/dev-tools/mage/integtest.go index f573d3df80a..064dc7d8102 100644 --- a/dev-tools/mage/integtest.go +++ b/dev-tools/mage/integtest.go @@ -185,37 +185,30 @@ func NewIntegrationRunners(path string, passInEnv map[string]string) (Integratio if err != nil { return nil, errors.Wrapf(err, "%s tester failed on Use", t.Name()) } - if use { - // Create the steps for the specific runner. - var runnerSteps IntegrationTestSteps - requirements := t.StepRequirements() - if requirements != nil { - runnerSteps = append(runnerSteps, requirements...) - } - runnerSteps = append(runnerSteps, steps...) - - // Create the custom env for the runner. - env := map[string]string{} - for k, v := range passInEnv { - env[k] = v - } - env[insideIntegrationTestEnvVar] = "true" - passThroughEnvs(env, defaultPassthroughEnvVars...) - if mg.Verbose() { - env["MAGEFILE_VERBOSE"] = "1" - } - if UseVendor { - env["GOFLAGS"] = "-mod=vendor" - } - - runner := &IntegrationRunner{ - steps: runnerSteps, - tester: t, - dir: dir, - env: env, - } - runners = append(runners, runner) + if !use { + continue + } + runner, err := initRunner(t, dir, passInEnv) + if err != nil { + return nil, errors.Wrapf(err, "initializing %s runner", t.Name()) + } + runners = append(runners, runner) + } + // Keep support for modules that don't have a local environment defined at the module + // level (system, stack and cloud modules by now) + if len(runners) == 0 { + if mg.Verbose() { + fmt.Printf(">> No runner found in %s, using docker\n", path) + } + tester, ok := globalIntegrationTesters["docker"] + if !ok { + return nil, fmt.Errorf("docker integration test runner not registered") + } + runner, err := initRunner(tester, dir, passInEnv) + if err != nil { + return nil, errors.Wrapf(err, "initializing docker runner") } + runners = append(runners, runner) } return runners, nil } @@ -230,6 +223,10 @@ func NewDockerIntegrationRunner(passThroughEnvVars ...string) (*IntegrationRunne if !ok { return nil, fmt.Errorf("docker integration test runner not registered") } + return initRunner(tester, cwd, nil, passThroughEnvVars...) +} + +func initRunner(tester IntegrationTester, dir string, passInEnv map[string]string, passThroughEnvVars ...string) (*IntegrationRunner, error) { var runnerSteps IntegrationTestSteps requirements := tester.StepRequirements() if requirements != nil { @@ -240,8 +237,11 @@ func NewDockerIntegrationRunner(passThroughEnvVars ...string) (*IntegrationRunne env := map[string]string{ insideIntegrationTestEnvVar: "true", } - passThroughEnvs(env, defaultPassthroughEnvVars...) + for name, value := range passInEnv { + env[name] = value + } passThroughEnvs(env, passThroughEnvVars...) + passThroughEnvs(env, defaultPassthroughEnvVars...) if mg.Verbose() { env["MAGEFILE_VERBOSE"] = "1" } @@ -252,7 +252,7 @@ func NewDockerIntegrationRunner(passThroughEnvVars ...string) (*IntegrationRunne runner := &IntegrationRunner{ steps: runnerSteps, tester: tester, - dir: cwd, + dir: dir, env: env, } return runner, nil From bc8123abbfde00c235c3daef3caead78ecd2289e Mon Sep 17 00:00:00 2001 From: Steffen Siering Date: Thu, 30 Apr 2020 21:43:00 +0200 Subject: [PATCH 074/116] Start to split filebeat/channel up (#17655) --- filebeat/beater/channels.go | 91 ++++++++ filebeat/beater/crawler.go | 4 +- filebeat/beater/filebeat.go | 20 +- filebeat/channel/connector.go | 86 +------- filebeat/channel/factory.go | 58 +----- filebeat/channel/outlet.go | 8 +- filebeat/channel/runner.go | 196 ++++++++++++++++++ .../{connector_test.go => runner_test.go} | 38 ++-- libbeat/autodiscover/autodiscover.go | 4 +- libbeat/cfgfile/list.go | 4 +- libbeat/cfgfile/reload.go | 4 +- libbeat/publisher/pipetool/pipetool.go | 91 ++++++++ 12 files changed, 421 insertions(+), 183 deletions(-) create mode 100644 filebeat/channel/runner.go rename filebeat/channel/{connector_test.go => runner_test.go} (90%) create mode 100644 libbeat/publisher/pipetool/pipetool.go diff --git a/filebeat/beater/channels.go b/filebeat/beater/channels.go index 38874cf483a..de65fbf5c68 100644 --- a/filebeat/beater/channels.go +++ b/filebeat/beater/channels.go @@ -22,7 +22,9 @@ import ( "github.com/elastic/beats/v7/filebeat/input/file" "github.com/elastic/beats/v7/filebeat/registrar" + "github.com/elastic/beats/v7/libbeat/beat" "github.com/elastic/beats/v7/libbeat/monitoring" + "github.com/elastic/beats/v7/libbeat/publisher/pipetool" ) type registrarLogger struct { @@ -41,6 +43,23 @@ type eventCounter struct { wg sync.WaitGroup } +// countingClient adds and substracts from a counter when events have been +// published, dropped or ACKed. The countingClient can be used to keep track of +// inflight events for a beat.Client instance. The counter is updated after the +// client has been disconnected from the publisher pipeline via 'Closed'. +type countingClient struct { + counter *eventCounter + client beat.Client +} + +type countingEventer struct { + wgEvents *eventCounter +} + +type combinedEventer struct { + a, b beat.ClientEventer +} + func newRegistrarLogger(reg *registrar.Registrar) *registrarLogger { return ®istrarLogger{ done: make(chan struct{}), @@ -87,3 +106,75 @@ func (c *eventCounter) Done() { func (c *eventCounter) Wait() { c.wg.Wait() } + +// withPipelineEventCounter adds a counter to the pipeline that keeps track of +// all events published, dropped and ACKed by any active client. +// The type accepted by counter is compatible with sync.WaitGroup. +func withPipelineEventCounter(pipeline beat.PipelineConnector, counter *eventCounter) beat.PipelineConnector { + counterListener := &countingEventer{counter} + + pipeline = pipetool.WithClientConfigEdit(pipeline, func(config beat.ClientConfig) (beat.ClientConfig, error) { + if evts := config.Events; evts != nil { + config.Events = &combinedEventer{evts, counterListener} + } else { + config.Events = counterListener + } + return config, nil + }) + + pipeline = pipetool.WithClientWrapper(pipeline, func(client beat.Client) beat.Client { + return &countingClient{ + counter: counter, + client: client, + } + }) + return pipeline +} + +func (c *countingClient) Publish(event beat.Event) { + c.counter.Add(1) + c.client.Publish(event) +} + +func (c *countingClient) PublishAll(events []beat.Event) { + c.counter.Add(len(events)) + c.client.PublishAll(events) +} + +func (c *countingClient) Close() error { + return c.client.Close() +} + +func (*countingEventer) Closing() {} +func (*countingEventer) Closed() {} +func (*countingEventer) Published() {} + +func (c *countingEventer) FilteredOut(_ beat.Event) {} +func (c *countingEventer) DroppedOnPublish(_ beat.Event) { + c.wgEvents.Done() +} + +func (c *combinedEventer) Closing() { + c.a.Closing() + c.b.Closing() +} + +func (c *combinedEventer) Closed() { + c.a.Closed() + c.b.Closed() +} + +func (c *combinedEventer) Published() { + c.a.Published() + c.b.Published() +} + +func (c *combinedEventer) FilteredOut(event beat.Event) { + c.a.FilteredOut(event) + c.b.FilteredOut(event) +} + +func (c *combinedEventer) DroppedOnPublish(event beat.Event) { + c.a.DroppedOnPublish(event) + c.b.DroppedOnPublish(event) +} diff --git a/filebeat/beater/crawler.go b/filebeat/beater/crawler.go index 60b4ff0609e..9ac830f8696 100644 --- a/filebeat/beater/crawler.go +++ b/filebeat/beater/crawler.go @@ -62,7 +62,7 @@ func newCrawler( // Start starts the crawler with all inputs func (c *crawler) Start( - pipeline beat.Pipeline, + pipeline beat.PipelineConnector, configInputs *common.Config, configModules *common.Config, ) error { @@ -111,7 +111,7 @@ func (c *crawler) Start( } func (c *crawler) startInput( - pipeline beat.Pipeline, + pipeline beat.PipelineConnector, config *common.Config, ) error { if !config.Enabled() { diff --git a/filebeat/beater/filebeat.go b/filebeat/beater/filebeat.go index b16dad08895..fb94a26762a 100644 --- a/filebeat/beater/filebeat.go +++ b/filebeat/beater/filebeat.go @@ -41,6 +41,7 @@ import ( "github.com/elastic/beats/v7/libbeat/management" "github.com/elastic/beats/v7/libbeat/monitoring" "github.com/elastic/beats/v7/libbeat/outputs/elasticsearch" + "github.com/elastic/beats/v7/libbeat/publisher/pipetool" _ "github.com/elastic/beats/v7/filebeat/include" @@ -66,6 +67,7 @@ type Filebeat struct { config *cfg.Config moduleRegistry *fileset.ModuleRegistry done chan struct{} + pipeline beat.PipelineConnector } // New creates a new Filebeat pointer instance. @@ -162,7 +164,7 @@ func (fb *Filebeat) setupPipelineLoaderCallback(b *beat.Beat) error { pipelineLoaderFactory := newPipelineLoaderFactory(b.Config.Output.Config()) modulesFactory := fileset.NewSetupFactory(b.Info, pipelineLoaderFactory) if fb.config.ConfigModules.Enabled() { - modulesLoader := cfgfile.NewReloader(b.Publisher, fb.config.ConfigModules) + modulesLoader := cfgfile.NewReloader(fb.pipeline, fb.config.ConfigModules) modulesLoader.Load(modulesFactory) } @@ -235,8 +237,11 @@ func (fb *Filebeat) Run(b *beat.Beat) error { return err } + fb.pipeline = pipetool.WithDefaultGuarantees(b.Publisher, beat.GuaranteedSend) + fb.pipeline = withPipelineEventCounter(fb.pipeline, wgEvents) + outDone := make(chan struct{}) // outDone closes down all active pipeline connections - pipelineConnector := channel.NewOutletFactory(outDone, wgEvents, b.Info).Create + pipelineConnector := channel.NewOutletFactory(outDone).Create // Create a ES connection factory for dynamic modules pipeline loading var pipelineLoaderFactory fileset.PipelineLoaderFactory @@ -246,7 +251,8 @@ func (fb *Filebeat) Run(b *beat.Beat) error { logp.Warn(pipelinesWarning) } - inputLoader := input.NewRunnerFactory(pipelineConnector, registrar, fb.done) + inputLoader := channel.RunnerFactoryWithCommonInputSettings(b.Info, + input.NewRunnerFactory(pipelineConnector, registrar, fb.done)) moduleLoader := fileset.NewFactory(inputLoader, b.Info, pipelineLoaderFactory, config.OverwritePipelines) crawler, err := newCrawler(inputLoader, moduleLoader, config.Inputs, fb.done, *once) @@ -283,7 +289,7 @@ func (fb *Filebeat) Run(b *beat.Beat) error { logp.Debug("modules", "Existing Ingest pipelines will be updated") } - err = crawler.Start(b.Publisher, config.ConfigInput, config.ConfigModules) + err = crawler.Start(fb.pipeline, config.ConfigInput, config.ConfigModules) if err != nil { crawler.Stop() return fmt.Errorf("Failed to start crawler: %+v", err) @@ -300,17 +306,17 @@ func (fb *Filebeat) Run(b *beat.Beat) error { } // Register reloadable list of inputs and modules - inputs := cfgfile.NewRunnerList(management.DebugK, inputLoader, b.Publisher) + inputs := cfgfile.NewRunnerList(management.DebugK, inputLoader, fb.pipeline) reload.Register.MustRegisterList("filebeat.inputs", inputs) - modules := cfgfile.NewRunnerList(management.DebugK, moduleLoader, b.Publisher) + modules := cfgfile.NewRunnerList(management.DebugK, moduleLoader, fb.pipeline) reload.Register.MustRegisterList("filebeat.modules", modules) var adiscover *autodiscover.Autodiscover if fb.config.Autodiscover != nil { adiscover, err = autodiscover.NewAutodiscover( "filebeat", - b.Publisher, + fb.pipeline, cfgfile.MultiplexedRunnerFactory( cfgfile.MatchHasField("module", moduleLoader), cfgfile.MatchDefault(inputLoader), diff --git a/filebeat/channel/connector.go b/filebeat/channel/connector.go index 73da881ec32..279c50c58b0 100644 --- a/filebeat/channel/connector.go +++ b/filebeat/channel/connector.go @@ -20,9 +20,6 @@ package channel import ( "github.com/elastic/beats/v7/libbeat/beat" "github.com/elastic/beats/v7/libbeat/common" - "github.com/elastic/beats/v7/libbeat/common/fmtstr" - "github.com/elastic/beats/v7/libbeat/processors" - "github.com/elastic/beats/v7/libbeat/processors/add_formatted_index" ) // ConnectorFunc is an adapter for using ordinary functions as Connector. @@ -48,96 +45,15 @@ func (c *pipelineConnector) Connect(cfg *common.Config) (Outleter, error) { } func (c *pipelineConnector) ConnectWith(cfg *common.Config, clientCfg beat.ClientConfig) (Outleter, error) { - config := inputOutletConfig{} - if err := cfg.Unpack(&config); err != nil { - return nil, err - } - - procs, err := processorsForConfig(c.parent.beatInfo, config, clientCfg) - if err != nil { - return nil, err - } - - setOptional := func(to common.MapStr, key string, value string) { - if value != "" { - to.Put(key, value) - } - } - - meta := clientCfg.Processing.Meta.Clone() - fields := clientCfg.Processing.Fields.Clone() - - serviceType := config.ServiceType - if serviceType == "" { - serviceType = config.Module - } - - setOptional(meta, "pipeline", config.Pipeline) - setOptional(fields, "fileset.name", config.Fileset) - setOptional(fields, "service.type", serviceType) - setOptional(fields, "input.type", config.Type) - if config.Module != "" { - event := common.MapStr{"module": config.Module} - if config.Fileset != "" { - event["dataset"] = config.Module + "." + config.Fileset - } - fields["event"] = event - } - - mode := clientCfg.PublishMode - if mode == beat.DefaultGuarantees { - mode = beat.GuaranteedSend - } - // connect with updated configuration - clientCfg.PublishMode = mode - clientCfg.Processing.EventMetadata = config.EventMetadata - clientCfg.Processing.Meta = meta - clientCfg.Processing.Fields = fields - clientCfg.Processing.Processor = procs - clientCfg.Processing.KeepNull = config.KeepNull client, err := c.pipeline.ConnectWith(clientCfg) if err != nil { return nil, err } - outlet := newOutlet(client, c.parent.wgEvents) + outlet := newOutlet(client) if c.parent.done != nil { return CloseOnSignal(outlet, c.parent.done), nil } return outlet, nil } - -// processorsForConfig assembles the Processors for a pipelineConnector. -func processorsForConfig( - beatInfo beat.Info, config inputOutletConfig, clientCfg beat.ClientConfig, -) (*processors.Processors, error) { - procs := processors.NewList(nil) - - // Processor ordering is important: - // 1. Index configuration - if !config.Index.IsEmpty() { - staticFields := fmtstr.FieldsForBeat(beatInfo.Beat, beatInfo.Version) - timestampFormat, err := - fmtstr.NewTimestampFormatString(&config.Index, staticFields) - if err != nil { - return nil, err - } - indexProcessor := add_formatted_index.New(timestampFormat) - procs.AddProcessor(indexProcessor) - } - - // 2. ClientConfig processors - if lst := clientCfg.Processing.Processor; lst != nil { - procs.AddProcessor(lst) - } - - // 3. User processors - userProcessors, err := processors.New(config.Processors) - if err != nil { - return nil, err - } - procs.AddProcessors(*userProcessors) - - return procs, nil -} diff --git a/filebeat/channel/factory.go b/filebeat/channel/factory.go index b145c4a34f5..7962377856c 100644 --- a/filebeat/channel/factory.go +++ b/filebeat/channel/factory.go @@ -19,65 +19,17 @@ package channel import ( "github.com/elastic/beats/v7/libbeat/beat" - "github.com/elastic/beats/v7/libbeat/common" - "github.com/elastic/beats/v7/libbeat/common/fmtstr" - "github.com/elastic/beats/v7/libbeat/processors" ) type OutletFactory struct { done <-chan struct{} - - eventer beat.ClientEventer - wgEvents eventCounter - beatInfo beat.Info -} - -type eventCounter interface { - Add(n int) - Done() -} - -// clientEventer adjusts wgEvents if events are dropped during shutdown. -type clientEventer struct { - wgEvents eventCounter -} - -// inputOutletConfig defines common input settings -// for the publisher pipeline. -type inputOutletConfig struct { - // event processing - common.EventMetadata `config:",inline"` // Fields and tags to add to events. - Processors processors.PluginConfig `config:"processors"` - KeepNull bool `config:"keep_null"` - - // implicit event fields - Type string `config:"type"` // input.type - ServiceType string `config:"service.type"` // service.type - - // hidden filebeat modules settings - Module string `config:"_module_name"` // hidden setting - Fileset string `config:"_fileset_name"` // hidden setting - - // Output meta data settings - Pipeline string `config:"pipeline"` // ES Ingest pipeline name - Index fmtstr.EventFormatString `config:"index"` // ES output index pattern } // NewOutletFactory creates a new outlet factory for // connecting an input to the publisher pipeline. -func NewOutletFactory( - done <-chan struct{}, - wgEvents eventCounter, - beatInfo beat.Info, -) *OutletFactory { +func NewOutletFactory(done <-chan struct{}) *OutletFactory { o := &OutletFactory{ - done: done, - wgEvents: wgEvents, - beatInfo: beatInfo, - } - - if wgEvents != nil { - o.eventer = &clientEventer{wgEvents} + done: done, } return o @@ -90,9 +42,3 @@ func NewOutletFactory( func (f *OutletFactory) Create(p beat.PipelineConnector) Connector { return &pipelineConnector{parent: f, pipeline: p} } - -func (e *clientEventer) Closing() {} -func (e *clientEventer) Closed() {} -func (e *clientEventer) Published() {} -func (e *clientEventer) FilteredOut(evt beat.Event) {} -func (e *clientEventer) DroppedOnPublish(evt beat.Event) { e.wgEvents.Done() } diff --git a/filebeat/channel/outlet.go b/filebeat/channel/outlet.go index 3211a9d7293..fd5c9b12fc1 100644 --- a/filebeat/channel/outlet.go +++ b/filebeat/channel/outlet.go @@ -23,15 +23,13 @@ import ( ) type outlet struct { - wg eventCounter client beat.Client isOpen atomic.Bool done chan struct{} } -func newOutlet(client beat.Client, wg eventCounter) *outlet { +func newOutlet(client beat.Client) *outlet { o := &outlet{ - wg: wg, client: client, isOpen: atomic.MakeBool(true), done: make(chan struct{}), @@ -57,10 +55,6 @@ func (o *outlet) OnEvent(event beat.Event) bool { return false } - if o.wg != nil { - o.wg.Add(1) - } - o.client.Publish(event) // Note: race condition on shutdown: diff --git a/filebeat/channel/runner.go b/filebeat/channel/runner.go new file mode 100644 index 00000000000..51a17e17939 --- /dev/null +++ b/filebeat/channel/runner.go @@ -0,0 +1,196 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package channel + +import ( + "github.com/elastic/beats/v7/libbeat/beat" + "github.com/elastic/beats/v7/libbeat/cfgfile" + "github.com/elastic/beats/v7/libbeat/common" + "github.com/elastic/beats/v7/libbeat/common/fmtstr" + "github.com/elastic/beats/v7/libbeat/processors" + "github.com/elastic/beats/v7/libbeat/processors/add_formatted_index" + "github.com/elastic/beats/v7/libbeat/publisher/pipetool" +) + +type onCreateFactory struct { + factory cfgfile.RunnerFactory + create onCreateWrapper +} + +type onCreateWrapper func(cfgfile.RunnerFactory, beat.PipelineConnector, *common.Config, *common.MapStrPointer) (cfgfile.Runner, error) + +// commonInputConfig defines common input settings +// for the publisher pipeline. +type commonInputConfig struct { + // event processing + common.EventMetadata `config:",inline"` // Fields and tags to add to events. + Processors processors.PluginConfig `config:"processors"` + KeepNull bool `config:"keep_null"` + + // implicit event fields + Type string `config:"type"` // input.type + ServiceType string `config:"service.type"` // service.type + + // hidden filebeat modules settings + Module string `config:"_module_name"` // hidden setting + Fileset string `config:"_fileset_name"` // hidden setting + + // Output meta data settings + Pipeline string `config:"pipeline"` // ES Ingest pipeline name + Index fmtstr.EventFormatString `config:"index"` // ES output index pattern +} + +func (f *onCreateFactory) CheckConfig(cfg *common.Config) error { + return f.factory.CheckConfig(cfg) +} + +func (f *onCreateFactory) Create( + pipeline beat.PipelineConnector, + cfg *common.Config, + meta *common.MapStrPointer, +) (cfgfile.Runner, error) { + return f.create(f.factory, pipeline, cfg, meta) +} + +// RunnerFactoryWithCommonInputSettings wraps a runner factory, such that all runners +// created by this factory have the same processing capabilities and related +// configuration file settings. +// +// Common settings ensured by this factory wrapper: +// - *fields*: common fields to be added to the pipeline +// - *fields_under_root*: select at which level to store the fields +// - *tags*: add additional tags to the events +// - *processors*: list of local processors to be added to the processing pipeline +// - *keep_null*: keep or remove 'null' from events to be published +// - *_module_name* (hidden setting): Add fields describing the module name +// - *_ fileset_name* (hiddrn setting): +// - *pipeline*: Configure the ES Ingest Node pipeline name to be used for events from this input +// - *index*: Configure the index name for events to be collected from this input +// - *type*: implicit event type +// - *service.type*: implicit event type +func RunnerFactoryWithCommonInputSettings(info beat.Info, f cfgfile.RunnerFactory) cfgfile.RunnerFactory { + return wrapRunnerCreate(f, + func( + f cfgfile.RunnerFactory, + pipeline beat.PipelineConnector, + cfg *common.Config, + meta *common.MapStrPointer, + ) (runner cfgfile.Runner, err error) { + pipeline, err = withClientConfig(info, pipeline, cfg) + if err != nil { + return nil, err + } + + return f.Create(pipeline, cfg, meta) + }) +} + +func wrapRunnerCreate(f cfgfile.RunnerFactory, edit onCreateWrapper) cfgfile.RunnerFactory { + return &onCreateFactory{factory: f, create: edit} +} + +// withClientConfig reads common Beat input instance configurations from the +// configuration object and ensure that the settings are applied to each client. +func withClientConfig( + beatInfo beat.Info, + pipeline beat.PipelineConnector, + cfg *common.Config, +) (beat.PipelineConnector, error) { + editor, err := newCommonConfigEditor(beatInfo, cfg) + if err != nil { + return nil, err + } + return pipetool.WithClientConfigEdit(pipeline, editor), nil +} + +func newCommonConfigEditor( + beatInfo beat.Info, + cfg *common.Config, +) (pipetool.ConfigEditor, error) { + config := commonInputConfig{} + if err := cfg.Unpack(&config); err != nil { + return nil, err + } + + var indexProcessor processors.Processor + if !config.Index.IsEmpty() { + staticFields := fmtstr.FieldsForBeat(beatInfo.Beat, beatInfo.Version) + timestampFormat, err := + fmtstr.NewTimestampFormatString(&config.Index, staticFields) + if err != nil { + return nil, err + } + indexProcessor = add_formatted_index.New(timestampFormat) + } + + userProcessors, err := processors.New(config.Processors) + if err != nil { + return nil, err + } + + serviceType := config.ServiceType + if serviceType == "" { + serviceType = config.Module + } + + return func(clientCfg beat.ClientConfig) (beat.ClientConfig, error) { + meta := clientCfg.Processing.Meta.Clone() + fields := clientCfg.Processing.Fields.Clone() + + setOptional(meta, "pipeline", config.Pipeline) + setOptional(fields, "fileset.name", config.Fileset) + setOptional(fields, "service.type", serviceType) + setOptional(fields, "input.type", config.Type) + if config.Module != "" { + event := common.MapStr{"module": config.Module} + if config.Fileset != "" { + event["dataset"] = config.Module + "." + config.Fileset + } + fields["event"] = event + } + + // assemble the processors. Ordering is important. + // 1. add support for index configuration via processor + // 2. add processors added by the input that wants to connect + // 3. add locally configured processors from the 'processors' settings + procs := processors.NewList(nil) + if indexProcessor != nil { + procs.AddProcessor(indexProcessor) + } + if lst := clientCfg.Processing.Processor; lst != nil { + procs.AddProcessor(lst) + } + if userProcessors != nil { + procs.AddProcessors(*userProcessors) + } + + clientCfg.Processing.EventMetadata = config.EventMetadata + clientCfg.Processing.Meta = meta + clientCfg.Processing.Fields = fields + clientCfg.Processing.Processor = procs + clientCfg.Processing.KeepNull = config.KeepNull + + return clientCfg, nil + }, nil +} + +func setOptional(to common.MapStr, key string, value string) { + if value != "" { + to.Put(key, value) + } +} diff --git a/filebeat/channel/connector_test.go b/filebeat/channel/runner_test.go similarity index 90% rename from filebeat/channel/connector_test.go rename to filebeat/channel/runner_test.go index fe6e3299188..101904b9260 100644 --- a/filebeat/channel/connector_test.go +++ b/filebeat/channel/runner_test.go @@ -23,6 +23,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/elastic/beats/v7/libbeat/beat" "github.com/elastic/beats/v7/libbeat/common" @@ -109,16 +110,22 @@ func TestProcessorsForConfig(t *testing.T) { if test.event.Fields == nil { test.event.Fields = common.MapStr{} } - config, err := outletConfigFromString(test.configStr) + config, err := common.NewConfigFrom(test.configStr) if err != nil { t.Errorf("[%s] %v", description, err) continue } - processors, err := processorsForConfig(test.beatInfo, config, test.clientCfg) + + editor, err := newCommonConfigEditor(test.beatInfo, config) if err != nil { t.Errorf("[%s] %v", description, err) continue } + + clientCfg, err := editor(test.clientCfg) + require.NoError(t, err) + + processors := clientCfg.Processing.Processor processedEvent, err := processors.Run(&test.event) // We don't check if err != nil, because we are testing the final outcome // of running the processors, including when some of them fail. @@ -160,16 +167,21 @@ func TestProcessorsForConfigIsFlat(t *testing.T) { configStr := `processors: - add_fields: {fields: {testField: value}} - add_fields: {fields: {testField2: stuff}}` - config, err := outletConfigFromString(configStr) + config, err := common.NewConfigFrom(configStr) if err != nil { t.Fatal(err) } - processors, err := processorsForConfig( - beat.Info{}, config, beat.ClientConfig{}) + + editor, err := newCommonConfigEditor(beat.Info{}, config) if err != nil { t.Fatal(err) } - assert.Equal(t, 2, len(processors.List)) + + clientCfg, err := editor(beat.ClientConfig{}) + require.NoError(t, err) + + lst := clientCfg.Processing.Processor + assert.Equal(t, 2, len(lst.(*processors.Processors).List)) } // setRawIndex is a bare-bones processor to set the raw_index field to a @@ -191,20 +203,6 @@ func (p *setRawIndex) String() string { return fmt.Sprintf("set_raw_index=%v", p.indexStr) } -// Helper function to convert from YML input string to an unpacked -// inputOutletConfig -func outletConfigFromString(s string) (inputOutletConfig, error) { - config := inputOutletConfig{} - cfg, err := common.NewConfigFrom(s) - if err != nil { - return config, err - } - if err := cfg.Unpack(&config); err != nil { - return config, err - } - return config, nil -} - // makeProcessors wraps one or more bare Processor objects in Processors. func makeProcessors(procs ...processors.Processor) *processors.Processors { procList := processors.NewList(nil) diff --git a/libbeat/autodiscover/autodiscover.go b/libbeat/autodiscover/autodiscover.go index 668a350b865..fe209e722ea 100644 --- a/libbeat/autodiscover/autodiscover.go +++ b/libbeat/autodiscover/autodiscover.go @@ -55,7 +55,7 @@ type EventConfigurer interface { // new modules when any configured providers does a match type Autodiscover struct { bus bus.Bus - defaultPipeline beat.Pipeline + defaultPipeline beat.PipelineConnector factory cfgfile.RunnerFactory configurer EventConfigurer providers []Provider @@ -69,7 +69,7 @@ type Autodiscover struct { // NewAutodiscover instantiates and returns a new Autodiscover manager func NewAutodiscover( name string, - pipeline beat.Pipeline, + pipeline beat.PipelineConnector, factory cfgfile.RunnerFactory, configurer EventConfigurer, config *Config, diff --git a/libbeat/cfgfile/list.go b/libbeat/cfgfile/list.go index db02c56bbdd..d3b05994e9a 100644 --- a/libbeat/cfgfile/list.go +++ b/libbeat/cfgfile/list.go @@ -35,12 +35,12 @@ type RunnerList struct { runners map[uint64]Runner mutex sync.RWMutex factory RunnerFactory - pipeline beat.Pipeline + pipeline beat.PipelineConnector logger *logp.Logger } // NewRunnerList builds and returns a RunnerList -func NewRunnerList(name string, factory RunnerFactory, pipeline beat.Pipeline) *RunnerList { +func NewRunnerList(name string, factory RunnerFactory, pipeline beat.PipelineConnector) *RunnerList { return &RunnerList{ runners: map[uint64]Runner{}, factory: factory, diff --git a/libbeat/cfgfile/reload.go b/libbeat/cfgfile/reload.go index 990264c3a6b..d5b9d9c315f 100644 --- a/libbeat/cfgfile/reload.go +++ b/libbeat/cfgfile/reload.go @@ -97,7 +97,7 @@ type Runner interface { // Reloader is used to register and reload modules type Reloader struct { - pipeline beat.Pipeline + pipeline beat.PipelineConnector config DynamicConfig path string done chan struct{} @@ -105,7 +105,7 @@ type Reloader struct { } // NewReloader creates new Reloader instance for the given config -func NewReloader(pipeline beat.Pipeline, cfg *common.Config) *Reloader { +func NewReloader(pipeline beat.PipelineConnector, cfg *common.Config) *Reloader { config := DefaultDynamicConfig cfg.Unpack(&config) diff --git a/libbeat/publisher/pipetool/pipetool.go b/libbeat/publisher/pipetool/pipetool.go new file mode 100644 index 00000000000..a05ec6e6022 --- /dev/null +++ b/libbeat/publisher/pipetool/pipetool.go @@ -0,0 +1,91 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package pipetool + +import "github.com/elastic/beats/v7/libbeat/beat" + +// connectEditPipeline modifies the client configuration using edit before calling +// edit. +type connectEditPipeline struct { + parent beat.PipelineConnector + edit ConfigEditor +} + +// ConfigEditor modifies the client configuration before connecting to a Pipeline. +type ConfigEditor func(beat.ClientConfig) (beat.ClientConfig, error) + +func (p *connectEditPipeline) Connect() (beat.Client, error) { + return p.ConnectWith(beat.ClientConfig{}) +} + +func (p *connectEditPipeline) ConnectWith(cfg beat.ClientConfig) (beat.Client, error) { + cfg, err := p.edit(cfg) + if err != nil { + return nil, err + } + return p.parent.ConnectWith(cfg) +} + +// wrapClientPipeline applies edit to the beat.Client returned by Connect and ConnectWith. +// The edit function can wrap the client to add additional functionality to clients +// that connect to the pipeline. +type wrapClientPipeline struct { + parent beat.PipelineConnector + wrapper ClientWrapper +} + +// ClientWrapper allows client instances to be wrapped. +type ClientWrapper func(beat.Client) beat.Client + +func (p *wrapClientPipeline) Connect() (beat.Client, error) { + return p.ConnectWith(beat.ClientConfig{}) +} + +func (p *wrapClientPipeline) ConnectWith(cfg beat.ClientConfig) (beat.Client, error) { + client, err := p.parent.ConnectWith(cfg) + if err == nil { + client = p.wrapper(client) + } + return client, err +} + +// WithClientConfigEdit creates a pipeline connector, that allows the +// beat.ClientConfig to be modified before connecting to the underlying +// pipeline. +// The edit function is applied before calling Connect or ConnectWith. +func WithClientConfigEdit(pipeline beat.PipelineConnector, edit ConfigEditor) beat.PipelineConnector { + return &connectEditPipeline{parent: pipeline, edit: edit} +} + +// WithDefaultGuarantee sets the default sending guarantee to `mode` if the +// beat.ClientConfig does not set the mode explicitly. +func WithDefaultGuarantees(pipeline beat.PipelineConnector, mode beat.PublishMode) beat.PipelineConnector { + return WithClientConfigEdit(pipeline, func(cfg beat.ClientConfig) (beat.ClientConfig, error) { + if cfg.PublishMode == beat.DefaultGuarantees { + cfg.PublishMode = mode + } + return cfg, nil + }) +} + +// WithClientWrapper calls wrap on beat.Client instance, after a successful +// call to `pipeline.Connect` or `pipeline.ConnectWith`. The wrap function can +// wrap the client to provide additional functionality. +func WithClientWrapper(pipeline beat.PipelineConnector, wrap ClientWrapper) beat.PipelineConnector { + return &wrapClientPipeline{parent: pipeline, wrapper: wrap} +} From 30311f6ccf9ca7eb47625d6fce67010de220f98a Mon Sep 17 00:00:00 2001 From: DeDe Morton Date: Thu, 30 Apr 2020 14:40:54 -0700 Subject: [PATCH 075/116] [docs] Fix netflow field docs by removing duplicate topic (#16784) --- filebeat/docs/fields.asciidoc | 7 ------- libbeat/scripts/generate_fields_docs.py | 5 ++++- x-pack/filebeat/module/netflow/_meta/fields.yml | 2 +- x-pack/filebeat/module/netflow/fields.go | 2 +- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/filebeat/docs/fields.asciidoc b/filebeat/docs/fields.asciidoc index 05f4677f138..34abb8e5d60 100644 --- a/filebeat/docs/fields.asciidoc +++ b/filebeat/docs/fields.asciidoc @@ -48,7 +48,6 @@ grouped in the following categories: * <> * <> * <> -* <> * <> * <> * <> @@ -26148,12 +26147,6 @@ type: short -- -[[exported-fields-netflow-module]] -== NetFlow fields - -Module for receiving NetFlow and IPFIX flow records over UDP. The module does not add fields beyond what the netflow input provides. - - [[exported-fields-nginx]] == Nginx fields diff --git a/libbeat/scripts/generate_fields_docs.py b/libbeat/scripts/generate_fields_docs.py index b52285f49e0..ecedb17d7b6 100644 --- a/libbeat/scripts/generate_fields_docs.py +++ b/libbeat/scripts/generate_fields_docs.py @@ -6,6 +6,8 @@ def document_fields(output, section, sections, path): + if "skipdocs" in section: + return if "anchor" in section: output.write("[[exported-fields-{}]]\n".format(section["anchor"])) @@ -136,7 +138,8 @@ def fields_to_asciidoc(input, output, beat): if "anchor" not in section: section["anchor"] = section["key"] - output.write("* <>\n".format(section["anchor"])) + if "skipdocs" not in section: + output.write("* <>\n".format(section["anchor"])) output.write("\n--\n") # Sort alphabetically by key diff --git a/x-pack/filebeat/module/netflow/_meta/fields.yml b/x-pack/filebeat/module/netflow/_meta/fields.yml index fc4bf3bb887..951323eb99c 100644 --- a/x-pack/filebeat/module/netflow/_meta/fields.yml +++ b/x-pack/filebeat/module/netflow/_meta/fields.yml @@ -3,4 +3,4 @@ description: > Module for receiving NetFlow and IPFIX flow records over UDP. The module does not add fields beyond what the netflow input provides. - fields: + skipdocs: diff --git a/x-pack/filebeat/module/netflow/fields.go b/x-pack/filebeat/module/netflow/fields.go index 6d6ea26f0af..93522209da6 100644 --- a/x-pack/filebeat/module/netflow/fields.go +++ b/x-pack/filebeat/module/netflow/fields.go @@ -19,5 +19,5 @@ func init() { // AssetNetflow returns asset data. // This is the base64 encoded gzipped contents of module/netflow. func AssetNetflow() string { - return "eJw8jjFOw0AQRfs9xbtAcoAtqFCkFKAUINGazBiPWHas3Ymt3B4Z4fTv/f8OfOs9UzXG4uvhx+VWNEFYFM28apyKrwlE+7XZHOY185QAXv5gRm80vaotVr92g6EK58vp/ME2vAHepOOLNt6fL0feJuVxB+LaqR4MIoymRTqfevcqrNMQxKR7JVbnWzA3X0y0HxP/Qk6/AQAA//9CcUYh" + return "eJw8zj1Ow0AQhuF+T/FeIDmACyoUKQUoBUi0xjPGoyw7q92JrdweBcnpn+/nwFXvA0Vjzr4dfl1uWROERdaBd41T9i2BaJ+a1TAvAy8J4O0fM3uj6aS2WvnZE4xFOF9O5y8exQ/gTTq+auPz9XLkY1GecyCuneLBKMJsmqXzrXcvwraMQSy6v8RKvQW1+Wqi/ZigX62KT31IfwEAAP//0cxHCg==" } From c94d4bf54ae4884deba1a50e76ee3457c871f431 Mon Sep 17 00:00:00 2001 From: Jaime Soriano Pastor Date: Fri, 1 May 2020 18:36:39 +0200 Subject: [PATCH 076/116] Add AWS tests to Jenkinsfile (#17480) Add execution of AWS integration tests for Metricbeat to Jenkinsfile. For that, a simple terraform scenario is created that seems to be enough to pass the AWS module tests, this scenario is started by Jenkins, and destroyed as a cleanup step. With this approach terraform scenarios are defined per module, this is consequent with other efforts we are doing with other integrations, where integration test scenarios are defined at the module level. Similar approach will be possibly followed for input integration tests. Most of the logic is added in the Jenkinsfile and as scripts. Some things could be moved to mage when we modify our targets to start scenarios depending on the type of provisioner. Some parts are going to continue being needed in Jenkinsfile in my opinion, as the archive of Terraform states for manual cleanups. --- .ci/scripts/install-terraform.sh | 18 +++ .ci/scripts/terraform-cleanup.sh | 16 +++ .gitignore | 4 + Jenkinsfile | 129 +++++++++++++++++++++- dev-tools/mage/common.go | 25 +++++ dev-tools/mage/gotest.go | 4 +- docs/devguide/terraform.asciidoc | 101 +++++++++++++++++ x-pack/metricbeat/module/aws/terraform.tf | 48 ++++++++ 8 files changed, 341 insertions(+), 4 deletions(-) create mode 100755 .ci/scripts/install-terraform.sh create mode 100755 .ci/scripts/terraform-cleanup.sh create mode 100644 docs/devguide/terraform.asciidoc create mode 100644 x-pack/metricbeat/module/aws/terraform.tf diff --git a/.ci/scripts/install-terraform.sh b/.ci/scripts/install-terraform.sh new file mode 100755 index 00000000000..39aa684d0aa --- /dev/null +++ b/.ci/scripts/install-terraform.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +set -exuo pipefail + +MSG="parameter missing." +TERRAFORM_VERSION=${TERRAFORM_VERSION:?$MSG} +HOME=${HOME:?$MSG} +TERRAFORM_CMD="${HOME}/bin/terraform" + +OS=$(uname -s | tr '[:upper:]' '[:lower:]') + +mkdir -p "${HOME}/bin" + +curl -sSLo - "https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_${OS}_amd64.zip" > ${TERRAFORM_CMD}.zip +unzip -o ${TERRAFORM_CMD}.zip -d $(dirname ${TERRAFORM_CMD}) +rm ${TERRAFORM_CMD}.zip + +chmod +x "${TERRAFORM_CMD}" diff --git a/.ci/scripts/terraform-cleanup.sh b/.ci/scripts/terraform-cleanup.sh new file mode 100755 index 00000000000..f1051b9b20d --- /dev/null +++ b/.ci/scripts/terraform-cleanup.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +set -exuo pipefail + +DIRECTORY=${1:-.} + +FAILED=0 +for tfstate in $(find $DIRECTORY -name terraform.tfstate); do + cd $(dirname $tfstate) + if ! terraform destroy -auto-approve; then + FAILED=1 + fi + cd - +done + +exit $FAILED diff --git a/.gitignore b/.gitignore index 34266183ac2..fe3a1459213 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,7 @@ x-pack/dockerlogbeat/temproot.tar *.test *.prof *.pyc + +# Terraform +*.terraform +*.tfstate* diff --git a/Jenkinsfile b/Jenkinsfile index a0382877db6..45eee99bb45 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -15,9 +15,11 @@ pipeline { BASE_DIR = 'src/github.com/elastic/beats' GOX_FLAGS = "-arch amd64" DOCKER_COMPOSE_VERSION = "1.21.0" + TERRAFORM_VERSION = "0.12.24" PIPELINE_LOG_LEVEL = "INFO" DOCKERELASTIC_SECRET = 'secret/observability-team/ci/docker-registry/prod' DOCKER_REGISTRY = 'docker.elastic.co' + AWS_ACCOUNT_SECRET = 'secret/observability-team/ci/elastic-observability-aws-account-auth' RUNBLD_DISABLE_NOTIFICATIONS = 'true' } options { @@ -36,6 +38,11 @@ pipeline { booleanParam(name: 'runAllStages', defaultValue: false, description: 'Allow to run all stages.') booleanParam(name: 'windowsTest', defaultValue: true, description: 'Allow Windows stages.') booleanParam(name: 'macosTest', defaultValue: true, description: 'Allow macOS stages.') + + booleanParam(name: 'allCloudTests', defaultValue: false, description: 'Run all cloud integration tests.') + booleanParam(name: 'awsCloudTests', defaultValue: false, description: 'Run AWS cloud integration tests.') + string(name: 'awsRegion', defaultValue: 'eu-central-1', description: 'Default AWS region to use for testing.') + booleanParam(name: 'debug', defaultValue: false, description: 'Allow debug logging for Jenkins steps') booleanParam(name: 'dry_run', defaultValue: false, description: 'Skip build steps, it is for testing pipeline flow') } @@ -352,8 +359,30 @@ pipeline { return env.BUILD_METRICBEAT_XPACK != "false" } } - steps { - mageTarget("Metricbeat x-pack Linux", "x-pack/metricbeat", "build test") + stages { + stage('Prepare cloud integration tests environments'){ + agent { label 'ubuntu && immutable' } + options { skipDefaultCheckout() } + steps { + startCloudTestEnv('x-pack-metricbeat', [ + [cond: params.awsCloudTests, dir: 'x-pack/metricbeat/module/aws'], + ]) + } + } + stage('Metricbeat x-pack'){ + agent { label 'ubuntu && immutable' } + options { skipDefaultCheckout() } + steps { + withCloudTestEnv() { + mageTarget("Metricbeat x-pack Linux", "x-pack/metricbeat", "build test") + } + } + } + } + post { + cleanup { + terraformCleanup('x-pack-metricbeat', 'x-pack/metricbeat') + } } } stage('Metricbeat crosscompile'){ @@ -683,7 +712,7 @@ def withBeatsEnv(boolean archive, Closure body) { "TEST_COVERAGE=true", "RACE_DETECTOR=true", "PYTHON_ENV=${WORKSPACE}/python-env", - "TEST_TAGS=oracle", + "TEST_TAGS=${env.TEST_TAGS},oracle", "DOCKER_PULL=0", ]) { deleteDir() @@ -750,6 +779,7 @@ def installTools() { if(isUnix()) { retry(i) { sh(label: "Install Go ${GO_VERSION}", script: ".ci/scripts/install-go.sh") } retry(i) { sh(label: "Install docker-compose ${DOCKER_COMPOSE_VERSION}", script: ".ci/scripts/install-docker-compose.sh") } + retry(i) { sh(label: "Install Terraform ${TERRAFORM_VERSION}", script: ".ci/scripts/install-terraform.sh") } retry(i) { sh(label: "Install Mage", script: "make mage") } } else { retry(i) { bat(label: "Install Go/Mage/Python ${GO_VERSION}", script: ".ci/scripts/install-tools.bat") } @@ -821,6 +851,7 @@ def dumpFilteredEnvironment(){ echo "SYSTEM_TESTS: ${env.SYSTEM_TESTS}" echo "STRESS_TESTS: ${env.STRESS_TESTS}" echo "STRESS_TEST_OPTIONS: ${env.STRESS_TEST_OPTIONS}" + echo "TEST_TAGS: ${env.TEST_TAGS}" echo "GOX_OS: ${env.GOX_OS}" echo "GOX_OSARCH: ${env.GOX_OSARCH}" echo "GOX_FLAGS: ${env.GOX_FLAGS}" @@ -907,6 +938,98 @@ def isChangedXPackCode(patterns) { return isChanged(allPatterns) } +// withCloudTestEnv executes a closure with credentials for cloud test +// environments. +def withCloudTestEnv(Closure body) { + def maskedVars = [] + def testTags = "${env.TEST_TAGS}" + + // AWS + if (params.allCloudTests || params.awsCloudTests) { + testTags = "${testTags},aws" + def aws = getVaultSecret(secret: "${AWS_ACCOUNT_SECRET}").data + if (!aws.containsKey('access_key')) { + error("${AWS_ACCOUNT_SECRET} doesn't contain 'access_key'") + } + if (!aws.containsKey('secret_key')) { + error("${AWS_ACCOUNT_SECRET} doesn't contain 'secret_key'") + } + maskedVars.addAll([ + [var: "AWS_REGION", password: params.awsRegion], + [var: "AWS_ACCESS_KEY_ID", password: aws.access_key], + [var: "AWS_SECRET_ACCESS_KEY", password: aws.secret_key], + ]) + } + + withEnv([ + "TEST_TAGS=${testTags}", + ]) { + withEnvMask(vars: maskedVars) { + body() + } + } +} + +def terraformInit(String directory) { + dir(directory) { + sh(label: "Terraform Init on ${directory}", script: "terraform init") + } +} + +def terraformApply(String directory) { + terraformInit(directory) + dir(directory) { + sh(label: "Terraform Apply on ${directory}", script: "terraform apply -auto-approve") + } +} + +// Start testing environment on cloud using terraform. Terraform files are +// stashed so they can be used by other stages. They are also archived in +// case manual cleanup is needed. +// +// Example: +// startCloudTestEnv('x-pack-metricbeat', [ +// [cond: params.awsCloudTests, dir: 'x-pack/metricbeat/module/aws'], +// ]) +// ... +// terraformCleanup('x-pack-metricbeat', 'x-pack/metricbeat') +def startCloudTestEnv(String name, environments = []) { + withCloudTestEnv() { + withBeatsEnv(false) { + def runAll = params.runAllCloudTests + try { + for (environment in environments) { + if (environment.cond || runAll) { + retry(2) { + terraformApply(environment.dir) + } + } + } + } finally { + // Archive terraform states in case manual cleanup is needed. + archiveArtifacts(allowEmptyArchive: true, artifacts: '**/terraform.tfstate') + } + stash(name: "terraform-${name}", allowEmpty: true, includes: '**/terraform.tfstate,**/.terraform/**') + } + } +} + + +// Looks for all terraform states in directory and runs terraform destroy for them, +// it uses terraform states previously stashed by startCloudTestEnv. +def terraformCleanup(String stashName, String directory) { + stage("Remove cloud scenarios in ${directory}"){ + withCloudTestEnv() { + withBeatsEnv(false) { + unstash "terraform-${stashName}" + retry(2) { + sh(label: "Terraform Cleanup", script: ".ci/scripts/terraform-cleanup.sh ${directory}") + } + } + } + } +} + def loadConfigEnvVars(){ def empty = [] env.GO_VERSION = readFile(".go-version").trim() diff --git a/dev-tools/mage/common.go b/dev-tools/mage/common.go index 21c07ba4398..9fdb189e3fa 100644 --- a/dev-tools/mage/common.go +++ b/dev-tools/mage/common.go @@ -833,3 +833,28 @@ func ListMatchingEnvVars(prefixes ...string) []string { } return vars } + +// IntegrationTestEnvVars returns the names of environment variables needed to configure +// connections to integration test environments. +func IntegrationTestEnvVars() []string { + // Environment variables that can be configured with paths to files + // with authentication information. + vars := []string{ + "AWS_SHARED_CREDENTIAL_FILE", + "AZURE_AUTH_LOCATION", + "GOOGLE_APPLICATION_CREDENTIALS", + } + // Environment variables with authentication information. + prefixes := []string{ + "AWS_", + "AZURE_", + + // Accepted by terraform, but not by many clients, including Beats + "GOOGLE_", + "GCLOUD_", + } + for _, prefix := range prefixes { + vars = append(vars, ListMatchingEnvVars(prefix)...) + } + return vars +} diff --git a/dev-tools/mage/gotest.go b/dev-tools/mage/gotest.go index 2eb7f9a0b7d..c6e3b6ce430 100644 --- a/dev-tools/mage/gotest.go +++ b/dev-tools/mage/gotest.go @@ -156,7 +156,9 @@ func GoTestIntegrationForModule(ctx context.Context) error { foundModule = true // Set MODULE because only want that modules tests to run inside the testing environment. - runners, err := NewIntegrationRunners(path.Join("./module", fi.Name()), map[string]string{"MODULE": fi.Name()}) + env := map[string]string{"MODULE": fi.Name()} + passThroughEnvs(env, IntegrationTestEnvVars()...) + runners, err := NewIntegrationRunners(path.Join("./module", fi.Name()), env) if err != nil { return errors.Wrapf(err, "test setup failed for module %s", fi.Name()) } diff --git a/docs/devguide/terraform.asciidoc b/docs/devguide/terraform.asciidoc new file mode 100644 index 00000000000..2c21c8f4314 --- /dev/null +++ b/docs/devguide/terraform.asciidoc @@ -0,0 +1,101 @@ +[[terraform-beats]] +== Terraform in Beats + +Terraform is used to provision scenarios for integration testing of some cloud +features. Features implementing integration tests that require the presence of +cloud resources should have their own Terraform configuration, this configuration +can be used when developing locally to create (and destroy) resources that allow +to test these features. + +Tests requiring access to cloud providers should be disabled by default with the +use of build tags. + +[[installing-terraform]] +=== Installing Terraform + +Terraform is available in https://www.terraform.io/downloads.html + +Download it and place it in some directory in your PATH. + +`terraform` is the main command for Terraform and the only one that is usually +needed to manage configurations. Terraform will also download other plugins that +implement the specific functionality for each provider. These plugins are +automatically managed and stored in the working copy, if you want to share the +plugins between multiple working copies you can manually install them in the +user the user plugins directory located at `~/.terraform.d/plugins`, +or `%APPDATA%\terraform.d\plugins on Windows`. + +Plugins are available in https://registry.terraform.io/ + +[[using-terraform]] +=== Using Terraform + +The most important commands when using Terraform are: +* `terraform init` to do some initial checks and install the required plugins. +* `terraform apply` to create the resources defined in the configuration. +* `terraform destroy` to destroy resources previously created. + +Cloud providers use to require credentials, they can be provided with the usual +methods supported by these providers, using environment variables and/or +credential files. + +Terraform stores the last known state of the resources managed by a +configuration in a `terraform.tfstate` file. It is important to keep this file +as it is used as input by `terraform destroy`. This file is created in the same +directory where `terraform apply` is executed. + +Please take a look to Terraform documentation for more details: https://www.terraform.io/intro/index.html + +[[terraform-configurations]] +=== Terraform configuration guidelines + +The main purpouse of Terraform in Beats is to create and destroy cloud resources +required by integration tests. For these configurations there are some things to +take into account: +* Apply should work without additional inputs or files. Only input will be the + required for specific providers, using environment variables or credential + files. +* You must be able to apply the same configuration multiple times in the same + account. This will allow to have multiple builds using the same configuration + but with different instances of the resources. Some resources are already + created with unique identifiers (as EC2 instances), some others have to be + explicitly created with unique names (e.g. S3 buckets). For these cases random + suffixes can be added to identifiers. +* Destroy must work without additional input, and should be able to destroy all + the resources created by the configuration. There are some resources that need + specific flags to be destroyed by `terraform destroy`. For example S3 buckets + need a flag to force to empty the bucket before deleting it, or RDS instances + need a flag to disable snapshots on deletion. + +[[terraform-in-ci]] +=== Terraform in CI + +Integration tests that need the presence of certain resources to work can be +executed in CI if they provide a Terraform configuration to start these +resources. These tests are disabled by default in CI. + +Terraform states are archived as artifacrs of builds, this allows to manually +destroy resources created by builds that were not able to do a proper cleanup. + +Here is a checklist to add support for a cloud feature in Jenkins: +* In the feature code: + * Tests have a build tag so they are disabled by default. When run from mage, + its execution can be selected using the `TEST_TAGS` environment variable, e.g: + `TEST_TAGS=aws` for AWS tests. + * There is some Terraform configuration that defines a cloud scenario where + tests pass. This configuration should be in the directory of the feature. +* In the Jenkinsfile: + * Add a boolean parameter to run the tests on this environment, e.g. + `awsCloudTests`. This parameter should be set to false by default. + * Add a conditional block in `withCloudTestEnv` that: + * Will be executed if the previously added boolean parameter, or `allCloudTests` + are set to true. + * Adds the tag to `TEST_TAGS` (as comma separated values), so tests are + selected. + * Defines how to obtain the credentials and provide them to the tests. + * In the stage of the specific beat: + * Add a stage that calls to `startCloudTestEnv`, if there isn't anyone. + * Add a post cleanup step that calls to `terraformCleanup`, if there isn't anyone. + * Add a environment to the list of environments started by `startCloudEnv`, + with the condition to start the scenario, and the path to the directory + with its definition, e.g. `[cond: params.awsCloudTests, dir: 'x-pack/metricbeat/module/aws']` diff --git a/x-pack/metricbeat/module/aws/terraform.tf b/x-pack/metricbeat/module/aws/terraform.tf new file mode 100644 index 00000000000..e767a028ab1 --- /dev/null +++ b/x-pack/metricbeat/module/aws/terraform.tf @@ -0,0 +1,48 @@ +provider "aws" { + version = "~> 2.58" +} + +provider "random" { + version = "~> 2.2" +} + +resource "random_id" "suffix" { + byte_length = 4 +} + +resource "random_password" "db" { + length = 16 + special = false +} + +resource "aws_db_instance" "test" { + identifier = "metricbeat-test-${random_id.suffix.hex}" + allocated_storage = 20 // Gigabytes + engine = "mysql" + instance_class = "db.t2.micro" + name = "metricbeattest" + username = "foo" + password = random_password.db.result + skip_final_snapshot = true // Required for cleanup +} + +resource "aws_sqs_queue" "test" { + name = "metricbeat-test-${random_id.suffix.hex}" + receive_wait_time_seconds = 10 +} + +resource "aws_s3_bucket" "test" { + bucket = "metricbeat-test-${random_id.suffix.hex}" + force_destroy = true // Required for cleanup +} + +resource "aws_s3_bucket_metric" "test" { + bucket = aws_s3_bucket.test.id + name = "EntireBucket" +} + +resource "aws_s3_bucket_object" "test" { + key = "someobject" + bucket = aws_s3_bucket.test.id + content = "something" +} From 3024b66b2e92aae62311a6227b96c8a08199fe76 Mon Sep 17 00:00:00 2001 From: Andrew Kroh Date: Fri, 1 May 2020 17:44:56 -0400 Subject: [PATCH 077/116] Fix broken x-pack/filebeat tests (#18149) The tests are failing due to the change in #16784. The `fields` key is referenced in system tests so it needs to be present. --- x-pack/filebeat/module/netflow/_meta/fields.yml | 1 + x-pack/filebeat/module/netflow/fields.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/filebeat/module/netflow/_meta/fields.yml b/x-pack/filebeat/module/netflow/_meta/fields.yml index 951323eb99c..c9bb9852ce1 100644 --- a/x-pack/filebeat/module/netflow/_meta/fields.yml +++ b/x-pack/filebeat/module/netflow/_meta/fields.yml @@ -4,3 +4,4 @@ Module for receiving NetFlow and IPFIX flow records over UDP. The module does not add fields beyond what the netflow input provides. skipdocs: + fields: diff --git a/x-pack/filebeat/module/netflow/fields.go b/x-pack/filebeat/module/netflow/fields.go index 93522209da6..f8e6a0b3670 100644 --- a/x-pack/filebeat/module/netflow/fields.go +++ b/x-pack/filebeat/module/netflow/fields.go @@ -19,5 +19,5 @@ func init() { // AssetNetflow returns asset data. // This is the base64 encoded gzipped contents of module/netflow. func AssetNetflow() string { - return "eJw8zj1Ow0AQhuF+T/FeIDmACyoUKQUoBUi0xjPGoyw7q92JrdweBcnpn+/nwFXvA0Vjzr4dfl1uWROERdaBd41T9i2BaJ+a1TAvAy8J4O0fM3uj6aS2WvnZE4xFOF9O5y8exQ/gTTq+auPz9XLkY1GecyCuneLBKMJsmqXzrXcvwraMQSy6v8RKvQW1+Wqi/ZigX62KT31IfwEAAP//0cxHCg==" + return "eJw8jjFOw0AQRfs9xbtAcoAtqFCkFKAUINEazxiPsuxYuxNbuT0KwvTv/f8OXPWeqRpT8e3w7XIrmiAsimZeNU7FtwSifWy2hHnNPCWAl1+YyRtNR7XV6tduMFThfDmdP3gMPwBv0vFVG+/PlyNvs/J/B+LaqR4MIkymRTqfevcqbPMQxKx7JVaXW7A0X020HxP0qy3iY8+JPzmnnwAAAP//qK1KBQ==" } From 522ffd5460d3b63bdc117ccacbbd033b5ed316e1 Mon Sep 17 00:00:00 2001 From: Andrew Kroh Date: Fri, 1 May 2020 20:28:52 -0400 Subject: [PATCH 078/116] Pin version of python ordered-set to 3.1.1 (#18152) A new version of ordered-set was released on April 29, 2020 and builds under Python 3.5 on Jenkins started failing. So this pins the version of ordered-set to the previous version that it was using and working under. Fixes #18150 --- libbeat/tests/system/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/libbeat/tests/system/requirements.txt b/libbeat/tests/system/requirements.txt index f979754ed33..d2aa5c3889b 100644 --- a/libbeat/tests/system/requirements.txt +++ b/libbeat/tests/system/requirements.txt @@ -31,5 +31,6 @@ parameterized==0.7.0 jsondiff==1.1.2 semver==2.8.1 stomp.py==4.1.22 +ordered-set==3.1.1 deepdiff==4.2.0 kafka-python==1.4.3 From f39d8695bf5faa73f496173e56a35ace3ef9894b Mon Sep 17 00:00:00 2001 From: Andrew Kroh Date: Fri, 1 May 2020 22:28:29 -0400 Subject: [PATCH 079/116] Optimize Dockerized integTest with GOCACHE (#17957) Create a reusable GOCACHE located at $repoRoot/docker-gocache to speed up building the Linux system test binaries and running tests. This can yield proportionally large speed-up when doing selective testing as you often do in the development cycle. For example when testing a single module in x-pack/filebeat: ``` NOSE_TESTMATCH=misp time mage -v pythonIntegTest 199.62 real 20.86 user 35.55 sys NOSE_TESTMATCH=misp time mage -v pythonIntegTest 104.97 real 21.44 user 35.55 sys ``` I also compared running mage integTest for x-pack/filebeat without a cache then with a cache: ``` Go Unit Test: 2m35s Go Test Compile: 30.2s real 12m2.626s user 0m21.141s sys 0m35.212s ``` ``` Go Unit Test: 47s Go Test Compile: 36s real 9m16.326s user 0m21.697s sys 0m38.908s ``` --- dev-tools/mage/crossbuild.go | 4 +++- dev-tools/mage/gotest.go | 5 +++++ dev-tools/mage/integtest_docker.go | 5 ++++- libbeat/docker-compose.yml | 1 - packetbeat/Dockerfile | 2 +- 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/dev-tools/mage/crossbuild.go b/dev-tools/mage/crossbuild.go index 539e4fba03c..d80276a7974 100644 --- a/dev-tools/mage/crossbuild.go +++ b/dev-tools/mage/crossbuild.go @@ -297,7 +297,9 @@ func DockerChown(path string) { func chownPaths(uid, gid int, path string) error { start := time.Now() numFixed := 0 - defer log.Printf("chown took: %v, changed %d files", time.Now().Sub(start), numFixed) + defer func() { + log.Printf("chown took: %v, changed %d files", time.Now().Sub(start), numFixed) + }() return filepath.Walk(path, func(name string, info os.FileInfo, err error) error { if err != nil { diff --git a/dev-tools/mage/gotest.go b/dev-tools/mage/gotest.go index c6e3b6ce430..c813693971e 100644 --- a/dev-tools/mage/gotest.go +++ b/dev-tools/mage/gotest.go @@ -456,5 +456,10 @@ func BuildSystemTestGoBinary(binArgs TestBinaryArgs) error { if len(binArgs.InputFiles) > 0 { args = append(args, binArgs.InputFiles...) } + + start := time.Now() + defer func() { + log.Printf("BuildSystemTestGoBinary (go %v) took %v.", strings.Join(args, " "), time.Since(start)) + }() return sh.RunV("go", args...) } diff --git a/dev-tools/mage/integtest_docker.go b/dev-tools/mage/integtest_docker.go index 6bbca1f6d64..afc05a16dc1 100644 --- a/dev-tools/mage/integtest_docker.go +++ b/dev-tools/mage/integtest_docker.go @@ -90,17 +90,20 @@ func (d *DockerIntegrationTester) Test(_ string, mageTarget string, env map[stri if err != nil { return err } + dockerRepoRoot := filepath.Join("/go/src", repo.CanonicalRootImportPath) + dockerGoCache := filepath.Join(dockerRepoRoot, "build/docker-gocache") magePath := filepath.Join("/go/src", repo.CanonicalRootImportPath, repo.SubDir, "build/mage-linux-amd64") // Execute the inside of docker-compose. args := []string{"-p", dockerComposeProjectName(), "run", "-e", "DOCKER_COMPOSE_PROJECT_NAME=" + dockerComposeProjectName(), - // Disable strict.perms because we moust host dirs inside containers + // Disable strict.perms because we mount host dirs inside containers // and the UID/GID won't meet the strict requirements. "-e", "BEAT_STRICT_PERMS=false", // compose.EnsureUp needs to know the environment type. "-e", "STACK_ENVIRONMENT=" + StackEnvironment, "-e", "TESTING_ENVIRONMENT=" + StackEnvironment, + "-e", "GOCACHE=" + dockerGoCache, } args, err = addUidGidEnvArgs(args) if err != nil { diff --git a/libbeat/docker-compose.yml b/libbeat/docker-compose.yml index e2c759cda81..f922e4b6e42 100644 --- a/libbeat/docker-compose.yml +++ b/libbeat/docker-compose.yml @@ -5,7 +5,6 @@ services: depends_on: - proxy_dep environment: - - LIBBEAT_PATH=/go/src/github.com/elastic/beats/libbeat - BEAT_STRICT_PERMS=false - REDIS_HOST=redis - REDIS_PORT=6379 diff --git a/packetbeat/Dockerfile b/packetbeat/Dockerfile index 1fcb4814bc8..c0abe357b8e 100644 --- a/packetbeat/Dockerfile +++ b/packetbeat/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.13.7 +FROM golang:1.13.10 RUN \ apt-get update \ From 3711ee63e516874321a82e5ccd95cf0f15071d88 Mon Sep 17 00:00:00 2001 From: Andrew Kroh Date: Sat, 2 May 2020 09:01:17 -0400 Subject: [PATCH 080/116] Set agent.name to hostname by default (#18000) Since ECS does not define agent.hostname it will be removed in a future release. In order to always have a field available to identify the agent by name we will set the agent.name to hostname unless the user has provided a custom name. Relates #16377 --- CHANGELOG.next.asciidoc | 1 + auditbeat/docs/fields.asciidoc | 3 +- auditbeat/include/fields.go | 2 +- filebeat/docs/fields.asciidoc | 3 +- filebeat/include/fields.go | 2 +- filebeat/tests/system/test_fields.py | 3 +- filebeat/tests/system/test_modules.py | 2 +- heartbeat/cmd/root.go | 2 +- heartbeat/docs/fields.asciidoc | 3 +- heartbeat/include/fields.go | 2 +- journalbeat/docs/fields.asciidoc | 3 +- journalbeat/include/fields.go | 2 +- libbeat/_meta/fields.common.yml | 4 +- libbeat/publisher/processing/default.go | 37 +++++++++++++++---- libbeat/publisher/processing/default_test.go | 1 + metricbeat/docs/fields.asciidoc | 3 +- packetbeat/docs/fields.asciidoc | 3 +- packetbeat/include/fields.go | 2 +- packetbeat/magefile.go | 2 + .../tests/system/test_0099_golden_files.py | 1 + winlogbeat/docs/fields.asciidoc | 3 +- winlogbeat/include/fields.go | 2 +- x-pack/functionbeat/docs/fields.asciidoc | 3 +- x-pack/functionbeat/include/fields.go | 2 +- 24 files changed, 64 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index de7ff112396..afed61013d6 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -210,6 +210,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Add keystore support for autodiscover static configurations. {pull]16306[16306] - Add Kerberos support to Elasticsearch output. {pull}17927[17927] - Add support for fixed length extraction in `dissect` processor. {pull}17191[17191] +- Set `agent.name` to the hostname by default. {issue}16377[16377] {pull}18000[18000] *Auditbeat* diff --git a/auditbeat/docs/fields.asciidoc b/auditbeat/docs/fields.asciidoc index e5144ce4b0c..3d9af508052 100644 --- a/auditbeat/docs/fields.asciidoc +++ b/auditbeat/docs/fields.asciidoc @@ -2458,7 +2458,8 @@ Contains common beat fields available in all event types. *`agent.hostname`*:: + -- -Hostname of the agent. +Deprecated - use agent.name or agent.id to identify an agent. Hostname of the agent. + type: keyword diff --git a/auditbeat/include/fields.go b/auditbeat/include/fields.go index 67239ca6469..841ebeae62b 100644 --- a/auditbeat/include/fields.go +++ b/auditbeat/include/fields.go @@ -32,5 +32,5 @@ func init() { // AssetFieldsYml returns asset data. // This is the base64 encoded gzipped contents of fields.yml. func AssetFieldsYml() string { - return "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" + return "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" } diff --git a/filebeat/docs/fields.asciidoc b/filebeat/docs/fields.asciidoc index 34abb8e5d60..0b93d699d2b 100644 --- a/filebeat/docs/fields.asciidoc +++ b/filebeat/docs/fields.asciidoc @@ -3112,7 +3112,8 @@ Contains common beat fields available in all event types. *`agent.hostname`*:: + -- -Hostname of the agent. +Deprecated - use agent.name or agent.id to identify an agent. Hostname of the agent. + type: keyword diff --git a/filebeat/include/fields.go b/filebeat/include/fields.go index 839cce5e151..e0ea9afb7e4 100644 --- a/filebeat/include/fields.go +++ b/filebeat/include/fields.go @@ -32,5 +32,5 @@ func init() { // AssetFieldsYml returns asset data. // This is the base64 encoded gzipped contents of fields.yml. func AssetFieldsYml() string { - return "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" + return "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" } diff --git a/filebeat/tests/system/test_fields.py b/filebeat/tests/system/test_fields.py index be1e76fb52a..2e05ca8a6db 100644 --- a/filebeat/tests/system/test_fields.py +++ b/filebeat/tests/system/test_fields.py @@ -63,7 +63,7 @@ def test_custom_fields_under_root(self): def test_beat_fields(self): """ Checks that it's possible to set a custom shipper name. Also - tests that beat.hostname has values. + tests that agent.hostname has values. """ self.render_config_template( path=os.path.abspath(self.working_dir) + "/test.log", @@ -80,5 +80,6 @@ def test_beat_fields(self): output = self.read_output() doc = output[0] assert doc["host.name"] == "testShipperName" + assert doc["agent.name"] == "testShipperName" assert doc["agent.hostname"] == socket.gethostname() assert "fields" not in doc diff --git a/filebeat/tests/system/test_modules.py b/filebeat/tests/system/test_modules.py index 37e5e63537a..23b517c0391 100644 --- a/filebeat/tests/system/test_modules.py +++ b/filebeat/tests/system/test_modules.py @@ -214,7 +214,7 @@ def _test_expected_events(self, test_file, objects): def clean_keys(obj): # These keys are host dependent - host_keys = ["host.name", "agent.hostname", "agent.type", "agent.ephemeral_id", "agent.id"] + host_keys = ["host.name", "agent.name", "agent.hostname", "agent.type", "agent.ephemeral_id", "agent.id"] # The create timestamps area always new time_keys = ["event.created"] # source path and agent.version can be different for each run diff --git a/heartbeat/cmd/root.go b/heartbeat/cmd/root.go index 8e24b28976b..a2b253a535e 100644 --- a/heartbeat/cmd/root.go +++ b/heartbeat/cmd/root.go @@ -41,7 +41,7 @@ var RootCmd *cmd.BeatsRootCmd func init() { settings := instance.Settings{ Name: Name, - Processing: processing.MakeDefaultSupport(true, processing.WithECS, processing.WithBeatMeta("agent")), + Processing: processing.MakeDefaultSupport(true, processing.WithECS, processing.WithAgentMeta()), HasDashboards: false, } RootCmd = cmd.GenRootCmdWithSettings(beater.New, settings) diff --git a/heartbeat/docs/fields.asciidoc b/heartbeat/docs/fields.asciidoc index 05507f95dc5..bdcfda7fa0d 100644 --- a/heartbeat/docs/fields.asciidoc +++ b/heartbeat/docs/fields.asciidoc @@ -40,7 +40,8 @@ Contains common beat fields available in all event types. *`agent.hostname`*:: + -- -Hostname of the agent. +Deprecated - use agent.name or agent.id to identify an agent. Hostname of the agent. + type: keyword diff --git a/heartbeat/include/fields.go b/heartbeat/include/fields.go index a449cc5fe7e..137a53cce86 100644 --- a/heartbeat/include/fields.go +++ b/heartbeat/include/fields.go @@ -32,5 +32,5 @@ func init() { // AssetFieldsYml returns asset data. // This is the base64 encoded gzipped contents of fields.yml. func AssetFieldsYml() string { - return "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" + return "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" } diff --git a/journalbeat/docs/fields.asciidoc b/journalbeat/docs/fields.asciidoc index b568e32ed97..c98fa74ebc7 100644 --- a/journalbeat/docs/fields.asciidoc +++ b/journalbeat/docs/fields.asciidoc @@ -33,7 +33,8 @@ Contains common beat fields available in all event types. *`agent.hostname`*:: + -- -Hostname of the agent. +Deprecated - use agent.name or agent.id to identify an agent. Hostname of the agent. + type: keyword diff --git a/journalbeat/include/fields.go b/journalbeat/include/fields.go index 5d1d9a80b6a..207cf4a53e9 100644 --- a/journalbeat/include/fields.go +++ b/journalbeat/include/fields.go @@ -32,5 +32,5 @@ func init() { // AssetFieldsYml returns asset data. // This is the base64 encoded gzipped contents of fields.yml. func AssetFieldsYml() string { - return "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" + return "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" } diff --git a/libbeat/_meta/fields.common.yml b/libbeat/_meta/fields.common.yml index fb76c921c97..c93e5b4fc42 100644 --- a/libbeat/_meta/fields.common.yml +++ b/libbeat/_meta/fields.common.yml @@ -6,7 +6,9 @@ fields: - name: agent.hostname type: keyword - description: Hostname of the agent. + description: > + Deprecated - use agent.name or agent.id to identify an agent. + Hostname of the agent. - name: beat.timezone type: alias diff --git a/libbeat/publisher/processing/default.go b/libbeat/publisher/processing/default.go index dea3fe88fd8..f020423b733 100644 --- a/libbeat/publisher/processing/default.go +++ b/libbeat/publisher/processing/default.go @@ -76,14 +76,14 @@ type builtinModifier func(beat.Info) common.MapStr // MakeDefaultBeatSupport automatically adds the `ecs.version`, `host.name` and `agent.X` fields // to each event. func MakeDefaultBeatSupport(normalize bool) SupportFactory { - return MakeDefaultSupport(normalize, WithECS, WithHost, WithBeatMeta("agent")) + return MakeDefaultSupport(normalize, WithECS, WithHost, WithAgentMeta()) } // MakeDefaultObserverSupport creates a new SupportFactory based on NewDefaultSupport. // MakeDefaultObserverSupport automatically adds the `ecs.version` and `observer.X` fields // to each event. func MakeDefaultObserverSupport(normalize bool) SupportFactory { - return MakeDefaultSupport(normalize, WithECS, WithBeatMeta("observer")) + return MakeDefaultSupport(normalize, WithECS, WithObserverMeta()) } // MakeDefaultSupport creates a new SupportFactory for use with the publisher pipeline. @@ -139,21 +139,42 @@ var WithHost modifier = builtinModifier(func(info beat.Info) common.MapStr { } }) -// WithBeatMeta adds beat meta information as builtin fields to a processing pipeline. -// The `key` parameter defines the field to be used. -func WithBeatMeta(key string) modifier { +// WithAgentMeta adds agent meta information as builtin fields to a processing +// pipeline. +func WithAgentMeta() modifier { return builtinModifier(func(info beat.Info) common.MapStr { metadata := common.MapStr{ - "type": info.Beat, "ephemeral_id": info.EphemeralID.String(), - "hostname": info.Hostname, "id": info.ID.String(), + "name": info.Hostname, + "type": info.Beat, + "version": info.Version, + // hostname is deprecated. To be removed for 8.0. It's not in ECS. + // See https://github.com/elastic/beats/issues/16377. + "hostname": info.Hostname, + } + if info.Name != "" { + metadata["name"] = info.Name + } + return common.MapStr{"agent": metadata} + }) +} + +// WithObserverMeta adds beat meta information as builtin fields to a processing +// pipeline. +func WithObserverMeta() modifier { + return builtinModifier(func(info beat.Info) common.MapStr { + metadata := common.MapStr{ + "type": info.Beat, // Per ECS this is not a valid type value. + "ephemeral_id": info.EphemeralID.String(), // Not in ECS. + "hostname": info.Hostname, + "id": info.ID.String(), // Not in ECS. "version": info.Version, } if info.Name != info.Hostname { metadata.Put("name", info.Name) } - return common.MapStr{key: metadata} + return common.MapStr{"observer": metadata} }) } diff --git a/libbeat/publisher/processing/default_test.go b/libbeat/publisher/processing/default_test.go index ba6a6042993..967119fcd39 100644 --- a/libbeat/publisher/processing/default_test.go +++ b/libbeat/publisher/processing/default_test.go @@ -184,6 +184,7 @@ func TestProcessorsConfigs(t *testing.T) { "agent": common.MapStr{ "ephemeral_id": "123e4567-e89b-12d3-a456-426655440000", "hostname": "test.host.name", + "name": "test.host.name", "id": "123e4567-e89b-12d3-a456-426655440001", "type": "test", "version": "0.1", diff --git a/metricbeat/docs/fields.asciidoc b/metricbeat/docs/fields.asciidoc index 82bdce6ea1e..0e3d1530349 100644 --- a/metricbeat/docs/fields.asciidoc +++ b/metricbeat/docs/fields.asciidoc @@ -4631,7 +4631,8 @@ Contains common beat fields available in all event types. *`agent.hostname`*:: + -- -Hostname of the agent. +Deprecated - use agent.name or agent.id to identify an agent. Hostname of the agent. + type: keyword diff --git a/packetbeat/docs/fields.asciidoc b/packetbeat/docs/fields.asciidoc index f76a5d2da72..2b273f13379 100644 --- a/packetbeat/docs/fields.asciidoc +++ b/packetbeat/docs/fields.asciidoc @@ -452,7 +452,8 @@ Contains common beat fields available in all event types. *`agent.hostname`*:: + -- -Hostname of the agent. +Deprecated - use agent.name or agent.id to identify an agent. Hostname of the agent. + type: keyword diff --git a/packetbeat/include/fields.go b/packetbeat/include/fields.go index 68beee23b26..805706ca77f 100644 --- a/packetbeat/include/fields.go +++ b/packetbeat/include/fields.go @@ -32,5 +32,5 @@ func init() { // AssetFieldsYml returns asset data. // This is the base64 encoded gzipped contents of fields.yml. func AssetFieldsYml() string { - return "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" + return "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" } diff --git a/packetbeat/magefile.go b/packetbeat/magefile.go index 8e381260a92..6aade6766cb 100644 --- a/packetbeat/magefile.go +++ b/packetbeat/magefile.go @@ -36,6 +36,8 @@ import ( // mage:import "github.com/elastic/beats/v7/dev-tools/mage/target/common" // mage:import + _ "github.com/elastic/beats/v7/dev-tools/mage/target/unittest" + // mage:import _ "github.com/elastic/beats/v7/dev-tools/mage/target/integtest/notests" // mage:import _ "github.com/elastic/beats/v7/dev-tools/mage/target/test" diff --git a/packetbeat/tests/system/test_0099_golden_files.py b/packetbeat/tests/system/test_0099_golden_files.py index 5a543af2251..5f747a3c83c 100644 --- a/packetbeat/tests/system/test_0099_golden_files.py +++ b/packetbeat/tests/system/test_0099_golden_files.py @@ -66,6 +66,7 @@ def clean_keys(obj): "agent.ephemeral_id", "agent.hostname", "agent.id", + "agent.name", "agent.type", "agent.version", "ecs.version", diff --git a/winlogbeat/docs/fields.asciidoc b/winlogbeat/docs/fields.asciidoc index ba31d833443..3763ebc12db 100644 --- a/winlogbeat/docs/fields.asciidoc +++ b/winlogbeat/docs/fields.asciidoc @@ -36,7 +36,8 @@ Contains common beat fields available in all event types. *`agent.hostname`*:: + -- -Hostname of the agent. +Deprecated - use agent.name or agent.id to identify an agent. Hostname of the agent. + type: keyword diff --git a/winlogbeat/include/fields.go b/winlogbeat/include/fields.go index 896d4ad5d5a..40c4454d9f2 100644 --- a/winlogbeat/include/fields.go +++ b/winlogbeat/include/fields.go @@ -32,5 +32,5 @@ func init() { // AssetBuildFieldsFieldsCommonYml returns asset data. // This is the base64 encoded gzipped contents of build/fields/fields.common.yml. func AssetBuildFieldsFieldsCommonYml() string { - return "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" + return "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" } diff --git a/x-pack/functionbeat/docs/fields.asciidoc b/x-pack/functionbeat/docs/fields.asciidoc index 06912ba9e88..f12ef78112a 100644 --- a/x-pack/functionbeat/docs/fields.asciidoc +++ b/x-pack/functionbeat/docs/fields.asciidoc @@ -33,7 +33,8 @@ Contains common beat fields available in all event types. *`agent.hostname`*:: + -- -Hostname of the agent. +Deprecated - use agent.name or agent.id to identify an agent. Hostname of the agent. + type: keyword diff --git a/x-pack/functionbeat/include/fields.go b/x-pack/functionbeat/include/fields.go index 884bb2e208e..bf181ef89d3 100644 --- a/x-pack/functionbeat/include/fields.go +++ b/x-pack/functionbeat/include/fields.go @@ -19,5 +19,5 @@ func init() { // AssetFieldsYml returns asset data. // This is the base64 encoded gzipped contents of fields.yml. func AssetFieldsYml() string { - return "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" + return "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" } From 7d0a45a482dd5bdab076867e7c73402fdfffd2b0 Mon Sep 17 00:00:00 2001 From: Ivan Fernandez Calvo Date: Mon, 4 May 2020 11:36:26 +0200 Subject: [PATCH 081/116] test: build docker image (#17905) * feat: push docker images * fix: retrict a little the changes that trigger a full build * fix: restore use of gitcheckout * Update .ci/packaging.groovy --- .ci/packaging.groovy | 50 +++++++++++++++++++++++++++++++++++++++----- Jenkinsfile | 4 ++-- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/.ci/packaging.groovy b/.ci/packaging.groovy index 32d2d428c7f..9b10bb2606d 100644 --- a/.ci/packaging.groovy +++ b/.ci/packaging.groovy @@ -125,11 +125,51 @@ pipeline { } def pushCIDockerImages(){ - sh(label: 'Push Docker image', script: ''' - if [ -n "$(command -v docker)" ]; then - docker images || true - fi - ''') + catchError(buildResult: 'UNSTABLE', message: 'Unable to push Docker images', stageResult: 'FAILURE') { + if ("${env.BEATS_FOLDER}" == "auditbeat"){ + tagAndPush('auditbeat-oss') + } else if ("${env.BEATS_FOLDER}" == "filebeat") { + tagAndPush('filebeat-oss') + } else if ("${env.BEATS_FOLDER}" == "heartbeat"){ + tagAndPush('heartbeat') + tagAndPush('heartbeat-oss') + } else if ("${env.BEATS_FOLDER}" == "journalbeat"){ + tagAndPush('journalbeat') + tagAndPush('journalbeat-oss') + } else if ("${env.BEATS_FOLDER}" == "metricbeat"){ + tagAndPush('metricbeat-oss') + } else if ("${env.BEATS_FOLDER}" == "packetbeat"){ + tagAndPush('packetbeat') + tagAndPush('packetbeat-oss') + } else if ("${env.BEATS_FOLDER}" == "x-pack/auditbeat"){ + tagAndPush('auditbeat') + } else if ("${env.BEATS_FOLDER}" == "x-pack/elastic-agent") { + tagAndPush('elastic-agent') + } else if ("${env.BEATS_FOLDER}" == "x-pack/filebeat"){ + tagAndPush('filebeat') + } else if ("${env.BEATS_FOLDER}" == "x-pack/metricbeat"){ + tagAndPush('metricbeat') + } + } +} + +def tagAndPush(name){ + def libbetaVer = sh(label: 'Get libbeat version', script: 'grep defaultBeatVersion ${BASE_DIR}/libbeat/version/version.go|cut -d "=" -f 2|tr -d \\"', returnStdout: true)?.trim() + if("${env.SNAPSHOT}" == "true"){ + libbetaVer += "-SNAPSHOT" + } + def oldName = "${DOCKER_REGISTRY}/beats/${name}:${libbetaVer}" + def newName = "${DOCKER_REGISTRY}/observability-ci/${name}:${libbetaVer}" + def commitName = "${DOCKER_REGISTRY}/observability-ci/${name}:${env.GIT_BASE_COMMIT}" + dockerLogin(secret: "${DOCKERELASTIC_SECRET}", registry: "${DOCKER_REGISTRY}") + retry(3){ + sh(label:'Change tag and push', script: """ + docker tag ${oldName} ${newName} + docker push ${newName} + docker tag ${oldName} ${commitName} + docker push ${commitName} + """) + } } def release(){ diff --git a/Jenkinsfile b/Jenkinsfile index 45eee99bb45..d38373d4f84 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -918,7 +918,7 @@ def isChangedOSSCode(patterns) { "^libbeat/.*", "^testing/.*", "^dev-tools/.*", - "^\\.ci/.*", + "^\\.ci/scripts/.*", ] allPatterns.addAll(patterns) return isChanged(allPatterns) @@ -932,7 +932,7 @@ def isChangedXPackCode(patterns) { "^dev-tools/.*", "^testing/.*", "^x-pack/libbeat/.*", - "^\\.ci/.*", + "^\\.ci/scripts/.*", ] allPatterns.addAll(patterns) return isChanged(allPatterns) From facc0adc8c7b768aad032d3cf9227befa58c4708 Mon Sep 17 00:00:00 2001 From: Michal Pristas Date: Mon, 4 May 2020 11:48:01 +0200 Subject: [PATCH 082/116] [Elastic-Agent] Do not require unnecessary configuration (#18003) * log paths within data * changelog * removed file * empty config allowed * configuration up to date and unified * changelog * unified * switched path * unnecessary type specifier * logs-output * var run to data path * inject run so meta.json is not created at root of data --- x-pack/elastic-agent/CHANGELOG.asciidoc | 1 + x-pack/elastic-agent/_meta/common.p2.yml | 170 +++++++++--------- .../_meta/common.reference.p2.yml | 170 +++++++++--------- .../_meta/elastic-agent.docker.yml | 152 +++++++++------- .../_meta/elastic-agent.fleet.yml | 103 ++++++----- x-pack/elastic-agent/_meta/elastic-agent.yml | 151 +++++++++------- x-pack/elastic-agent/elastic-agent.docker.yml | 152 +++++++++------- .../elastic-agent/elastic-agent.reference.yml | 170 +++++++++--------- x-pack/elastic-agent/elastic-agent.yml | 170 +++++++++--------- .../pkg/agent/application/config.go | 8 +- .../agent/application/configuration_embed.go | 2 +- .../pkg/agent/application/managed_mode.go | 6 +- .../pkg/agent/application/stream.go | 2 +- .../pkg/agent/operation/config/config.go | 9 + .../pkg/agent/operation/operator.go | 16 +- x-pack/elastic-agent/pkg/artifact/config.go | 15 ++ .../pkg/core/plugin/app/monitoring/monitor.go | 5 +- .../pkg/core/plugin/app/start.go | 2 +- .../pkg/core/plugin/process/config.go | 9 + .../pkg/core/plugin/retry/config.go | 13 +- 20 files changed, 722 insertions(+), 604 deletions(-) diff --git a/x-pack/elastic-agent/CHANGELOG.asciidoc b/x-pack/elastic-agent/CHANGELOG.asciidoc index 8b635c85ae4..998d310c74d 100644 --- a/x-pack/elastic-agent/CHANGELOG.asciidoc +++ b/x-pack/elastic-agent/CHANGELOG.asciidoc @@ -44,4 +44,5 @@ - Allow CLI overrides of paths {pull}17781[17781] - Enable Filebeat input: S3, Azureeventhub, cloudfoundry, httpjson, netflow, o365audit. {pull}17909[17909] - Use data subfolder as default for process logs {pull}17960[17960] +- Do not require unnecessary configuration {pull}18003[18003] - Enable debug log level for Metricbeat and Filebeat when run under the Elastic Agent. {pull}17935[17935] diff --git a/x-pack/elastic-agent/_meta/common.p2.yml b/x-pack/elastic-agent/_meta/common.p2.yml index 651f02282dd..6ea3b10c033 100644 --- a/x-pack/elastic-agent/_meta/common.p2.yml +++ b/x-pack/elastic-agent/_meta/common.p2.yml @@ -23,99 +23,99 @@ datasources: - metricset: filesystem dataset: system.filesystem -management: - # Mode of management, the Elastic Agent support two modes of operation: - # - # local: The Elastic Agent will expect to find the inputs configuration in the local file. - # - # Default is local. - mode: "local" +# management: +# # Mode of management, the Elastic Agent support two modes of operation: +# # +# # local: The Elastic Agent will expect to find the inputs configuration in the local file. +# # +# # Default is local. +# mode: "local" - fleet: - access_token: "" - kibana: - # kibana minimal configuration - host: "localhost:5601" - ssl.certificate_authorities: ["/etc/pki/root/ca.pem"] +# fleet: +# access_token: "" +# kibana: +# # kibana minimal configuration +# host: "localhost:5601" +# ssl.certificate_authorities: ["/etc/pki/root/ca.pem"] - # optional values - #protocol: "https" - #username: "elastic" - #password: "changeme" - #path: "" - #ssl.verification_mode: full - #ssl.supported_protocols: [TLSv1.0, TLSv1.1, TLSv1.2] - #ssl.cipher_suites: [] - #ssl.curve_types: [] +# # optional values +# #protocol: "https" +# #username: "elastic" +# #password: "changeme" +# #path: "" +# #ssl.verification_mode: full +# #ssl.supported_protocols: [TLSv1.0, TLSv1.1, TLSv1.2] +# #ssl.cipher_suites: [] +# #ssl.curve_types: [] - reporting: - log: - # format in which logs will be written, options are json or default. - format: "default" - fleet: - # enables fleet reporter. fleet reporting can be enabled only in fleet management.mode. - enabled: false +# reporting: +# log: +# # format in which logs will be written, options are json or default. +# format: "default" +# fleet: +# # enables fleet reporter. fleet reporting can be enabled only in fleet management.mode. +# enabled: false - # Reporting threshold indicates how many events should be kept in-memory before reporting them to fleet. - reporting_threshold: 10000 +# # Reporting threshold indicates how many events should be kept in-memory before reporting them to fleet. +# reporting_threshold: 10000 - # Frequency used to check the queue of events to be sent out to fleet. - reporting_check_frequency_sec: 30 +# # Frequency used to check the queue of events to be sent out to fleet. +# reporting_check_frequency_sec: 30 - # Allow fleet to reload his configuration locally on disk. - # Notes: Only specific process configuration will be reloaded. - reload: - # enabled configure the Elastic Agent to reload or not the local configuration. - # - # Default is true - enabled: true +# # Allow fleet to reload his configuration locally on disk. +# # Notes: Only specific process configuration will be reloaded. +# reload: +# # enabled configure the Elastic Agent to reload or not the local configuration. +# # +# # Default is true +# enabled: true - # period define how frequent we should look for changes in the configuration. - period: 10s +# # period define how frequent we should look for changes in the configuration. +# period: 10s -download: - # source of the artifacts, requires elastic like structure and naming of the binaries - # e.g /windows-x86.zip - sourceURI: "https://artifacts.elastic.co/downloads/beats/" - # path to the directory containing downloaded packages - target_directory: "${path.data}/downloads" - # timeout for downloading package - timeout: 30s - # file path to a public key used for verifying downloaded artifacts - # if not file is present agent will try to load public key from elastic.co website. - pgpfile: "${path.data}/elastic.pgp" - # install_path describes the location of installed packages/programs. It is also used - # for reading program specifications. - install_path: "${path.data}/install" +# download: +# # source of the artifacts, requires elastic like structure and naming of the binaries +# # e.g /windows-x86.zip +# sourceURI: "https://artifacts.elastic.co/downloads/beats/" +# # path to the directory containing downloaded packages +# target_directory: "${path.data}/downloads" +# # timeout for downloading package +# timeout: 30s +# # file path to a public key used for verifying downloaded artifacts +# # if not file is present agent will try to load public key from elastic.co website. +# pgpfile: "${path.data}/elastic.pgp" +# # install_path describes the location of installed packages/programs. It is also used +# # for reading program specifications. +# install_path: "${path.data}/install" -process: - # minimal port number for spawned processes - min_port: 10000 - # maximum port number for spawned processes - max_port: 30000 - # timeout for creating new processes. when process is not successfully created by this timeout - # start operation is considered a failure - spawn_timeout: 30s +# process: +# # minimal port number for spawned processes +# min_port: 10000 +# # maximum port number for spawned processes +# max_port: 30000 +# # timeout for creating new processes. when process is not successfully created by this timeout +# # start operation is considered a failure +# spawn_timeout: 30s -retry: - # Enabled determines whether retry is possible. Default is false. - enabled: true - # RetriesCount specifies number of retries. Default is 3. - # Retry count of 1 means it will be retried one time after one failure. - retriesCount: 3 - # Delay specifies delay in ms between retries. Default is 30s - delay: 30s - # MaxDelay specifies maximum delay in ms between retries. Default is 300s - maxDelay: 5m - # Exponential determines whether delay is treated as exponential. - # With 30s delay and 3 retries: 30, 60, 120s - # Default is false - exponential: false +# retry: +# # Enabled determines whether retry is possible. Default is false. +# enabled: true +# # RetriesCount specifies number of retries. Default is 3. +# # Retry count of 1 means it will be retried one time after one failure. +# retriesCount: 3 +# # Delay specifies delay in ms between retries. Default is 30s +# delay: 30s +# # MaxDelay specifies maximum delay in ms between retries. Default is 300s +# maxDelay: 5m +# # Exponential determines whether delay is treated as exponential. +# # With 30s delay and 3 retries: 30, 60, 120s +# # Default is false +# exponential: false -settings.monitoring: - # enabled turns on monitoring of running processes - enabled: false - # enables log monitoring - logs: false - # enables metrics monitoring - metrics: false +# settings.monitoring: +# # enabled turns on monitoring of running processes +# enabled: false +# # enables log monitoring +# logs: false +# # enables metrics monitoring +# metrics: false diff --git a/x-pack/elastic-agent/_meta/common.reference.p2.yml b/x-pack/elastic-agent/_meta/common.reference.p2.yml index eaa97536e0b..6ea3b10c033 100644 --- a/x-pack/elastic-agent/_meta/common.reference.p2.yml +++ b/x-pack/elastic-agent/_meta/common.reference.p2.yml @@ -23,99 +23,99 @@ datasources: - metricset: filesystem dataset: system.filesystem -management: - # Mode of management, the Elastic Agent currently only support the following mode: - # - # local: The Elastic Agent will expect to find the inputs configuration in the local file. - # - # Default is local. - mode: local +# management: +# # Mode of management, the Elastic Agent support two modes of operation: +# # +# # local: The Elastic Agent will expect to find the inputs configuration in the local file. +# # +# # Default is local. +# mode: "local" - fleet: - access_token: "" - kibana: - # kibana minimal configuration - host: "localhost:5601" - ssl.certificate_authorities: ["/etc/pki/root/ca.pem"] +# fleet: +# access_token: "" +# kibana: +# # kibana minimal configuration +# host: "localhost:5601" +# ssl.certificate_authorities: ["/etc/pki/root/ca.pem"] - # optional values - #protocol: "https" - #username: "elastic" - #password: "changeme" - #path: "" - #ssl.verification_mode: full - #ssl.supported_protocols: [TLSv1.0, TLSv1.1, TLSv1.2] - #ssl.cipher_suites: [] - #ssl.curve_types: [] +# # optional values +# #protocol: "https" +# #username: "elastic" +# #password: "changeme" +# #path: "" +# #ssl.verification_mode: full +# #ssl.supported_protocols: [TLSv1.0, TLSv1.1, TLSv1.2] +# #ssl.cipher_suites: [] +# #ssl.curve_types: [] - reporting: - log: - # format in which logs will be written, options are json or default. - format: "default" - fleet: - # enables fleet reporter. fleet reporting can be enabled only in fleet management.mode. - enabled: false +# reporting: +# log: +# # format in which logs will be written, options are json or default. +# format: "default" +# fleet: +# # enables fleet reporter. fleet reporting can be enabled only in fleet management.mode. +# enabled: false - # Reporting threshold indicates how many events should be kept in-memory before reporting them to fleet. - reporting_threshold: 10000 +# # Reporting threshold indicates how many events should be kept in-memory before reporting them to fleet. +# reporting_threshold: 10000 - # Frequency used to check the queue of events to be sent out to fleet. - reporting_check_frequency_sec: 30 +# # Frequency used to check the queue of events to be sent out to fleet. +# reporting_check_frequency_sec: 30 - # Allow fleet to reload his configuration locally on disk. - # Notes: Only specific process configuration will be reloaded. - reload: - # enabled configure the Elastic Agent to reload or not the local configuration. - # - # Default is true - enabled: true +# # Allow fleet to reload his configuration locally on disk. +# # Notes: Only specific process configuration will be reloaded. +# reload: +# # enabled configure the Elastic Agent to reload or not the local configuration. +# # +# # Default is true +# enabled: true - # period define how frequent we should look for changes in the configuration. - period: 10s +# # period define how frequent we should look for changes in the configuration. +# period: 10s -download: - # source of the artifacts, requires elastic like structure and naming of the binaries - # e.g /windows-x86.zip - sourceURI: "https://artifacts.elastic.co/downloads/beats/" - # path to the directory containing downloaded packages - target_directory: "${path.data}/downloads" - # timeout for downloading package - timeout: 30s - # file path to a public key used for verifying downloaded artifacts - # if not file is present agent will try to load public key from elastic.co website. - pgpfile: "${path.data}/elastic.pgp" - # install_path describes the location of installed packages/programs. It is also used - # for reading program specifications. - install_path: "${path.data}/install" +# download: +# # source of the artifacts, requires elastic like structure and naming of the binaries +# # e.g /windows-x86.zip +# sourceURI: "https://artifacts.elastic.co/downloads/beats/" +# # path to the directory containing downloaded packages +# target_directory: "${path.data}/downloads" +# # timeout for downloading package +# timeout: 30s +# # file path to a public key used for verifying downloaded artifacts +# # if not file is present agent will try to load public key from elastic.co website. +# pgpfile: "${path.data}/elastic.pgp" +# # install_path describes the location of installed packages/programs. It is also used +# # for reading program specifications. +# install_path: "${path.data}/install" -process: - # minimal port number for spawned processes - min_port: 10000 - # maximum port number for spawned processes - max_port: 30000 - # timeout for creating new processes. when process is not successfully created by this timeout - # start operation is considered a failure - spawn_timeout: 30s +# process: +# # minimal port number for spawned processes +# min_port: 10000 +# # maximum port number for spawned processes +# max_port: 30000 +# # timeout for creating new processes. when process is not successfully created by this timeout +# # start operation is considered a failure +# spawn_timeout: 30s -retry: - # enabled determines whether retry is possible. Default is false. - enabled: true - # retries_count specifies number of retries. Default is 3. - # Retry count of 1 means it will be retried one time after one failure. - retries_count: 3 - # delay specifies delay in ms between retries. Default is 30s - delay: 30s - # max_delay specifies maximum delay in ms between retries. Default is 300s - max_delay: 5m - # Exponential determines whether delay is treated as exponential. - # With 30s delay and 3 retries: 30, 60, 120s - # Default is false - exponential: false +# retry: +# # Enabled determines whether retry is possible. Default is false. +# enabled: true +# # RetriesCount specifies number of retries. Default is 3. +# # Retry count of 1 means it will be retried one time after one failure. +# retriesCount: 3 +# # Delay specifies delay in ms between retries. Default is 30s +# delay: 30s +# # MaxDelay specifies maximum delay in ms between retries. Default is 300s +# maxDelay: 5m +# # Exponential determines whether delay is treated as exponential. +# # With 30s delay and 3 retries: 30, 60, 120s +# # Default is false +# exponential: false -settings.monitoring: - # enabled turns on monitoring of running processes - enabled: false - # enables log monitoring - logs: false - # enables metrics monitoring - metrics: false +# settings.monitoring: +# # enabled turns on monitoring of running processes +# enabled: false +# # enables log monitoring +# logs: false +# # enables metrics monitoring +# metrics: false diff --git a/x-pack/elastic-agent/_meta/elastic-agent.docker.yml b/x-pack/elastic-agent/_meta/elastic-agent.docker.yml index dbf7931b690..bccefc9b92d 100644 --- a/x-pack/elastic-agent/_meta/elastic-agent.docker.yml +++ b/x-pack/elastic-agent/_meta/elastic-agent.docker.yml @@ -23,73 +23,99 @@ datasources: - metricset: filesystem dataset: system.filesystem -management: - # Mode of management, the Elastic Agent support two modes of operation: - # - # local: The Elastic Agent will expect to find the inputs configuration in the local file. - # - # Default is local. - mode: "local" +# management: +# # Mode of management, the Elastic Agent support two modes of operation: +# # +# # local: The Elastic Agent will expect to find the inputs configuration in the local file. +# # +# # Default is local. +# mode: "local" - reporting: - log: - # format in which logs will be written, options are json or default. - format: "default" +# fleet: +# access_token: "" +# kibana: +# # kibana minimal configuration +# host: "localhost:5601" +# ssl.certificate_authorities: ["/etc/pki/root/ca.pem"] - # Allow the Elastic Agent to reload his configuration locally on disk. - # Notes: Only specific process configuration will be reloaded. - reload: - # enabled configure the Elastic Agent to reload or not the local configuration. - # - # Default is true - enabled: true +# # optional values +# #protocol: "https" +# #username: "elastic" +# #password: "changeme" +# #path: "" +# #ssl.verification_mode: full +# #ssl.supported_protocols: [TLSv1.0, TLSv1.1, TLSv1.2] +# #ssl.cipher_suites: [] +# #ssl.curve_types: [] - # period define how frequent we should look for changes in the configuration. - period: 10s +# reporting: +# log: +# # format in which logs will be written, options are json or default. +# format: "default" +# fleet: +# # enables fleet reporter. fleet reporting can be enabled only in fleet management.mode. +# enabled: false -download: - # source of the artifacts, requires elastic like structure and naming of the binaries - # e.g /windows-x86.zip - sourceURI: "https://artifacts.elastic.co/downloads/beats/" - # path to the directory containing downloaded packages - target_directory: "${path.data}/downloads" - # timeout for downloading package - timeout: 30s - # file path to a public key used for verifying downloaded artifacts - # if not file is present Elastic Agent will try to load public key from elastic.co website. - pgpfile: "${path.data}/elastic.pgp" - # install_path describes the location of installed packages/programs. It is also used - # for reading program specifications. - install_path: "${path.data}/install" +# # Reporting threshold indicates how many events should be kept in-memory before reporting them to fleet. +# reporting_threshold: 10000 -process: - # minimal port number for spawned processes - min_port: 10000 - # maximum port number for spawned processes - max_port: 30000 - # timeout for creating new processes. when process is not successfully created by this timeout - # start operation is considered a failure - spawn_timeout: 30s +# # Frequency used to check the queue of events to be sent out to fleet. +# reporting_check_frequency_sec: 30 -retry: - # enabled determines whether retry is possible. Default is false. - enabled: true - # retries_count specifies number of retries. Default is 3. - # Retry count of 1 means it will be retried one time after one failure. - retries_count: 3 - # delay specifies delay in ms between retries. Default is 30s - delay: 30s - # max_delay specifies maximum delay in ms between retries. Default is 300s - max_delay: 5m - # Exponential determines whether delay is treated as exponential. - # With 30s delay and 3 retries: 30, 60, 120s - # Default is false - exponential: false +# # Allow fleet to reload his configuration locally on disk. +# # Notes: Only specific process configuration will be reloaded. +# reload: +# # enabled configure the Elastic Agent to reload or not the local configuration. +# # +# # Default is true +# enabled: true -settings.monitoring: - # enabled turns on monitoring of running processes - enabled: false - # enables log monitoring - logs: false - # enables metrics monitoring - metrics: false +# # period define how frequent we should look for changes in the configuration. +# period: 10s + +# download: +# # source of the artifacts, requires elastic like structure and naming of the binaries +# # e.g /windows-x86.zip +# sourceURI: "https://artifacts.elastic.co/downloads/beats/" +# # path to the directory containing downloaded packages +# target_directory: "${path.data}/downloads" +# # timeout for downloading package +# timeout: 30s +# # file path to a public key used for verifying downloaded artifacts +# # if not file is present agent will try to load public key from elastic.co website. +# pgpfile: "${path.data}/elastic.pgp" +# # install_path describes the location of installed packages/programs. It is also used +# # for reading program specifications. +# install_path: "${path.data}/install" + +# process: +# # minimal port number for spawned processes +# min_port: 10000 +# # maximum port number for spawned processes +# max_port: 30000 +# # timeout for creating new processes. when process is not successfully created by this timeout +# # start operation is considered a failure +# spawn_timeout: 30s + +# retry: +# # Enabled determines whether retry is possible. Default is false. +# enabled: true +# # RetriesCount specifies number of retries. Default is 3. +# # Retry count of 1 means it will be retried one time after one failure. +# retriesCount: 3 +# # Delay specifies delay in ms between retries. Default is 30s +# delay: 30s +# # MaxDelay specifies maximum delay in ms between retries. Default is 300s +# maxDelay: 5m +# # Exponential determines whether delay is treated as exponential. +# # With 30s delay and 3 retries: 30, 60, 120s +# # Default is false +# exponential: false + +# settings.monitoring: +# # enabled turns on monitoring of running processes +# enabled: false +# # enables log monitoring +# logs: false +# # enables metrics monitoring +# metrics: false diff --git a/x-pack/elastic-agent/_meta/elastic-agent.fleet.yml b/x-pack/elastic-agent/_meta/elastic-agent.fleet.yml index 8d817c8212e..b8b3eb2ac99 100644 --- a/x-pack/elastic-agent/_meta/elastic-agent.fleet.yml +++ b/x-pack/elastic-agent/_meta/elastic-agent.fleet.yml @@ -2,57 +2,62 @@ # Beats is configured under Fleet, you can define most settings # from the Kibana UI. You can update this file to configure the settings that # are not supported by Fleet. -management: - mode: "fleet" +# management: +# mode: "fleet" - # Add variance between API calls to better distribute the calls. - #jitter: 5s +# # Check in frequency configure the time between calls to fleet to retrieve the new configuration. +# # +# # Default is 30s +# #checkin_frequency: 30s - # The Elastic Agent does Exponential backoff when an error happen. - # - #backoff: - # - # Initial time to wait before retrying the call. - # init: 1s - # - # Maximum time to wait before retrying the call. - # max: 10s +# # Add variance between API calls to better distribute the calls. +# #jitter: 5s -download: - # source of the artifacts, requires elastic like structure and naming of the binaries - # e.g /windows-x86.zip - sourceURI: "https://artifacts.elastic.co/downloads/beats/" - # path to the directory containing downloaded packages - target_directory: "${path.data}/downloads" - # timeout for downloading package - timeout: 30s - # file path to a public key used for verifying downloaded artifacts - # if not file is present Elastic Agent will try to load public key from elastic.co website. - pgpfile: "${path.data}/elastic.pgp" - # install_path describes the location of installed packages/programs. It is also used - # for reading program specifications. - install_path: "${path.data}/install" +# # The Elastic Agent does Exponential backoff when an error happen. +# # +# #backoff: +# # +# # Initial time to wait before retrying the call. +# # init: 1s +# # +# # Maximum time to wait before retrying the call. +# # max: 10s -process: - # minimal port number for spawned processes - min_port: 10000 - # maximum port number for spawned processes - max_port: 30000 - # timeout for creating new processes. when process is not successfully created by this timeout - # start operation is considered a failure - spawn_timeout: 30s +# download: +# # source of the artifacts, requires elastic like structure and naming of the binaries +# # e.g /windows-x86.zip +# sourceURI: "https://artifacts.elastic.co/downloads/beats/" +# # path to the directory containing downloaded packages +# target_directory: "${path.data}/downloads" +# # timeout for downloading package +# timeout: 30s +# # file path to a public key used for verifying downloaded artifacts +# # if not file is present Elastic Agent will try to load public key from elastic.co website. +# pgpfile: "${path.data}/elastic.pgp" +# # install_path describes the location of installed packages/programs. It is also used +# # for reading program specifications. +# install_path: "${path.data}/install" -retry: - # enabled determines whether retry is possible. Default is false. - enabled: true - # retries_count specifies number of retries. Default is 3. - # Retry count of 1 means it will be retried one time after one failure. - retries_count: 3 - # delay specifies delay in ms between retries. Default is 30s - delay: 30s - # max_delay specifies maximum delay in ms between retries. Default is 300s - max_delay: 5m - # Exponential determines whether delay is treated as exponential. - # With 30s delay and 3 retries: 30, 60, 120s - # Default is false - exponential: false +# process: +# # minimal port number for spawned processes +# min_port: 10000 +# # maximum port number for spawned processes +# max_port: 30000 +# # timeout for creating new processes. when process is not successfully created by this timeout +# # start operation is considered a failure +# spawn_timeout: 30s + +# retry: +# # enabled determines whether retry is possible. Default is false. +# enabled: true +# # retries_count specifies number of retries. Default is 3. +# # Retry count of 1 means it will be retried one time after one failure. +# retries_count: 3 +# # delay specifies delay in ms between retries. Default is 30s +# delay: 30s +# # max_delay specifies maximum delay in ms between retries. Default is 300s +# max_delay: 5m +# # Exponential determines whether delay is treated as exponential. +# # With 30s delay and 3 retries: 30, 60, 120s +# # Default is false +# exponential: false diff --git a/x-pack/elastic-agent/_meta/elastic-agent.yml b/x-pack/elastic-agent/_meta/elastic-agent.yml index 12fbfabc3fe..6ea3b10c033 100644 --- a/x-pack/elastic-agent/_meta/elastic-agent.yml +++ b/x-pack/elastic-agent/_meta/elastic-agent.yml @@ -23,74 +23,99 @@ datasources: - metricset: filesystem dataset: system.filesystem -management: - # Mode of management, the Elastic Agent support two modes of operation: - # - # local: The Elastic Agent will expect to find the inputs configuration in the local file. - # - # Default is local. - mode: "local" +# management: +# # Mode of management, the Elastic Agent support two modes of operation: +# # +# # local: The Elastic Agent will expect to find the inputs configuration in the local file. +# # +# # Default is local. +# mode: "local" +# fleet: +# access_token: "" +# kibana: +# # kibana minimal configuration +# host: "localhost:5601" +# ssl.certificate_authorities: ["/etc/pki/root/ca.pem"] - reporting: - log: - # format in which logs will be written, options are json or default. - format: "default" +# # optional values +# #protocol: "https" +# #username: "elastic" +# #password: "changeme" +# #path: "" +# #ssl.verification_mode: full +# #ssl.supported_protocols: [TLSv1.0, TLSv1.1, TLSv1.2] +# #ssl.cipher_suites: [] +# #ssl.curve_types: [] - # Allow the Elastic Agent to reload his configuration locally on disk. - # Notes: Only specific process configuration will be reloaded. - reload: - # enabled configure the Elastic Agent to reload or not the local configuration. - # - # Default is true - enabled: true +# reporting: +# log: +# # format in which logs will be written, options are json or default. +# format: "default" +# fleet: +# # enables fleet reporter. fleet reporting can be enabled only in fleet management.mode. +# enabled: false - # period define how frequent we should look for changes in the configuration. - period: 10s +# # Reporting threshold indicates how many events should be kept in-memory before reporting them to fleet. +# reporting_threshold: 10000 -download: - # source of the artifacts, requires elastic like structure and naming of the binaries - # e.g /windows-x86.zip - sourceURI: "https://artifacts.elastic.co/downloads/beats/" - # path to the directory containing downloaded packages - target_directory: "${path.data}/downloads" - # timeout for downloading package - timeout: 30s - # file path to a public key used for verifying downloaded artifacts - # if not file is present Elastic Agent will try to load public key from elastic.co website. - pgpfile: "${path.data}/elastic.pgp" - # install_path describes the location of installed packages/programs. It is also used - # for reading program specifications. - install_path: "${path.data}/install" +# # Frequency used to check the queue of events to be sent out to fleet. +# reporting_check_frequency_sec: 30 -process: - # minimal port number for spawned processes - min_port: 10000 - # maximum port number for spawned processes - max_port: 30000 - # timeout for creating new processes. when process is not successfully created by this timeout - # start operation is considered a failure - spawn_timeout: 30s +# # Allow fleet to reload his configuration locally on disk. +# # Notes: Only specific process configuration will be reloaded. +# reload: +# # enabled configure the Elastic Agent to reload or not the local configuration. +# # +# # Default is true +# enabled: true -retry: - # enabled determines whether retry is possible. Default is false. - enabled: true - # retries_count specifies number of retries. Default is 3. - # Retry count of 1 means it will be retried one time after one failure. - retries_count: 3 - # delay specifies delay in ms between retries. Default is 30s - delay: 30s - # max_delay specifies maximum delay in ms between retries. Default is 300s - max_delay: 5m - # Exponential determines whether delay is treated as exponential. - # With 30s delay and 3 retries: 30, 60, 120s - # Default is false - exponential: false +# # period define how frequent we should look for changes in the configuration. +# period: 10s -settings.monitoring: - # enabled turns on monitoring of running processes - enabled: false - # enables log monitoring - logs: false - # enables metrics monitoring - metrics: false +# download: +# # source of the artifacts, requires elastic like structure and naming of the binaries +# # e.g /windows-x86.zip +# sourceURI: "https://artifacts.elastic.co/downloads/beats/" +# # path to the directory containing downloaded packages +# target_directory: "${path.data}/downloads" +# # timeout for downloading package +# timeout: 30s +# # file path to a public key used for verifying downloaded artifacts +# # if not file is present agent will try to load public key from elastic.co website. +# pgpfile: "${path.data}/elastic.pgp" +# # install_path describes the location of installed packages/programs. It is also used +# # for reading program specifications. +# install_path: "${path.data}/install" + +# process: +# # minimal port number for spawned processes +# min_port: 10000 +# # maximum port number for spawned processes +# max_port: 30000 +# # timeout for creating new processes. when process is not successfully created by this timeout +# # start operation is considered a failure +# spawn_timeout: 30s + +# retry: +# # Enabled determines whether retry is possible. Default is false. +# enabled: true +# # RetriesCount specifies number of retries. Default is 3. +# # Retry count of 1 means it will be retried one time after one failure. +# retriesCount: 3 +# # Delay specifies delay in ms between retries. Default is 30s +# delay: 30s +# # MaxDelay specifies maximum delay in ms between retries. Default is 300s +# maxDelay: 5m +# # Exponential determines whether delay is treated as exponential. +# # With 30s delay and 3 retries: 30, 60, 120s +# # Default is false +# exponential: false + +# settings.monitoring: +# # enabled turns on monitoring of running processes +# enabled: false +# # enables log monitoring +# logs: false +# # enables metrics monitoring +# metrics: false diff --git a/x-pack/elastic-agent/elastic-agent.docker.yml b/x-pack/elastic-agent/elastic-agent.docker.yml index dbf7931b690..bccefc9b92d 100644 --- a/x-pack/elastic-agent/elastic-agent.docker.yml +++ b/x-pack/elastic-agent/elastic-agent.docker.yml @@ -23,73 +23,99 @@ datasources: - metricset: filesystem dataset: system.filesystem -management: - # Mode of management, the Elastic Agent support two modes of operation: - # - # local: The Elastic Agent will expect to find the inputs configuration in the local file. - # - # Default is local. - mode: "local" +# management: +# # Mode of management, the Elastic Agent support two modes of operation: +# # +# # local: The Elastic Agent will expect to find the inputs configuration in the local file. +# # +# # Default is local. +# mode: "local" - reporting: - log: - # format in which logs will be written, options are json or default. - format: "default" +# fleet: +# access_token: "" +# kibana: +# # kibana minimal configuration +# host: "localhost:5601" +# ssl.certificate_authorities: ["/etc/pki/root/ca.pem"] - # Allow the Elastic Agent to reload his configuration locally on disk. - # Notes: Only specific process configuration will be reloaded. - reload: - # enabled configure the Elastic Agent to reload or not the local configuration. - # - # Default is true - enabled: true +# # optional values +# #protocol: "https" +# #username: "elastic" +# #password: "changeme" +# #path: "" +# #ssl.verification_mode: full +# #ssl.supported_protocols: [TLSv1.0, TLSv1.1, TLSv1.2] +# #ssl.cipher_suites: [] +# #ssl.curve_types: [] - # period define how frequent we should look for changes in the configuration. - period: 10s +# reporting: +# log: +# # format in which logs will be written, options are json or default. +# format: "default" +# fleet: +# # enables fleet reporter. fleet reporting can be enabled only in fleet management.mode. +# enabled: false -download: - # source of the artifacts, requires elastic like structure and naming of the binaries - # e.g /windows-x86.zip - sourceURI: "https://artifacts.elastic.co/downloads/beats/" - # path to the directory containing downloaded packages - target_directory: "${path.data}/downloads" - # timeout for downloading package - timeout: 30s - # file path to a public key used for verifying downloaded artifacts - # if not file is present Elastic Agent will try to load public key from elastic.co website. - pgpfile: "${path.data}/elastic.pgp" - # install_path describes the location of installed packages/programs. It is also used - # for reading program specifications. - install_path: "${path.data}/install" +# # Reporting threshold indicates how many events should be kept in-memory before reporting them to fleet. +# reporting_threshold: 10000 -process: - # minimal port number for spawned processes - min_port: 10000 - # maximum port number for spawned processes - max_port: 30000 - # timeout for creating new processes. when process is not successfully created by this timeout - # start operation is considered a failure - spawn_timeout: 30s +# # Frequency used to check the queue of events to be sent out to fleet. +# reporting_check_frequency_sec: 30 -retry: - # enabled determines whether retry is possible. Default is false. - enabled: true - # retries_count specifies number of retries. Default is 3. - # Retry count of 1 means it will be retried one time after one failure. - retries_count: 3 - # delay specifies delay in ms between retries. Default is 30s - delay: 30s - # max_delay specifies maximum delay in ms between retries. Default is 300s - max_delay: 5m - # Exponential determines whether delay is treated as exponential. - # With 30s delay and 3 retries: 30, 60, 120s - # Default is false - exponential: false +# # Allow fleet to reload his configuration locally on disk. +# # Notes: Only specific process configuration will be reloaded. +# reload: +# # enabled configure the Elastic Agent to reload or not the local configuration. +# # +# # Default is true +# enabled: true -settings.monitoring: - # enabled turns on monitoring of running processes - enabled: false - # enables log monitoring - logs: false - # enables metrics monitoring - metrics: false +# # period define how frequent we should look for changes in the configuration. +# period: 10s + +# download: +# # source of the artifacts, requires elastic like structure and naming of the binaries +# # e.g /windows-x86.zip +# sourceURI: "https://artifacts.elastic.co/downloads/beats/" +# # path to the directory containing downloaded packages +# target_directory: "${path.data}/downloads" +# # timeout for downloading package +# timeout: 30s +# # file path to a public key used for verifying downloaded artifacts +# # if not file is present agent will try to load public key from elastic.co website. +# pgpfile: "${path.data}/elastic.pgp" +# # install_path describes the location of installed packages/programs. It is also used +# # for reading program specifications. +# install_path: "${path.data}/install" + +# process: +# # minimal port number for spawned processes +# min_port: 10000 +# # maximum port number for spawned processes +# max_port: 30000 +# # timeout for creating new processes. when process is not successfully created by this timeout +# # start operation is considered a failure +# spawn_timeout: 30s + +# retry: +# # Enabled determines whether retry is possible. Default is false. +# enabled: true +# # RetriesCount specifies number of retries. Default is 3. +# # Retry count of 1 means it will be retried one time after one failure. +# retriesCount: 3 +# # Delay specifies delay in ms between retries. Default is 30s +# delay: 30s +# # MaxDelay specifies maximum delay in ms between retries. Default is 300s +# maxDelay: 5m +# # Exponential determines whether delay is treated as exponential. +# # With 30s delay and 3 retries: 30, 60, 120s +# # Default is false +# exponential: false + +# settings.monitoring: +# # enabled turns on monitoring of running processes +# enabled: false +# # enables log monitoring +# logs: false +# # enables metrics monitoring +# metrics: false diff --git a/x-pack/elastic-agent/elastic-agent.reference.yml b/x-pack/elastic-agent/elastic-agent.reference.yml index 2fb40550bc7..1db6a77cca9 100644 --- a/x-pack/elastic-agent/elastic-agent.reference.yml +++ b/x-pack/elastic-agent/elastic-agent.reference.yml @@ -28,99 +28,99 @@ datasources: - metricset: filesystem dataset: system.filesystem -management: - # Mode of management, the Elastic Agent currently only support the following mode: - # - # local: The Elastic Agent will expect to find the inputs configuration in the local file. - # - # Default is local. - mode: local +# management: +# # Mode of management, the Elastic Agent support two modes of operation: +# # +# # local: The Elastic Agent will expect to find the inputs configuration in the local file. +# # +# # Default is local. +# mode: "local" - fleet: - access_token: "" - kibana: - # kibana minimal configuration - host: "localhost:5601" - ssl.certificate_authorities: ["/etc/pki/root/ca.pem"] +# fleet: +# access_token: "" +# kibana: +# # kibana minimal configuration +# host: "localhost:5601" +# ssl.certificate_authorities: ["/etc/pki/root/ca.pem"] - # optional values - #protocol: "https" - #username: "elastic" - #password: "changeme" - #path: "" - #ssl.verification_mode: full - #ssl.supported_protocols: [TLSv1.0, TLSv1.1, TLSv1.2] - #ssl.cipher_suites: [] - #ssl.curve_types: [] +# # optional values +# #protocol: "https" +# #username: "elastic" +# #password: "changeme" +# #path: "" +# #ssl.verification_mode: full +# #ssl.supported_protocols: [TLSv1.0, TLSv1.1, TLSv1.2] +# #ssl.cipher_suites: [] +# #ssl.curve_types: [] - reporting: - log: - # format in which logs will be written, options are json or default. - format: "default" - fleet: - # enables fleet reporter. fleet reporting can be enabled only in fleet management.mode. - enabled: false +# reporting: +# log: +# # format in which logs will be written, options are json or default. +# format: "default" +# fleet: +# # enables fleet reporter. fleet reporting can be enabled only in fleet management.mode. +# enabled: false - # Reporting threshold indicates how many events should be kept in-memory before reporting them to fleet. - reporting_threshold: 10000 +# # Reporting threshold indicates how many events should be kept in-memory before reporting them to fleet. +# reporting_threshold: 10000 - # Frequency used to check the queue of events to be sent out to fleet. - reporting_check_frequency_sec: 30 +# # Frequency used to check the queue of events to be sent out to fleet. +# reporting_check_frequency_sec: 30 - # Allow fleet to reload his configuration locally on disk. - # Notes: Only specific process configuration will be reloaded. - reload: - # enabled configure the Elastic Agent to reload or not the local configuration. - # - # Default is true - enabled: true +# # Allow fleet to reload his configuration locally on disk. +# # Notes: Only specific process configuration will be reloaded. +# reload: +# # enabled configure the Elastic Agent to reload or not the local configuration. +# # +# # Default is true +# enabled: true - # period define how frequent we should look for changes in the configuration. - period: 10s +# # period define how frequent we should look for changes in the configuration. +# period: 10s -download: - # source of the artifacts, requires elastic like structure and naming of the binaries - # e.g /windows-x86.zip - sourceURI: "https://artifacts.elastic.co/downloads/beats/" - # path to the directory containing downloaded packages - target_directory: "${path.data}/downloads" - # timeout for downloading package - timeout: 30s - # file path to a public key used for verifying downloaded artifacts - # if not file is present agent will try to load public key from elastic.co website. - pgpfile: "${path.data}/elastic.pgp" - # install_path describes the location of installed packages/programs. It is also used - # for reading program specifications. - install_path: "${path.data}/install" +# download: +# # source of the artifacts, requires elastic like structure and naming of the binaries +# # e.g /windows-x86.zip +# sourceURI: "https://artifacts.elastic.co/downloads/beats/" +# # path to the directory containing downloaded packages +# target_directory: "${path.data}/downloads" +# # timeout for downloading package +# timeout: 30s +# # file path to a public key used for verifying downloaded artifacts +# # if not file is present agent will try to load public key from elastic.co website. +# pgpfile: "${path.data}/elastic.pgp" +# # install_path describes the location of installed packages/programs. It is also used +# # for reading program specifications. +# install_path: "${path.data}/install" -process: - # minimal port number for spawned processes - min_port: 10000 - # maximum port number for spawned processes - max_port: 30000 - # timeout for creating new processes. when process is not successfully created by this timeout - # start operation is considered a failure - spawn_timeout: 30s +# process: +# # minimal port number for spawned processes +# min_port: 10000 +# # maximum port number for spawned processes +# max_port: 30000 +# # timeout for creating new processes. when process is not successfully created by this timeout +# # start operation is considered a failure +# spawn_timeout: 30s -retry: - # enabled determines whether retry is possible. Default is false. - enabled: true - # retries_count specifies number of retries. Default is 3. - # Retry count of 1 means it will be retried one time after one failure. - retries_count: 3 - # delay specifies delay in ms between retries. Default is 30s - delay: 30s - # max_delay specifies maximum delay in ms between retries. Default is 300s - max_delay: 5m - # Exponential determines whether delay is treated as exponential. - # With 30s delay and 3 retries: 30, 60, 120s - # Default is false - exponential: false +# retry: +# # Enabled determines whether retry is possible. Default is false. +# enabled: true +# # RetriesCount specifies number of retries. Default is 3. +# # Retry count of 1 means it will be retried one time after one failure. +# retriesCount: 3 +# # Delay specifies delay in ms between retries. Default is 30s +# delay: 30s +# # MaxDelay specifies maximum delay in ms between retries. Default is 300s +# maxDelay: 5m +# # Exponential determines whether delay is treated as exponential. +# # With 30s delay and 3 retries: 30, 60, 120s +# # Default is false +# exponential: false -settings.monitoring: - # enabled turns on monitoring of running processes - enabled: false - # enables log monitoring - logs: false - # enables metrics monitoring - metrics: false +# settings.monitoring: +# # enabled turns on monitoring of running processes +# enabled: false +# # enables log monitoring +# logs: false +# # enables metrics monitoring +# metrics: false diff --git a/x-pack/elastic-agent/elastic-agent.yml b/x-pack/elastic-agent/elastic-agent.yml index 24331ef7f9f..1db6a77cca9 100644 --- a/x-pack/elastic-agent/elastic-agent.yml +++ b/x-pack/elastic-agent/elastic-agent.yml @@ -28,99 +28,99 @@ datasources: - metricset: filesystem dataset: system.filesystem -management: - # Mode of management, the Elastic Agent support two modes of operation: - # - # local: The Elastic Agent will expect to find the inputs configuration in the local file. - # - # Default is local. - mode: "local" +# management: +# # Mode of management, the Elastic Agent support two modes of operation: +# # +# # local: The Elastic Agent will expect to find the inputs configuration in the local file. +# # +# # Default is local. +# mode: "local" - fleet: - access_token: "" - kibana: - # kibana minimal configuration - host: "localhost:5601" - ssl.certificate_authorities: ["/etc/pki/root/ca.pem"] +# fleet: +# access_token: "" +# kibana: +# # kibana minimal configuration +# host: "localhost:5601" +# ssl.certificate_authorities: ["/etc/pki/root/ca.pem"] - # optional values - #protocol: "https" - #username: "elastic" - #password: "changeme" - #path: "" - #ssl.verification_mode: full - #ssl.supported_protocols: [TLSv1.0, TLSv1.1, TLSv1.2] - #ssl.cipher_suites: [] - #ssl.curve_types: [] +# # optional values +# #protocol: "https" +# #username: "elastic" +# #password: "changeme" +# #path: "" +# #ssl.verification_mode: full +# #ssl.supported_protocols: [TLSv1.0, TLSv1.1, TLSv1.2] +# #ssl.cipher_suites: [] +# #ssl.curve_types: [] - reporting: - log: - # format in which logs will be written, options are json or default. - format: "default" - fleet: - # enables fleet reporter. fleet reporting can be enabled only in fleet management.mode. - enabled: false +# reporting: +# log: +# # format in which logs will be written, options are json or default. +# format: "default" +# fleet: +# # enables fleet reporter. fleet reporting can be enabled only in fleet management.mode. +# enabled: false - # Reporting threshold indicates how many events should be kept in-memory before reporting them to fleet. - reporting_threshold: 10000 +# # Reporting threshold indicates how many events should be kept in-memory before reporting them to fleet. +# reporting_threshold: 10000 - # Frequency used to check the queue of events to be sent out to fleet. - reporting_check_frequency_sec: 30 +# # Frequency used to check the queue of events to be sent out to fleet. +# reporting_check_frequency_sec: 30 - # Allow fleet to reload his configuration locally on disk. - # Notes: Only specific process configuration will be reloaded. - reload: - # enabled configure the Elastic Agent to reload or not the local configuration. - # - # Default is true - enabled: true +# # Allow fleet to reload his configuration locally on disk. +# # Notes: Only specific process configuration will be reloaded. +# reload: +# # enabled configure the Elastic Agent to reload or not the local configuration. +# # +# # Default is true +# enabled: true - # period define how frequent we should look for changes in the configuration. - period: 10s +# # period define how frequent we should look for changes in the configuration. +# period: 10s -download: - # source of the artifacts, requires elastic like structure and naming of the binaries - # e.g /windows-x86.zip - sourceURI: "https://artifacts.elastic.co/downloads/beats/" - # path to the directory containing downloaded packages - target_directory: "${path.data}/downloads" - # timeout for downloading package - timeout: 30s - # file path to a public key used for verifying downloaded artifacts - # if not file is present agent will try to load public key from elastic.co website. - pgpfile: "${path.data}/elastic.pgp" - # install_path describes the location of installed packages/programs. It is also used - # for reading program specifications. - install_path: "${path.data}/install" +# download: +# # source of the artifacts, requires elastic like structure and naming of the binaries +# # e.g /windows-x86.zip +# sourceURI: "https://artifacts.elastic.co/downloads/beats/" +# # path to the directory containing downloaded packages +# target_directory: "${path.data}/downloads" +# # timeout for downloading package +# timeout: 30s +# # file path to a public key used for verifying downloaded artifacts +# # if not file is present agent will try to load public key from elastic.co website. +# pgpfile: "${path.data}/elastic.pgp" +# # install_path describes the location of installed packages/programs. It is also used +# # for reading program specifications. +# install_path: "${path.data}/install" -process: - # minimal port number for spawned processes - min_port: 10000 - # maximum port number for spawned processes - max_port: 30000 - # timeout for creating new processes. when process is not successfully created by this timeout - # start operation is considered a failure - spawn_timeout: 30s +# process: +# # minimal port number for spawned processes +# min_port: 10000 +# # maximum port number for spawned processes +# max_port: 30000 +# # timeout for creating new processes. when process is not successfully created by this timeout +# # start operation is considered a failure +# spawn_timeout: 30s -retry: - # Enabled determines whether retry is possible. Default is false. - enabled: true - # RetriesCount specifies number of retries. Default is 3. - # Retry count of 1 means it will be retried one time after one failure. - retriesCount: 3 - # Delay specifies delay in ms between retries. Default is 30s - delay: 30s - # MaxDelay specifies maximum delay in ms between retries. Default is 300s - maxDelay: 5m - # Exponential determines whether delay is treated as exponential. - # With 30s delay and 3 retries: 30, 60, 120s - # Default is false - exponential: false +# retry: +# # Enabled determines whether retry is possible. Default is false. +# enabled: true +# # RetriesCount specifies number of retries. Default is 3. +# # Retry count of 1 means it will be retried one time after one failure. +# retriesCount: 3 +# # Delay specifies delay in ms between retries. Default is 30s +# delay: 30s +# # MaxDelay specifies maximum delay in ms between retries. Default is 300s +# maxDelay: 5m +# # Exponential determines whether delay is treated as exponential. +# # With 30s delay and 3 retries: 30, 60, 120s +# # Default is false +# exponential: false -settings.monitoring: - # enabled turns on monitoring of running processes - enabled: false - # enables log monitoring - logs: false - # enables metrics monitoring - metrics: false +# settings.monitoring: +# # enabled turns on monitoring of running processes +# enabled: false +# # enables log monitoring +# logs: false +# # enables metrics monitoring +# metrics: false diff --git a/x-pack/elastic-agent/pkg/agent/application/config.go b/x-pack/elastic-agent/pkg/agent/application/config.go index edec6b4c7f8..57945ba465b 100644 --- a/x-pack/elastic-agent/pkg/agent/application/config.go +++ b/x-pack/elastic-agent/pkg/agent/application/config.go @@ -32,7 +32,13 @@ type Config struct { } func localDefaultConfig() *Config { - return &Config{} + localModeCfg, _ := config.NewConfigFrom(map[string]interface{}{ + "mode": "local", + }) + + return &Config{ + Management: localModeCfg, + } } type managementMode int diff --git a/x-pack/elastic-agent/pkg/agent/application/configuration_embed.go b/x-pack/elastic-agent/pkg/agent/application/configuration_embed.go index db61023400e..7ccc52a40a8 100644 --- a/x-pack/elastic-agent/pkg/agent/application/configuration_embed.go +++ b/x-pack/elastic-agent/pkg/agent/application/configuration_embed.go @@ -15,7 +15,7 @@ var DefaultAgentFleetConfig []byte func init() { // Packed File // _meta/elastic-agent.fleet.yml - unpacked := packer.MustUnpack("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") + unpacked := packer.MustUnpack("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") raw, ok := unpacked["_meta/elastic-agent.fleet.yml"] if !ok { // ensure we have something loaded. diff --git a/x-pack/elastic-agent/pkg/agent/application/managed_mode.go b/x-pack/elastic-agent/pkg/agent/application/managed_mode.go index 08ff4c7a479..c440c6e1243 100644 --- a/x-pack/elastic-agent/pkg/agent/application/managed_mode.go +++ b/x-pack/elastic-agent/pkg/agent/application/managed_mode.go @@ -24,10 +24,6 @@ import ( logreporter "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/reporter/log" ) -type managementCfg struct { - Management *config.Config `config:"management"` -} - type apiClient interface { Send( method string, @@ -92,7 +88,7 @@ func newManaged( } // Extract only management related configuration. - managementCfg := &managementCfg{} + managementCfg := &Config{} if err := rawConfig.Unpack(managementCfg); err != nil { return nil, errors.New(err, fmt.Sprintf("fail to unpack configuration from %s", path), diff --git a/x-pack/elastic-agent/pkg/agent/application/stream.go b/x-pack/elastic-agent/pkg/agent/application/stream.go index 1e8a1f10048..5d8880a9e07 100644 --- a/x-pack/elastic-agent/pkg/agent/application/stream.go +++ b/x-pack/elastic-agent/pkg/agent/application/stream.go @@ -72,7 +72,7 @@ func streamFactory(ctx context.Context, cfg *config.Config, client sender, r rep } func newOperator(ctx context.Context, log *logger.Logger, id routingKey, config *config.Config, r reporter, m monitoring.Monitor) (*operation.Operator, error) { - operatorConfig := &operatorCfg.Config{} + operatorConfig := operatorCfg.DefaultConfig() if err := config.Unpack(&operatorConfig); err != nil { return nil, err } diff --git a/x-pack/elastic-agent/pkg/agent/operation/config/config.go b/x-pack/elastic-agent/pkg/agent/operation/config/config.go index f1c15df7007..f54737151c9 100644 --- a/x-pack/elastic-agent/pkg/agent/operation/config/config.go +++ b/x-pack/elastic-agent/pkg/agent/operation/config/config.go @@ -17,3 +17,12 @@ type Config struct { DownloadConfig *artifact.Config `yaml:"download" config:"download"` } + +// DefaultConfig creates a config with pre-set default values. +func DefaultConfig() *Config { + return &Config{ + ProcessConfig: process.DefaultConfig(), + RetryConfig: retry.DefaultConfig(), + DownloadConfig: artifact.DefaultConfig(), + } +} diff --git a/x-pack/elastic-agent/pkg/agent/operation/operator.go b/x-pack/elastic-agent/pkg/agent/operation/operator.go index 4a2b2208ee8..c6c47d444ef 100644 --- a/x-pack/elastic-agent/pkg/agent/operation/operator.go +++ b/x-pack/elastic-agent/pkg/agent/operation/operator.go @@ -10,7 +10,6 @@ import ( "os" "strings" "sync" - "time" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/configrequest" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/errors" @@ -23,7 +22,6 @@ import ( "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/core/logger" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/core/plugin/app" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/core/plugin/app/monitoring" - "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/core/plugin/retry" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/core/plugin/state" rconfig "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/core/remoteconfig/grpc" ) @@ -65,7 +63,7 @@ func NewOperator( eventProcessor callbackHooks, monitor monitoring.Monitor) (*Operator, error) { - operatorConfig := defaultOperatorConfig() + operatorConfig := operatorCfg.DefaultConfig() if err := config.Unpack(&operatorConfig); err != nil { return nil, err } @@ -99,18 +97,6 @@ func NewOperator( return operator, nil } -func defaultOperatorConfig() *operatorCfg.Config { - return &operatorCfg.Config{ - RetryConfig: &retry.Config{ - Enabled: false, - RetriesCount: 0, - Delay: 30 * time.Second, - MaxDelay: 5 * time.Minute, - Exponential: false, - }, - } -} - // State describes the current state of the system. // Reports all known beats and theirs states. Whether they are running // or not, and if they are information about process is also present. diff --git a/x-pack/elastic-agent/pkg/artifact/config.go b/x-pack/elastic-agent/pkg/artifact/config.go index 406677cb8aa..aefe35c26e1 100644 --- a/x-pack/elastic-agent/pkg/artifact/config.go +++ b/x-pack/elastic-agent/pkg/artifact/config.go @@ -5,9 +5,12 @@ package artifact import ( + "path/filepath" "runtime" "strings" "time" + + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application/paths" ) // Config is a configuration used for verifier and downloader @@ -42,6 +45,18 @@ type Config struct { DropPath string `yaml:"dropPath" config:"drop_path"` } +// DefaultConfig creates a config with pre-set default values. +func DefaultConfig() *Config { + dataPath := paths.Data() + return &Config{ + BeatsSourceURI: "https://artifacts.elastic.co/downloads/beats/", + TargetDirectory: filepath.Join(dataPath, "downloads"), + Timeout: 30 * time.Second, + PgpFile: filepath.Join(dataPath, "elastic.pgp"), + InstallPath: filepath.Join(dataPath, "install"), + } +} + // OS return configured operating system or falls back to runtime.GOOS func (c *Config) OS() string { if c.OperatingSystem != "" { 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 67fd1a96aee..07750ff5191 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 @@ -32,7 +32,10 @@ type wrappedConfig struct { // NewMonitor creates a monitor based on a process configuration. func NewMonitor(config *config.Config) (Monitor, error) { - cfg := &wrappedConfig{} + cfg := &wrappedConfig{ + DownloadConfig: artifact.DefaultConfig(), + } + if err := config.Unpack(&cfg); err != nil { return nil, err } diff --git a/x-pack/elastic-agent/pkg/core/plugin/app/start.go b/x-pack/elastic-agent/pkg/core/plugin/app/start.go index 9bfc40781e2..68c9df887d4 100644 --- a/x-pack/elastic-agent/pkg/core/plugin/app/start.go +++ b/x-pack/elastic-agent/pkg/core/plugin/app/start.go @@ -211,7 +211,7 @@ func (a *Application) checkGrpcHTTP(ctx context.Context, address string, ca *aut } func injectDataPath(args []string, pipelineID, id string) []string { - dataPath := filepath.Join(paths.Data(), pipelineID, id) + dataPath := filepath.Join(paths.Data(), "run", pipelineID, id) return append(args, "-E", "path.data="+dataPath) } diff --git a/x-pack/elastic-agent/pkg/core/plugin/process/config.go b/x-pack/elastic-agent/pkg/core/plugin/process/config.go index 0791692aeb4..e8a236b5de4 100644 --- a/x-pack/elastic-agent/pkg/core/plugin/process/config.go +++ b/x-pack/elastic-agent/pkg/core/plugin/process/config.go @@ -19,3 +19,12 @@ type Config struct { // TODO: cgroups and namespaces } + +// DefaultConfig creates a config with pre-set default values. +func DefaultConfig() *Config { + return &Config{ + MinPortNumber: 10000, + MaxPortNumber: 30000, + SpawnTimeout: 30 * time.Second, + } +} diff --git a/x-pack/elastic-agent/pkg/core/plugin/retry/config.go b/x-pack/elastic-agent/pkg/core/plugin/retry/config.go index a9487792a17..11cd1e7b418 100644 --- a/x-pack/elastic-agent/pkg/core/plugin/retry/config.go +++ b/x-pack/elastic-agent/pkg/core/plugin/retry/config.go @@ -21,10 +21,21 @@ type Config struct { RetriesCount int `yaml:"retriesCount" config:"retriesCount"` // Delay specifies delay in ms between retries. Default is 30s Delay time.Duration `yaml:"delay" config:"delay"` - // MaxDelay specifies maximum delay in ms between retries. Default is 300s + // MaxDelay specifies maximum delay in ms between retries. Default is 300s (5min) MaxDelay time.Duration `yaml:"maxDelay" config:"maxDelay"` // Exponential determines whether delay is treated as exponential. // With 30s delay and 3 retries: 30, 60, 120s // Default is false Exponential bool `yaml:"exponential" config:"exponential"` } + +// DefaultConfig creates a config with pre-set default values. +func DefaultConfig() *Config { + return &Config{ + Enabled: false, + RetriesCount: 3, + Delay: 30 * time.Second, + MaxDelay: 5 * time.Minute, + Exponential: false, + } +} From c3dcfc7a9da44d36d05ef522c2c959e4358fdc38 Mon Sep 17 00:00:00 2001 From: Mariana Dima Date: Mon, 4 May 2020 11:58:10 +0200 Subject: [PATCH 083/116] Metricbeat add IIS module dashboards (#17966) * add iis dashboards * update changelog * modify dash --- CHANGELOG.next.asciidoc | 1 + ...tricbeat-iis-application-pool-overview.png | Bin 0 -> 452399 bytes .../metricbeat-iis-webserver-overview.png | Bin 0 -> 208312 bytes .../metricbeat-iis-webserver-process.png | Bin 0 -> 270611 bytes .../metricbeat-iis-website-overview.png | Bin 0 -> 349969 bytes metricbeat/docs/modules_list.asciidoc | 2 +- metricbeat/module/windows/perfmon/data.go | 132 +- ...ricbeat-iis-application-pool-overview.json | 1026 ++++++++++ .../Metricbeat-iis-webserver-overview.json | 1602 ++++++++++++++++ ...icbeat-iis-webserver-process-overview.json | 1510 +++++++++++++++ .../Metricbeat-iis-website-overview.json | 1685 +++++++++++++++++ .../iis/application_pool/_meta/docs.asciidoc | 6 + .../module/iis/application_pool/reader.go | 2 +- .../module/iis/webserver/_meta/docs.asciidoc | 5 + .../module/iis/webserver/manifest.yml | 4 +- .../module/iis/website/_meta/docs.asciidoc | 3 + .../module/iis/website/manifest.yml | 10 +- 17 files changed, 5914 insertions(+), 74 deletions(-) create mode 100644 metricbeat/docs/images/metricbeat-iis-application-pool-overview.png create mode 100644 metricbeat/docs/images/metricbeat-iis-webserver-overview.png create mode 100644 metricbeat/docs/images/metricbeat-iis-webserver-process.png create mode 100644 metricbeat/docs/images/metricbeat-iis-website-overview.png create mode 100644 x-pack/metricbeat/module/iis/_meta/kibana/7/dashboard/Metricbeat-iis-application-pool-overview.json create mode 100644 x-pack/metricbeat/module/iis/_meta/kibana/7/dashboard/Metricbeat-iis-webserver-overview.json create mode 100644 x-pack/metricbeat/module/iis/_meta/kibana/7/dashboard/Metricbeat-iis-webserver-process-overview.json create mode 100644 x-pack/metricbeat/module/iis/_meta/kibana/7/dashboard/Metricbeat-iis-website-overview.json diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index afed61013d6..74ecf4c2232 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -357,6 +357,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Reference kubernetes manifests mount data directory from the host when running metricbeat as daemonset, so data persist between executions in the same node. {pull}17429[17429] - Add more detailed error messages, system tests and small refactoring to the service metricset in windows. {pull}17725[17725] - Stack Monitoring modules now auto-configure required metricsets when `xpack.enabled: true` is set. {issue}16471[[16471] {pull}17609[17609] +- Add Metricbeat IIS module dashboards. {pull}17966[17966] - Add dashboard for the azure database account metricset. {pull}17901[17901] - Allow partial region and zone name in googlecloud module config. {pull}17913[17913] - Add aggregation aligner as a config parameter for googlecloud stackdriver metricset. {issue}17141[[17141] {pull}17719[17719] diff --git a/metricbeat/docs/images/metricbeat-iis-application-pool-overview.png b/metricbeat/docs/images/metricbeat-iis-application-pool-overview.png new file mode 100644 index 0000000000000000000000000000000000000000..15c43a2190ad96692ee3555655567bc8b20ecb35 GIT binary patch literal 452399 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 z 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 __Z|J$*Oxb_uCEBBYuW~yjG7tk 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 z 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

PU^jGzt~u+yy$kIrLLMbS2Mx= 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 literal 0 HcmV?d00001 diff --git a/metricbeat/docs/images/metricbeat-iis-webserver-overview.png b/metricbeat/docs/images/metricbeat-iis-webserver-overview.png new file mode 100644 index 0000000000000000000000000000000000000000..b872c7cf527e61ba41022e1f7a1182638954c17a GIT binary patch literal 208312 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 z zOq~!M@9@~;E0%w2P1V?&vPU{d7o^{H%Ez9cQHU>t84#wQlu?06Vll4rey%AGwc3Q4 zH@*$}^0NZ(`A;1N{;P{W76R;++hwlNP__OU= ziv4F%lircTZ%*EB`@Vbiy$&)pM#xIdz{BI3|iA| z2a#L#BLniWi(hx%g9h8vU_1ycXTsLH zNjUngA_l#^BX3TIAdRt+8=3>&1-YR(s8E2=OWn(>TrQ~w@*7l%{*Zew!u_n*k9 z8=Ic@Lkjy$>3e_`?*cxESr`ac1KY`A#Nz<(G=2I56gjj&ZqIS<88U*8>j_X9P5Tky z36jgR+HqKrIue++*7ZNhB>!p+|BH$C{~-5@>&wB6W&lAp*EBwkBVI99deN;Vg6zo& zv({d|x-tZ-^JuQN%OyXA?$EM#6QjJlLdgXd@?m%#UpGyn zZIS*q5_Z?`J(O}AkA@=n-PKo;|ExutBbhS?){j^(&yUrdzY`5TsbI+NtEho$ziOj= zv@%pL?{3WKeyRnzG7Si-1ud^`zeUZL9n9vU&DW=TSKlJ?9901^#}ywRU$F{TdYCth%~EK2E{8+?E)Jsyubc&(TExq9Xn&6H6W$_<$|Nn$UBQB-lNZC(T>Xs zOp^y=m+pOGg^ZbpY`)N2*jg#{z&BKXKuWrhVoXSoIn^%yfIVhC(~l!FU9S9+lPrhy z@U42D%%uqZun+~u_CH~}3rvJr>g6@8Sjm|D`S_ksh)#9jWV`Xt+}Xj5j=!J;Is0VZ z*iR^7f8^i?^xvt!u}{2I^@kWVQMz?)qlr?>O<>}FX3l6~NXNB3I@W3xT>IhNR-RGt zQGg*(IgiyLW;v_{uVKd|`U}#~sEw6cQU&9@{Rn#3uWvhiP+xc}Fjd?3u%4ibEdEr2 zAf<8;;vj`omy%apcMh$jg>QxmjewLN%WncwK?XT(!laAdN)vdvQa<^wPQ%bV zTDDA-=C#7jRxyE07zlBxfT{+g`_L2Px<|VV{KwU8Gq+srAe|$>jtml{?K+bRZIGVu zb}BDDaL*S{st}q`u5M4$ibE7(74wb9YmD+P--gnw8j@CmY!8y^g22LiDad1YX%)HG z-WtI^PzC>gipGBOxeVz@ULomviX1U5K6wQ)*4Wl+hfjF^66pK%MB?Xd{BGc?O|u$0 zdoZCHMp!~ic8lbfxQ=Dk>&OVMocq^dKq7dHD#%S>e%p(m7VY`I4g(OYkK$Ur-ngNf z1A6=}x6UN=Fj1^qr-)O;=Ef&EGZB>q72(H1!d3hkl>N_%Cn<3q3}H~E*|Os)jmq17 zIh*#~bM&08g#XMa6Lz|09fKUj)om<6SySm*%@fTT>5Z)Y;^| zq+-tkp}JEJ4vw@n68Y{0WH&>mLv=oZON;YA)!HIJ#$GTCNDDczR&ojB~POdlpK%B%v18;Uuqo)mj7`T zGq34kWK}%_=rl~%zfFdZJe^n}DeF3Nxgh{9hE;%z#A?xRc|H7W#~*)P67NO{tmL?}~f<1SRoAJRU5*gZXcG1Gk2&ZSM6=>TXJgnd^=*qPXuUTL7KoKI9&& zBeRsj#j4>*6DO&xud@)m@K?=8?R$ae;&6blkXCcKtvSo; z^M`D90~g|VseCV#Ua#`uVd3}pp>PTBN$8hkogeNe1T38Yc@7L&e;p0vQU7T*_rkHt z5)97@7${$S1rL`CX6^ag0p0}9B=^Z?9v&WlXWxamu@T|QZ|^&^n=QRZ#(P_>%`8v) z?a`U)W5>Wc)_n?LB3Zv2#jB@R&($5mgZD&D^|+m2A1fv$`c;*FNsVhPT_yJgK<#8? zN!sO!obnldC)`-%@XNG>*eF#t>g)2Is_PpDpsv)4}h{T@Y3Q36oIbj}y6x^q8?OMXV-;&ui?C&%vQG|Wx z0){&b%mu4+N7r<<+_%R}5pDSEUH^A8*o>y!rmrjGB$P#C zdpmpmoidUJoc$<$_|Z|z$;-u2$Wqo!mMNAaJY9(t&Tni@j4 z2E<^I9xnMX>}5FCK8Fu^nP)Zj*@{bpN;&|}!93+cmBOcc^o@@1m%c*IhS9*(1^>>k z`gd-vskXBh?yTGPPBt%{&qL?cZqI3(S3=}jzw)Avt0q(Uss&XP>mHm-=_r0pFrp&* zcJ(3W3KwJgTKn8e{k1{wX?yeIBcW5qxbhijCS zpNCvv&tKclDoc9g3-Yhb${n2Mn`s2}s6L|^?NC$K`v;*0-4Tnf4r@#}_U&+$MqKd+ zKTUgdmyp?~{7e2mq`MW8U_!4E0#;4 z!4r5BO;0IVhLXOdB?{$~O=L)|`dUF-$*2aGz~JC0e38^SAjnz*Mn6MBI}&f~zPR(q zy{qRO1}ku~^*QCx_C`sJ(}IU(r^p^?)Y1|J{p9BRrKO;24f3%^qZ3dh-#G}?(8&jl zj@G~3nfHq7l0*XZ>^nOcRc_RkeLrMuav}~ILtc75`_wTWo3V=R#E*U`?$l3qe8~Xg z-Y_nbaH>O@C?%I5;H6h87necK8{WOk%%EcJ$0S~rH~tcIIm+lRw9%S-cXsaq0~E$e zif<^FxPH|mAY(sg6jSRQ9o#nRsJYD<)f~n%kF3uVvvu?IJP>uTwbH?Wu-tf)okx`1 zxJEemGBk)e<>u4}!$~hhyDvhcpY(Hfa^i}w-@m6CeI`Et%;Qh80)paBfeAikWGkgy zp18h33V;SoB~I~1LWKyA8riq*68nifj}TEf)BqbVvk!^yGlQP(c;v+IVHl^|A1p^UT<$h;t}I#^U?p=` zi-m+A*Ew4M$2;}qxDdk8yQwrSk#6?u3R|xh2NoVRmnqqLH78In&B(FaVuYp{D&MNj zh&I+j3^bSdCiQDlcO(S>TiVHZzEfF0I#r@oc0t|f6{TviGS_ns&ty_U6u8e*&*tTT zYr&A>Z7x@ATV=?|Ei(4Ag%`3XjtUC?VRIoSK&X7GU^q3-i9e@w*nj?lz;S~dfo(ez zz2^zTui7<_=k9U=X(A(ZX41#CocQH$HazPuCxBoJniKqG9eK8QKBLi-mX0o#W>ez0 z&cdu~2@yoy(zvABo^KS-J;p`WX45uBLhrV;9mlwM!sZBLq7=gB;Q6pXfnKA%XEnC6 zFsQem%~|@oG!h)OXb>ia6CZBZQZO5Y{gT_NW^w2LcC<0LO7WmJJU@*TX`i-S>; z-#_R!-DbQgQRL`_T%0E@i!+z{*k9qXxqkbxJ?-^;u2|YQ^uJ0eqFrtHNDzdjPVe?e zU78E_Gi9V{Pn?DW;)|L@HllrITgw0cHLUae*87T-L)`Oi3pqdF8daAwbE}M5-5}j^ zV99sJE%CTh>8Gu{Q*N)^x^jA3?adqp3yO+5J_9^JuvtS;&5_@I9P7MAnBF+)&pC;1 zx_5@4*l@ZL`X>i;p}exAV`KTXx?{>=_}vG1oVOXP{yttugoSqDT&{u3YWN*oR=ev5 zUpTf~x~G>emg}yrdtz4^^HAx_U-ue<*F;SLO!$qEr}=VM&H%C|B2u{Qq66aR*J^YZ zG1&q6yAPR|1PS4*3d5L;rhfmvpl!F234;-nZeMXp@f~X;oSoyeO|cl9qIbP$*pk3> zH0W>$lbsMi*qLRRpXB-Z`NKxlxQ1sRcM4Zr#9n*51J9R+)j`^0KjazI{dlw2Htm4I zkG5cc`y8A#iZ1;(P{QTD;%VxO_MtrK)PEy!7BLg5~sO*Oo@`AjhBrm#QY=>Wb2(;ee*p zdpm_npHTr;-$T{^6G_+Zf&im~Q5o#}dFP~E9M1iv`KDhaLvqjCUXzqR zr>>y|@p*ioWmdnnHysF~U;a^d$XOs@l`(Typu%Fm+gNoWVS)(2-`6P5l$Z8Rl-Ug< zqwA%Yf*hIQ4RzwvsR-^$J_(PqJh7PIHN&@HRpJS)P>oH#jUy4Gg-~c_!5yEiaW)}z6!4RM=N_$c5qgU5w<}=!7;=gqFr4J z`kj6U314<~oy+k|;4A~)y1?`6n{8<)caBa%H`bC0XEYjOpyAN2Nc2Y(9Y&V{iyi}r zSuCLcMVwCgjlS&c09u$Fzc)Zin5ehaGtBLW;!s5p*uazo!Qz|HFr0$7$Z z?eCO+P6}wulpAdn>N4{%oBN=v6``>Y0j53Qo^AkB2mW|9dL^ERH-lKu#)`Pg#+X7g z__hj_C&UAHLrW%SnBZT54LJ9@(~~n$Zv}rh)TlGY{sK`k&KLdxM7eMI3q-v?wpHNp z29o`UQ}gCd8IKBQ_4*nm00|ygPtSx$ymPAj-f4?3WvNxU-uUbsMLwf`Y_D<-k9Kw_ zcD8V6C2sg_f5Pz78#sj$=kC1c4keDtzK-+qwqQfghAv(rXqs?;7v6uZ_hka+4ueFRtOYiq=!E;?YeB{Ha_*kZ3 zC1H(yg#CmA1Qx>K>5P@=j#2s^bJz3bvOH4msj{J~)t&oM;>sxR*ny&X$_5Z=L3ReO6#E4Cdzuj0r*4TWIS8CaS&C9o={TTL zwm7{b9h1y)MqBWmbIa~UT8c|%VY}fwj-R2Lbw6)yyFI@#MLNMS58uqC8F+%%R| zd+av9v!2Fwf-pgB8h3m-In*>78$IoI;NjHd6PcM~i6!st>1)$ToGqsd%k7!1Fl`g$LHsD;#cphO*UhLouQeQS}m_^ICE}K<+eY>;CEq2$zagU?B z6>3o293;o()hyRdG%aD(v{D9zuDebbj`d5^n$UvaBH!`U8P#MRTA@S2ziRxnf(^#%G~=17*H*;Vj;L^q0FxzJ`r`)O6e;kv86IVRyN*kc$z7ntereiLZ zxxnnnmyq*pG34ID*{sfl2!DRu>`eKrP=e?1<4(by$7kIJV6{#KvODOoSpB_Pgq`sA zmVLA|Rh+K2nuM^1n!ZnX7$%m1?Gma^i|-@35cKp!u2*a9d#v5$Q3-J@5~@HPE5B1} zcInezKSL~h|5!v$M(Yv++JNw>3KUne?3e}W_8+O;sC;F_D@Nu|G;`~@Dbt8+jgwD8 zl~(dIDly11HPGkvd^Ga$T?2)ycdke%UY8fLwo`BDU@y4iN)FMh>Epr|PScjhw;sUk zj>p%F$r;46+It;w_Gj)hxH>C9N1TIF;Eo~+(SLS&*1lvhYevp^JKuN~rJ@sGr%_m) zs5bDoT{L6!SARd2A!Fagwq~Rvgd2~wPaO*_!)mL}G*}eUts=n|uq+4KT@B#LMeL|H z(l1g6)%@ZnsrKg9tzsczpB>I+I3?y1JU1w}9e;Bl4%&cl3V{MyQ0l0lr)f$4n>26c zxSQ)v1=R~*4Ytqd**v!wE;~@lrb@5$#0sQUR?i)0W&^WMC2R^WBl(vNkHjQoYCb8d zJSI8lITD116q?nhYb39Z>B=S_Tra*ed)vTvF2sz$NjScc(NJeZ;xbY4jSe42^|ko`1bDX!>xik zwLd@v7in(bYM#@1?I>)twX(^aOu+(2g9Giv^YltvI}9LB>c2UejR!v`mAzeZJ@pC~ zZB6`qH<@#^Y%%HPVd}G0S=dnHrDDxuIO`At=q@h53`i;PUsKoEhm%_tG|>1gE-#Nl z-G45p)iD-0B^KP5cjfG4O$p`r37a93? z2v6lmL*hlM=Du>izyGaStd&*&mI=WTms|O4@iA}0F{kQPvMp~N#+1&wkKMpv`yq^j znZ+3%Rv0TeK75}Eb#=h?qGoO3nVXt*$^5C=$3wL zZS3A(^ODAp0K1ucTB=CMW|Tr?)f`xA*TDV`av@q%1zi(1wWUm6HL32*I zm?MRddoXP|pgu^g$Xsg3AChM949_v4Js z`JD%7>wAmr10Y7YY^;6l6}$6V@>2^H#RYH?fwP!wwU z&dy-)v6(Tk9GnK%RH{^-G>n9bWvp~!?msfU%}XKZan5&pPG*<*13&i)Iy+iPeTrI% z_KEjfTW)vl3K3VVJvNHC+<&JxssaV=Ab|qw@lQFg>-kQr4|?A=5Dm+nvWk@5EKhSP zm+*PZA^2V8$|Uf59^IFfx5k=-t2-N9Ia4;-nAFEm%4SQiq*-PToY*|BFL(qPCpq!g zW(V2b%*AP4J@6or@U1rSChM$h;MxtW|lMbL`k?Vfb^u%Dmg!_le^E^AQ#6h*nF2hiFO^`%gvkU zHG>xxRwXYOilXLrq+vEuuz zDk!=QTRMtg2DA8V*>%sa6p^@jo#J2irAfA$XFJVhhK&p$eq7MReka!?P{h0u;!PPR zC+!fX%5VKMLuRWIY=co{Mlnpj!tdo1@h}C$4*51$I}`ikhMp|Z85AIGW8E#Xv(Lq$ zhXxx01oY?8>%VRhLg;^Bw*PHafSN$4$c+@mo40*9H!#Dmgz6-moPaHb9N9UPWj@0H zepo=$B_ms3McbZYsmbHv>a}$b$>I#^|Bi4QT z=#_jsjUifB7V%POf7*1W92B5MPF7WNjFWH{5~^OAA4W18Y?#rI=eeTll_}j@ zM2er+bAI2P@0|Gffng)Q>h7uL3T9UeF@d7~W5Q%-J`(=%m>tQv zV(y%gLzLo>Cd(FLt(hhYQq0Oj@O}gD*5Krnuk%gB!7zd>&(dD=wU%di5WF{DWq+N{ zrYn)&R^>BlDIMIjD3wF@8N<2l>c|JIVisSo4b{O~Eb$p6r>-?DhCO3s$~xzM6~_CC zRDD9%MVsOgTR(+`#IPxQsFln1B9U)GYR0`Nv5jb02qunJ4cG1Q?m-Of9@+GpW{^#H zO-4w6EE7yMHe>Z$nZqkHnO#D1ePblKL{N^8@{1$s_~laQx|8W}$8(`YqG|S_GP1|V zywCpJ>Q}1*+5fVyQBrPTW?vtpS{XDWOcZ!27@8FTD<@pw>h?zI+JsX*Lr8MQ^hS@> z*vR}+Rw*jm6{{FTnXRt&T>A8|DQx@(5(;AarPg-cd~uo#X;glv^6BD&rza?7ph{`_ zqkC!uXdYp%5|Ya_rHRZZ-R#ptzOz&47!kH}L0<8y3r0Q3?0Y}Pftz;`Q`vK#F0HV- zqBD!!-(pJVV-BEn9#dfKgK|dI6t>FCIKGpoEyLu_yQL43kX&2u+$Duzhq#dziv@YS zsks*F`~azN!+PL#gvf_qT^YkaJv3Iwd>u{&$r`pGbfQg#g#{)MM0fh(27Um*c^fOj zT1UbE7|06+omv)XP1%uTcWHu~hnKf0^W!BAbr4L&G7;Kr8`TGYrH4qJ%j>=aKFI&s z&vI6y>V}gbk^>98Ybl==yND1b%_=#>6%6ttA{ik+gmw!5N+In<% zIAjTAWcs`7I>f&1CLXw@`#BC>L76GfW!?8geKh6PH;7B5F71o!)7cdp0UbQK?!QFH za+VH9Ocg^~F%ykVHDC;9-26>w!E5eU*>CQ@={U<&!wo*^C$gt#3;2XIuQ*$B9Hppg zPXMQA%4W`a9}jse-j5LsQU=OzLh7yXV~CFzkk0yM{5h_Ev`Bwemo2O-V)11kd$I3M zp9Qw@(nA01+{*8fNID-z1tl~!u@%wj& zzN%#Mf)SEXs-Rmf9F>PtvC8 z<=c|O(67l!)vOk5VLs>d9QusznH*NrV?NZepkE(C0jmE&L3fGS_rX_8gVyWut=e)P zdY|JKMQ8)~t~+%#vA=>f&Rl%HY7;Tys*dH)17q;MNqZXub2_=*4T8_ zE5tUXa!LF#<xepuy)|olBJ5<#F*3i7WIVdD$Cphcsw-^M%ZSXbPTy0L=-I(9nUlc< z+CcY(E0U;I0)D1#b`tVePGBg+rCx;aXBv*Vc?@Dm5!Fr06{8E35%%@c73`pElc1G_ zUJj#(I>B$bg0#HpYATkZci$Mu3}SZX{rxmoQv*rdzf!R_gCGFqrWrRMA8y|wGKEp6 zxpkjPY$)x8@uS)%$BdXDeQ09RBB1P!gabRxbVrTw+)<`FjAN;iCGXXqJ8xyzN$)k< zb*RHfOLk6ac~$*>O8ot*kiG&@5B<+MbRm5KVVOQ9Onv8Ut~gj$Pv5U{Z#5wJOdro( zgab{#!q7g51c?9zOseWV2HXvd9`$Kd!x!W(B#cXf3IFD*yv#7Jf0x`tLdZYH>o$Ev zBnD}$Z~3^z`GWSG8R7B71F z8bowanRICvU&$ef$AWGTFqYpx57PN?9gnxcFRvDxzUHz9@S7xCQt*H;8+vb<74>8AnA+^2i>MH!H-2zbHcWLAKEve7%AOURvbcJ&v zO#E9_Iq!o05D8@O?Fhw7)qx|N+_2Z8CB@|o92M2wN(h~ zFNWN??dESiehF#P2^F(OJ1PsGO649|)Z2VcY*luA{$@dIS?h@UCxtVJPH*VhJh~ys zb>sv98c)k#Sl{&tH7Y^gsd?QL@`p)Xh8RhwrH|&FEWG}|gmbCIj=6%-SsSUp!$O&8 zbJ&z)G|urvikINGmQfU)jqW}f5fSY@DleXi2djoSos5>%z4Ar>jh2_r?rX>|K_6@kmCw9G6|mAyaR-xDeem8G{80i)<&pEdTEPp^CHl z&g)wvs3!Vh$e}Sj7tkk(qs88l<*S;1 zXw1ZKa5R@KZ1uXl2c4~3R!7ymyeuT<RZ)cCz_tJ{sLlRZGyX4zjxqkS_@rmYA1Q6BWm(1bhZV#cYAsfpO@pBod zEK-yk#4S{Y;Mub1_{Kd=+SIm zxOtQ1s_N}&(`{B67GK6zn^rLn=+w8<4I>5BtX-zx=5O9$_f#JsT}%C%_^bx9!*Tdf zxF^)Ot}aPNxR|lu7LQ#@sp*Q<@YZsmk~uIP!eW2Z#CVl zXcx*_ub5>WimQs1-G*F4yE)*9#Hqvz&(U9J1Wv{Oh==#C=JLyxbY#bI&{jzddRn*; zxT6@rvwAc?zvo>0QW4kXMPy8zrQ2gOe|J+$QFEtGSw9@E&6NI3ARQ!;mQzbSnFjI{aQu z7!jz5e0xk4j6#I^lMMY&kK%R$)GCfg;8CoaIy)mzNS5E2&RMN!0l_W^!c^n61<8jQ zFd;lonYP(qS{Mc?iVj4`QdXbfa&mg65CB=-eft}#spw>1eL3@@oAI0e*eZlCcTX&d z;l#gbcDNMavrQFc`J51N_oP2Y1oNe0fq4AT5z%W)pj={LGWT{t#FL#|4}L|L|6QV6 z%lf@?V)>q4D+?Ax=?u=<8S7ac_@%m*Y{3}9F-FRs*ouZTp-kJH85Yo6cSH%A=p)LC zu|;S&BZTC_hoQJhpqicW=b`@}i+285u3l?b)^RewgT?7n&N1n$*cJw8^|KP+f?Ve= zTqk7XIvrc)rV3jlhu9$pfMl#egNQT)99y6!OVOXWXp2lroCFbFxEGSOvsMD}pP`9I z;#HKaUzZp@#_xvHT;X~b=FFob^(=mvbNN2@V)GWnY+hd8AS9l+%Oxcppm_AJ@Bu5D;3Q!Q)fRt5vp(?9Cs~)+?$GSU6m#2qDq(5%}ra z;q4K(VTY!9+ z5fie^BfO^`wmvn3K9P~x7?G3U`{TAsSvzXi9uPAg<4@KQByfBpy?9w=-5pq@WqlWmi1JS8JA&$*zC zVolH{20#;KQx~v6Oo&tyce4>hj2jgn|6SzBe9PBB&b-h`{nHkozB2a=@`AW-7W*M* zM^)i-NN6{zJPtx$)8FD`ML1S-ZdTbYM1pxKH!Jr5qXtQJfffd6_TcQOAq=|Bgo=g} zpgQIKE$h4a{cHy5@pPZZtKtVUpbgAg-POXtC!eO^@3Tz{Wf=|I`1k4+HuavUdRhF{ zH5DQJyKSCE89|vw#_G!T01(&}7pEG4w&*O%k{NvC@A-wzuRvdk9-AmC!bgeW$6M&L zvp-#PTUK|RkBOy+Lv}=1^eQab?s6ApJY9lYa7o_f%SYL=Lg)kwCPnk$jza@^_^#(eu29)S`Fj){cT*GeV zd<#+zdMu-;C@~`Dt~%k6r*H_W3?S{q7JVE<^Jh^n%;$&ShA=_t+r}@$V^n0dtBn|6ZU=*TcixXnwnxP7 ztx6@JHpCU3JbxyCcI7G4m#B);Yop4)K1aiNfy(BbEw)12JKi35$oDE~Dh`finC|pp zEW<;p!(YzJwAX1L01$Re2qdd^s-d?0`t;@J8+vq0=jzoP)ZEmW7N>l>?`55A%(QVO z>QHuN9YA>Lm@Rkh#m_Hj2-vCh?_aQ&Dn(3}GRZQM=BJS^GGxWa!S_g_-qF`rG5EC?9aGt)e zcyUT*eN=g(A)w`mFtj1x_?35&s+NvEA19&pfzgS`Xj1IpIDI=Mb%rEol7%W}oYQ>dy1+B}b;s*Tf(EyVxGpX)>!1{N4&23m`vqNAf7rte?5I8~lEif`ijNy{<$vP)>mrmLA2AhcT5y_JALP z@4mkfR=Vw40mwT4U9V5P=dW%m62nj z_dwP94b04xLU0dE!Fz?31wpivLi-TiE$vX*Xw%*V`!I<WCbh<6 zyi-l%`ZB9`#*Po?RP(-d%Og5e1cO8rsdC1Q`J~Y}p`i_5S+_YSm7$9~|du(KM>Vi#9yec`)k$&1w zxNOYjmUcK6Ua$Nfogl?am{PaEq|m-TcHOZLqZ6au6IIx_uCcy;{U9UK>{*K2B?}Q; zwNo=I&E+Fhdy)T|8asTQI$1m`>HX}^c!S?T>ITS(YM3B5@5NWaju&#AA07t(-~i%N zJk+u1%Ffl&XPfvBfA2CE_9Yi49#B|cZVeD4N+y?16*M(IPS!+f4P1cNj|?vVcxR{o z+}zwedDk&S;)1tLi`U0qf3J$K*{fh`k`=Kd9r6n0RLZK5D4hu-7MU6qn^v5l%1mu+ zx@Qq7baHj2yL`#&{dg5+<~RamU3GXv?Zddja>Fvi9-0Gyo3FaT$4u4g)^iW>R_lkI z(~@7dbZHi;5AWmMJLI!C@bJK<(fnYAHP1POA!}}e49n#6Kkldt>0!J0H|;5Xq@!5uB^@I@LeAdLl0SpFh%iTTwFq!_C~2D0_T#$dRi9lI`yQYETn74 zMlY6hXczdD?8@xX$4!bbkFH7pOjZTlfI|7L(K4&eqYWT{=;oC&hxLG$; z0>yPO{*OMaIcZt<(Npx0xYnqYt5c=ZKv`!bO)}SgEy8^k=Ow7UHF)BqR3{1wi4*}qEwsQ9*YUw8E-rnx0xQw#eO zT)tpEJQ{J{v8=8{tlJmX8kmOJL{gv8tRfn!*4e$MiI?4T!p?ON>(*uW37fHl6DMj> zYb*PihtxtyCOpTzKRdonEHwl}2$bUOx)J7`xRT%!y5iG7W{Dy!kYuIn!(A2vZEV!J zcQew^XlxV#pLJj!x@7XT$KGL3RC}r~ts$GQc(z+R%gp1W!uFF2nJ9FJ9ew74M!xex zLx5TUDEb=$9;!XRr2ne1R)fwkljqT{eS^M~>9LO%q{d?`kD6;Q>dQ4hL(^60oPJtw zrQDP8_(-P3*u;J#ob2Z3d}WO{Y@arKwAgxB_jAMnyLQ=-9WVP6RMU4US>NHi8)f^f z!KcS#X3~^z>t%+cHmNR0q)zSLt_QL4m{?4^QF?@Ig5F-;XA!Kif81t(+z3(@+uX{R zbwQIDaYIz<10QB7#wLM}J?!yXR%!(2J#m>!^cP~DHg0(j68($vTK@YAobMOCKGukH zA>Iz7*DWy4b9cT14S9OqdlO(Mc8S@u?(HyzD<>F8Adwi6d1AghR*dy++GVzz^mR77|~h>suP zAQZ6(6C_otG(R~MYqgtzwtBREbaiw9D^>zRK4X(QiT5X>?&PghHh8MmDZKxTWleBf zn`m^BdH_xGTYFD$%e%y_z}q!0TMG4*7_ky7Y|^UfsYL?|VmzsoP(sKhEbx`Kbajll zb~KB>8k@wL+X!&uq%|0f;${8V3w-MG+M}^^QGt&wW##Vy6%t~zj*bPDvVTjNbPwFQWNu7tITo;0=DRcXTZMAWED9*s^oMt3M>cJ4j z&swT;QyPnxDIs%_%|_#%8E$?ww|F+RLW)$cxH5IJGwYDdwLfJRGGum{>q`y2!l+HR zrga&XR8cZs?0N_`2tYf;0&}5YgYIOh_glU@u{j)h+t);6!qjT5FQ1^FwY~mGR{m~y z56@;S-@yh;pRIrDZhxwpuKweNVToOmiluVat`n)(j^pz!EG))OTW&p)Iozjya11AU zIVRW5e5;7qiP*Q;z!=<0;^j>!!(2s>M-L43FnJ=)@ss6xz+<#6?ycPZ8ibLj|=>`(A{!69IA1T)BR zom<$ZS}M17=#jYQs8q&-;VI$ZO!D3ncA>&yI}0yD6^Iga$x%7$q?XgNN=SQb*DQr8 zF{@K3pt(o{d?4m)W~ME+yBQ~~RjjJ$Sf`_-66Z!1;0cjM1x47Ha*uG@dNxVkoM|fc zboDzK`NxS18RzoFBa0*yk0x5XJnWA&jnA~uANfG2CUg<{rd?6ke1B6pPTZ;8_Bc+h zVy(dzX0_4fd3R#Vf;UpR)adeLB_8bL!9G{>gJSa)AtGU;bNzd1-Z4-kEb(TleCssj z@m!FUtvd^0`4j+k!^#^z)2%PMx;;PI)V~F79Pkdq+`>Xm^TW**{Cp zH1_eaHC1sc8}liFVl*&AJ;&pe;?XWdSZ(#j{$N{Z`GWJjQ#r$%R7FlvzKF-k44W+9 ze}M*WzEZeEpBK0zVP^@G(>hNh4(t=V5>8lWuo%-JbqodDxi5^)K!3h1CmN;WI7Io@ zYuMNFWF?5pt~}6&;0n*oD&r7ft_ z8v8nMH^&bYLJ14a>97qQ;Axq?*jJqg?vUwWBI9vH-$rgYF+EtB)(tETnemBAxkNOM(pvO*IY-QHH9H+Mv z@nQVdp~p4{#yG^j+`+jynvZy4;jI4gT##=(kJ!}OALV01a|zy5#spLs(2HThWn-}M zsy`SU+!^hy#AF$>yOi9o!8`FcyjD^g_n*v z;~pW}l30#g+MPXHLKa3xdspIS!)z;SuII17^~MyZt@iR!w)tQZ&%9|N8M)b7%N9Ro zT7%qh1C$I2MGU*4QozbB*i^-bIFe7yRwIu2HgTWW3a0KPp;mcfpS)s`z= zrU-iH=7Qz25}c!5iexS2J8F4ES3O29QV-D$ug~j&>yAO!tO!*0o>^Mi=!s>{wj}I1RX=~TjJr5V|?h4ZiK-Q2xVR=C1SRUW#wOYt#jn{)pvq)}u`i z@!i?}W@x1dDS`l6sYQCFW`E3Ir;31iyIU)YO6WQXs-MBolndk6$CiWT6?^8& zgjh?y#cc_*&#x)BS^8rJ@yt&DdgJ)4!%gDe2HA|oV`*Y-x^^MHOK%>kTp{RO5{r0aaS-@iFN-a(go|2cL9!;|&%J}h zcrRy|M=QK0T+jdqg2IeXt$5>T&%(k}S1!LwnUnvHKep@rtBdvR=v$p>N$^8S^Cl%7 zsy@-BVsW2Z+Ij=M@PaDCX8L@IKC^r`9qD{+reNN!B!4Te$6|VM5sbQ5ncpZVkq9nT zWh^>qzhXhi`q*dqCce*;jFVv3aZi+ei0@L6xH7@p@(x{aT5;!Y5MkzaROud>I0P7# z)ThD72hrK`65K?u-e>S@KQ7tW>X!nQ!49Fg_c;pac0N<>t@7;5>e-bI_V?Co zWnIdhOn$jZbwF0h<#p0Sx3&nhHTNEPY-U_!85fj)LW$xpf^w3|+waq&8tm=cXj$-V z=$sJN1XyEd@$jMwI7^4omfoXnMOVDPU|@I3l}ls7h26DblVC!Mcu5@!}jnmTu&r9sId8ACAZvH$M@u1 zEV9NtP0v1ArG2ChG(+((^XmB;9SmBT+0MG^7j* zll@TsCOtd**b2#PNiB&F?>?fC&QC0CZe6rc&ADL&+!UZ705z+Fe+7BZ+yE%DW0|w{ zuivdeiEWwPEsLcZ%(4_DWwj5dG9ZTg1mrRK&6Jbva19r2vV}Q#?Z0ORq_jC0J%sYh zeoy=aitA&by{$gvx#d!gJ+ad+c;e*{m3R)&=vVKBkZx*h(||$d4}D@lkR?-vOuQZ$Sty z13vw2a||=Li=AxL{YaV%=&_TE$tL5& z_Jo4<7*Fop$c8gzq;9|}0%V8-fxrHakKc{pNY#AWon%sL21%6NKiO;!bW_0GPuktQ zX=&kHi@8_$lr&g_vU;rn`;DjOJt}&~L{{`@jXx^>Zi5c^&?7SSAZZ}{*O5DN6c&^} z^|oZ6uX&m&&<@l{qyiIi0%kgQT9p z(|OR`E}BWFrZoX6FOR+Soxyefq&DPFoB3r`I7)2ivcy8qN{Z! zD`?^&%9^)SZ&^hENX_j4Dtf1FtWN$llXM6YsUTdy`!*EVxhM9H=D(~=iD;495gno% zQ>(K2Xm6U(Uk7?~%k((#y^^LS1v1z8E1Cu##6e{;S4l@NM%m#$AC?gc1R>YoWH2_& z0ddc0C#85{gfP0ruEMuyPoXlgA{!!a1( z-p8Thi#`o*eFK9%5Mu09iztJV6s$QMt9TVJ0k^0HnqtM4&|3g$=>w}swIh~T7yHNN>6OULAM^80>KYw8PAUfT_x>$eyU7n4&U%yR3={h$MeoZz?dCK?KCvT9e`AelFzIG z$qJD8&1!@7R04{Z)jW1$g@m&m%SH6Of8^$!7v4z+iK05euj_KrI4rbP4IV@_gvE_0y~o)UEy!3P|`>rqD}-(uQH+o=WFI`Q*pz z`nv+%1`X#8@pT+`wkLrz256=PDU>vUuZq?=-jlZ+d3HG(Wj8=jvn zlaWsZE$obra9l*Y_0|;*hV74_`g3Z8SBEjoVal=X2u2gaN=f3y7Lf>sChrVIy@V{D~JK&}dIyJxAtCB@yxL%E4pmMTkNY`Oci*z4gWVoS!3)-3`Z@x1+|FpM?|s}#YU zA@pwVe)8yLxPlreci*ucv22DgQQBw)3W0k{9(GA5us=y?8Zjn4SU1R{q)?R9D%KtW zrEciw7EfpL0f`ZGdD*uExYe^|5MJKLO(@s_R5GuSbrDOt;Egj=0V1bHvQNJ#>}59Cm$#;KtsYBXXkA8{-} zw3Lcr^|P;zcr_g)@E`8pK&E5Zbb_19+A1_Z-iSbJh%85|&+%@RbWCb!0>=VM2t5+9 z8d?5X6lmu)2QbJ@ZA@s!Wl)C3x|!#?r4ny&c9$nKPIU3asm6~a4M57{5RI<(T|xcB z%9b*}QFDu`TCf@S!NUBHt~g5L&!o!zT*yONA_gFBS6&4-Q#}`cYU?rr7$P4elA^hA zkB1M^e6nT$tCS(pDQOrhrGr+0f9)8qow>dHglDgb*}E0>3I%S6^Xs^%y`Kw|hL)7O zi0c1>?$*f^8GqMr1Jh)FH|$r^g2d@op)o3#od8ef6}bEmDs)l-2f8+JI#)duw=s%S zvNuyy5p5VsNg}XFb<{C1Dy)Q%S$hKik#5fgP=~+=Y1VK`b#3$Si8U?tS$d>kGYWNH z7~w0BOjlVYB~vA>7vDvj%D|RQsbfG14<&E>avex15qua-96N-TS^nx`E^PRU{w`2B zdXD^oC-jZ$Ta}4e`PP?73a3UqKkgu4IiA6}{)cC%fM|7~W_d42>GMI(5ilzI_6$nv z6j`I54uF7w6Pti24qg81?dJn&fL>y8fwPC*MHgm@TwXxJ1YcZ&CpJbxcH?OONwMF` z*hW;;?1Xayn_|nl7?>BXu;s;;acj~YGpH!+?aIg~*iBn++_wr5HMB;ial1+gy2y?+@bM9%0bayA7re z$&=K=&7PdI2Y=Ko`<#3Oq;kes@c`*>a3&lfwvu|jBvLsPfS;qa?-i&W-7{QCItps` z`MFAkLf3hkUc+9PG?ttlEq;@RHkxgF!zo6EhzIXSARuxT10+Bpw61^~-Kq`nzLLA1 zU@@doj|c@Xe4yOqb`eW220kHOg_!LVEff50o8x+|+(R8Zh1a?wTg4+mFnc3M=!b$e3-(MuP7FuvN%|WijbM{SpF{=$6F{In=Q3`JO zeVPEJ^>Yhb1t!8A2WeTg+h-mqNWFOdG)?>=C+`c@wggWbBQyIp$SNwD1dsC~Re%Tq z@hT9y4BYt8>eoXgY$xE+~!(A#f>e zkG8X&OsZ|R6ZyFh0gQ4}n8KkK=iocGp--OPx(uw-XRI}kO%am@ z*>{S{aGuv!OWc|%xsDW4S{tnsPbb1bSb<;&vvs}tESyqyY(_`xl#D!r$;}RwJzs4x z483e4E7nwX{o{l2i?(Z*$;yaDWk-9RMkx9$gtHQU+Ob}BUeJdw%WN%W zT%fIvAa6XhGWiBQ9Z%g$1@@!_wt587To%+#qd~Avg`?5{WH%Q)?JelQDDP)qok?EA}b=IN_NjH9BJBkTh zoE!)071EkZPkHz`RT;y2-3p0?#~p-)nNzwkP|*bt2y)R}ZFka?pJ2{W1|Yv2>?&sF z>+JDcGZXMSTc6SkVJcT3wvA1Q*s#ekROwqB6jN>?X6D??FY3*Prb7q_ozT77j-!%G zX%Hvd`Yx1#|P>^d?rl#rly40HEkG@)oC~^QIF6rd=cUI7+lqBGRWFK={?VD&F1b5p)v=e%3{ zk7w;xS4G!Iz&i1pAG6eR!xaHYP)qwzKPIy~ms-pal7*0}da|3nn6 z=zZg>p!L>tMPuQ;o0=cLNAc*D+Dh-*cd)y8l7Z4R=|CA8>Dn{(^u2_$*Z6+;t)JsF zcyj=da|ESs4x8CX+tK4S8d8ggW72SfFq@EDjQ2bJ($Z46%(p(qKJI^vdrW*xdQ3ej zlXgh?m*?7lWDhPGe>Cmc;PvK}8?i(K;1kvjtCPhIw>;wbv6#w|fJwqT?agHlVu8nL z$A8{WsfI*roc2Sbt${|Mr^PP7=l{2VK53sR{V^gtH9c(s?{^-^YdDp8EjpC>x%!lO z&jpYtr+0AM6$RJ>g)*u7bF2$O7d0>1TnxRKd$GRI+crqkl?QFKxs30$4c}is9{Ou# zmt@oSR}P6k)Dfz^1p?lQ+eWS#BQ`*Hw0NM(v3!JF1wQz(i^QX|}gqUX5%D&3my4df0e9)AhS5bk}>C~Rf;iXRKA8 z?OUdmS~i0bJ;Luso4xjhSnn<>14N$xW@PWD8^7(%Js7)o^4+l;Zeg#URlEUxVD})t z5NkrU-TAboieF)UnfGgt{_VxP?A(oP)A|H7l(qgcXMegt%E3A8@BaG#y!PqYLko}< zwxu5t_dDVTg#Z5MH=vZn;knz`*K1#}vh946LVx@XkPbLLG`VBvS*GCWXF%g_sU@ z$KMRit=RLT6R^Me`v-c>ID5jvt>_R8Xp-|q&U=`#QA4LBNke=#2R z&wR-FRIWRZRU_Ju-?m8|8sS9YTqpL*cW4NXZ78FE)%k>1;{5^1p7P*)ptqQvzb@~6 zH205NG6ZjJ@7hq7KX6m*2dR6?Xp&D3^+@%>n)v45pNVuMGYHToIF|^@{){u;i!AqU zD6`+m`)d$&9tJ5xcVsCGM+ajNY*yc)OmGCx>4~q92l5p$Kv$t=c`5=R$tytrtSu(V zP_?*fzWM-Y(6I7u-oiX+W%hMQf;T_P>B*$f&><>8 z_TwcD|4}R5#wK}#u|RbJm`7&A35my4>OF4ZV?>?Q93PuNEBzBa**|F7i}o+kk@pm} zYS*T3H3+jlBl+1VHkmDZJe1ADeo;k!nH3}?Xi&xmzMc_XxKpij zWp%orRhP|Yel&0%!Jb42p3F@OrBpO-FDW%AJI@8Z^_qA&?xDHh-+&0FzB+3_;SA3S ztB=lM=`Qn6qes%3+Qk=lWcW8TPM5~)G{G`I@GzOz&(H*fu_}Q?G6Ta-W7n;IPB1*S z_vCbMTWRfOp-4%V>?H+y47xrV_C6CHA%(~4 zG}W*vbqztRX=cOFyP5qy{14Mz-m_olBPCfVHqL_&M2A{4fLaK?&bFM$6ai`cqn#>? zv);3sK0F!W&E`v=n;1`OkAvB$$hasQR0=qDPR(VEuaQlA*))F^_*EQt-AVEbf8Vq^gSAV>&&1A8-uqT4=i^uMXYe{g*G5<4DXxgZEvP^$n6!@QK)p zn8i?X8`yjbg4^duYhv1@G3A2J2mtcM5YnuTcGf#(ZEwW+4Id;-*ey>h67DxjXf9`X zU%o~FoiBoRUWKHzR?2;15Nqx5y|(N|RCEmqjT%8H%kQ`bj(i6QvZLN@ZiFN0a5k2G z)ykh4M+gZVGd5t-o=!9$)9<|-{LVUFjnu-EK=%ri;?bxb@3N*>)4#)b;XX-?f{YgwR%(RkAY-ue-3yLlR|RbMyQ3$^M#G&3Cs^( z4bKxaP51QZHhvtAVH?tE0+IVATx zCV_Dptv@L`bYOR;oJ9AWMnUEk?~&e-^Lv_GL*)!FWqdbZysAcwD4>eKF$}@Q#dg0m ztLoq(!4G;q!i%yJoLhR&Fn!NwO`S`6k7T2$$!4hm{Wa`@+80Zu>`9NbaB||h*=d%Q*0%%}-kPwIpX+z0A12wZ z@YQxS_ZQYp4ElH?v15^#zUVt&^L^6tr5hET$(NpL(RGB<=+CN)eB6WBxX@P&R5j*~ zaN*o;3#Me!MuE3js>S(&J0TfT&Gf7KRScFe>pb~2V$CYhb~%$GG)b2Up%oG^y?HO3 zf($Egv+&_77UHRr$nOWVCp-F{M|*F~ z8OiN^BvuHd2qxm;PX*JFF5>{-V9me%Fm?0IN#4x@>Gf-wx7ysi2@yp*lTA{}t1p|< zKrPW^A$PmdS0u4^W%JQqeHDpR6v7K>~=;sZyk!?8I`BHCvoXj~7zG95($xiW$Pxp42n4=?GTyOPj$*K1RXys8mDMR)X?zlj5`p5&Uw zG+jTJ(cJviH|I3eq433wC(S;1 zbp6`0he43ls9&#}KtRGsE`PkY%hW84N_b}-G`9>sn&hbHjBL|EVOXCA+d7X^2Wj;t z)0-GUfrf>1sX#veor#gnxB?Gei@~GxK<2V?G?42dl$oW4l~leC zk|}I@@%qJ`Qg6qIT5fr3>FUeMUvX_gS2di(o#l*pdq17_EehOWftKV|w(x?LyR9T5 zwJO^t48X=k@Ka5-$*s9cc)f_07xn7tKqccik#3jZw$B?9VZ7`n|Ab>rNgkMqPD*6~ zSp-G6&}gLWhuj!CglgO8yPvOs3F-qmqXx>c+Mdx?+^Rdki0uoNv7Z8a)^4A`go5FM zkPg2hUlIXiB5jmx@Y6hjb&0Ucc$)m)MCO-{kWVLwI-Chgw_WVp@VP$Pz?c8-_A5AF zsz$Nyo(4;pS?c-`v`o^v48B)y4b6ioJ;>ugg)ZLv1U+r9)$OTDzGfuk4~c7O2<8RV zg`D43)eUo%sYnECBOzKl2X@SN+UY%}y2x)O2uP%-BuOA?vv9-T4G;UC=8y5$qlMTw ztr*pfLB4wc$xqHfb8tbV=G0J}W=$csZXL(wd@hCvbjxxf1Sg8r-_o zSf+l-o2JfrY=tImAhsGp)wtBDh+k>nA-KpCPPK0nKy(K9UV*JSWvkjx+8qRr zS9aG&STv6P5iUt;!(dcA_2$B9zQZOc$FCaYwksRrav-?d>5-)cr8y&QT18wYwzq``@t}KzY#4S;JN)BID*ek|g zaz9Y?xY`v4vTwwUAlqO(7jqApML2uRrpM?7J6slFuN*2pUP?ZP#B{d>om6}`_XcTP zay79RImS#Q9XBn$I$G^pJu6S|^!bu8hdxw_s;*V!7*mJBtoXyhD~szvpA4t%)i|%U zP^lHw#C@NF6#DIR+;X4V`sDC(&YRa*iZ8c}f7TU-vDQL_kDATCHr;CH)q24c9emq2 z(4F&XXZy94wGqk`58XU#O}P$&SbEjqz@)0VUyIS2pkH*{M{`*k?5dZI3 zL3Z)~rZFr!1EwDU&@iKGJJSmpLNx2J4+}LL!oT@%+4M0`XnQ>F2v?@VmrM~r4>N*@ zNc)6^Wq=^^Rg{o)LYqC*yvr4olr!jcPS{+I29Dnx0z2%1wfYEB{5v&t`9Yz21IR{M zT5dOthc$66Nn?~dKR@*kb$S8^-w5b`Rv%XvU{p+=H~2+PEAjk}ugy?l{C0_cPX{@R z9Lz=W;&07(f9L)ZYsYn4JsmC&5ZHR2c#5(MfSg!L>$>(m|FQwXn#Ms=P`VSUyXB;8 zV{94E2C98;&5Zfuz09nAO}r>A5@`u_pdnBU=rGC$dU{@l>b3U(5oH(EP5WsGB4~+e z4E4+6^tfOG$>tIjP*uz0hJXk~P&O6nn)Fmz`-;9$qtjDm+QCj5Em%SF!{tPB--fOO z69rwu6Zu_mq7#=wqWHtW$1Qh@Ch_oGaI?tI+2&ae;fIo1P(^Y;bu3;lG}1fXN$fzp zmG}$=wM9iPqjnLs<*v9up7=urQHXTXFfd@Q8Xwpyna>AR27t$aN_-tBwD%acsxClX zO(338eglo=FLoT2IQc!9A%ebNj2id>A??+canO!V#uVuOYKrgR1)7phP}Or< z2j9o!#n^WLeaMKhEB3lHxJ*|TW3CNn^hCMQ+d?TFH_{2r(WNOYw4fXKXEw=if+tY&++Q)-zWDVqfiTyZYvJrX$>TS)V$RQf(^!R z42%NA$$y0<=fG2HyBV)`C0y`fi@PFF8YZ6zaJJ z^^W7{-&c|9>8;`q{Dx5;GJx@G3Q)iGgp`Bc%MYM}1_s+PQ-sq<5O6wWAmM6?U?3>? zCrp4It<^62yP%eYUZ9O-GB&Ac5|?HhidNsd@8$p5{hbAx3_-x~#CHOnlBClzVVb*D z#{h5Tc>s&HHzr5T|DAh|@PS6IcIzDU5n8&y`$-+Nfer^7=O!TGwfzCG5Wyth6Z^!;*SsS_W4K0c7E6kNR{ zEfmCP88<^rP+vBX*xzbmW<09$h~y-E{Pr#nAv^NzctWZr=_rdaiZBkI`iPX8@MnLu zZXlRh$#-put$k3NS{*VLgbQyt57+3Z>Q+CH0{XsS$8PuNJ39O#gRFou`mUbNGJMS; z5EB6E#+j1PqPh^LJZ3%;yx7qI?95V0tH>5p@0#39MPJs*JU9;|NukLVMg*7LS)TSr z4jN{PdY^d+wGS%A1_))@4}o@DhLGg2-N$>`Ci+*lidO4}HZ2Av;YO?J5xXLrK7G=m zDd&UMJDq4VsThkSP66K%=}E=E6i{@Lod{iBH;K%mMA+quMrfTj|?mss4T8` zaY%LvOP8R%xdn_J!1Ccwe~vn|E@QVB(1tg3CcsK-rG<}xf&!Nl zUHF_3^3v+p*Axn@lpg_L@GGq0n49H?==)Qv?XSMOqfd@9L_7_c{_bRn5=7TNsBX!K zJ(3eVozdWi!u3{1(Bwdav>G19JS5-VwV}T!vW0Yrw4~ z>~piJ*!3(3PlaJnG`9PX;cIwJtNSsocn`g3W2o(ypinQ(f`u>!y^zKi&hz`a2Q(L&qS>qSWsdln5z|2+GUzqBtem!fp@}_}-vBhRyQ_(wJnE~Sb2`m{ zmSt09vDlgJvl2K+=;vdv<$3T)Aid85!&RfY?ZcT%VHnePb3?uj7Hyp}!bvHq<-0Gd zlkq)-Tg|u5fBcx`Q6lA$U{y`Og--HaPULE_~#BWQRMzvA%LwsKj+60ZY z?X?L;2aooy^m?5b1w9BqK@D}3jzg>!La(3!Q0pD{CNIccyOwaY!GKcz)t5QoZ z*O*v#;Jq;ip%aVuPI>&xsGff5y^Y4su*34g}hEkAI~o47+pw8g0DQxsIsj}Zxg_e!+E2hW7yBffjLN% z*aPs;l7P8)%2%-`tm6sc!YrM#@x-Ql{bO&3L2lwj*6l;P`xRBsAyXXmD_!>Qq!}wnD>NYfhp&YA?ZiY2u*-$%%1A?Kf zMP!`XrEpl+3*|{hCq<`dXz9J8YcI7S8`JY8KfXx|j~h zpfa}SfBptBpHQV@W@*hUA5#}WjL-#GNnP+WejpAi=t45st*eqtO*8?8CYCP7W|+|> zL^lo2Tb^QlQ53%&KbWs2GBMuEzX#x3*j0KBZt=r-W0w2z+>rT*@~0v@wwQc8g#m3( zP7=#N`%nXS+1!D}DXKroOLyif6G81TDiY;rib{lR&4XFw%xh21(J zJT$@fY2Yu`#Y*%3f>}ngt5&-r;TAjNoldCRI&FM;lX3V=k1Y%3aWFG|AXLOKc!uzTTU)|W0hSIb`9>-tC7j0KjF1%cUuJ4{*&KJCxxp8c< zA7N$?n(f&wNzge%$G`7Djo2`z?kBZbnYOrPR5iP0>{@*vsc8dbZDT-?RL{aP7tMAS zi=~ieOl_;@R%)UhiH!2)(GTZSVWrh2;0M^5E4<=hCe@{RrRLJ!cG44jiH{#_V z)ccLD_4%JEyZ$kNJcvc0L7K*dKm~lg^u++D&O=Qp6D1|y9Y4l-;2+1i`ekJGz@XC6 zG5t-S*wZ1x(q@}mFK9>c(VgtRF8AXE_^>z6q6K{$uas*WiEl1=#s=$5&%t2z>orwRI@=RYGUu{hRHYW~bfL z$V>$VJnq$jq=aB(;Ry$p2tAO^&HC0gzyjpbN%gUe#jXvq^8XwrhfmURcdB-OVs{f$ zL{7@=$ni=7DZ=rto7#!%C`ES3{Bd%skJ>cNCSYJs$rxzP%SD7Xszw48rV_yGJ%4Ze zkdp`;cF@uH8F@XOWMb;H)~K9tEWwF7KMG}`2PlsRa62(1eUAx z8s@FgD3UWI_4}1eU%ZF=?C4bqAp&H7M`;afay)@OTN-1J2+$7PNU)L>UF7an;#d%9 zmc0#(%wdfAAA@yhmD-o@-1PCVkOJ;A=wv#)+9iYwWRUxwJDDkq?#f7aMg}y0WrCdZ ztJ*K#TURT}jq=>JMIu-hs*>x;@-16+d-v(DAh*O7HD4iu9qG84`E+s~%jj9yuqp3} z+S9zBx*z|=dHp=dbqUginOEH=P6y4}k_mm!>Bjx(kI3{+S$6yJ7qlnU)7sETwD>ec za*qQ9E5Y>T`(gV?Qi%i5HPEpX=JpiK!3TcI6=FmbIUzIHBSxyCOL(@d3w&hhFRjh{H#bPnLp}S@BLGo<_cxg z0N~zWR=+j}aR=ZE3&`F~IdmcnhF2}W*$vK9Mw+3ZPcWf(Pf7jc0}9S z5+qB&{&Kp>o5tTSn0YE=?wv$G3vzPyZL*Urx^Teid!}v9O0D3FEp{#~fncwl$^qX< zF}0}#FO#VRvhfshQQkQoMV$v4dGwig9XwGX{frCbmE6}qMz%}!1~Txj2eXM9lCtO~%6Y9lO|+uG0;v>#56!z>ctX0Hb`I1-Y$M+fZNCCu7ou$3qrAB3~VR zi+vI-ZD?)1L$t5N(zYaaiH=yEALn6B$wYHplNO_Sp#3;%%!u*5X3T8o5<3k?UmU-m zWRRisxl5Y%eQ3sA3i#==nfA5jf=fk%WqN0i${1eu$2gYLP+@xcg z3@Y*%sP1X#t9$#OU0d5QIDXJc^iSZ~!GEjwYzOpdIY6h5nba{nr7Pp`s{;w=$6B@;*6plyT7V}Y zT@XeZ;)RL_U+aZAyn9)M|00P+PE7gHX533Pykke_b$LHsPWs2SdZ|Wfk4I@uO{^U; zV5afgtHxQnig%!LRE;RLV4V;AYB}Y-P%ZZe{v}Zc>O-<iD-nroHcn4^QujM!IT(h_0U$bZMtq~d9?}~%I!iJDnf)JH|3#GU+ z&;BPD`2*{E2F4ZWi*TTTKaOh`z|H@8tNGs`ozMSi&*stxjwTnnVF-)(pHp$m$$i_C z0=N|7#2@AzAqr%QJn;wLi0IiT@c;kl)_4ipN6>4&7M_3wLPrz12LK%okR4yfAtqOVAU|aV zD2fpmL+FbRJMA>1i+ngM$}H|6fGfxT4z5hn+w}XAN^DWq-{pxfh;m(@=ZXIpkP7Yt z5hNV4^m#lD!*KKh@YEg%H38LhBz{O1Q#E%`npnpvFkVEmYdK|Ng(cMmuBlLS?Py*@ z^|Fy0AS5K%FQS-`&`mt3&V(AzM(#fz54!ky)2GqpNOkuCBG*6qp8v(9-8LBoofScIS7x%Yfn@2@E`e=zp!ZE10JIH2 zJ>u_p8%5S7y$S-0fc$9f06^LThu{2TT>c-|_=!6}VjpPY%6Wl;>KrK_tsaGMDvQBb zINJ{Jh}Epc(FMnDwCSi5Ai7tr-NxT1;;1VN+q6$lvq zfmegpK@XuOZ78V1U3tXwbl|(lTP5fljp7eMDQ!OBvCg#8BPIXIv0QU$UA1W}A8wi( ze(s(fbgBG=-m`esgt6oJY+YVxpP+sF3y$~0W4#He4PoBH4JHV#9$3W)#fAuLdWHtN z&2(z2udF6Dl(HI0`Y|A7?Z3Al=Xnu3A<=t^2D)rsMQtC`YYJ>g>F*r=^d8W$qIp8V z+jIQKhB*U(q0K%20WYhrvV_>rk$YgK>V4IYZ)5FvKP_V6wl*EtK|c}b;alny6d>T9 z(+0{40DBF99AO%201)Rv$25Nb5ii|AokSnJ!b$wrT`rzL77t2~wB3kOhooL%)I}iE zzmrM*o3Ww6lR)&B0HVKK-9FnZ*$V@tJxI(1;^@TvrqF!zviY#Sy1JHqevRd7O>>1) zg2deOll*d+E^HMZYoPf&_T;4c9n>WsA$@2Q{|Ar%MrBrQ{ynaq{aSN{`DFQtkR#XH zb78t3y89A=U}ghj0rKB#25N7wgYJr=ly4KQ&$2MuFnDd-;*j7YhDy6v9&|8$8%!v~ zN%s{Abd)U{0rgL(S2}0Ig zJ21~C?S zVGLPo7>Fq#*#?jaly`YiqSU$hqjnu+f{ITqYRu1x7M#QT3s#*TOdB&rb*$7M{2tV$ z@VswBBIYAn?jvv_Bh`-8!8@@a$+RAh;wLys;c!rX0O|wc$omO2plUlC1r&s;8JY=o zAZr4ZW7bVqR%k&-6a;-e?=U;NaQ;(1;o;gU!Wk6RmCn{gY`m z@<3|`H1il=fIOBe%EliFLD0Oeo!Ds(Fs%x2!wC?bw3mc>(@%kRby5-MH0YNbSmxH@=8xg8>+Flz)o`zhKAib0ETT6bfMSPvEVr6Kzp&<-abDNgJ&Dvy` zLT%8)@=vN~!ik^xpya6LpORisi1su4qJXPU5FA_T5(t8Sx#P(*y=lm23*HbhY0Fa_ zoh-({BEA(8L;@UXj%NH4xwmhsav`;5)mdTN(uAvBeaB~gI%T-4Np++T-Y3*y`y6Xx z+Z?Vj=x~*%Dr>JXUhs7Mkt8A2l`Ic*-H1O> zu{CG(IAH><UhD*$PP24hRT~<7G`dOE#rqhM{!lApTJyR59Oxgt}LHK?w;^ zWNk3GbOMmkbHR4(Qa?qkc+_MHJGru3a$oDM;kqDy+SU8JMh{_ZX>~26J3n#}6Q}=> zi|G9IFS&?j?aiP&%6;>#va=~m+~0vyLqPys=+cx23cx4TjrTjr(+ZnwNgkS48OqQT`sAUWLpt01WvW@_tpxrw8z@*TW zpVJEE^O?OV`rAQLzxyc&I8F+JcnV5_8@cxnZtw!S6e(uW`wkpXTM#fe<|GCQX|^`* zYI>^J=HN)O#gx9H-OZpJG|ao7TUI<4M}~h<(tO+GuQn>L&K>dE*gjkgSo)#ymkh$vRVp23n$CJk^kZf-<*k*ue_N}lJ#LpwD z3F4o@L5p(l_aoN7Wlx{b9(>+FHY3qJ(cXrpljj{$p6YF{Wvyiu>mIb)C<)u;sCQ)j zkibAEm;V?2%?@IR)_)f`T)L7)Y!0osVD#?T z(=TF_Rm0FV+vuA4dQw+?Ia}`>9w%rh&Qmb?;KKYZTn{ z0Ex4})ZVg>cVg!Ok?T05fPdOv*5#Tx=!!>fvt^Z!huvvxCDB~NUV+A(m~W|l$GP)>M8FQ3vfP-B z*1dTO5PTDB+{$%I;c#C zwKV~<+q<$1a1yffxjw%k)U zBVI6+uk#2G;K_6bG@aDyf29TD91=%YdK-pkNXN%){PXukRTTyVjW?`kiv|+en{|Kt z34R;$Zcu32lE5^8Ku$sS6yhex805rl0V;5fwZm>hwRwUEO84)V+cc&rp6m5-rUZjt zoq*G4#s`FRFIiPHTFYfcCcf4wfuw=Tt0)knJ>%dkd4G?yXf4iR{O)`_ZT&MS(ci2? ze1CfrDM52mmrwv_vx=X;+~Lpf zcra4@IA!?<-YWsym6GP}uJ+PZO;y!IsLi!$L{)T`p$5~H9OofqQZF+HlO~jpSacjS z^L}Nle{IB%Ze47-vz3g(kOJY}Een35W2i$RJkZhn4^S}1NUD68z2wzUe=D?jz`vJm z7OTVXXkc+`TGwhUpPk|Lbof1S3AhtJMdtom&={(}o!?umOU*Bl6`knIv1esio+}IX z?Tv^)=LN?Xomv{Y#l6iRQs4+?;xPW^9}s!&0gRpcse^46J z-%iI4w*zBbTSv?2lla>FI}Hc~yEo9Y1erc1_+jag@7sO?nLf2ACkAnA38cNmZ2xah z$sb}-`QM-3j8;*>dFpxn89#P5fRZ0OUJ0+>}Uit;r-1e4iQM6&{f`d;VTwrB-8Rcb+k9N*n@3=aO^aO z9Z8aAeNA9J6P)dAJ)KyVVd-A?d=<)2alrUh84Q)!utg$A&h6P#Yv4(|O*f4|3FKD$ z;!O)UD6h!)j>MowoWy0k1Ls;Z6>qhzV~wOW_oIuu=hA1yH;C~{2(za0+uBZ}veE~x zG8R^mfUYz_Tl8n^L-}E${b{XaThDJ43b^c|Jq`pJk>VT5%P)(cFQCeUNNN*V=-;uj zLlzDwHxu&8bXE+?%tqqukw!DYRi1`x)4G2!_(fpojB(urmLEA|m`#_9M=ZfRXj^xs zfM{ohJROU2Qa@?G*0L=n){TR2kLdXF--~T=%8Jc$8v8b4Yh@m9PaWEDz4Lhn@EQO# z%f+vUP{iN2#6O~lqYm%YU~t6a^^Z=EOdb_{GH6127MC4CDZVi9LA6Jfb51&?j znIXOI;AlMBRyAJGFuu^hCTNsN9iv|JqP%wM{2p@PQ&1O(rT=7gu8E_=^oDZ{Upw>u z^=w?g`N0>tdki|7)1!e_>}1bL408 zmCat6r{hYF)$ss&4>NLnz{Ozp)A!! z`v2H_^SCCnEo&H+D&c~HS|y^$q=FM5GH4W%Vi0h|8KVp;lL&$)A_fRil)+L!WDY}A zRJ5d|422j*=KuksqKPON6+*;7kOT-JB)t2e)%W)8zWw&K`}h66?zj94DJyxB=Q-!B zz4lsb8#~W1G&-rOragW7n&;OIlTN}4)`?5gVzbR!1JxYv zb;558&h}hPu37xz87?kzC{RT+Rr^soyxHonZ)ODVJ&WToC;m7H$+aJa)MK60CYqS4 zP8js`n;br{<5IG6(Th^tq`ir!4X-)Fn@#)jW>et3nZHeaz3kDXy$4Pk2CN^_b<|#l zF;crOFkXGVu;ee{1MZg!6uK%-^HnVuz?&t*CxbUTgx)(o{As+AzKT=n3rFpQsdE>v zKGw4klGc{2ju3f1Wj2!hY6{-SfvhTT04;X~vj&n+{XxX)YHx1;EZUwJuO}>x5Y@W+ zfW|fy6FvMn)pcn>kJV|zy#zcw!61MpoHN+nG@C%c`?;jjz}JE%}^N~ymn%xyf5Np!nRGAt5G2Z9n|1ki;()8 z1Tm&F6^_EuxpCigg|=aD+v27}aOdkm3obMa&u+chO%JpvG?)Os~({cHIyPo zfVirol;im-^I1o2wpqq8oUW_Wd;Vot;>i#TDFI84)HedwhWm&)bGg zY7;*(>ZX4Bc++vNV%zmV%J=uQ)g3mRfg3jO%MC+MX7awdzwb+&eDCSf3~lG+>hZ)# zG|cFdQ$lj|;PCVVcR}7>`I~dI#f;dxqMzog^}{fN#&!V=^yt-*iW9^`D}Jdw7dc z=2t6>z+-&8e73^@c$DG`F5o+Dkn`%#Z^&?sGeq)6 zIs~}UiGpS^Xu%^u(#f3b-}kn-wDSsKHuRtpj zmff}a4^{s9T>q<00Kd*CF?#pIQ(D-SD>RNnYo`IY(pAT755K#2Qrr|4Qwqd+it;U4 z&G>%2U8rJY&>w=~+9mLmf4Z*yNH;L=8F`&^n2_@;e?(Yf0dweYeai2uX-$Aji@N-} zEZDBpG;v)=sK+|9Vvvp;6$Z2&T6ZI^ER0tZI<`_|d-cVGb+7CM z*7()v-^}&GjIqO0h5`>}E~#G4HGZIz@^81KFWaGtCc5dJvqr&8kOiq?$3qWS1T<02 zA1xEz9fi5n8L$+K^j01HSmzP*Lu}pS=cHSw=c5%YhNq5vP|cX858E9GQg*&v-9(=8 zF8g;vzms`%ozk9SarH(25}}FCcE3B8qGND{4Yw`7UU!D?hM~lc&~5N(VknuyO;6fy zrSIOA!@K&V{o0GA+eadAtvPL>Z zPk-LR|M>_Q5Cq0G8bX;HgeA4!-ch|}BCeE28LRu1R(Cs+nLM|>m&}jJG19X;{u?qp z_CTNgx>5kBs{yriA*Q``ZikiuwoO4Az`$%TY=*}4Z&}vVt8jqE3<0iPiZCl47;}70 zGzl;PD|HVpRplXS$fJR!A}qL-!Z9e<`P0jrEQqRSp((YY5IK8m@@&kVTIg%?Z~7<> zMvR?a{1y_=A$PmzD9@Cg)|Vr|!;JgOJ_qu@g%Y2Eu{owWp+G%}2T?GVw zhDYY=zA9pfnz7eI3(+}5?XPo{Rl@Ay*r55mZh5OIDAk~=xPf+l1$ztak_XpD4&jzG34~ZRS48wHKWkIj0mkR6b#@akS;%6fu4?EL11Zd^qPd zHeUJ)*iKx+FE&a%!nx)3CQg@d>0b2Ysd^u=mLK42S}o2nh%=mi5xF^8Fp+n2C^)dh z;5mtya8_oN2%{PFNPdd|-wCl(koR~;bxYd{`Dd@nV>@=l#9Vz*T{aM$4Tde*q zrhHz~Xt`3s8pR6EqGt>}%1r$%Fkn6=z&)M&rt0pA_=34kQ+15!UDBd zOPqnYTMIdoBjmsfB-_3DP=}o-nX15NZ{V$Npv*f_?>9Bu*I=3M*D~qKao+6oxNiz(5Y(clu&L#<0FvtNWHJYxmXEenVY-Rr}G_^WU;F{)w#qL{@*+H-E=1 z{t1r%=Y!+zPTFuNFAS^dcZmqm7l5wOJlFQnVu(Xpw*r@VYO+=)5s0YoU~it0F6}5hY2=MUfG8_PUU(MHzH8X zfEWoDT#Ppw@PtGr1OR%OfZt<8A7b_1Ada?XI0DzK0; zU@izV-tf5vNT*y`_R_W%@)$5)-$G88X+J_)DkymbKslvR+qouF4!Q)A*Igr;UaA7X zad{hIz4XRZ;L-h|d2m?g!Y;U{I={@C;?S99gk-h^rA&|o_ov(w1~s*lbt<`O-*hc- zpu_XSB_DL^nlNWYs9k^>IPiaLH+ATNIcRd##5rMAx2xb;A{r&4fm-X$8CQCEwY3`S z{~(Apu<9RR+K;dRCyfHhdjk|>6@6aw$ef-zCcj44MyHHoZzvH(3a(U|9ReqFf0TqPygnx`TldOa1=BmuP0061Z zawSdWHju8O4=1L~u{}zAasI@E0~S{0UI04%fkJGjwe&@Qyp&Mk9(la~u}-D-qh=Vz z;-BTU6Oi>D1e`@&kjj~2qGyho6@;0AjV&`N>V?12U_lydIs{It7~hhH!Sq zl9DQV<@MgS|1fL(tJ*`&4cUWu8lVd zQ#Gdlaf`CUKt+#sDVK6)(-BNxcD9Ps2o*-CZTQlRhymYaQRU|chzCcz6+_$kZH~3W z&#DH`|A5z(hL7PhZAGa{XxJ11rUXUw9jwTvv+JsyC^Jo3q{)P4jSVKmdwTK{_5ayM z(M#<|Q21`!S-i_K;?hsCe+jD|+{xuO_Cxf^OMh^Qcpcvd#Z$X4+GXhoOShL0m%4v` z-?W0)Bch#00_sY^I&H+_Go=~s7wS56_Iy*D2F{Vv#s8~2sehuK|M6(&&s+HCE&OMD z@!wkcj|(64-rhO12M-zcNTARFr*=vk{V8N6y=)giklIq9YCqK+L)~IA%Ob1yO}W9# zu}1ESL$6=ho5;Hc+*X9Py|S+78@N~Mj!GUG%3*I$Tj_a{PRj0*Up7mPVuY5a$a7>mC%D#yG1%2=OTo^+FTrD&#dVcaz^9 z!81d14W;&odm;$(BaWrufqz7xPkQ$o#Seh#(3yrDiJ@VY!2S{-rR^x~`7$0bnyd^D z@7B5HGY-`Ctq6JbG;TcNZM@kYb<53S0EYZ6L4bqNd*@A;qc(~SgAgXy2wHUQ%bOlq z@YR=TY+HKmTl$=+;oS8Gy7D@)ZVUIQDm5aco4^HS`#iqLS#&+qPwg3o@F@(49}^-j%j8K>+(PJ~(6!c(4sYN)p39ZCM+dUdT~`8^NZ z_-g<226kRwYPE~90I4A;w{jUz?!L*`stf0dbHPP^aBnN>8fJL?#6~3{@4eyCbkL zgucDVwQBgoSueHykW2{$2I4EdelwDKl^uK;3vCnGB?$DtBT6#`Es-bELX>K;HGMCv zQ`ryMmhExJ7>C^!g^xB+n<~~`4;rr{${uWoC#;TRxYQ~W9e!)!*Ss<^A^1B52zfb>A zS5-*KvK!ZO;Djc9EA75&IhOyyV7HQn(!bOXJ?!OY>o)BFQYGa$9fS|mGK3$|EDGh%J%UPw5WsIu1m-dxVq( zvaJdg=lR3unFCC5^z28V#;*W3>7G03Fsboa^|R;?kAD7fNd;3nST`@_Xo^pWto7Tp zb(yi>vX}j-+4%dN;19jzpAhj+i1_~%MEr9V{&%lJB(Yct*mE4XqI6i8+%pa&=auW3 z2`(^vL=tGpry~WW53iO&ApxDnf?mHM(zg3lmr%$p=_|01cj*ZwckOCQHs;!H^vC7* znSr)V80Ct<(cdUU|E~3!q{%xChnjG5>+GwaWx8MW-e1I(c^4l5M%=A`{M|_c=uk4z zG~*CnjiSyzrW35H<~-$4xj9@(Er#Q^Ne)x4zQTYA%Ev-@%k-`TN ziV*vwu~g0}uX*|1EEJUs#Z`Ga%AS@xCk{9O9NdYbQAM?5%0fqN_&q`Pnp?AT6NaBI zlB%!m+_rFLWf=9FXz$0`&O>E^`QbZ3MAVvEqW5d90*$Dzyy)yWP4*}eJ06)%Ky#*n z(#=315AogYruKTmG($q04`_6x^)trHPum*<_?EEz-%)4bSl`?BUY>-Uv6WeJI$x}A zrqK67ba96trwMpdfZNRMo@rl5RfTdXx8Pi*7m`{8&|Q&)p>%W3uZ)?~YL5R~AHx?9 zh05LonOoH^)4uE6rZ4WElIC{(9X!HIQgF)St-l9t(}{h>AO)W_( z2gMDT9nHa@Bft$ICGzXiz!cE1bT0edZm9TLL9Zf$hofslF+6AEuRwk_5?H{8pZdbMEUkZFlGLjPK=w61nT-F zhz1~mHM`RN97PV`BvwdnNzN{kpKDw_ z5V&tSxo@GM>Yg8=jb*2ueeOT!foaIlql)n-4=wEQUTK-C9@6HCA@}hp zxv6STA2bM_wVS?2n*IoOD#j5z&Dj2_UdxONb*5`N+lWXv4`e`hjXU<*7-^EmUH7F7m_LsGxx|NK^p=+E>2zx(|EQ#t+r8{^rZh1CD}LaG!58W@jg$f0Y1 z@fOL^`~!+!ce{RP#PGI)Bv?9#&;>Z;GJ#w6y%m^-Jy4^BVDlxX!$ak@Uitv_#NeyRL^&f>h@J*@t{HIB-V8XTySmgd*95W}aYjixs7Au2 zQP6>9fofzoYRA$KzWjxb@OMYjicAFIg!$&zMm@iQ4+XV(D;wplYpjeOyy-J{POMv) zl~F>Qg&XbOAY zs`&JaXomiN7#+lilXxlI%0p7&U2W%rd9?430FX8tDOmL_eO_di8(2y#I1b!50cM;e zO>yw!MLnFdA{~h`(9@z}?RohAcU~vnqt*ygkdROc5K_1d{ymr;cXatE`97Ixn^xV3 z<}dMCge3DMD6VE!e!R)YOaQ|6bTYKXuzJw`Et9(3RMe~qTYhNh}} zZrGX=-YI}NK?v}ljf3#T7O%Wj<6Q5jl)vgjT4{@RYym;V$~5B5BjfUs=Af@)Wz+n7 zI%)sdX9(73M~0i3)v1A%<9a2S&U#b|S_u!hNOPqp>KXy1&f4MO^^z=p0D@{#ft{^U zB5i{&jT5|1tERR5Yp?bN%}IY_ruthGvWwc~^oI#r5&IHO z>ptgtq9P`zuQq$-S!b|v!LIudOr9Q zafAD=4n-3&?8J5R_^p97GvrjNsX-mx;KDL+m=iue zy;K;Ah%*?6%w-Pk6_=rrVwF7Dknu=ecdJhZ;R0FVLQXhyP#FV5>H2EMklObR3W{L# z$v+mRWTGi^2JIk=TrIlIy7v;RuMT0V1wqYN5FN}Z z{t|xy^d}2-cMHI1a|@Sa=omv=r~UrJSqo7pyWki=5<5%x zi|66}-=?kh=>!V12+6%kFs89WQmbk({is{rrK}P`iyrbR#m#g8Ksay#PqrPUl!+&pwIzRCUAojpvno z6i10BNQ#Dh>pMWOl82mFda{O;0iy(vH8hbMoUlfhSKw4rWe>qBSn2-j=e63)mi}{T zDqUywV%@GAos0)zX_hDSz%l5afPL<1zScEY2Gljk3wez8L0jFtlZ~bX%07A2?nd1M z0x*IWD{W2pq73q5<$P>W{;}QTML-p~&ZxEkYASzFHn{2!o{3htj}5pBK3R;3Eh-J4 zSev`m{g)qBv_uFXf;#39cY2Qb-FLAll0y`|jMFFIys9Nn+>ZZZvyj3E%*r?Audj%uA1x+QuOQS8O6gh1AK6R<0nHmP zYwIt1jee6q^cs-8ao`KN4A~n*`WF;G6sX>o^hIwnd}2I$UO3kaC9t$(eBUmzq&@u%Sv7hu6Y3>i zK=QVmI(G_D98r1uu{}D!p}5yB$Ege+GJ@WMYTbNUs(@w?TX)fx=9bD7>@WD+a1QI) zu@jRmnB|R=PW~gEwBN7^@}lDEI1A(0>_0M;YU)5b_)MqLw zf`_8xJ)x5EaxRbcjQ=ZZaLFA`&Txx{gyAIw{0<8abNQ&E1Cv}HS!WfKlM!1 zCEeN{t4;Fs0hbT8;X8F%X;OV`)VmaZTL@w~79`0!8kDp~<%`}j>_aCON{i`-!*dlMgh~8^H7PX4cV;(WSkF#D(ztKWd z?D2ndQjk(L5o_?Rm&@th?S1RjFpGa-3a%v6v3gj-e{1GrCdVBt9eeqO(Q;Rtc*2MZ zCZbIuq$bsm{CJ6uh8cr6WL|kR{R~;&Th0QNq|KlP7UHq~z1%$)wGXB1wFZDeTC@a4 z2d7|14oKzEy`SGdUk-icK@P^c)PW_PV}&^+Gi>`jJDf&uwU!7 z*Xqk(qXxBARh+E186{e`MZm;(lvHx-J>U~=77`e|sdAb6L?(d@+iP$0*iy)XLRo2l zBzOHyv=Q5!IRI35HN>{h9jBO4S%Ui2m1GG%Fnqet{kT3wmAKNM-Tz7|{yuW`ZuW_Dge`__$ce$|MynCoV$FdELyMXMt z_96$UbqH+~R4%zE)y;7<`^Pf&1E76Jx17{-n^`eMC~B`nc0rDU`h5q|DnMf0tLpVz z-Mk2#yBJofyb~|=>HKi!}hCrDS-_02j=E_4~NtaS_C86c-j3A(Nv?3CuFd z6bnH~PJOvEzk0qVf zB}{lan)BrLI`_3VDn3aCk*@1Ddgn}{ez(|$)O~aD4v(S0buT?Q=X{f>e6b1r^J8o9 zphCdj-8PVKwVT>qc}9V$2H{gKSbq+Kl?1W}+Gr1H#g(e4t)Vy&E>~P~=&hW&;C*&H z6&{JL7*?2Hh>1SAdS}kq`yJFr_H)F{-p^ zG$PDebs9r~@;4u7+g$ps(Pg=h?h5WKk5*sMcge=gc8ELWd&~V_&^FN{9*6X-=&$D@S z|Mj4PX1e+Sf<8N987R~8i@Bz1dV+0w%65*{@aS?)_L*NI*2S?+gBQxz%k73o3CWpf z9Z#@}C`BI!ep1=1cG?hI3CgvjG|^|Up=5%UAQLi)ewEvVJ%w7qt;E`=7TLkkNUH0X ze#;vR;N8M$q}7jF%8AwUvOSZD_!am3rXPUKw;nQBS__nmHkfmR_>pGd&&~OZ!XWWi^1#+p`OfLV^d%m*dg3RlyDv5QmsRv7H1S{m!9{fF1`e3GI$ zqNb(#{k8yI8M!7C?-pQ~b7a6KQobL^l2!2ba1echs2TM3Sl*Vp)WqjdlJtz?`1`=9jRiOBnZ`K_S8WxeuJlBb>&9MiMt z_XX)1<-`!UQ)p#4VUo5EkJ8yz@8a2D{?VK#UZSFT5~U62&66KP`90=zmeR9N%_Qq= z&vW8;CTY-h`}n;{OCNi{eIKEfc2#As8|d~H=6*C7eK*-vsT#c#@5-4Dtk`zJheMNs zhog%Z954-DyHX2VtC#lt)Zvhcdl#Zq;>gn0V3oA)DA%sp%P{uF*L^A9hc^yg{2EL= zK?VI;IvXN8(VvS8zW%WHnVQ4+Z&b|Gzy5I6MQ!5vuMVvJ`ok`3P_&^xKdt}z!@bMX zV#j}@vU~d1A7&;2E-C#E5WKJL_KmT(9m$7yV@j!jgO6#V?fVGf7|}0oo0jy)q-1ajlr~Ie z+gMBTA&3Z*z3|;rBlt*V(d2zdTsK#?Skpt9TMKTyLXM+%M#MtrAP@6O&?678I|)HE?iyB9o|B`g+gdk8$ccu&d;cBizf9-6{p%(1TPT} z$Bc1s*x-OX-~2iL`TXOVZ^ZUb=5kf`P6x&8&RV8^uR8xO_Vu=gK4yMGmr@b4SNT?X zTu@#}udO4~Y1y^hIxb;P^X-(u<143$M+zvnUeitu0n??l8&Pf=;&wX>(x13+y}IH$ zhF-OHNCm%ROlNtO^HBcg}ib)x)XcF3$NcBo0WxIK}hTxc^ z_DK!WW+L0igA`q0fphw_%iaXNPvepKCH>FFngwOp$h?&tAy!OnwhTN<$rK0;Xk}I- zqt!8o27-f+9_nO_C|W;Ss>K@$_Z=O|R|U!E5)1wIIuLE0S%~gIcbMgqV8* zDfr!jWJto?Zkih}qaUF_JV>Q{9KpdZlPA}uch^81Z9{pjqL+ZiFrgQ|FHN{a>Mi3| z*hm04&XuiOgrX2IeN~c*5_F6s-cN(NvUL{@n9cT84UtHf-DU39bjF{vrKUhI-hx1u zs5dTrC4&umk2W%GbSdd=$d%H9(qzILSOw8-Ce!l{$im2*rb0N)6EWlyv*+p%2-&`C zk}-Ga2L(6^sB*+wV}!+}6*d#kgg?;;` zPm#*z^=cm;txYk}S=gB;JvmuFTX^ysmk8?M2zf%!JVMOKczAYF!vXuXw2Jm1z-Q^~6p$hxHaOYabB2eJt@(Ez_2y^f}92mjYv2#HRn6lHD{ED67<$t+@U26 z5O{jE1igAGyW$OU0`KAUf(`e%J=TW}-p+{k&J9A_OLqrsTPFAVWOe5hfaHV7JTnkX zw|!FIzlhB4xi7x!-F3RPgWrz{IJaj7eh$>r*}~d@na}#Bt$^J4-adjJYt&xuHY>I# zhhUz{4dL~%OGx=K9Ayw^fAiYs^2T0LK~H%X%d$64$2)tqv|id%GWZ-;Jt3stdufSK zx$DBMTm*Msqe&Y6@&FU|cBPPzJ7;8?N`Wo$JtP%+x`_ z8vK&f4lbnnz3A;F=r!2vQ^=;PM9*~8miS4ir%0P-5@U0k!YIG7O>8M5i&4Fzuu;Lp zJ9&3+;Y+W*(eAursiW-d2P{rJ|HR)`4>O$7;PMWo#+1~I9E>muvTcyrOje*8kDcG- zU~Am!nG!tnzP8`Cd=1Ca8=Fe6YB~7jcka(Cj+2#)Z3zm!hM}fC54qB!)-WPm(cZc= z<=OzV+k((EvYt2ynf-m6;;r=R;#UP*Ha+#M;;snTcR@W-UPJwo00ZAYW;@4$<2UPcjV?GpKX$zV?F6RQng8JB);&Q09rMTctCT2S=L7R@X(7-s80}nVl=s z_BnDmA4l{|Pt|zt&FI)#V2X3%>`M+*AGn(GtWQQS=#i)lFFkFzDbMqC8krc|z&QVV z?LD*Vr+Ks?!DHqHjn0v`+suDwk;2S>YM_-S%!-Ivy;$%bGq&jfM(3qJ4@$@(%T{S% zN-9xY73BK%`oqUsc?WWIJHu%jrjJxJU1bHzprMe~ zw5!q_;)PLcRCV#MvJzRN<#HW(g>c{26#lC$0SD)wNZ6FRH^^JqN*gpv9F4$oXLp51 z6-yGUl*H(|b7lf1X+wdjQDt#y@zvs!N5U>YWkuE%EEO(_DjzTwXBQ_Gb3JF|c&`c2 z$+6P!+pVKA4A556c_P=uZBq~7Vv|D^?_z*&e%iAbqTS+?ah?~`CXv3Mz_@F^vCebp zNK#mIVP&0l?$6e{3EFI8mvKT!EO9w?irXebNwDpk)>%?hYEY($>{S@G_@#SIK|Fs;1gxcO95GU6NbadUL_M^ER2}PBc$p${YF}dRQa-f=yb-j^u6l z(x4$fwN&yV%`F_q_~#g0ecXD*?RkW}21{S%?mD4ec~WpdfLZ6P45ptK|5dtTZ>=q{ zCc`G%cTX}7kHeqE^sSp=E41#LpVqOJT8gJ(y!lc7yqu1!XK&2QeZ7~0{oLY`&+dFn*8kj z75;A7zS|cD*ev4X+$x9;A;!s<3VXd0KAKQCJ6ws1mW z@%>`vz#ig0uYx5!T;GX2rr`4Dfdg{)Gbd&&klclRD}b=?AoOU3*39_gBANZ=0fIO? zFCw6Q{^qQLEpReVrG}?+DWWv@o%O34U)M!8$U;Je>?opsuFdn3q=EB<)R2PJk_UP7 z1c4j;&9iN$n(n@(sH4^kqpVd^^|uUpOQeAe`_cqG3uF0j#U*+=bfz%M29wiip5 z|0x0Xov)^K!&dFD=)_o=vR}Jd48d&jZhe})7bh&)3txKJ*nejx*VkKGXq4zX=q{nM z4`EEsi7NO1PN5PnC}-cYGZ8rCM0CEgevNA)@?Tw$OM~Z?p6Piw;I4alRBC6HpcjnL zG7$0JY)lyYP@%v&H5^q+N~^`h_$p56Cc1eS#wBFmCdm*WE>I@Z=1uz?Qs<`#{E@$&8Qh0((1P`boAw-pCmz7COK#^Zd~O}E6?3w&-oK!v_JFDBZCL?HX@{LW8qKq_os$@H&&sdvQ0oi9gX+-@ zvKU=`zilc`>D(ai@}Q+%j?H;?F6I+APBPW`WkE>#l+fC4(VQIo9?YU9mzA~dgQjY4*JV6oS(=Ci#SiUV^=aYft9N%sb-qet@^__2yl(6(EuaTB zR&qPB_*^_shGp0Xmr`PYR12pX;hQ~REn&<%z3K?f>D3;y$MEZ6T~8vlUDvxQ)&m`L z491{g_9u1`p@;Q&J}El+P|$^drp~IHIFkUwRI-nVX79)G#ksMu7D?PTt@wrTB+{y` zxx)73(MseWPb&%JWpR%z4z>{B@m7TdJJ#)`!Dhi0eVa3D9FjNcZWv(R=@E=*%pvME z9I+6WcE;##>cnC^SR*WUmhZm!}d}vW+r*#}E(W-i*a%C=WIh ziEpXZ&O?)0ovw_fRXdt1F&x>U7@C4q>rpQWsHOGgD%O~DSZj{M|IssF%2Jx>D6abC zlI`-`NE-zs5XpyVr#!B?!+>Lj?`-RIZaDebNfNTCrq zcCMV=opvjPCc^ph;YK4#SuqoqXWJ_I=VIEx&tgF0&oroTysxUG$gM2uMgS`0-2LFJ zqGFb}*4be@emx1L?n#1^4KP_v)OyB&NDxhXvjLgS7u4Gm(ATP4WC=mqXahRz?iXy$1ch0Qf6=V7AGGkc3qZpbR%_dDhSeZ-X zhI7_>zaee#Yw}CFO0k+9Teqs-^p^qVFFjKFSOc}evPi4>st0xN!Q|Dl-6J?got<>8 zn+-dQwkRw%jkXA90pCq^;M`8T{nn~Sij_g$lGY{uv~*@tLw`{pt!pSxgP(nY*%=Ib zrRZ=CCQ%dpfXxoM#u@d z#s`8!1ocW`uE@?~HdS}#faz`02H29{ckm3}q zC5Lvd;SfHhH8=2(ANg8fF8O#&uf_5P8KG;bqqgRnu6Ou(WVOq2;mv^00&1B}J3-`f`1D6yS6!nsJ=_ zXD)QG;A56Z2CU@@{0K(9X|C|W(>6*BtZ~KJ+TpXJT^a61>(myqY22~vrwyMIn~1Q1 z3EbRmgu~-Gng*;x44}4=tV7Wd5ZI|6Tfb*_{i2B!S5dOfZMh*wd4M%WZ2tVLP`qs8n7yF>{=dNcVr&( z7dzf4*ezmm#$67t5I(Pva z?o^&YSRHLYXT*A~#9n^3S`JxKISYa@?0k*-IKx>L%K4>Q4$d40Z((X#=J!F=+(Kri zw?0}h;W?LfZ(7;%v9Y3V@7{raIKXq7>JM<%&t9c#d__HX-yV)T)joAFlXl66wrslu zgo*Mhw-e?MdwhFK(G^0(g->qK(EzQiC&KGJlO&>9m^*wWMt?APNw8Hdn|Nhfu5Te) z^5izSL(I&oJ96J`u_OjLCees+EA4zRup5WOuj(up7+vnsk&I%R2XsgSF4A~Xfpu`p zPff2`BTXZ$BbLF7oX%BQZLG!ayAI1N)dyaM<7!^faC=d(Z;fUl@#N0tGb2nk?hb!e zy8eUF&08a9wN7&3Y~Xkr#LjA;6OiAP{}RR`jaR(IJ?iK+-frft@0D^DJ(sfHl#bQu z+eiJ}FShX zb|%%V4YYtAh$Uq;S}3l2UZ;jfqiHWvHaqofJ#ph%v`LJm8PaWmXf7IbCl*-NQmEwZ z9LMCjz**zCS?5H3&`7hh6)qR7CP`lR$mlVEZ|N{(Lzy?9dEV{#Lcx4RZhBI~5ktYp zR-C`Zk9br3B!*cnyQDRkzsq*9grP?n5$;@G&XQ4veAO-ZuQCSH^&K34fSyb=&0zHPO+TWnuo>ZFTCLVl4v%fsAnlq7%RZCo^` zBV-og9-B)~{Rn^aVF%3DnT+Z7`}iczkiL_iZ0uBO?eTnprrM9xo~ zefqqT{Ng0fpRydhYh9vR4+~=Z+g6;4FBV$I5{#)co=4jYt>zr=Q$BdguT`KYKG?`K zNjH%{|F(}$A%O?@%J1Fzv)hQVtvGC-XvN5|ow-g*S9Vs}D++b}rVYemmP)n320ycA zgY1XeO+azYP11LI{<+8Efqo!tZ6#yH88N+3)-l-z4Oi>atM|pfBZ)MZ(L=@6rFTjT z#C8n(KC$&!|1xV#AZ)3#yfjEnfxGgFS<2v4d8g;X5{aINi@$=8DaE!NpDP{K3nUo1 zHCKv3RxaHo&Op=Aw~~1)4A6drh8F$h@Yh3Cyil4RJ%mZGUBD9-!iw zUcdZvQKqF;)0|?p7(Sd0L#K4t`PF8>b&gLU6%;>I9q7X8tC$$69@+0tvx8^Ko>c4F z+)o}DJ-U75CfVI~U*bT*^BJq&%6hN&IE-Linhglk)J=a z-oT#HO_C~LaiooFjxqMupF4kPmQL)f-f4Vc4y#G4OG-C^g^ahrXrcB(MaM4Av%5?< zvyK&%rS@g%Z|1l(2etS%Y*f0>z#(W0h1_raKcmb{6@#APg|?5rCw-q*tC_X4I(V+{h!S5Hpd%I?)$jn zVUSqx6VK-rCu})=U$b=WHtQ5tWsu;CZSJrgMU)kAz?{<@7sYV59Nfmk3$`n?Id`3&O zS>LhO31>&nG9^}5b#o)`xt&^=#Fov8nnkS|LRY)Cad(5^Ql0bHbg5~Ldai`Vx{|_E z9d-e*8sH#xCrk?}Sixl2nG>HccCb~|&S#mNfz=PnQ>vq|o|CuSpG*yi0k`lc)%X{jmKiiqh@99k<8o4D$ z;ny?nSqi)qj4cK8aNAkrBLq9eZ}B?-#j_LUF{TG*N+aK~La-T`4Z zJZTqfUQNDRYRc-6c*@8_VHg+w$_sM86df6Uc?+xGTwsN#6f#$+Tn7!9Cg!f-isfYf zD3i2*H*6Eb~74vmnc*m!Jo%5H!H2pX) z_>#f&D-&MO&IOq9`$+|PZ7#OG44+LW_h)kn z)C)nsb(Yb5x5Pg!>^qpw1YV?LPr(wxL~2>i;H#3jXtU=%_1|3u)jwx1XHW8>LME-a zKPG>$FLHuYR>$V=H5F^-;tg7T`8EacV&m4;g0l27o5&E>I}3VDi#0F=x0m78ch)f6lc7}8U3c!5)vRAM zKhm=rg6&7DeLw^;Pq8|~UnNUIs|xn}$Ywu=opsv7J zP{;5Y!4*o9j|j?d3>45k>fd77sfsnCGzB?^CoLG!xIpLCKXxq?v>(2Fl}#nSQpP?j zj>|hWB%qV;tt=6Es%F_d?aM0to}SSBdUkK$fKIHSAHSNhYhmzw9Ya2&B`|3qop59; z=gCLYV!xzLJv$$Fi&wxL)C)1pvjgP)zvH0M_x5^mU)Hs9D)Gg$94~Jk1c%g7@1A2 zKVnF3staC4zEoo|8)NHN?Pp$~Q5Hq2f%5(mt(9K`Xu)Y9rRjBpbWU$?mlBrKokkn_ z+r0~)w`{QZoz}}A#N47#xyQ|`HicO;uL5;S%&Sx0thMK+>D{DIJ3NQN@N*QV{56~+ zphGgalp#-r{?Zazo-lAqrwxr8LrJEUJ?-PukBx-C^yvD8H(*e_C9>Vk5;<{HkNe!p zZLcx?81Q*XJ(J`eYdM>4y=;hVP%gD{hGF7Zt^ctKp)cyC|Ep-}okkGMgyDl!mLSs^ z`{0xAGaToG6xlyPZ1(5WJt;Mc2#PdFycHRxhHa+_`t}e&piG+qthBk^ALKQEUt3vhNU1CcE10sjLfx+38@_JeGas31C zq#2{x#{9@Y%6x1b*CKdqQ)g5yuwolGQ7$~y-6gP;E5c^O74wN+KZk)DbR%FDI**YQ zn`Q%^#J>X+;oJ`mJPGVg0hzcvn)qlj=oSYeuMQ9z+U~6$VgO~_2k}0IjZXV8c$y`f@X7n1vgl4_kzd$RZm3)xIPe%#;maT?+N@gXbyV< z%oWX-$;pTy`v9QP4qPHYZBC!&QzG;{3-9<5d2U%0^<{RJwh{?~)HG4o%fzC&t@}jn zZS9D79}e8$s!AW}LTrPQM+hj+{XAkm-~C?%rB^Sf>B{;+Tzimn1~^@UY8~KHEHshZ z;`At6wy|5}f=3OG$L47-C|Ctz9F@(z>hU!vGf>x8-(ASfVta}T+_!NML4wr0m=?no zXy*~UJ1&;TjSDt@ogmiqbe*@&McqfxxdsNKaYVVFG!cp2kD2LZ&}IEhy@XknrY9(& zdBD%RvG0m1Jc&RY32+t8VQB=q+xR~$%}cIY5i*vIgbpDK>Ei8vz{S?#rTbspD60P9 z-ugDBe7=R1R>Or7(&xi2d?d!e2gtcbRzF>=4x`VcX)0))-7%atn&{@izK4okg7IDb z6wYf(72=QtxSpRbS)P$ZJr)%rZ^}4>WzuMoctf+EEg*2eNhcBQBV;HV{~8|*%LVlw z#n~);7xzs}E{O6~S^l>V)mbCf7I51VKUp01R%YZNvcCO5~MlZ(iiQ$B*o(n;dIe;455a ztDC5){HiC|0dcy}sc++|nW0=yWpa&s3l3E-*YS&ik|hVmSNN~QmXRm5E@B~$KV zO-}kzGyI0;9*_<|$xiwwdAn>|fud##v2^#|#I5qfHYWn*z2%@ZT@WjwUS*?(bDi6S zqVr6iP?X!Le<+@`>=qA$O3t~0IJ+F4Yw92wVP5?Sav4>j8}PTfVKdLMfW_xC0G)V4 z6JMx$6NbmDG)*3g^Z864y{Q5{+o?d$X{2Hd!Ij(|-8C!$*s1>=?ax=Y(R6M3>YQj8 z)^5bG{_e7wmtEaxwLnuH>aEM;$n&bgYeQMx&rD}2Mi{!`N+Ax_!~;3Ki#g%ZB;Jkt zS-KZlhi%em>SCHB0UT3BZ|y62TbSgvR1S+oZ(Y_1+glHb>I3UR4!+ye%=yY$T8MC? zQ@`0y<48UioH;Jo>!G}l0E4`Dn1zN&M zH3C_+eKuoC3P0dVxeoe~iUlP=`GUCeDcX70S$jev-iNBQ$zgBo~Z!U3wQWQy`cf7V7pIXw_&m$gqTJ`(_7t`K^}{x2nf@Lx=>9g*_*wV zDhNJzyFGIc4LyqDLGA8lmxqd7&4nag3}z)GPU}=L+o)v z5Wj#Dah-?Jd!9HTRTt1a%$p-^GFPY`h1gakvoC!)lWg)`#2o%^Zz}|G5es0Jhw@!7 zHidJ$g?iS{0|1j%3#=V{##j8>@%sIOFHl*Y8{^xE%s}72V6C z^!w)+7`##vSnG@VRKtS=8qjpqk7PaLXiaS3%o%Y}MeAE-U;mS|_cw|mKiqq1U-nB- za%~S5@vLX1!_>Yn(cC9(#@;&F@*G91-3>l#pPI;)XS!J7A}hWj!)q`LB|C1v-XB|^ zF^}QfFSf>4Sx`d&MH%|V3ILYcsW){-E*&oD40vV0XO7#7mM870s7DqHLBd_=|HHxZ*EW z>_3h-VHvgLrd8!poCwkg5ZR1lHQ9mZ@Wj%+g{vm*Y%`C9i>QVlumrnh zr+dAljSLLj{xlU1o*{f71JtAMMIm$b%3H8f5HAD45i~?Z%VnlLrmQ3$Au3>I`+xTu zq@_owcimmm#v}`V6jE#>R`JkE;DJT|^@+H!o(*Xj8HI9&bD2=Jg-eV1C5Ku3Sd`C9 zx@#I{j-P>kPlV6&kQ& z)(LojUEfMXU8>KX0{@JqiZO*K6|zbbQL4!t30;@EtksnnqXlm^OrI@BWZ8e7icAVD zByE1iZkkE6YMGtY_{2}f#a`|zLpx;cV`I00@Y+q8ag^;?r|ndZW9Ck7z$TPNTIqC3ZN90* zv`pQ};7V(1gQ#&fv1x!9XV>mu6;lUdK9|>S=ijf>LR;7HgNPByecZGMW(R~b7G zo4ALIh_%h)A?i&i;5#nQIXT2r5RCQAo~wUE!ziJX)@1SdqA%8=J99eKoi1wzVniE_ z`$Lly4xiL#gZn4^swaaw+t&N%^}PvkvSQj~3T+@7CnD|Zd>YN5WlMk!3gbFTs8jCa zNWV$5#Y#@FkuH4&OcJ^&p<~qH31L}K#BP4)f}e!q$~K66%LIVafXk5GL-S2gcZjC>VWc7>f4tnZmh+Opli;OK%cfo^nk?j*jR^Vl&&7aBBbn@hkCl?KmC|6k|z@ucs854 z*{$a>&rzQb7dwlEs$q>rDZsUzFk8ZZ8|tc53jc-(&r7yvWpSdj)jEh>M`UmMPvtAd zV-nx&>V~P8d$S1Fidgtxu~>=tGs2>q%fci4vv)iM)zqp^rAvPsS&e4LwGi{-Zg?HP z<2f}$TtSFmTH1njADuFl)^uxdiVc_<{iKSjdWg|g0zBMse z0m9x5DgmyWnuG6l00~Kh-8(_PCh&TVqJbdaD!}nA!yMs`6~D8yKDxdECmZ}6k=HwpTtE@oC*mPwBMYCHYB__aZqJ7ZlOT@CF2YdYIC{p>n@y37XOi+QB98DZ=J$TT6zt*ggHDP6s4t4G7iH~A+RPAXGd-hl%Ju>~$ zPj5Axw5~{Qn&-SxIL`)Nj|hrIr`0K{MQzrO5v@B(&jSlWS6Rh7{yubb%o zqQA4mEO^ZFfx<(#|5#*)(la67`to*oMb61z9+Ae|L67`InNCtRzJFWguSX^hK$2%i zGrzH5Lr(0^giik>nU&%CA=wmGC3F*@XuY&M?-h=%#j-UBj|zDU#mxb4oD z8@N^h8x!23jcs!~)fB(PoX@XKYtySMcddv`VScYY*}MJQiHpK15J1K*$0VE`cSoUmTq1SL56|NN6Om!Ja4`gzmdlImE+n1z{?)^w1=!X%*x4jIpCCo<2|HY+z#0oA9s9Tdz{Xn)g zXJP7D)n1Z=*N4sK!hLnC#)x)aW0#RtWl-1hOdR+TGot*xid0u*MqNv`@+ZB{cB-Sy z$cRJce1?iX*qmRrU0XLy8x6-U!lT|Ib6Ui24}8pGDOc`|>66SFjL4jF_GC-gvog-o z)Q^l5|E-Wn(3{NhFhA3w$}dwlI;V2FTyzk;T-!9_u=3`6J&3(3Mh|;sFlUlrwn&~$R+Cb#KkPOxhlU!z4xp3BoDiyZfH5QEpiM^3 z#~RQlac>yppnn46ua4rCf#zjprY>KUW}T_8m_G8~tMAkGgCMqT^s!;}qwb;>|6$Rk WPisCszLt3JZTiyR=Q@3N^8Wyjs0dg9 literal 0 HcmV?d00001 diff --git a/metricbeat/docs/images/metricbeat-iis-webserver-process.png b/metricbeat/docs/images/metricbeat-iis-webserver-process.png new file mode 100644 index 0000000000000000000000000000000000000000..04449505c2663136728e496b6e2282231b5db5dd GIT binary patch literal 270611 zcmdSB2UJsO_de|`MhTRn)N3x>{iR!_~ggrjhnw+_zAXd-R;CnzaB`&Xpst|p0>9a zKG^i))PbimBvqn)Z<8h6vhVh^(uA9uWAMwJzy1p`Q(5=7x9%9c;H)<|@%MM1)SKn# z|NZhiBi#;H|N4!_Ef^+&HVY%yQO7ue_VrT{x4UV12?fdd(<@J&`8SK(}G1o zoX&xxe?RX@>Cj8n&CWir9*W!7ZgW&AHHKNjpnpHhK&SY+ea?I~ws`xNtohze>g8h* zbmD)Qz{X}nlK!}!Z0h#R#pFE(|Mh#?I1a6MScNmnJ3qw5bJ92X*vvxzZs1*&zS$^) zq2FL5wbr@`^B(*z7|Iqb(k!Eptj3vqK16;V$*S1Gq@3es?kkf&8=g85_XL|#lb`(E zi^1IR=859?_yN-2Cvn8nDa2G!?bpnl35__oW}~y>?l~r}8-Iuk9F%0SOE8a?#K$$E zUPXB--&@y4J~^CoLI3DMZ+wK$+%)$E#yo*XQlbg`HXl=%qEofX7vF^D)|?AsXL&Zq zp^)=$d9!6F?ld)v+iu;jxP=w{<^E5SZ8xX$^NfAYXm8k^wjp|6KW)pJgVfI5*G4U} z3nC4&at{?ykAfFHwu5ZK5cZ`Hl2gxIVGv6iXZMLImpo9UY@TntCz3xBad#FsL% z6+g_!YFUuYE$N$Zq#6Rc;|-@0r9zjb=uuQOAYKsG@N^_knkA!?1a_cQ~VXLn3k! z5!Fgs?n~g}HD}7|{Uuh}3vzJLU=?E{8zrA;To8obqCk3rz0`waWD!S^92uKkPk+Rb&OjP4F3PC}vW0k5nvRC3G&MCdm$hKV-I(!f zc{63YA$?QEQR9{%!?4c+XL4BvDmXW@2LEgslg|)$Y0so%a`C-7tDZ!(j5*vfac{-JQF$ zb9zENpzy$`2ZXsRCT)6E>@Nycw42@a+joX6y>)<k z%u;LYa&puLNf_z9#-&?Y%zfwdZY5#(Cq{*@)0iWL=p9hfQo+*9H3n$S_MelIL)F2j z2$M~ojaP!@_mXEvYs^5TWoeWcMXJO`&&4!~QIu+~XLF6{_-mF!D;6_@3*b*YC4Vcb zuCAUSP_PLzUvgSqcoIa^h=aYX0;)G^VPZMTEnIe}iA;QXZN7^Y*jT=pBDn{Vp7O~v zG}L2{kMeFmFQyQeIp$F_N{PfucoJ%Fb>21B8+~wjQjc-Yj;KAnB%6k2nUqNGr>Ipy z;zzt{5=8|8w6^3_-`|VbY=h4ZEp=fCjuy5M0@YRdkX-)yyOYsgzOn znk|}=e?XZ)N$#DL``I|YX*nj7(d-uedysHyQ1xirN$V3rxeq#F9?|hlNi@V+Vm1{o z^auaon6T#znsFL0B~C2nNtqO)VBkhA`gnFTY@oj-j;z;-lOI$dIDFX`Mrc^R~1#* z);SfM>0T<*o(ycKEXK2aJ=eQUl4u9?lKep?*u1V;t*Wd!hhrgxQ5-jY9Y&;SI<8#S z6YCm3;vaUja#!8_%6HC+GabYXWi%e#PExqb`yE@FTg?2?vA+=JKV1#TzaTClVxRce z$1{kYuVTDv{35V*}C9P<9Cgca=d@ zM2%+#DJfnQ=|mg^hI^pe3}>skU*pKe+hy!CYp+X(D>`PcT+$zRx{EpdCG-x>H~O<1 z`OKz*w9_5z-(DuCBGvbfSw9^H>G^memX%QgtR{9p*6B<;yS8$aEs(X99<3D=S!ftM zK~_gg%ZKWXpcJH$qzRND8AuS1aYXRt!4AdY9%4Ri>deoAWw>W49%TH!X!3)1dQ={~zCj^iU`*Ys5}VO8j|7Pr z`O?A!qGhVtQWJ;LJ+mA7`Wg;YO~@1=!e#yummnsXy_2&{KlL9c=Z_8)yt6Z=8m4XPDE)pib~5;#ea9QumAz2y)Y(xAu3dgK z=;EBzG)&Zf-#+^R=TuQ@o0C(5w%RLeK9`tXl%w)}CVRt#r|yaN`UskCp1-JN=|g{- zIwCP(D^8=7a!G`6O@Gy}*aJf7aiwh<6n zOf9ZME3rg{KR6RV1Fds61Y+8fh2~6ddZbn-PK!$A6l#)S7~E^mr4qJzN1uRT1heHi z3N2W%tbot5<+eifMh`!D-&guvY z2z>$j>;pPR6xEY>W~sNk70trH)9=vv#(vB0NN(-x<*+;f_+C09Epg86eqL~_{_ZsG z05UDZ(YJUh<%cUON5Vlvh4@G)>%SN4yZ#_WKlwmti5my4#D2URy;RxC;; zM8Ttqky)aLuZI`CEVwUdD>jVL=!Y&Yvjp#}nk)P4Af13+{&))ur05Ao7tRk&#qgx@ zTiGmlE-M^jCj5-f-c9Q6>y!>QV%W+hw9EzU7G^no=48hZE<}GfW^r=kuT1etL`)VG zyx>`*4^b8?{YV!BqtKveWjYo4$g1TY&W*_JQseu`iJj%ume9gcsFvn5JG1~7&Wt-i zYKR^#s~q5zAIu&x9TxwJIFc{G8)6hJc(1JG)4;$s#?sU_)S77TF={7|h17*L%bipk zI^~9GF9mfkYy>Ch&UAlz1Rd=jJ9*!Iu?4BXMAC}M(=2e|eFg)O2oAdBCT;wS=j)uz z91M{07&b4gCXRYlzw2<3Qv1>lHhc)zO4}}I^2X0a+H6)?_xaK8l){;^^B(FZlZ%%q zN{3@j+*|Zg1DRYb#ajLzh|=G!`2*dW#g3ATxf%9;E$PW+$|-!nZN;8!<jmd> zVR$BYsXMlToV9#x1c=tfWxPtv>jfa8CZQD-0S*`BZC&JG0Nq%En!zQ7!sdW+45;8^ z>0#d($%ZHzgK^FmWT?71`r#L+c;&nitodUR zkR&c)VnI+d8Q9GZw^CV0i;p2+H|o& z)YiSak%3>n9`Fr!1vMd?;yWw9E2v~$p=1^hEY7K}Wp~lrt(=ac6NO+enBu-7Qj4PtKG(I^Bv2*Ro{ociflgwBswY@^s1X}f~}Cf7a8Hq zpec4Fq^t^D#&S4TQqtf9#^GPuH+0UnQyvUGVD0N<*9R5FIuq)bL;~IVeKqb#*GE7*HMFfRbL{r_;f&%F*G@TUQ$6YMRb6N|Gg0V;Q z6rq+O9P1YQAzBa+fpr*xK5C)*bH_8q+}jBoA(7R)S`a5#tB$}DB~>s@Xv1qixV^nS zSsnonae_;bc?=Tku~hq32M+G}kT2SwrqV$R4}zGG(xUY`+559^+{XO^eips<2VN)_ zmdSeat3O6z1gh9|;z}&TP&F(1<%>!J43WtE;%NOmM~dL`{=({FD$Tvq`ATk)T6(F@ zJr)W6{9#o4g@@6FcE^q(Vy+pPV27-C>AXlzH6nFW7Kbpx26;(3U=yma0u(O;IEel+ zbpxbyLc!kf#Z&J@d-OLb^=~E*In-m63eV<zv3B@e!ak-|Xs~{kX^Ip2~U_tD?S3TB5QcQ)R2Q z-OFI%;BZ}%3qSVrqBw4=)6FYLZAlSwwxcir-Y2SNyu|PLy*)XtjdG`DnBM_t0)r8W zCN}YICXG7Jqif*IiAf+0!4=}CSqSnE<5R~Q@bl)tJhU223{CO?#0K*27BH-f2MDVg zL-=9tpl_+yCRtaxuT~tNd&$F>7PKiEpWnbg*V!o@Lyk$T^pIbBCkTQl_P2|^?Q~>I zCT;55uSS|**nyqb-e*$1@K*LP9DfT}btNgJUrpO}qMa75Fj#Prlc&jLwI)#Ft9pod zOxXcIqvz7IqER-}1e+$1`QCtn>Oqj3L}bYb!nqUBK#VmhmSCJ@T8fdANyghF^RRPJ zaF*}Fn5X*L$(P3@W>v-Vn3t#;*?C!;H0S;hRQ=H;^W(J{31Nlf6Ww6={Sa(+^o>%a zdWEjg;eK{ALVvf3^!;5IP4UyHWHg@VX0H&O6KIieuaR>S?IYkpcM`8-wu@~}g6uMc zQli_RB4u4FXAnA)T~py~lFwLmd3ov2>-z&qC4+3qei9b`@F8D{*X}u(rZVz*CH9xg z_ZX_K-i6W4lb#ncVG8`GZlZ^}wz<0QBha(Wk)b%H0S!cHVT)GF~RmMK`k3CcJviB+dN^(Q}gbRY&wl<1G zs~-elM%l`wQ;jk$IaTWo-zula7sSQj;7)e3;Xd+J=}<1gtH@t>_xP<)Sf*uO)tu!U zS9f)TNe4%`^>;m1ldiz;y&KE7^As~fE|eb`L~Huz<%p=C((GP2>W)7OshLS9CH>f~ zoHDNoJFYzb$je{J9ZR~k2Q-Ol-(!1^1wShyY=|pHUXVaIWT zn_5)o@h%C&_e!B!gcfAH#jeWt2CWh0g?<$Wa_x5UfmQpSr!~r5Rn|$SC%bUL1A*5S5sa~Z3ZHfLq!3%v$xB)0IL*$8j10s->#> zDp@zljdhi^5<#Q&P|KEP>DS1J+EhYu%!f-JI@(*WR3e?d@g}UYSL|afnQq4(y(7f! z2sHj7X{IiOZEdW&87$Ak_#s=7fq3XWtUngJSaRRVXbHdxyGxR|*T8CTwL*g+I$_X{ z9oN1}K1jU4`&biF!4{5?$PR3GVNqS!uuAQa{ZNX)x>J5gOmRO5C7H<6aNDpQSgx$s zWx^2J@-m@Fa$t!Czjt5i&kXkB;-FwDp~IzxN-0rUkHAOoMpZE4S)ymM_`oiyE8Q7H zpw4z#UXV;Zo(YPrpt-q$*_l$7!+mNv9&)~g$a<`7yOy4wZ*SY{xy*`Lxh|Lq!eb^KBu|yca&+i-}N)WgI3^|?zR_&?VL6Y29=Qx1;DYe%( z2lE6V5wiISBog1{LA0q^0+<}KKlK&gG$&HJ!SrHlDqU&uPn26oBgwzqRuP=| z76(G_iBP=F4xD&8M7GxS^;#OStUjn{j_myu&}@{pWd%u3EC@kOHW7_1C;lhGHS(tQ z9UaN3ZJqZw9LdQ^QFSwqfb`c}AIW*AlG)H4EVSj{eW9_<3DQ1(i@;zz%bZDjwAKS> zHYmFcBQi1z>-$}-&m%A+Iw!u4(6$&3F8UoB{k2QYSu524!U&Hc}I>pp~gxV;Tq3R!!-_3l9D-&wW0qeJ z6Xh)dg>mA(w~ORT#KnYx3BJHOl$iN5tA~7nQL5rbM7s&lMgR!s+xuAb5IsQtdS4|G zGI}suT1}t}z-}MdW!f7*yC|-I}N60{lVE ziJ&fsTE+E$gfD_i7o*T?VB=3hD*M&cP<=7V#1P6v5_qw3+uyAPcU( zz6PA*s>^z4*WmzaCnz87yR~|Gh<@b2WqV~nrFYBXm)qa1@L0RUtt*7;?eBzY!rbWw z`nG2A0?1pp+o9D2ElJ?V!rfHXsf0_HwK~&x-x9>-5o#)T6trJVP18jk%r)|J*OrWr z#WQ9tPaFd93sW8*)~K@1r-7H%MJvyCHuYp38!abyXQpSqmObGC7*00H^DQb$57*Ls zXaU2jG2pjnR*<2OQEsuNlJ%>hhpx@p_YP{Msy^S*Wji`(RZS)39n#|T&xM^X*QhRO zCYrw~7fP_2G(|#3%l+&hTL9^qEIx`ZK!Kt7{Wd%(=vKEd`oyD)uWgh;)O+^`~Gn*QdhJG}?m*R}^*3+M$bbiVYB!+ocX?I^qK5Lb6DlhyN!wRVqH zZBIAaA8o>(rOQZqmn7PGjvEmq0XrI8CKRgY(caHYC1s(gc+DO-R9w4R7NqPp3e zv!r`srQ{OHbTb{-Q?gqe*I5_!B;b!~&jOUa8~wuSm;*&d*ZEO(Yjxw|;8O1xY5$rH zyL?_oMgh<|(7LC`6n4>c?fl$a)DbP@r}Vpfb;6iP-% z!9yr26|*f5$(|Y(I5SE&g<)V$zKH{3ZvX&2%0LnYq^|qfug8*82ejy!eo5s}?As2h zMNa^`joju1axjM6x79>jV!llfE_&hDim%~Ya7@zk#|P1 z;;VV_Xj^J9xEhJwoVl_TPWmND0o$SvoA_+0 zB$!BYbGo(T`9J(?O>=hUhFTs=>w#~ie=LnPTaedz^j_rvuUU`?&-qc+`+8rlF>k9A zvz6IP#8)+w17%`L=*Yx(mHLGM$Q^W^+_ z6Xo$!`G%Vu*XdrFB_ttE%PvpNP0Rv*g#=_KW)%yi>CL{q3)CdL-UvuP+P;mF;k(rH zfFXh!71H>&%!X5-?XV#0{gEP31Em@OU${-8$4uxPf_j{EJ)mBf^;PPqfBHs}<%F;X~*7_Rtcb(Ql)G z%T(>}ZS>^!kw6U8o@%5&Nl?E9t!M-~B+GSZ}F z=@Vh1CYMzvWe0Xk>tPJyQPN7}vsaJT-_0Ac7mq=UEF~220)i++PoY9aG2U`wD1q*z zi+XHTe$*?XFr5XDLV=p7r5ck=>T!Oi)XCcQF7=}NuG*$Chx@o+=w+SNjTZHQ@`iTi zozWD0)kP?UiGrjuyr5mhT0aMJGJ4}vz9eLY!sZG2N0dCLKz#OOc!^j96G;)i{}Tr9 zvxb0T^48mJC_Eq}_#x005R*ScG=pFx3xK$;lp~EEnq)`kK-p(B1hu}U!&YIgxn(Q_ z{&YlP-Uoq=pjI$@JEFEEg+N`ic^9BhS|eSTNeYVTI7SK$Y#qpJOAXSkB97{3T}w_~ zsZbJz`M-X|us@Vi6iA|RkO6)K4d{nt= zI};b`@h%T&(m5k!o%k*n$npN79lKy+6R_~wM0C93>poS@*DgTuD<%C}Cy?~J;WQpY z@D5C~pi`pxk#x%FqNW()YxF~RXVDS~d)5eyN$MI2p!w=!)nehzzA?q=`MmrX4}L6| z7#;aA>>KwRE( z0|UF#2eoMRoUtVu&eydFOO4kR?G`hU&V^Xs9{{0iww-N_X4X1&ElQA`E*^*>j z-}NZjj`KJjmoe5Xt|g3JV4*rF`84*dUkTG19xe4nV%`}; zZskoG&j}AGFWJV;9n31oN$0|)G9Dn^L#E;gzQ{2v&-tlW_f^xheznedIm3w+8DF=| zImEG~wgK{e77mK+`hCbJS1jgXWUo`imcTrvgPyvq@tb96A3H@G$KOx(b5vm!K(Ix2 z=1hq~94;D}I1KcygwgBG>LYj%oq=CdjN__z9cmYBkqH;3jt{DYW3lb>$=Wg|6(aH= zOI~=cO`3gjMYdI7q`5rRI&24N=yX}6uJQQ}+_S=!SHHnWHUaVZMhW&KeQBDJDV9EX zK-C=uYkrvun3_6Hlmt&pK1{46byb{YL(9?~-uvqt(ukUXOKu%Ykm7HERX0SxjS0~{ zvE%C>!ej&?tGw^kO4+DmHTfw)iFY6&cI*bU$@0}RZY%?T^kJ0$-t6l=zRXtElzdO9 zXoWhT1(g$l!oGS0R_ehm%*57XvyJrvnWAUfhZ2}wjwwHTRY<2VNCN`#`u2K`AX&Ey z1y?>(_d!}fJub0YBh}S;E)Z$WfOA=Kc)0^E9;`s>cA#Dpux?TpfDn5Ph^B;CoTI;s z*Gl1X=v;&c(ZV(a!t3-Ni+Cxx@x25!+^9D>dpG6#lX*lpiTrFXWD8t6b+PJ|yBOfc zi01&o{HA85kg$TiLTMdhsO8#ak3HDr zR8vn6k9br49HmaB?}-Ok|FMpG&Wc<(ktkeN^*L3*nxMMZ8{^EftLNXU>h-&XleA9_ z*T9D^`cZGy(H@7wT;vstySf9|B;}BhkO=JUt@h(%Ue4 z10frK1N{=H6={e@<6a(rhbUxmEKL@wLJahh0$I!uR!yC4&Y`n)%SE~OX&Ryt3xA$# zb_*b-JvoTx_D7iTm?ucwJzjW?x@ZVZLNvt1Kj+;RBn3YodN#mWQxS#TIW2$4Z}Ry} zb$T6miE;PO)_T$#0pRXempa|GwST?B-ml7XJ?3wL3p}FrKiwGvK`$pFS)xq$HJguc zZ6d$O69NZvmpwRn>1W`84*sJpL`RzOy8EW|8Q2x#!Fe2ZVcsBG#b4Ye@V@c8C z!gGyFJ5Ka8R=_>tE5{c60i(`NPv7)`Nu>cBAN{6(5t*B^HV8dkLDoO^;qTpIyEJre z0<2b+ssuVv)U{T6nbO~;taLKvD7%4D+vw#Z*=u@6ZzI*)LRKa?^75%!J@0Ed=En`B z61kK)a6CPbF3ZzM#JzrvuKu)W2`ZdE|7kjdy8A1W0@m;E z!+ud^)F#&c?LX)9OZXqw{x2PjA%_3|oJyf9cpcDj`ma}2!OIX%?!OL!U%u8^yW?i% zV%lu|phE6UXi*eV6?v5nJNj=xBy50BMZHZ_SU^b!@*-DR7b zLYe@o_@|TGYO2?aN!9xY6l3s*JB|%|BtQ}uNqSor2fI(S?ECglXC6r+Ey2`(n1jq8 zy?6|9xn%91K65>ortz!&3-od-st)sH;-^1-^t=3Na@v-?>tj~>b^rN#Lw}Y&t$#pjL9rg>l&Aau|K6 z)=xNV;VhcVV8N%z)*j$(dL8VFDXgE3VRcFU;lWr-7{p)9p;P?iW~Ck{rDsrB+XY!{ z8?%80SDYUuRtEe0dq7`qTtVrN-^Y`$&brV)ZL%Emx%7(>u4(jwg;9pfP_=NzqoXgE zGEj$u*mpU!-mb4$TrNTStMC4pp~myusL|!Mqk$npA`ho+=`K8zl)0!qLS)QZsB^rv z;{6!5sq^FMK?5bPWxDzBD^}wZ)~3v2l?{12and**vm}xwJPk2XM<8PdSOxuJydxDF z|4}K)SPI$_M0V6t4{wMV6h;YHR0^|DqxerNn~_zoy7}jrO-;$EHIIT~M`Li zdeUVd6wM)QKQCL}<19y~H#@{C>x%hH#?~40=Y4I;5K#M#J`B%|Sn7CI==L^2iC`fL zoO9?Bf{Y0NYMJ92K34mnZs^kGQ{g*+2JnBqex|f*7|ea%EM&LrOm7fDV6(R@1KTEy zYbxwGKoiwWgJ`Mfsc9g6#yIXs$-g28N zv7({zBCmmlme}5_jnnkira?*a+P0x8ehsMHzGN!-`dL)-+Y*wa)CoKUYwMS4zIVh~ za?~<$k_!!%1>57_J4HiK0VhQg^X4fqRQtgAr@Rh|bvtpa6>V23e|k+Gxv(Kn|52Ik zV2eN3P$cy;&FNuVzrbifVumtCs4sR#O=pa-LZKdV?LY09pW6VtENC6J`mOsO-6a$1 zQDOl!TdUL{fklf(e~${6ZULdnJ!SUegq=}+6ZWY3vAVFMMX^VDahctTzi}SDy&;Ih z;15+3mwGHzHSh8OpBWn#3#Q*$g686XO8y208+it%iO_eXqX~pXLP<|8=YtpoYlJzy znL5sw5XPX@RbY~rq^awLJuGjrg|`KizfGem)OQK3U*BB}pRWt)msur=vk|7T9p_XV zBt6Bj!G@h*5<_s30b!gj&^j6aP>e3Iw4BnmTRwWtz9Mj@MJKASNDzw~X;4L<%IhAZ zzx0zzPgY3BzlOr~FM5!{vGkAmHaMU)L{jwe0I{0ke0Mj-|4(7`U0j&DL730hpn-bZ ziyeh&;N7I8bEjH}adOT0_p<0mOZ7iSbtC#F92wI$tZ6e&wqLm!JIPnYWg!hTao;TD zJZ({^fy=6r+?=zo}T@~(-a2s<=Mq? zwaf3H!t38xPatgmu`d9H2ULy;kv|HD#Gu~6&n78yA*QbroFlxEU+WV!mxrn?8&Lc7 zFdPDRS%Qn-s>XLAhE6|{HUy#UVtl|*v%s0_^w}M#S!Q)-s2Kj~lSSIk%RE~1c9FVf zQDhJ^aO5mQHALF^_#)RNd@ijN2j1WF2~gV-_LI`6g*)S0&Zs|_OOJ(5yKs~-beG~W)L;v=?x<{Vnw!apZSFYEQw>n`mj$#U zsFV!9(=<_K77h1c(>^8glDu~L@igt=_6H{18`(utASB>;w>3WroG$lsDU}JxB(xu? zC4K6B(G<=01eQJrtvj(>3?gxF`877RI0=nye*GKNnKO2wk)JUtw$21*2d9#J$s`-neVR<1La! zoUHc_$K-3*={2GKKYI0HTpxPaVt<3oa~46V+~%*6huhR9CR4;DbdrkEc^I*TeytB4 z;4BPSxM-4~x0^Od$q3eo>Na;FrwP&h$fW{=DJJRNv8CnNxM{*OGT+?MWG!T4b6iYY z(-3>x5dTq9=cAlk^3wCr7-$PLhLZ7%if_dE^$QlcOwyT=~VGyLucZ`ixj5|`L4~~6O0Yn@Hfe+%N!8~ ztW^XA3`=Ol`O2(9Q27|$FU>l~6?00nyUOa@<6YHw>X7cxBX-R{=2J(6XyTN|bp~*O za>?O81CT(9sZEaSP|Lnt67k40I%HpWQ)7IMXx&|UMip7oG9O*M5NJ)57L3e=3EqmZ0f@AHuGoS{a>)%hXYiPVvL zy#gyU5c0QQK6=)I2g4aVn-iBI-9SCxnHYM84EOeiV#b_&yLna`ZVl@ zKtltA^)HSQ5K(=VankncKLi%f10(Z z^lUAWRNJ7JMI02fkEX2)fnWsImVaF!Ai;$}H?#ZerLMxOUt?R+hn)iSFG`B=QQeus z05qy5E_i5^h@B+KZttwa!5zjOu$)Dn_b$yvul3+|PPNxY>_=hA@FyOjiaNxl_yLg` zwHw3ZbY;AlEr>8lGks>Kmj|vFe2M&NR_&t`6#s@eW6FiR+&48UI?rPs)I>Cx=D4ok zd0zYUg&~49`Kmk@aQWxo#ZQSo-n5pl(Q>D78b02nVHy46h2H+x;FX|->8`NvHkU4A zNiB=JrZ(AvO0aULQ0!5Vb=9Om{5J(KWP?eN6@)Rt4#>4CSoF>+;!^H#Pn&aFU zeiVo*JAE2pnPlM<*-Nv(S~-#BnDTv+`yyyP1opG9Jv`QkLEqU4g>Lp>?`y|en(5j! zUO~L1ak3=hsjIRP$+^7uvWZ0ta*_FZ`Ik|zC^GH%HL@@0E5jI+>aX4F}t-| ziNz$&13P%c^Ns!4#QOLBv!M-M&0{X~xH&1{P zdi3!4T`?$=T&Pvjd=h)m|J~r4%^?;>x2S9(YFZdIL?B>_Ya+;>8ecSGT*w3I1=;PU z9fQU<;extq`|B<)-mY_Ovnk`u7vjN1(TLpW`nKiu-<_Weq=gLKruq9Oox+8c+gsMj zh4)vt3?)Ydc{56x8)P3!+@QwR z9Y4D5-(I-G$y@Kljx2lIz}afl06X&rs>To^x90@=uc909PDV5^Be(C@YijZ9N?F_0 zBwnD?7u(*Z1vK z>dml+h#q0kQajUP#VJ!`LYlu?0@O43(Bo)Qhul#7x?B$1Z@j@Ej5l{{ENSsypx3xv zWWfPBzjjt#^=0^Qkr>HY=8x9#^-%H`OH@$78GOF>&&Rk>JeH+!uhUb1_B1(FcHMej zp}PJdARMw*9;f}|wP5Z)=rx&G2-I*r%gn51V^;lMa~V)T1zTp}w-W}wjoz^VPpV6L z@}0`MJr~~PiVwQ{6BjFYnvURB%X(E6sb zZ)ZWQ&8jz^X28Xg=>yDMbnQ5D`2TP~>kZ~;O7t9e!~^fDos~MOjD}Q>Kni!B*zpaz z_n+%^1&D;nUS(!S{>Q)o1d7{v;>-RHIZ%DvG^wfckKzA5xZ}i*{8$^um37?#*elZR za86{ZukW(aS#_b(H0KS! z9T>vY|L2AGC0bGvb;2Z|MfwtR=Q%oWE;P| zqx$Qp_-ITVH*4AqS~(hFr?my>vybDY!(4nUj2ghrx-W0a{pXd6u@Sf@EXsSiJwIU~$|X=<7?&k2T+A%w4% zd`ew$#Tmt6Ar*)1<1MT_2h{KZnowRHZ?x+(G$t{ildz9DHH%9Znlv8neqpT?*iKBP zZ71iV+-TXSarQCikh9%c!)K#(|FNov&>Mn^$;(05?7Joya0&2m= zS*mAi?YuntKL1lqO(Jb?Xqn$xyX$>rpci%|H^0!!ZTHe4TlNh^D1q~Sbheg`BrJ55 z^RNXa;k~A!J66K}bGM{P6EA|^Z^$-D?=HC&9y-#*Eie}HvO2rL=FT5EE;i=f1b(k) zQ)wbCH-?wd&Cv=Zx8?;*7mSeNwdu2Cb@SazzjCZeyt*l2?t7HP`+U>k%fvncs;-*V zH!*mfJ^Nadu~Vn8yy0}399btx)E#aydd2{of~7YJ7!C(_Ee^)>?3JHA)iJa@;S?A? zAaVNG?P)9RZu|;wp~8Hm@40z_9YZ5DsNsM=(?gHA_$3tRiAC-O6$s9T?YxaD@1n#v zN<;6DNz7Or&k=rf%cb#)ch-mNG)P>Q~a(+aF!3>2{GbEZPT*|ho9eZ;ww*< z=5K=b3I^L_9_*ZRjWoe=GO&b3ZvL)cci^EhO%p8aSify*q`ZIW0}gt_+6A)nE{yad z-fFxfZG!bqqW5CYS5{JpSiH;*a3udzXnnI}UYVRaZ5G&y2Na36DdFOKwR0s0w|^|M zK%IOytr|c}hfk#Xo`TcQ@d2FjzDpeUz6*l^6w1o!!d>+)K7ljlb;4?|gpV5xCRNj> z9F7VPz5SQxO~#r>*bZhWfJgZA1%Mh?uGbQ9ImSH#{#_;JNH_Ips!NfMrJbFz&1J~0 zyVX|4&a&YHQOo!8N-Jg#KRqq#oC<7|vu47*d+O%vm%>03veq9>r}+h(mJMDz&1TP@v6X-1 zjc&iY*D~plfB40t2~1%uqtApqM}cMGCXdbB?ap)>5;GJpu{aAT)%PWOmMu^eZO=Q+ z8H2*ANF{6tz4Zqk;Ga7_Nk0BaM)P~Ki+7!)SJE#pa56k#%2Rw<37TBoB7OgEXp!rP z&EL_d>UW-K>0exw*F|xqF-ClUN4qjZk<9*lx~Z9N@n|!&Wa*kMbAbDdLmMd@K_b(d zNfsS%e=mb&2|M5ZzIPugc>cKJeh777jyHf6;vn9@_BX!F7?vg!Vml|22oikd%eayE z0WbA=P$Bh8Is4%l_Nd#)GCrmevL&mPdXE{dKi(H_d}DwYcRWa}Sn6K5gj%B^U<5h) zZH19&I@vAttS{4hEOK66@*_*AZPTym6?#X<=d&+IigO7ia?{xwAbbLuPR=sf_YP=} zqT1$O-~98OX7P|-`b|yWogVfzyPFETX`;5ksN)>MO^wPq__F6E{ePz3=ZB+J)~N;@ z=PYOx+Zux(YWR`#a|!Ve#}At)HMDJk%;*F(&XesouyvXN)Pah; zQvL3Q&BW0X_gH^_O4(kMukaC1vl*3jIL1zF$La@LsK6gJ2n#sH@UQ85PSet1*!~D! zo-Qmc4Zb~fOO^V34EJ{XYFPRMkA9H!>B)pS@c$s*V^9+m=S&~A*uM2B@7w;7X*I2Ea1T9JZy0O_vYnn>>aQ{p`TgXrH?nlT zBK>5%NxCYiYZhYOjXhjtb?}(SJ5CHI!PdXZt&NeXEP)!VNwspo~evk%1hMIqG zWH_`I;$7T|=;b9pn*ONiVhMq=9?wjz?%XpEbPFY-H=g8upwJ12@V*A%g&!6HR~xP| zeg^eU$|S}_6e*`i;1Je)mgnKHP~8l(Q0W&*wuSK#hP`dXbIz81>WlEl^-6U8q8a}c z?^M`yo%RV2O5J@NKiTbE63?gaho$<(7Z!SqaV}1^#mqKBevBhkZUJJw25GCIg=oyB z*}^#ev*@@O+j9AGAGP^l9hNavJeYL8=q`8*wrjlcR&pwxmq+aCkJH z<2rg7AMOw9tN#Zkc(Y^*lzG#xZ*tCmMc^H3l@{}RPWJ`09*0j{OWwYf$M|H6j6CCH zGz=|4&*|+Rr}gFPzas<^(|n8KHu_ zeb3jNsPD(io_5($YY$sM>4QqLTs3@8T(? zIxr2v0htNqiLa3j@c8EZvMSTCPHNsWu^9bm=ANZxTPYNRSW;QXTR@j9C4I{{q0J%X z)U)HwqHj`8mWd#be**p1b-d298(ceFTQ#fD9|RXU?ihHtn>;J32@PCy#jf>7doQv> zXzF#t36xG#*4(*?_cycETYgXI`j~$N5%SUBEy+j?Rxy^Rz?~i%4VBtIdmH98AYQwH zb&PueCmsXEf{WANyQHhS>)2Fr*r60!&0{K>HZ|)79reWzqmy{z-f0c>dWWVin%2=~ zcRL+OD+CU=ZX)4rujjECvgABcrzc!WYA?vtT|ug%Qsc~QQo0-vkg|kv<6H#SPQZe3-Gx-T(n!Wwp93$Ll5GI(J zTz+|mBn)8`virG?OlrA>_(i=g<020iDyrLqTvGRRI zFVg$P(oW>hc0G~)&5)F3<*8sX`_s7Ml$8zSTvlFks~PFhWxc%iP>w5TI6Lz+&A0B*-v342d&f1Iwd=yzKt}0 z?EJ%zU-CT9TI;#jU9bCE#Y4g{*z}#(V%T=N( zb7&(M>Ht|uYpBL8jo}N^18UwOb>pHQ4hYaJebhxCE1_?(Zah8_40}RoP4Q?NwF<~V zbo4KN&G2s9rP08WD`-bQhQ^fC&NL59idjn%^oISOJ{)!%8?`L)94MK3rQ@&>5kVm$ z^-d6s@pIrcgfsJ3~j1v+OvmY%iODTnwgf`|zcBvn= zyg~NH^!a6*y!u(f3hUUseB!iyf7*D**puhvL4Gf+!5uXl(;G3zh7;bZevv=tm`k;q({rUt!V zyTBn*3WNbT3i=hNj4s?*IhF9)r}Mb7%J=EcQ-WTuJ1a!inn>QHuU(p`;nR${)=r!m zVh1SmeCyw2*+}epm0>a^eHSbf>js)GI z2Zf>TL)I47`wn*xuslPvaZ#Mbi`Z!oA^~=5x|fJ1SyU?+E0&-oF82wiEou9{vAaUq zl($xPb7ZZaK*wq{p9>qd=7U0gS?{ssOcGcjJ7!xTDRy>Ay-}t`zylvlcZ;k57)Et! zTx&UpN+}}YBggGByb-kuzqF#_|&etKS69j64$-OQ`v#ljPM_UpQW4Xx{itciV_D^N zaAjlnMIG`EL14#-EalRG=g|xd!fW>{kZ`p&Br#V#zpi`JSgCiE<^SAG5*z?@w_}VY ztWAJAZk>jF%%MYWasl|YP>(@T`gk&{oji^OMc(SJF+~vmLMVMwm!0pyTd&`-&U+#i z>~C7yo7a=|z(?e$;**r(f!sp?5$UyH`jS+Hv_6vn-I$oJu|Kz9lvi(LTcz%pg4#J; zrg2Lbeoy)3H=Q59)q2o=S^~yoDBxN(C5!AjOdOz0<-KODG;K6A^ zL1Jf)FsOOG1C#pRh&B$rchtARpOAQ1d1U;J;)Il+*GqPEA6)+cE2H0=oDw2Dx2kY3bI}KGQUEY%rQw9n}$?Pd_$CC8P1Y?k=y46r8n1~pxQWXcAHo8 zNz~_p<`M_)vck)kjH5x(j*zF2up9sTV6S0f$OC|*y@8zj) zUe%g?U*$aJ!cVGQIT{hpth>%Da@jdZvbPPV)usqt5<*4C=3;)de6bpS2(MYZ%IaCr z7I@{>lkI(*;&jjZLR@%WB?y4WoPMeO9s>Wm@!SUk0MBO#BuwUQRxuimjO z`jXhQS4SX|9e)oi$J8;`kj}V^2t|6ZF!!LUpjr5o9r9~PjyLlr(9~elS!EhplYJ3} ztasADl6H0Qm)tp69(jHFk|Bp#88W(QFqrFeJ&TE5^9;VD<@IYVTSI%plM^Y3Gu?OU zf?tCSMAvV_S*!~r6(5FP7jOdAi_$t;OWpIdHakYXNfUd)3H@1=*! zq388pd;ojEvhr$tB+6P;+(+r9%OC;aZJq8r%Yx_4{~$g#>gJk)$But{d5%z9x=9I4 zt0)|1%xYo0keJYg_Zw$F zwIehtgQ8gPUrw%?n_W!#Gc3QWs|CXH;JxGTmwdv$F45~@2JV)S7oR>nYf`K3b8KGR zAn+a>xSjW8u?}J>`irx~B@rrL#IsA#BUy+G4^(m+V?ZZH)E@r2U9X833wuxjSik1{ z@+ATIPYBvrQ&d`OVbDAkG2!eU_MPNsP+WQi-88DS5~%bR@0n)HR9*ti7^?Ko3U zgR#HGLgdi3BzuA*c3KeBVMBjrWu1Fse|FRt1lj0d)n~l~b$=ZDZt4EY_VW{C&Oi3- zIF)=F6}f>hgAE%$5AwJg5_IP)Vv`DDc5G}E1U5d;j(x&Bb5iQ=uRcrbd?=KizHNME z{}frPdyGh2;=4Vfcb?fkDw3S_z#gxo<(Ud|MpE_cLGbTu*j2Hzt3Mg-3#T(@1*PuX zBmqOxoH`60zAGN$%51A2Km>V3<##luW=_Ld9}Ns)qVWW~Ko4XQTrpF#ls2wE4?oSn zuy$#zMh8`$Q*o`1Tm^Tu3g|rLJvEVH??JrPPP7>GwoLC|ez7W0T^{iMX&8&Y9Z8-rEE7Qqs*rAbWy-#J*w?F7X=J_Q;ybTE1Ami^fS-$HkvrpH4!DmGT zAbERMsuTVUXO=-Y0~$rLB||q#b|p{q-+P;V`u%T7VGkfLHg64KD-agptlDv^&pEc4 z+pE(Xe{EdeFW^3ARaHDpjv z8QcWWp@=CUKbNhLV=Oca*D)Q+xqJr)dZ>!plzqqJ>Xpumt#cEV%z}#`*9l1|RKVAO z1kvw>;{}n#7z!V0p(r{MZ>qz}B#0{neqJ<$x{9efzeCGa1bRj}$xa{GOO2bgY+efC zJ6OQW8-n(A{hBY5;Cu^ok_9@t&ji}QN9^u1`Tq!IBuEjEr0cd4z97lCj&Y%9Sw5?; z*j6b?+ObED)Gt&<9Juw_?*2_g#&x?ZJTI3Md zzHB^P6dKD@%dmMPSgtz1qoEzOjS+o#**&(O#JoyBwVGH$Ln07~>1(Ab8v^5T%jCDP z+q%~U_T5A%N;T@MeID1DCzk~K`%n-l-kgck!cIPKL{r;w6U^(#n8EVvpR04dGmS zqhiI>E=I#LQy>Ef-6v{QZTl=%ANN+Secty$1a2;cbJr;pj~JMZoeTeQxOV)JD7ry< zr5|SmKa#Z`CGdLDc`wv&83I*}BZoccZoYn=h<_w<>2kJ4@NglF+?U40r>(pU>qfnKr7nvEeSV3zA0(sx2Bj{0 z1Bz2&h_p!&fYI27>g(TRZoHpm5j5Ok0k=E`pDUN!Ke{Jv%|#*kr*AH7Y`8SFpFLl# zaDug3?D24stczYJFZ$F01=^hTfSWYeH{b(36&^Ve2RfaLyJoE?f?cgx3s*Wiygz^a z;zTfpye55WJSV(ULter2UKsghFQJ5%vrT`!#<3yXsov0}=9ptIgn~Dhi@jh;fcZsi zL?cABZgdoW@E8L>EEpwUo? z>w4j1J!~g<{3)73pvFDthnQ$vkKGC6XNy@{UhxS<8ynyzO) zbZeY^6Vq0#AK61UVD)hZb15z&&z!HAP^!}&8`7P1Z(IWrXus-rN*Q@D~W~<*}xxwswV#nWQWUB(_W{m@ba~XGRV>|fMdyQ zP=Fzv;dAmR8uoJOlD)`LZm$NqBvmDUHNt7kfPRPW59CcSGcI9QEbqcC_mc^ZZ&fm} zdZaVflB60|Rw{e!l|_xGlRIfx2&93sv0)FcNb9Ukg@X0|L#N$8T_=4x^+4bm$aRQ^ z2}Wr$f5640L2a)f{1eh{@^{VkAs6qqviQ9tfsHXKq&TO;9x=m{F0ndeGUXJ7(9&95 zD_tc?$*K~2yC|k(%$o2DDaY=bbT`hz&&{2U z)hEZsD#CIDmECFE`#uU+3CsXb)If?gvbVfN10EBUg|N)CV%)8JHLWV|sh1rft(Y;u zTv;qIv4LI-_KSF;P%IVYEcLfMAhJ z)m4_3$c+D~)v(5_7h$WTM|;?n3m<*m7?h_~&UbpQH-lzR05u7?`l4rTDQ8Mp$Sn_b zxKcmE7BAr@p1AofA>2?=Q~rb*|Aidj%^K+8UepcSy2VU$Mh>{lk$geFUC|4%Jn5;k zpRPlkVwmbf9IVly@Ea7?U#d~58Dh%oqYLtX<^)-*v)L+@n z!`yH6xy#xS;!G;i8E`za_*Y|%GgEeKn}#fh9Ob}Uip-J+rP>s=%?$R)=*1;nni9?k z%pNxvY%s|TlB+P!v}K-K``}-$q04A$X&_jRmC|~Nn^$f24+Df|*<^K9$?B!qZykpO z`juF{&p;|PQW(Hu;ko&RqL=>=Ql5f+$Rl=+SBLw&HrOllaXHYK6Fc3w7k=0kI;Y2K z-c|Z)RH!6SFj*H+)(dnmzs8FU8QEDlbTZba6(Y>($^g1{t&n%_FotSyq$)ee+uP9;NdIipG+iFHtsnQAZQnzPTA)UH7A? z3n+Z^xGBB*=8o=gr+-SUVIhiKE;yYREQtG1xxTKbFR3F;P#<>06Y?#(LjopctIP|m zPGaP5?bFaIW{UNGx3U@K+EXVIeWd_7K%D7tOl`N+11^H3b7@b2q1pqEC3`(?<#%B zmRJA$CHQC%`>RN|7sMJ7m96qLqS}O?wdM+RCWW&p=+~WW4uxuC*F-z!&aZ6)ktGAa zq3jtuo6@V$rF7rywQtW|gW~{RoEIR;&$Qf9mW=PIiHxn(#HU^OJ7S<(n1~GOh-+Rd zoTw-yQBFr3ENFxFpWU%X&DI8kwuq9}Aw?@{WNNrMd2bnRcgWLGfn(`5c9eMa@I2|LLxR&|1pq{EtrELr0t+F3Y>_b8u?G_DJ^WEkW3uCiX7myU z;5u89^O9`^2{LHU(>F5$t1baZeqh!_eX9PqNIx8rIole)V=oq<0GifYQ9lfu+FlrV zD-sM`20ds~wvb75B7!=Q$}(@vd^RvxTN*et`7G znE?3`v`dGN)@H}syA^J$$$%?)M}frUH;M?>4T`tAwF%9)3%TF1rQlvif0ak|<(hTx zXUu`=7l(0`%urAWx>ZCPbQ)Z>Y2Vns`9vZ5`8AS(n{+E2&skB0G*$qqRkyF(t+ujj z9QC4ec~knPttwIT?RDwPYcrt!myk<~EMWZxv~R`|G`4*T!5MjXj_+gXn|SKHnmLA1`#C8iw63pSLP&@G+(c3-`w}` z48C@4-FvEze}_2NBVBvmNo{`}fIJcR@Vl5tqCv~XFmt@)Vwzk#dU0qmaGVlYJ1A!V zMipRN9u(fHzq0cDfrhqN#5P2g@~!D;Cecm%r*pIYYikn;YjwF@k)WN_ZUBZg*lDTJ z*i_gC2{hj9pU{Gv)E3C#LOGuhjH9$c;L9ef4FL~?#jl>UkpE@xSREsXJ#a47okz>&z^v z9Ft7}>E)S()Q$P(pjuxi4<=!JR0mq%0lR0#xTCb6sKxdvUfwfP1I}h;_9u&IMn3b3*(f3=nFMW=Te*0-us2!&zmBI4C)GUC~zP~-qqTxu zbpuOJOCQ!6uFPDyS86}{K6SBt$gn`)1s6YEn73Lp`D%7xWvCJ}x*TBBep3O*9!jQv zm_J@lu}zpYZA*Bvs83k^*tlyfIk9wjP~1pHEAD#N`f~lixQ^e0r>C)dKe-KJY#_z@ zNnY%F-|?I>XzVN+FPSk&N_cHTx-}o!xT_-*56Zu!lLH zcW0DAOW=YTmg}?Y@r$32RSTjPGxC*d7xmAOnJQKB@B&H`q0!$wL?yCY%myDpt8VW_ zWTqn;om>h2m#$XlF=n>q*?Rk4tx>~I^NDPKqx$XadEo9nSF}3F=)u`O!?71X(o&|* zJsdm9zh1Sm?^Jh5?HVc7bWE-iq%eeg5xJO5NQy!}CZT(={bkrzjvNz@+T$?k)J*?5 zDM{a_F5Uj*Xr3flu3d4G=e*~W!Kr%%6)r7zff1!}FLpu;JPIA_S_W?H$cepDy{O8d9(64S%KL@EBqX@}Pm)xy{Iqx(n#T!R1 zn(N*eWH7A=ACJZY3>I#~h45|)3d85laN!bySrs2JN*5O3V7h{+i|giI2uehZuJCO)K3`g%3j*E+a#`F^8KE)t+2wU9cwFKc=r%)I=EbHz(^Y8Y#F#L-{MtgsM{I5V=?5 z``tqD+?l0|YE7`(L^kEwcnYu`6jdk-Dc11TLmSia8yj+PTXt zQRQxil^Z0Scb#jc=!b!oN(NS{>+JbvMiia{uZLMP$9tX3g$<2QwzrIitK=%2Zfmi7 zU_vT?*?uq5BP#5HLEZKPfCD>VX4$IG7NS>EJNEju__*+rdQ(w6GbGUw@CiyTiI+@! z0U$l1LirY>ZC5=pqt>DK69yYN_BxO(JGG*}I{%S^$5bJlBL!ER#9Pz}6~!KSKPPtu zw8MCffuW^R(l^reCj@T#tn%v}Gxro=xK6~k*!tZtH}^gk{)rlq{nNhRY^&ksq;n5W z4C}zqpf7o4U4D=xRNFjAUUK;pUc<9PJlX%S!R&zOAdwvBQ41q{n77f?FxCvb1CU=Y z1>JQ+4Y|w+BB~~GpxW?f#$-GoqfJRwaW{1Ur=smvb7|Bx#jtxbjf)@j_-vvAIWwHS zX0<#oUs78(yz6!K2$P$dBc%6cn>dZyjp4!Sc|bcdQW zUocIcSjSQmq49l6pX@XNEP_+RmUxdvN$--Z>_r?FP`mG4b*Dcs!Y!3B47(X&I=0C- zC}Ab$QXGokNADY8k#7zJNVC+o(cf+5<10xNg$SV+aHoxT z_Gtwz0xd~9_H>O2I#K2&f9}Jp;E)R^_o!*eN87re;oB}YKsZ$9xGH(+a->?)^`%Hg zQOs@Hs?_r=liI*0MJZj?xNmO8={nFrKXoV@Br!tz3pBBGZvfF#MB0f)+g|@01?{yd zB2GRdP*2UsD5YRCN?}j+qv+0{jBCDyOM0UVEfN6-Jv7CPb$#VRG>SE8K?Hzx(`12l zs|VJkzmQk^-3MadJ8W{c!iU|iQkOLy-Qx_~wY2p8+aJd)B_Q4MuFbQ4V*_B1IYIF4 zSv<_rU36^bfP{2HQ`mz`!8af19z5TSH1|+BVbya08BF@5sdr6(J7Xz#9b0L(lv1f7 z_>(G3T^|w1!VPOc*l&E&N++wtFaM4I{y?|G6qRx5$fVh;Md60`MG`RXHikClJ?jpD z#Hp@S!;z$k5Ec|x$Np$~B2!EjQM9@NA5#HPQ=##QF95V)sThq;r$4v%rW>MDMKjD& z9(MQ?0jt=aAg&+44GzaJ-mjKg4aXxCGY(1@V-d2MA$QW*Sh17%T=;QMRbw7^4glFe@NUU*pm=boSE#!6xR=U}2lMQvhD_3OB7^?Nvp^H90jF z7OGz|H)&W^dO>`C*c|13XNYm9A=u$IqcN!5;rX`A7bStu8Vbv#8YHj48eF!K?iL;Pzj1h+PKs4T@}^ zMa-IstiE+MsJj_Zq1+A#z&yNr@jvaEP){KV%&zRQV zdlg(kFCWawoZcePQ-Q6lUF6&Mr$oaiwPOobgqzZ=pepHMJIQ{$PFo=NxZbnp2}D+D zxo_Z*65M6ewhbl4iIzF86yw8bW}RVF4$*u#;vIjtr@e%mk-d3u_=6H-Bd&lE5Si`7 z;p@QC&iYa368tSIn!*(o5|J?GUOKXNA-}m{!b|gyUX~$}559$^$fzq|mGuM78BENi zX7-hq$yIoe4$<5P4_HeWQYw`y8u+#U^)!AoO$wuP%J z80lI!0t$7Dv)brE-zeaxH>bJT6Ul|I9KJLM?GZ4g8dgIl5y^nZ$%)&LlESp@LEOIg zIqbnx^FC}KNWl#V*9OqyGiq!9v}N~st4mH02ou_A0f!eieB&YuUHM`5i z($(C@+^_gMsO0`X;P}tWd7RV!mJ#snOOOA17t3fTy&cQY^ClOW(UHaoE&WZ(Z91QA z+F`D;r#C@nqM(rKGl<2`8m<-p2fqSOQ^En!%Y8L&w%m>od!D0{JfDIEu~LzZ~N069-64z^7uxPXq^o)pkc z4z&_#DQuH&o;vPrM_CkZ8@K~UJXvQDJjsi}+_bx6t<7_ggw>Hh@uOp7H-C9oN-IF* zeDk>C;0CW9)23M*B#SBR{rj;W)>P*KLw(IT5QzFC4$|%(nV2t=R2Xl&CSJlL@0q8Zp-+b#8ZG}9mC*2%`vxC*YD=T2%r%D z`e@?#-HsgP+ru^2W6ww>r*Z6_PuJ)a*_86*0QLh&{>a+tA0w2ZgReBnpuE!im7+36x}sgKK4dZ>Ib zRRdiG6~bwtS7`%;*vjt2sBfaNZno{V1h^Lyw_^`>dW*<|@-^y;(}X{l#YD1(eLm2m z_9C>GI@md|zS7hgglH}CmvfsgPAujV=w@TJr9ytQxtc+!MgBD>9v&riFso4v=-G(a z4U(3-`PTh)#1dlwbEf|@+eEF{>4q;U5a~o{i6(kii}aU^%(Ag8t0GQ_Vdw;y%L^?2NA#vls@G0pLTwqMrW!ZXHFB8+~P?io7^L zc=D__@xnJ}H(ftC1+(PXXbqh&LRBdCwmuh82C&0Sf>M5mpjGM`f^$%kWg2U4BhT zg=8Dxbcot5+@|-Q2d``YX?!rP!rIEy()KCxTbKpg%O=`TzI?S04lSlFaq{9x}q z=uejdIL;m41@4uWvXlAvY<}?BY)pcvH}l<&YlHvLLQ1!|yk^a^qyk+UkmDGjtcp5| z%Wj$3oIuN=%jPiI<7ejwIzZbuq4l}xnHoGfKB6l?LQF}-0&d3+_O)-!jOZeqi89I5 zk#Bp^A9(i_z~rst2A|zJ6ZJ%#Z|CZn_`PnnC{WI34@%7>h(1XG=T5Os?q`*hnbXE~ z{YW9`>|`eQVAelAA1sssiU^bupuD>0KS^_5I$SqP`9f|c7irUG`ov7pUv{y=o$sXD z<~s#jWf-2W%9W8qF&w{YI-j%BEamwBxR|~mXB}eoGnVFaO33u z27k$N!1CZP;OeORzM8K6w_gh$E^#G7ElH`*>!VlP@`@ymYEfBbcYh?M%pP5F{7W!Vlz>D9X`qV${ijSe68a+q*cs_;?p}c4F&Wvpnt>| z5ybk~RUphD^iT~1G$bVUzmbNI8nAfx+z5UU33^)J9?wPORZwzX8whwD`wz{`O__H5 z$Ie)E-QIU#XmQXic{=Y9Z9ba&Zj-pd_axANlm53dASd=Y9kF9R)|?)4p+CwmENq8- z6BQipQ+@l^@m%P;fagL8V(4={WrrIABB}zryZNNr(hem;-G(Y0FVa>dK&e^KeqBZ- z#Kbo6W&7-Ul{9KG)M}%zx$h3-KTvvnD0KpLj9DlZrpE5LsKP68?DNqF5cWAkyO&`P z{tC<*$AbT0gOY0#|IquKyYeOt*U;uDR7G0>w5-bNJxOwj1}_L0c)vD*z2ZQhYU`_U zyf@_E1h6`7S`7ydi2;FwTKtHDK<`|wT3fFoCtoY|2mGSCc{2KX-i}t+RC zS329inyTXO7^)e$b51{W{C5jXA?NgK#bCmcF3d~10 zBz}!SJ_|okPEkxBgwJ5C-5lm}MB!2`4*}6H zPsZcJ=_Lqb0ylG-7~XNYq)2L_h2iJe*SY4g1?%p z53IB!?DBli#>m*jv_rB5b)=dAW;}0{7TZw~E=|-|*{xAD*L;f;ZSYCM`=9puaiz z_CIbt(x?3DKZy5-wY&eXx`Y4s9MS)wPp$(9-+tRU>=|Iv&1lSu0)}5?3+z0P=CdFZ zAki~e*)ze=-8^1JB3MQQQD{4lgxn>no?Xk1J>l42hxm9tq~2J`EH+<(VQ&SS?B;2D zR={bcnz8UJ#fzCdfUw}#KfVpvy372VkoMlf4XjNeMs_EUX24CX6WNSqgFXa!4^)X3 z-cBK%Vd1OWd6aThaprQwS~U#niWzM^qBAI%wrAT&-?ue<4^fDRC(b0E)^ll2&n}-H zg3;K?Bf=CT6|TdM@_bn9UbFGYAqoiq)}Tzh1A}N$<_*J?x?VYR8@Gq&zQbi)WDCP* zJ=60I18D{Zaeq9V0mvF8sIX6u84)}@P5Ma1ojiAsv28R#EiIVXOM%(X#ip5sQ^h{epafZubz+<&mvup69zRi7Ieu#c=~ z_XTDEE*h6XDn13&siPR#GyEV|nVH!o5WHiUxk`ZE0{os$uIrf!q``9Sjyb(7@UIh5 z2pkk1pHUFOoA7`UFz@;IjTE8|UR{d#(`0e|daN62A@u&wG3aZqnxtYPH0! z)fI^-jawexn)2LHzX^q=(CCX;n;zIX?li@ugH6CmDyr%QVn1r$sK8fdzh*o*PUAmr z^f-aEiMhpxS04eXEcJ;j;b&(^H}QJ_o2jJYBn%UZHa9L)#|bZIUVofJf5O-a zer|{_I2R_9pZwQtXy0!`*~Gr>h8`Imu`3TDCYE?YCAiOMTGX?_5n33bSQ;M_XY~C> zkN&*T(J&d8H;e~cGmA(4F&sAbZO8o`dteO7D7|4O#v8l^ZGjd>(7KHxX~_)h{l9sS zUqRy&jR0d%60NOa*#CGJHN5n{OzA%z$p{$9_c2Jx5jA^>U;puVq~JyO| zVh1%JM}(NnV8LiR*S?VUf|uDnN7^gC#X>6hDLZBzXm=KE$uo;e(Z!kVx+M=>;v}31&FMj@e^|W-oW+%;?ikzH; z^}$iqEP%ayzqmP6;d&%X7uh^)%6|-OOP9}oi`FA9S8tAYD&)j3k8{4lwu67NZF`c= z3J9tS9#KUO>)yW(DguZMgFRbMRK3Zz0Z+`i#j)x8%;nkb)ch(fblm91)L$>*NF<*E zcJoEhYPHrwyf*8~e;5OYiqbsKrRR-k`!CmUV&QDrRM>;5r9uGVNuJjD@4Qq3nRJ&p4w{%aV$$z=5|5If< zA?Lp%zyFQp&ZrGA7x+Qq)c^KITcts+g9BZ1_We8E^#9%Us_Fx^fwq%xKRiqb2kxZXPRH8#EoS_Zh4aGSO2hS?i~bS=Vu84*rj4{QH${0{N;EQbUHV z?j#_g(z<8A83Jeh3@+We^5&Md{P((yOC>;4j7No*jBffhJB^{+b+Lr}YvDi0!ZoG> z2;5^7Ronid7ypyz&x7ZS3JGQ~N=?VecpD)-8lk<2LETG=#p}<0^ADXPzFzSKDxfj& zFYOiIg5WUM?m`1G_%fAM=&*zynUnyRBgIfP4qqe`;^ijrOYe0R!HK+})AY8HHt`Q# zkTAVCY|UWO44q6&+^N)r%0OB|Wx#yY#lgxZOtIzOKh6yB8N|c|MgZ#z^j>Gs8O{Wu zjB$3pzgkhdk{Vr14s2{R8+2$Dw{&PIOyeHoF5lkb!Mz3|{EN1f8&`2Bbc8*~Oof@& z1*cp~v|jOjVF~BVMpiKA6dfE`en278)Bn<-O$N+L_qgGusruDOgjgCbFt})aVA`Qa z6nPjVI>F;xgdF-&95j6jwJAt%$pU#Yhpp<3Ut)?ao z_>jdfYcpu1BxhVEu^!1>sH2-mhbpm`Y&XZ=f|I;K5I^NV?J?mo=`rPDyWTA44W;0| z4!?UX;+T|Enh6XyNb^Bks6oEOjUZ~)ary|uHfpdP_Oqw%lDRj<1s?}RP6>O@;?u;0 zS+geNn~=;BBBKtWAX?AZEt~?d&(7T5K(m!@u~$%y8!fnlw9PeUsY4^$f%fh=oe77L6OA7y zcz#zGofd9}CMK9gYAtIRXYAb*6Smiqhr z#*fp9;|#<0jY@L+@5dyo{}7ez{#TSihs88#<1ReYI(_4-3Y`R5ygYfHGyajYNVaXl zqtxUStlu7yvM4`IBw}mXXD)X>Y5_(%zM=OFT-K3o|LaiTN>SB%OJaRJWZh`%%1ZV; zZtbJFbRrY_LQ+wmeaYP#pS^WuA=ly4b_7`M!^2Xj)9rHU2d6g|N>lNUrps#!JYdJ1 z`%9C|Iy0CI!TQWyzOgcV6Q4F;bLQ%j_cEHERO-_q4K-KT>J^aXt{H@{H|zQ1oDR3| zF0%Fw$+eO2)Y^MjyeIBkrE((ioA)L^vFH;>#B#^lV72df=46Wl9R}E7P8o?|xm&-3 z%3?sGDi4D$2#KVb?2ZPY3L3z_n6D_mdBRRANC+> zxOAOl?a5ea1zw=v-%db+AB{F2XWh;@Q8DE0QN$KRB@iI1 zCZ~lbeLssMH#yPe!t87AwT#Gyg+{(%>lG){$Cq-yTSu4obMv#&B=^4k1l)#eXvoyp zwLC$6i|Ugo12$d|B7sza1#EiFcG?GJlRt!Ql$|n?;X2mZo&J;|;n+-D$;2W0jZoTr z(^#SG<{O`1^&_pkI-jmFqr1j67`Pkzpz~y+EmHls%WvOjH3~u{Vg@67Z6iNVVxR727ksvKI*57ipo_-hDJTeXzco3kV7m&I#)ybP2&2)YuX#w$#bQe0~5c8$cbh z{tQ}Bf^9szOb68!mk^Y9!IF;?P{0jV9iWijDF?KL{-jRY$guG5#m5pL8=L|wKNIq^ z%mtHi3OZE1VsoLUZ++ghl#|iA;$-%;sP+eL--l@tSSOF*en9=yvr? zW-olj);PhkJy=&Nt@{94zw$KD`}vT;$Euh++}HweF=V+DUOc2P%Yd#N1igDM`$Q;p z)nYyn3gq-Bpn?)mM9xUKebrqP^T7GOYB}OC+r~ZE$NP^r8jVULq zC%o|&S3g9+dN1iTcwo!?E!|1P+xt}gdZ<@S+7WeoJC&1wzj!H|l}2bo_n&V$oJ25|# z&i2CKVNR@`M>!wGzXxXn9%Lfcn^R!s-J7n(wMcKdm(c3K{@tVuRnrSANeLND2EkU+ zNK6-~Sk)VpA)=(OG*SlP*U?4q5klOocFyczlq3DkH`gAE(rfdcU1WF`Ivp>+wKd&P zfnyTxffQ|h-x+QF)!|gKQvq-{fPDtkT1>%u>aaDPy4u$F{+^rnvEK_GvYW!eIqV`! zrBq^5a~$?d@jnHro{q_lVm?nr<>^X4WSPAfac zKlEK{yML)3VLbw4M`PCPH!B78ZdRYo%$`ODO+2a^3Bue__=7G4EFJtgQ~(F*xc}40*L? zwseEFF>6-5etXkDSW;R?1Hm%h@h9yj9|4rAcE%VVmM%jJf;+r}^6fbA}FGttkyu zlAbN{`0?56e;CfQ6c~qtIH0uVlTGPH>X-x%x^pvm;^EJ zfCsXjNpCJn_NJMYjfo={pEN&Qh|^1fySnPGVYTRuwB8a~A!*rYJgucbLisoJG%jKS| z+#n}%ZAZ0(lb=5=o@6Xu2=w^@L8jY{;(Hu8YgLjMx%PJ zx4)S6rc8@fFc-P5qwb%sqhan2K4PMEEu+2y5M{)tk@Bs)OqlA>ez8Fk`Ame);z^;$ z5h$QJc*!4Nw8je5u&5k+E~oh+MS@9W&*2l>W(5hh-bUl)-wnzsSRe^kwbZIHK9QGM zVAa=FO7m^k7srP%DI0PME|@9sf_U8suwnYwcANM@22vv+0YFYXrul^J`QLbvNVsN~z7AH>H9Ls05;b&hDuUxqT5`Hw0+7QEnlhp!g zvoT!J8(8VtJ143|*6>UStPH#a4(8fqGw^8q50CG(f^35s#j^Yi*IHu0`Jnke2XvZH z$$4;HBCBB{2-yT%!Jh35a{X@a$7{ivarNbBoET>*I}sezJH569%?vqB4Ivi?q4V-^ z!k5KNK*lJiK)o3B8CQ?LUYk0_xXJyUFJ%@V_Z)BWBP?LPn6~S2qiHcWtEQ}zw7*p6 z7UXATV}*9`U__#U->5>rGw{7s}LQpEJ}Ty;OHb(DQ=1u2~zK?@!*_EsA2#h6^@oOtr?6JcjR(t{jtW=PvzxGN~laNZ1%{ z^|Ebk8EEPsP4P^tqM)93xQp&qzD#l@?7;ytuth!9wTYPw@-#?j<(q&&kRNX~lDT(S z!+q#RHBc!|4$A3oWoj7OId8ht89O`IDB)P^Qr0NU9>h_%s*gzi;wGDUKnIm!L-IW) znfN`zo?Sx&@2DzpeQ~W!ROPgAj=}8L{xXM+jg=KW!%liCa1+KWGqHSQ>yat-@lNal zpL5zzshf?ar3?LLTR*Yb(nXtFdAj^bAk41l9ee^@!)HI98+ck6<2(PEpk&(;dhIw+ zE%8mO&^y9gamKtU$8vJaSLAa+8Qa<&`J3w|&dC-Iee8$%B9lm6zUJ9p z2h;JB^{x?0|1x{*m|s`!j-4-#Dj)r-DR9^QcX1Vw?IKqtesfUT?SAFm+qdo${g35M zK0T8UPCC;sW_`oxcz#Z&#f>lIz*{{fo%x-od-88&}DQ_c| z(|tpxWFOiuYHfdXrgzBl7sFo^za7hz5J>WkYZIQ^v1{uXq~P68qInv3hCfjqkLqT^kHy<3e)uZ9eIju+ z!e(Hj`9R1-ns~$3kBT-4iO!7Nt{+82OwEOPsxms(&%A z_!N5dtf!6*@oc+1@u%-!6OgwrO&1P!oAL2@{`N0ph|>63`;s0OZvR;OC(iHaUAPlG zi`%&C;j%n$P!5&|Q<5tw4v9NaVsp#x;on0JxS@XlT57(4zcPFL2Xbpa33n^(c^W!{K z=l&Q=w~G3u_Dn6A6R|S;Z`A{lnJHhIcJXul#X6a1gf~@j=V$qF+^?q**gIDb8y+c& zGQ?Fs3h>xW|9xCUXnR%kzX-|V?USn43=HbJdxm9BWXbH0{z&-2Rwb0eGa(kYhsVbd zNr(BZ8)`%ujqQHW)3GV7@feS}Y@NX~;+Nm}Ktz0dRXg+AwPisR;`E7VFrKDQ?fTiT zo*nl0-+sT#4!8eEpoD_Hi3w>rEA<$dlw={ICO(1YdedB%NAq9hbYffkYCjaWjyZYTEtJ<2 zemtx2A(iI?_VGOJlE}IuRzb-#E4yEs=#QxnC2q?Ssp|2xpSw+7~tsw@o>J zNZz}(OVW5w`iFg8eSJZITfTCvV##Tw{Szu0lD7Q6*m~=*sJbq0ct8*Vkp>0n5)cp( zkPb;nrIb`aLArAYks6RzL{hpLK{|$zyhVl{7-A@;8EP1s?~L5{^S-aw_a|KIoLPJC zwO9OBoX;~fTWbyol$|`Ukzbl?@A&i+TUv4x8&AP5yoX-kbGN zP9(@{HRYjXwf%djDC{?A`kEC!$orOaqkK8}UAm+e%4>B57T>`FSAyO17^p2BuFYxE zp9MBz2HAusfgjh{7VSse2R$Uh9dZ7*;<(wvu|mr#fbHZt`xNLakjss~IjU;gzUJH5 z?6zBfC3k8{TM^A7ZAq%yv*s!p><+f-Kf;o5!Cwt82W?iG(Ol48Q zy)kq4pP_F+F5&uCitowr(hKYJ2}{{o-M9~pf7>&F((!O7=xS-iA#f&Ws4TyvvR%`U zcmR6%?yqa?HZ(Eu(AOVqSyi7DLXD7^4E#V!v8{jh;qc~8<$~4ka7Aj^)gxm97gFF> zPiLRRprX`gA~!m_pl6h#U^KLN6-75V)&yg@aei?sa-OlztmG8}bp(C2xYRlWyQYri zDWfsH+NZ8hD9?WKWDO#I{8+U(*dn{Ub=rR9>{9IrG0+4#j%D55ZeihK#)J$Xmv*WB zb!GI(w>q$<(An~V%l5@`)($!k%UVSZcDxdN_O}#jgtV)#t+P-heb1mmNY9MQ&5`qz zSQu@F@9$JDTJ`oM#ua;%Wl({&c58^|JOBxNIOQ`!XczFJGNm_0jro()sa~VoGW#0% zH{P1>GVR*Q3KPSj2PqlTv@%Cw?BGPcJI#h|Hg6D%S=Qq-Z0g_IVyNNtlgBR`RIUc< zFmZxgX?M;(9qI}Cs`KwgxD25SO~{$3s2eEz7U6*QJ@G#nywVdpREojuYd^Z;*x>ik z%hO|!68dQXgVobul@Fk%ejp)J9_!_H$`B~f8iD#THRre^<~!9)K8s*?%(^g{svG@3 zKSU0QUV8a|qQ@@I?kiopZRWRALFI@298kTNp;vve=&paZ%2guc^O3<$vUu~U5XR&B z@YkUCipCb7NLs@!exT>EyJ9{j&04c!=yzC5M@HBFinw@`qar6ctoz8A2y}8YLuizU zL#(k4A?kxwu28m@*KJ-_=)7Jucpqm&codHIF8Cg@0QKWL#+i zCjG-yj*JOi_yAc-|Iu2S!T{U*WQi|s={58=$eTDOL+(*_JDw-OrikcV?vQ(7Z>u}M zFDme@WS~2X=!FNHndx@Vf{nuIMF&FCVh{8p?o#+SjHZVbgA9!MxLr72(=$0ZJLZc7 z1U#U)UczCU1>12gS>4QcG=dICFAogC2pHRvX>^aZy`6MLLSHj zyoL1NxBRs+?VH@(Axk**J?C|m=gSE&R><1=gW}c9qN|hbm!B}_p)1<1`3G?~nEEAf zyESK=PYpGZ0|?vG_KWOTo*;z)6LHCc6;{2ZmYD~;N5X3})yYWamUn^zry@Hd%l6wf z`mMS9VwJ_b2@e-^^Gw zt?W3r@c-}0zbuu@H$azpnz|bf`$C~Y(-ptUgI~XQy*R?Nq&OaDU z2>Pmhd6f?8SFj`H5jP+Z1l4W~ny78e`hfYYa3851*Vvha9G$%Y^9 zHFC|Viu0rqZ^nWgXyY_w2~k(TLZD#Jztqm0%E-tFplj%pS{xzT&6WbzeeGH<_B-7K z)N01ZkNZu>en|Ar;FoA4xn4T>%AL)vl`ms9@L>TW^5F|ULAp3IK_V0>SO*j=3izLM zw|TrohlkNH9&X!ZH=OlsIhzpV6DrC+&UJji(B@rvMdrJ{{|a9~)z-t7gCF<(4zt7~ zdjvndLleSyKy3_vOFhO~q9dBj^R} z^0t>@a2Dl5Myczg=eJ~zj+W=~3Dg{4ShA<5x8^mb!cuIyh{4XF;3M1$uX8!yrcSDI z7tK5$irC-zVep68+C>^5o?mV8HA-*8UInGVSHRh_x_^p><0I~UzUD-AlQ}zfKv1}H z>YtUht?cZ!dtr)fWA|McWxc^Sj!YjfPy91yp9{vU<-6oeNoCrMj0a&KIYq{$WVKCzaK=CGcD5X{R_pQ&4-U`Pu7QGY z0SkR01pgR#Dbf=Sbgf6{ulY;^QU(lbb=B4_SVbNMrL)sCdYgVl;4a)sneg&Ur@poDms6$%*b{rv0rwZ&zH6=r75h2Kt_H89|_8CxLkQCsNy@;E)B3d1e3F6~6Z<0B5_pCUiCEGd*El|Z@ zqkcCTlIgW3gj8}QM{#FZ{>Vf#_W%C{95qs3-ei7C!Aj=0ag7i>)LeJuIu?1 zrq??v?~@FyspenY@V8oq9 zOgx%++$q*M=-^h1|Iz&zO|4?!u_3B!gE-Fu1>S}DY)#)58e_vFV@<=IP&r4IDGhbm z%xyTzT#2Y|Y^d|5VXs?iW~>5o=YaSagPo;^XQbsioT+1Y@mLxEUd@A3k7Ge+Xv!By zFAq=b;P$o5e!Lz0?;K03yHSYulkHf1!&OizP7Ik)5v}ypr85iy*t3+YUQPyCH_{zS zpr8Cu{z<7$=OsRZGYpwy8e>4!>(Qm;v7U@uG;(qqHi-sS1<5(K_!$I9x>U{Xxccoa zC?6vSG8P&r3eAA%`+Q|`RBrw$nu>vxLmfC6ys>&6&}Vl#(em!)FXbU zb2aHtPB9&05r2N;zNAGFISnVSlzfRzlRG5oeN4=Vs7_r<|Yg^>VW zBt1!3FPeo4%JS_UniwVy>IV?8VA1B*peLF|URKpIU@0=DJ4RX8r>*@QJ{;G!Xc#$N z)cQEh+}zYTEj;!B-DA0g6RhdwewsZ79+?h2y!X-Dqe$nf(|t)%nWx$|H%y-ZKf(D>RyiVS?G1)Wd7;76s?6f}^B?+4 zGZzY8aL!5HGYegIz|W(rcS~Gra;ba9qn5eHQ``kTCk7RKw`PAx2-RfevPxq*yAqh1 z-Z{MU;yToDZhf5|%JuC^sdw-sAb$5OmUk_pAfryvv4w#O0Gv>)L+G@OYKSooJg43< zZ4#)MJ$r;H2C?e>yEM2^91}Hl@eG|tN$ILC{c*5qWqE#>(`>7SG{PyGWpk=fmKJ19 z0)$V$r|Q93DQcUw6WgQajV{e*0emh^i@(VB*Mz(xmywP;yE6G}dU)~$|BBkt(sY`~ zcPptPoil1sVyQ)4JhOo6g-wPapT7fJxlG}ItHOh7vvR#iy9Iz?4`gMtjY09501AP_ zq7ccS+p#3U&vENQj9r}43%^Bbxo);H7Y@0Lu2Cyg+>)%1Mlv@%lEykk{g69~I#vS3 zLoO|O)gqBUHi~%EDXK>&GmjxlCxE?Iv`JEOCH9}kJJU_PutxVn962!{ektquO?A;Y zTJrOC3Nr~rxh&;Ust6HuxKY&`vOKG`)BlsDScd@g`QD}MEl~sVcQZVMpfsBmugwmC z5}U{liMl%NzvYxR5$U8xl+_2G`PIvR*)y*5DgR6hhJ#Y@0RPloGcE>D0q2i3)4epR zW%l(LBs`S>#yoMc_vuL3Ld{%q{XX0xZ;&DxMbN8s>5}e=+!s|2G6O;o?ItcKDR4@x zPYZ7K{7!GOfpFuqk3c;aOy5-VoYn9Yh0o@Ayx+)Vb_n|J$TjIO!O zK+IzHE$SSpp@emv9HPv*yOqYJ23(PMh?$rZMOqJ(yt8M)a}|hJlz0>ULFKbcQJNG zd-@^tPaWW>0+HJZ3!hUVH~KM$u;azbX zFeLpTPnHsNGwet)cw-M68`(GP1fjh+rer!=$~k@zufxq#fWw*hCpx_U(9cAnpWzL^ zx`bDId4Lt%ySq$ErFb^#Q<7|qzlN%H_f-gq=@FTDt|9tqyN+QvqEG2-bX^Y*w{aR} zGhYW_h>;?}68LW^*H$*Dpb8=(XtrR7jua4U698?}Q0L81&^sLl^`4CH(d=SNb*>!Q zg)bNY~9jYAv0>u5rfQ45;lI@`CobmxL`RN5gCU+{jf&hPiU9Yfn zy;Hz7r%*M30dx-^xLT8A;93pMc&|sNyDZ~+t0p~fCgRX0dl_{zI$=V$>7dkgN_ypg zs5@}p#0J0GHM2Y|aiKiIb-OWg55=15gYGbo??#z9j}+Z(G&4dsTOV23wOY;UZY_ZcQAZ6B_=oHz|)+p}2- zPruX9$i^Mr2e{h^U_T4wVXT)M!2G>;ZQV(|7iB zRar_E#-uDi1Qa<_y}bP6uVNg>$3%B%s(!^WEM-F=Uw(CofhGZmQ2yY(=qLm->Kmk}N&MhzxV;~l9pvX$X<{SEoPJbJza2o++2m1@H0xJy+WdN&q9xESDO^>QnDEN|)?JlyB0q!7~GOF~>pFp_3F z@(LsJs^Hm6Hi0{8havY5WLMLF;t>+h>4qDs4Q>?^S~O%tO^vo%y(aO<%PH+`;iNa^ z`uHn^fTZS$=S#M+6)A!ma(_eK-aOXqEWTvbSof+9iV;p4&d45-AMA*j1BtQLT>NEk z-T7JW$GOd1Qll8R9^{O^``VJ`#*ESrr?G*)a$}R8QG2boA)!YANcrrIpI@pZ)H50~ z_B>9ToQ!b$7u7TLF=l;K?%o}XPuv73J+L!gKRu9pn51kT=jK+DlBt;9VPcrxgB;U< zs^03<>FzB!M#;2JoMopOKJ!P~ag{HBh?Id$ z`*33JAfOg0{lw3U-Oq1eZ764QnU+QK!;G71E@WQK2MbrSw&VAaP&tSoL?;bs&Szxu zUkAGWx2dn(>>g_th;cNwv^) znI8YXD46v|eOXUB+$DVfnVG2|GsI|hST5*Eb3r=8w0UEoSpW%0?OzOi@wA=wOq`7p z*|$^3#kJoZMoQE9j=b&5d@-+I4J;G(%=E6go_37To6W9X3RY3TlJ%o{2MGj@s?x7- zj{59($5i`|yh$D2at3j9tMU{#e+g$k>b|;@>SpKSFKl)#J{1SpP8i2i2narNmA@d+ z`&icW8)Trtf9IMGC%NgYmVH)p2Hi}3Jl(X1lz>TH8UuBvG&8^2%35Knmd@aUGE&y5 z8M5W;lysuaK#YdU^gQZ*1mX)vk4sBBM%$)~}nj-*j~tan4!jTRrN znJ3RQ-tL`_+HQ&jyrLAC@((LP0H5-9Bk~>PxA6czagIcfGk<^Q>Qn7^s+=qKg)H9TAwoDb9M+pPWMmoHz$L$r50g|T;_W)`fXu# zwN5gqSHfXtsF+aW9f3E} zW~xLj!&wchMFf5Ey;E6P)zjxJ*XtbT%blOb?9V{GT7a;)-k*9b0$F7Fb4CPlSkSqC z7gM_!8{xGX5jXYfXth1ug3&f7%2Q{MN&Kg)Syg6|{^4}u#5&y!?+pKtRne4?Guor| zxLi9K%P*`SPq7K5GIW5@tdte3Eq0KQWE&fo2Egh=Sk9K!y_!AyPyvM(6zdvv0>Xap z4fQ^s%R(^p2|?rZb_jTe_?yjN>)dVvGC{0?O~oftF)k?QM5MUJF(Gm0a8u1<7e9y6 zY}1^#>T4*~my`74#cqUV<+xF|`f?3~keIkfmcsdGq=;S6fXz3<{;%Vsrx zt^fP-R@cn8h`-4btf9F{DwkW5WiFVBItU!$#W>Dh4!Qst;H%B-}LdmwU`%zDv-Ux*uO-Mh0 z+=WBgR*`OY;Pr2C{dGLnTev$z4lO#vc@a2be}1eUkS0AQ0IjYzB3WSJdG{jT5YRIr)e&m>qf7t~{Bf)suNZBU)E*vD8>iIVb2@7V(b{_Jx$vml4-4qJ}v zaXn<>f;nO7*KeO=*^LjLpUN;}k@bTtQd*z9wsNNCIsHCr{=6_Rn;EHucP>9`5d;fX z@6lYPN z?i|P12+#YkawBPg{>7H6xq-qW7w9-D9jO(@?~IVu>)0{G{==lfGFkz(wV()aRJ5Qd z%g4++%4&2?Bi;n4?CgPK?9Q~RZzwtn8G|V5I|3F;Jt7Xiy)rqJ;VFjM|NiFz5@k>X zv(r2LmYx?C!ZnXAm#8?oo6~z{+sr!8?zmv!A*Q?qZ=2<*hSOhQ*);tdz$xB#Y%fzuO!q&e7whl^7@n2P!`L#YAbTs0+U_ zopK(}&p)X#0@>`4-+&;>Z?$7@%X0(4T}l=%cuN6ocGlO=#T7fRtBkn?AI;lCpuDTb z?v~X;EhaU$L7bNn>&?Bf45=;xTb^$Es3_aTU!8?jc-G-iE?7aH{ZEK1Jb|O+7l;+` zHQG+4-QBZsZ^gk=9xnD7b`ou;xE%Hek8%m&@e78q5|FzvE}}~#rP6n+t7v0Qybev2 z9wQ;Kk5wJH)>O1%L~28F^Wh}6yOTiq=Y+a(sfPZmgb)HYUg6a1Wg5B_&wmi7rhljy zaStA+U&&m$Ey$pzhX2}3d7j|sGt0mSFYgP_Cl()|^h$!jlEsCA&{&yC**tUd$zepX4k?}QKP?ARXBb`@ z3ztO4`e*OpPpa!VA83k}sJ-Ma_9?IpwlD#!H@!SF8>Me4pxQZl3SC~D$f<~ETUAT^ z1|xHKlJb(SUaxWZZ5+)M$C#D#h!~-}U!x$aeY$@jkZ@x2`&s|(rml7Efil1B8Ty1h zFO3Z(q`bb;wCTVS(Sf&#T<&`-vX>f#|PFqiZ0BT|&)wNH4}jxe;>KbIfYzb}=Up1;t{gHoRRDmV&NfaH&Fi6BvLE&_v5L6W=hYvpox0zO9R zcy`6Uz3b*gM$c*bG>qxfXzN+io6j(TCqs)~d!_mu%yplEi64k?G~70;P#vgGzK~T8 zWajrEG0g+a_z-u%4usohe{%bP--F3(37z6qUKFJIlTS@o*)fj$d}w7#i@#p0k-c== zzhx;k%<)&JSy1rRs#W| zV5Q4jmv?X`}A<}9L?lWQvqCER;k0(1$&Lg-|08Iphl(1SLE4o^$K5GSlONi-%EPPs10syiZ|}K zh~i}ICZV7rp<##YlolJS-PgS95y|PcmKg5I-kLV49nhYmrf65C$8Yx}QlpTDVTV$z3?9hTrzL3*^L;O&%>SBq6c{kifod5yIQV@98+>ALoqMV&o4y zi{RTIr#N(s;^?jxEi&@!Mh4#a=)>UOqPt_wpL5oIGR(&k()oE-*ufMX0V}p&}re?N6P8nKU;yWR635d-NYTH3bB7)F7B_SauBw1si zL{D}q5_v*7D<&wDeb8CUff#OMe?HkhnOMKLgpknfO3`5yMtSzaRx#nDaKg!ut%Bkq zb0k0A(;l%}H%N|b(nJI|bDf8IS`I7QFk&?jN`3vh9&5#%g3BvCpBj?0oqIrtAZy`T z>ea7i`s&Ws*gN#=wFsIA;Bpi9e8 zJL*ZPW#<;&SVWKcln$-N(X$nj`4`#%WG3S2*Vtvf>JW~K%U^1jDJUrDT?VksUk+DM z{-9MH?jqtyex|SK<7MeWb>O+_Oc{LZ5V>Hl*?5qC6NW`S1wQWAatSB>J{b8P(G?0v zr+ZD!15ufB;~PMA08kCYZRW52-jhBIZH5N|V*Oiwd)aQn^OKM#yDFk#)hD;wuK$lJ z>ZgtV4?Oul8H^1fwMdPUI8ORCY9|cjYeHzZ?Xcwo33niHJn?Ge5O!J!-Td77v)A5c zmXSxevf7?Vz@++K)h3UT-peC(J9VFGw}+6VtZBgwf8kc~}hN^m}_-@$6W9C8w!p7q(@zQDFkcNkL!m&-+qjNQi-PC`YMxceOuDcf#h@r43`dNYcVJ3$UD45W4RaCpg%*;}Dw zs3k5c55gOZEPa=J?lxnHjdPiLQfto-;Q|=?VfcgY6RAyRfqI$gEkf~`awfE5NqwFn zQjOP5=vJfn;X}J+ubtn>L#4x(^odN}HtI7?*HSShuG@eNGs5URgT$#~&GMir0q7`2 zgJ=ehiy8({&cEQ*)?2HdP9>YdF0!n^$5O~pqWgF$!MMt?Jo}+Hl?}z} zI9ebH6c6*xn&jb@loG(Dp!L7e2c5JrfXjD8dCa1Vj$szBhXc95 zcN5L%A?(ZVpz~@$bQd1jUK!@uq z%rN`cvKJvzHTJ^C33=y=$Dg^uW;fT+?D&pEMLwH)`@wuA^)H90_?kcPxjq17yFuI% z`zkR~>V~?ko|59Oza*5*BKr9qwVS2+81LO92u)kL(1g+}Rr}&!Plw8Ct@BygvS-vS z(hiRRmz<{}p0)fOklRQpFXA2v1+ah8X#ht2iRWr*V=Hy^%3E6&d{?0U;5CqSjwBwQ zd@m2`x;Ezolm_k~2VQTtTzf%-lhak>z3P;?jp~)dp8+%*4hH2{zh1R)2XCq)@*kBq;Q zIbS~Q(etbG%kkrMeSUE(x37OkPQ!A*+L(+dg+V|~_oQW>UwlXg`zFg^%E^@7<6))o z$ytE+h|Fp0@g9ZQlX-rPqPt_ia*=m4(t0IE0t2oul|MLmQnQ^TJmS(eRy!o zYFXr{)(ZHPvXtN(>C>YiT3?;&O?3;#wZd*ap5ov70=|deRN)s!=J^bdbXpO_H6I^+?FjMtf6+opv#ikdsF-vjHTtbq=k#ws<3gh55K4I%Dy95 zEux1-*YYO3H&X#pTrpm~4oZTHTTF^Kd3xI)`Awqh-5Wg3?*9mjq(T%8g74t`Qx_!# z#ici=0A3R}L!*t1(Mh}Z6vLn``>PY1W}5_~=z|k4MB8?~om8(EAQF2rxmqznosiqN z8BLvZ`w?HZaPr+LotR%iUpf|vr>-++iyn9W&`7@-paJ&pI4*|O$MaPvp`2M?R(-p( zJ6XFeX2YW;<}@7pqdaJ^JoU?=&QsS_C%c`}ARcw@SoKm?1+FLdQ_&jN;-a|LN+YG5 z&-5#$clWxG%j1Q{bsuCX1EbEW77FU~Yy&q#M3G`SS+1H>=jCh5YZHWU=NzI#;zRO_ zw`-n^WXu6#!{LkuIZlVu+pW}z;GWn)5T@hBZ{ir4?7d$b{P@fYJs z88k)!z6caO8ARi4bRpO+l7?&6KGYx9$mJx6@aRY5?V4Psk$1!%hz&e`?PUxOf-_65 zVM^$(OhiMKVchxlW4t>(Q?)~o%m&!aE4?g04a2?PZOM|t*^}x!3uauS&)(%oxm<?BbfNWrCAdMIL1Bu=uB(5yn`2mSZ;NiD#jpKf_{=n%s=Z?wB(XjDu zXjc4;3ML;OPF;?H#3d*V5Umd7*TI`>m)SqcumDpS_J#<00wt^6o;>{Aa+v5Pzi3{l zGw>efL|iV1R0^0!h(7UbPjtDzp~+qZG%%Rq19`nQIeC63Q#I1$X7w@01Tjnv(-(}! zq5527-p|g^38nNGsVWMJoDj+9O(FVwO`k);o-b>CdeFKJ1Dw_NwCdNZS_yexXFIXn zpa}R+c4HSkP6&7>r`P{Tx|3@3T}`#Dah{8fb=o>YR@uAgte+eaWq!Wi_<1_(NG>$bf-vMvH&k?!;t|RUoRil+{=>{tduKLcn}^ zf)(|Q6S!l`bZVT592kBpjpZv3*m5#1GH0=PKy`ua(vel>XND=ah{3MCDEI>8sEiv^ z$LX6N^Ka?(D0t5v5^pE$!6KRG+}jZy--i$~$`KvJvu65~QsEl5WLZ-oWBc;^>8n%+ z-u9|V=RV-$Lmi&~C z(&0Q0_~j1A{wxPzgchMe-=}Bgquu2Hq9gGFK$gyu-D5mwKGS^d^%R$BC{WdnygJZcH>~0i=Tvw=&me5FQd=bG2n4RTE;)P zTlF3{fz7=A?u^X=1o!sUG}*k^AF?>&Kcx&ec-7|e)U;_}0o>|(I$ScdU%Aiw_9?%Q zcJ!qUaXQ8_Iu|bg7^6P?IH6b6Blz^?1K!V8LsUo)$$9lvD=qy4b{_Qs2lsX@@~87h zp+XqNGyQLG)bCcB%w_^GW|YjUpvhTI9Y?g3dfNWxxaIu!pf3yobj9%$j1knD`JE%y zmg=LVt2gF5`)Tg>E(`d)A}WV)N0mPFLD}&&l}W`8x$~!|5qisNyfUJSm0*c@_VQhn zviAtXCb-AY=*7Idh#n2a4MFV*kNe4;_Vug^M)%ym|Lk~7`yTpPR7CxJK4q*zlay*U zFSX*z{%M9tPk7v7c0G6DgqLl~8Yy*bgva#6ZKziB#j*CqTjmz^px6dA`6j`|#FQgu zGba9P>c^6+HwPqbD4)+bQ?1f*ZXYeIKW#L6T~RZsQ)dH)uE2%r_G0P1b)(OTcPdZ( zQy54wX9MhhFUExvkCiu{(TxwJqdCu%w}%U!v8hel2#=za2ekIj5}qkx%O=Y;*mb(q z03U$(Li{dD#$TB@W1ITG*;GpuT;|zR4wFYg?R&I>q@PJbMRns@XEeTDB=^f2k+RGx z#UMq6Pfl0hXPT}2bhOu&qfcU@LTP=;TPlCjg z<>p#h;(OHQEE>~mE&R~ybv1S2FuRz%4l}|_Y)=8baLUCL{wapkwDx-J(YljvClD;+ zYkpYcvnk01lyzN6WoCR*PS~q+>76U39~`adL`8Kfy)Nu@uW=9JA$w5=G-NH*tbaJ1 zpKW z^}>Y2lN8tG%=}V9c&ROF5LXXt6kmTe*OhrG5x?M>{bI?pxb_p-kl3q4okS-E#oD~d z-`fqse*Jna+DQYR!p@V;bRLAnm^IaTD-pP9PVdgWaoO)srs&&?`h^^BsQicYtGB;9 zY>6u%fggIYy%$=1^3O!2=EX5Nw|!{)UebAhO&%-T6-XGqI};AGy1{YwF#(~pV;^&H zWoQ$a1uUEaFWG`r!xi?;-G-OFST(sjz@IaeW>!}LQ1nfWc<{8CsHieihS@?(e0ker zj`-_9*wbg0m6`|Oph}uAydEFQ`k2(vwN2K$s5FLWP-W657boK{6<*%uVePPx9b7K}@cJ6ypTnB-{_y#USg6Rnr zFAF^u#D z9$5p?ZaPpUFwM>GJAMAmq9~U~-7H@BtLwM=8EeX9U2E2sSdRWVd&;et3Dl!&03Ok= zdhceQJu@_AKRu>?{Fi}0elH6Gn&f5S8I4sE3qe1JdE-$5snvK>EZQ&%@FW7bH3uvhLB{5peey6TjlTlK1D_Xo8oWKRk;L%HNGE3~l3G6S{@(e5qmI^3?<1aGQJ}S| z_q0i)GxSwq=XT}0l6=tCaNVnSK3Y<2f`W(mF`3^-T-fCD|8&tERpiweXg>3Le0!s# z<=R$tb|p||k%238RuTmeY+m_5Tv_nO($){ljw;|oPL*C`PeI-iU-PwbWaI%Y!4>K2 zhQKBW{a2+K5<=j;DlNZGIy<@m@*Rz9lf0w$HC3IE_^mPD_*U+t4B(5uK%yKZ0RnC` zuZ2Zx%1V07_Q4z}GpHLI;t^-f0S>rgCj_DdmeDUZk@zN)N@1l5@o1tdPq{D3DOt002h?|Y6QMzh`U(F$V@IULiatIc-6d5<@%dN z!QeYU?cZyApvS~Ar)s8M$2n`GUFKTD%LO|QGhjplzxldX#B$iHl8>Mxjz5J$y*A#4 zfT;cBMI_hKV(+0}L)A9JKt!|}K09K0W#&!Jb3X)-$q8h#yjJ`@G7!@KNfxBlR8RIGBBA?4B9 z*s-6m!sOPW+w)sm+B&6%_)FnCx{>FWCtf?Lby8yDqQ$_!h=48coH`od#gY1~MX>0q zHSyrN^(}SM$n0!tEr>dM>nEoh0UVS6&2f+Z9qY9bWZ@1WUvMOvY&Ek_#lzp<6|Ha+ z&`Bcr20+Tix@qyBP1F+sf?h4Hy3-_|&8b_>?0qW|(q#0zs((ED__zbpCT?!-DJE2T zkuHw#qS^k_C82r@ywLbsJg%ny-lC~NX-bNNcNr}ASq%lN_}KxfE)oIDXy;Efm53k` zQ2b9K{wu9klmw*JQqf(^5~y<8Bm{ASi&UsD1-=?SzkYD|Er@y zg^Y|W8qz-*2~-OrNkRQ#Xxdfge^jo=y)S8zhv5JljAdA8d0_+Ix6B9rb`trkJe|^NanTYV*h3EoQ(zC@&-tq~e)`Rr zk?B+p?@5yWvyDoyxN}x+_NiO+^rw}$mp1Ij2tlx+8ABGkFyQa4%ewmAJ}ELXOZKXa zoiKK3Li*j23qf8=G_jNwP74E6z~}+c(0SYd1k&r({m4Q{xBN=4P+XvUu8+>dEno}6|FqfPgNuhpsE(S0Sed|RHS8!jx3ErU^UrXGc(jT(OD^ffHmZx$ zT8hJ`TniANR!z)Fr+>!)kg_Lz;CDmkd)RdHpKhb2=tUWseYsjE+NAi+_qFpc#}tE4 z7Z_RS0ZtbIhoq=`Sq~?qDMF!O@1yF7z@xZz+P!3+Ig0Ud&LXYYkB^&rNE1**zGhImO4pX8%**7eF#m zD%wnxXiBMfC)a~bFMIx%-Yvs3X{qBj-vgaWz!C0FQ9_NHRX48xkGGF7Pg70lT9J~+ zLPdbo)`h2P?=V-2)Wbo&27hau*I=-srhcaLb`&WnUgmX~qy*2F(TFUw32eBbCHqnI_m7b+39u7v$u6Pg| zkY84H4Fnry)*f&b%LHlws%zt+NWp_aTw4^5YGc;JxaJ0EqF6?2p~ce3C96GvNM7*o zehyBW)PjmFbKxhxvO{AtxsHtL1E9%HEDg$JSVlZcenB{dh`G%z^Vyf$DAl0 z6JW|ZD*`YG{OX-cTjPl}GKwbd@#?aZ_GOa)WBP42NVcRvZj``rS~&XnK~r2DjOO~* zK~+~w1|Zn?0NejR+~ftd331r`u@gW-0 z?G?71&tvcdB)<@FLbx+q$zM;7*DLzRc~TVQN3T#&$81Iw%3he(TnBLqrAzV5N&)<` zff|5u2=Q?Px9i!t8K!7iT21qfuYwYpoU17RO4}$97WRm&B%d|chMyx?lsM5gutVtGNpS6fO82(#%T z0GN)k2q?i(G@t*tzhhp-Q7?qo%k`J64>B^}qNPq;raleD6c_UXs^T6s${q|?$1Z>^ zfj-(zJU76G0i6U*l120WK^)xlCQM|gV^W4zusLu_?{7BwBnzJH(Rca(vo;3{J)sFU zy=;(O%85;&JO&0vP$or}$F|-T$S1vWrQ{XpMd##s;PF$}N*|r)Ow*rSvhd^i0mFNg z2j!RlLDwRL`vD!WKeeanL2)u(Fl9Nt|4M?h0|lD5^xX;7mLAA0>@V${Q&3n~T-v0n zsgkMlJ~p;k1{6$tn3=TIb)+O@DA7lXJMBLSn`aKHhu%(l<@muK`}D0svwFZm@`4)J zFE4}g51lMJ>gdf#pi>d7BdwEPOB-pv>rYL)UR4&|*=LUTC zXzT}I-$~>v{&f@o=M;?2y^z;b%G5a|iWnWoYkLevt7DhIAx64yr-A;erD$McNmr<) zRL;6LzkVr*l^T)huisp^ac#>4Xp=AN<}|2KPEWx!M&NO^3K zj7xCAHfkEQ2iUJdMsGA7S{OJgx?s~_VAJTGF_!F=V>>G`jK!Pmh7)+b;XwcOLcnW47hIY1`is4rfR(0>Tc!r! zF}rop3!XoWEdn02(!HYE8x25)3B#4HV$`ujD95Mv}jRaXKrrU z|2M3umo`^VuX_6%ypz+ibTOCT=ntiNIh6kYPy(TH{CdcuUiDhx)E}~y*ys`iP(0Z$ zLeYhm*Pbpc-vh{k({Rbb|7$8lHsF|OOAp*TCb~M7jMqkdnE7FAO!GS;qEPHNF#UlQ zfdN9;6nO_vkCxL9c9bA%y{TO9NNoC8JMLwdX1`$pm`~J20C2VnmopN;4|X_y{wm)G z4%}M=sQwmUA`qL%!v@@29G>gt8v{)d)muYm3o>l%X&RM5k#4T8B z(TJf4a89{q{SB{TMW>fR^#+%|J*go1jbYpfuBDg!ivmb1Yo-h zdO;jBzNoGSdd)D=V93}p=fI7C z;me5LrR&S7`XaBqW{<9vpnAgvVxHA%j*pc!l6Xv z4!5Mexqjp7Xfm4-Ze#f-h7{>r4|*(zzkVFDsCth3*(Kjq^&RHw`}Od77}0;DyXx$T z306pzm2rfBDk%WM{n6#DXzUkYYi?j=Ku)2+-8PV6dbd9p;@z3|l0Vn&#CMDxbZ?|b z$O!U($o`t-)vwb&$LZMJkCI82#3rk19|3}zchefru|HA`27{&!#U4$9>4E(z5eT-&0crM zXWcjM+*Kf&S~~?k+KQBw_k`X}=IdO7nI&)T^+A{1>nI8dRy|-77txD-Ef{jv8VIP^ zkRhvK1#7eh<65>M+^a|57c_Jj;8v>hz};S+=YWdy0_1soFr&)nXOxAA1Lh3_aOO1( zd*(l==uJ&YNk$sZ8*z$(90OFOLgn|1rdSZ{Z?Ht^fg4tH=}(N_Qv0m$8JB*J=W$GMR0L@7E-!vH1(&uz$EyyE=L7Z7-2YsyeQ^o|5vDy2#Aa%I z#qVn3#}5^+SKd3Xq+r6>Z``hDm(GO#?P=iP;EyMt zN2pCb{QUf(A?Su{+E1oXZxPfRo;D)Bo(Rby4 zUPAK+^-dwsa!A|z_9sTqY^Y!pzH8cx2H)<3@`&YEv@>wToHegHT-sQk=cfZjmX=Vd zwIqZ+v)F$T?`nAQCzZ}M8f}Kk&$$*CSQrY__tS0+@enoa^HU9%s(&6k|FXhU?UF#T{eh6-}rcB$BxjK-|tMo|qn^Vc)!=z!h z#{V0vc5{t--#c7bNCfhRz(v*ei>6vYc{xWwGVsaOSt}pmp9k{S-YnXQ9Z1#57UKQq z^QJrzdWu&o@1!B?-)5D2fK$%a@=GC4r#xmvEj7+h`lt17QsG`(S|yGP;l(vX`9j}H zjB4qxPnCD|@U+Z_CW<}eq!QD<$$D9r>Qw)kJ5?jDCw&pcnkulpEU+rM^N9o#IeE3Z ziUgJS!1NS|F3P`Ys9QY-zo5l%8#y3-o$Jq4=HVInB3P2I3K74&c)h?i3djZ<2I7yv zkH?3ADC%uUE0uKi8*m*4urJn#TP`3VU~q6yDuGj<35imfot<5(f&qZpFAwdy4&JwY zJbLD~Z-5%dg0MeSzDZMhoVhCh9n6-#*-Pf>I$$%+#eHacc$_+vG?R*w$J}yGd(oWB1AB5 zo#<+Ap+Bj8lZ2`1P0_7x-t7$f6S?qD3JFi$+#X`6HvW~66qm8zwm2uA04z=R3FrF9R{xCQ$4{6c>kNF$B%z$L$ioq6y%Rbx1tR^F(FzstDtgI3=1d6NDjv~&l@GTc``ac=FGcW}Eq>=E`%pAG@ zJ}(5hg!R41@1&?H7S>C;#8I9mmtI#Gncjf0Em<#RylF6Cn5xyp+Ebd4#rSg$BhMa~ z)fpDj@?J{TJi%FCF_$AiA3^!Cyj=aR3>pqe+O+>APKIX{mgJMp<2Gdm9VO3Og3Cim zz`f3E5sIPA3?BY*aWf}r^7iyRL1N7n+^oKO-;bt}jhQ}c^lg1(&$|gjOL;~;-YWuD zhs<~2B22X1{LvN$J+fixRSz-mJo2#8u&2jHJAne0-jj_}DjwpIype;en%$W8_ZnnB zc?<(3!fRaoqZU-%I}9d`UNt87Ia#;rjPX2uYY9=Zr!Jl4Y z-b>fA;=BACOsHEsP&Br;-{rfq>dS+HnB2*gx2*a3+n5C6Cyim|hipk;ds5!6sWxbR z@)!w4i-jKDfw5iW6cfg;+>=f%hBL!L*^W`7I!?Ir!xS|w( zA@`;{TBPxENe;)y#$GP=e{xv!Li^$5R8(lx8$+k>6+5y0*oBS% z+;JbuZ?pvEkQq_9ylYL}{Rl2KeOwbvd*QPpV;+o=yao3iDoCO3ou4%i4SoFII(N&$ zf|%5|Fw|TbuE_xm%dCe+%^ToF@?j|;D?vN6ISYj1Hfpw2YDMJ%8ydG!e`m4<7(We| z@*iVlyKSJP?zvzb+QzRQfkqFgh({>Q83N0op{Yr2ppQO2*eVOf;I|o*ii^t)Qz})B zZbTGjtH7^T=_BhyL-BU-K1+hU)pgCB#P7KXc7FuGAXNQ!-JCbxEY{$edR7eC=k~ok zCn8<)2`vmk?Ta|#I+(W&<7JsjnnP3 zc*J-_Du%JI}3nv`-F9Ab{MDkN>tmNA$z*xPf19(E_?Pq-YX?<@*>~U{%4vg zLISh|ygug0DV|m^(Fu9$Rbc%cR{>Ego)N=u%!LlZ|D9Knh|^UTdTPpJ*B9s^0s~dn zUvDjKZ`kH_-B<081ksT~{G;AuvTrP&UciwUWt((Fw%uIcdW@r_q_kAkzO%jkz0~yU zOl1Z5kH7Noz9?hxOBMCtxAmA{wWS%!20-pzjQ6cj&WmU32b4(Lr^}3IV>pj8w`ub? z%(i*2z=;d6!W={JB1uDEPEJm0Z8glF$r;WE%(89#?-QVS+|+ZY3Qj=^IMtqH^

5 zJM;hX=~_S%5?NB@!B87ull5Jt{rvs?5m?@*E<*XtQV!D?6buVhJI(l9-`CeCR17+u zC7LU&h8L#a&_~)tor#|jEXVac%4%xrE++g8aOY?)9s_|SJdOZ~PeU0bAy~)?Z`Xx3SXLh!>n#lN%42*k9Dk^c=U)guD z7cUoA!d5}{ff$mqDob3q(jcI1rND;Sw(b=Zb75eO-0X7HF8CzKhm?^ntX&<&(!4P3_=?8xqu zXf#1xsox;Q3lcESx?#APe#x>qQ+JaQOX#*;{4uM(&Yg)ix!QchZI$#4@Vn`Vj* zKE^b%f^G_MXoGRpZ$s6$KR^pRfr5C$VRj-k+ z*|)y_`H*8Is5l}Lj0%TG+wS9RI0*=V>iR?;D3XPBHE6bi3XcXmn0viX_iojOJ^kmr zK)xnSP~mTe2+#{Mu!7Uj8&;?5U_d}01)ERgX5_Xa3Opf!<4ZC80lheLBXC1a?3 zlQ%@~PL6A~=Ifxa(Ncc|;S;PQOs2C^~uurZEu&mV~^y5rsOhde*~CmHO7Phg?Y4$Ny1XA6tF;U z8v+aNw=yBcY~ACALQGp}d-}`h`xl{b+|Uk~Mh?y@;IfwW{RWX63^qUOk+^2MNLR&# zAi88v4TOuP!f=V4)T{6<7*z3ifH52wVKtF~(i|_pSS_ zfn#?BLAr~U5ydVJm2c1tTx&rA1^`Od-<_NVO8Ok89CzMz6C4GjUX5dm$xBX2(gN|L z@%Ihhtvay#A92I7LFF8w>LwA9IAuaBAv<0d;GYpTVkg^RG-XE|0{AoZ*1P#5#U}O8SuU_NcJ=8#dy{+@VS>jck z{z!3?Pxur`E1W~$whw#Yw`SUq{mEl;`JCF2Ti9;y4Q*pFESnWLD}5h)G7G7TnJ3ql zD97Y)GaQIAe`d)XOHfTzLv2{Re7%0}EZvk8~g(=5gaA{~GjgF^jIMnsmwgnjL-HGj~qOey3^qa~z z4uI1qvKGT-71t}6DjVFTfPHv{aZVfH=PsjJ{$$g=`()Qh{RbfJCBst!-eOWPSGrOtEEWxb=yP4NkG=2^xtd@AY*)Qz>qkz#Ggx)te;*(Lx-o8T=%J{% zTEWD5!%nLZ2C=lKYHtA(s(X2pr3$H8bEsKE3WbhC!N~(Sz^HENE)g>c}y*%8weWZFM_%3 zLwC1oa02IbZw!XL4=NaMLEePu0mGf|vhF3())~S&MHkdyOn-U-X37*YQa|31=@?aD z!u|F17#XSaH4D?(wTeD)H+{TZR=)IDOW<9y3uN7uti6&BeuHW}>>=m0hi=V$;gy0K z%zyaW_nZc9cB(7VbAy3xqn=}OOA|{|9|IaBjtEIr;pGB@?;&>LqQ?70?WB!{$p0K7 zZoJy5Ib$ztP61|tf{($>zRofM5}9((gI7A$uS~zVZg#O*Y|OMdtVLZutE;QKF`RnI zwCuYDXy@9Hr{IC0H*PATI7A*WNv?+&60Mt_b_#oS>Uf3GH3`%R0bz@HlXf8|a|1%i7 zx@CNpG|qP*!oUzY#v~Y|9&JC@c^the*DmMpHFo$BWGJj zva_?7JoUe1nr}z7PL#{xuLd$t+y32=RO6?`qkW#g1=R6@@@$B5P9B-bj<;0hUWbiv zT_pSI!uffvnFeLE$+a%|*8wYLJoIYzdLqY(=ruWDpXougr8MR3S(P|w+qKMHjUz^7@1 zM)3_Nrey`&odrRk;)?P#-~;?8M$Puu+d1p$?3+(F5H{leEx+#@yi-q0%heT^_=MBM zJv1Rv`TNo1gWZ+$MQ0T0Ju}deZ=$vKcHBBluzr> zH{#;rUirjGtlyzR>rs;W>W2RSjYRjtx6suB$ikg7Nwc++iiO*k_o7m#?3h)oqPS4O zq};vn1P5j>-q7*M0~Px44D(#R!rpBe@O&H&eVMnD1L@K^0}J0~@h%uPQvIo+&+WMx zSkJO(>KnW(r0q!zRME3h;*kmyVC@^xOZPMt)xw;dx8uKb?dqjTNlAhAHmzmw6CX|! z^_Z!we$ho@X=9^N49i{s<7AG)olKmZ6m5u8!0>(hFfdKD?FEn{Fr^a+j;~((Ud8oq+aKVDU%}Ql@o|D>2d_6-kc8n}BvD^Qs*}U05IGXa9(C!fqnvU^2E_^Q zIQ6NkCYfa;b5QOC+pC|u({x@+`* zJMg?8UUtnbQm%l(UQwZbKczQvo(MQlwZ8&|%+@+3hKj%VguncNtN-gYKKp!l(@p<} z8y~?q!^GocTSaAMptS=jAZOyVI4kL(%P1=9Uh9a7AqHBLFjLtjP2|Xv!N5pK&=2N| z%~tB_=@oe6D>r!s44P-sfKnmgB@-~I9H80Y0};=peAN-sR8!Al1ux&xKE#3@MBjh7 zfJ%rDfw%yT9l#KzE&krMWu#SX@agdI&|a^`DdgB&99YerJC4%h=z*eSdB`0194M8pjXV=OCPP~P@tOOju?DG7y*rO%ke!~2Qf++V`1G)q6Ig1GId}Fk;u0qpw?5USACDu!27^- zX|+A!d$s?XTP_#3FaRz=_K)qt{>5f}WaNn%Ufw@V6V1a$zYsCkFfb6aGFA3E_ZH48 zWA=XjR9ocr_4Va8h3L-~u_ivwUgN$wQ3a?4)bAVnf=!m&4?`Vka& ze5if>P;rh=7#iMC0Slkm(3F>#pGeVhbSyh8tOyS$U#s6U7(+NPzjkuU)JU=4oa;jL zCa#QBJ7$lmiKHdZJ$Sq*I{LP1$~`Rj&6}aAO(N9RQ$r(XJ*!nUV3(u*x6jje5{%hm z+;3OYH!S`E#D@3(4lyUBMsVmoAA%9lOXQ2wL)W&)-47SDbCF2H6U|`%z~gqWz(JQx z8YpWEL*EDXh-Tk~JVd_EOm@$zH}CqxhFLBKdhh*dLelUnB$JPI0?$kAV+AUO(xhbI$)5<94}z0;&A zt@i0G$`(J>`_vJR2%Z`O<Tut|u5#P@)f0>)74_Wzj7YVXE?rY~vwfOW)vfvBx5x9UF>pV21h4}_=m8P^BWiUL zhhW6p$+lSMW+QiZWt>7_BhF%G9F1lWJv-iV7(ZOdh(kJ3ZLD_Be<` zkZ6ziDJsTxn}{F}Xe;QF+S;!iu(h%PeOq`~ZHX~g26_r>bz904h?~^6QZ!c}bC z>P1(#0Eh}}Vr42o^~C;Ez1kl?$>Y2SB8;wm^%vIn@DLU?r}~NkgPm>5OG&7&H(qjx z6R%tL5K{QEMUnL8DsAZaW%1Ej;H8(yyMQKs9~%?;&Aah53oSAsE9WI!)3>ssp^KFb zWxF314Uw#nFsO+Y1G4baWw3~~ah#KcREwq!kOXl&>+=N2@x{IkGzXhl;oc_%mSJMiZgt>39c3cbI-V?jty;b4YVOXa_?nCsYiRkrgN%FQ1votQ?p_0DV5%i{Kr?ZbB9{#a6V$#ME z%q!ei5-4)38M@xog;+~F;@L+JHv+AeSNA>#ZN!FG0x}r zkHWBV)~cT0(*4VFAp|(_ULhb$kXxdFhWKU=ZcRz((>>t60-ARA?M{kT9N@Uzu?W_W@xssBRVv(&dT{OfI6*!ZSPCCLw(7S5xMay zyX-e~?ze0`nS;o&aqFU5OX$S8dpccGx%MFnSZT?_Oug4DR?$cf=KOY zWTB&eQ=A8Mf}GKD;1G?7{v_fK(SF+tl#|KJD;hhtO{AhC(;0Bs44O;(;Okc>_)JsR z>the*Uuw~fDtObTzrjC?mSDn}fhVrU>_hjmjks3XGsRdYArvl$e;lxc%ELQfUh%jM zFJDpD%mIEQFF!w{cD06`-)x%e6zJs4yN9w8k5)Hq4A^#Y&18p@vZ3JN0{ti0wmFd9 zHm6@^qGFy6;t^~LLi>$}PI~PztD)&4C{1nc2l=(ebuX$ zh&|o+3zu^XYh6_#fr0$VKSUh|OZ1@c5!4z@*MYgG>=)Dbvb0aSphSNkNtEG}AR6QF zMR$%AE1D&kEOrhk3|yPGhcI1_>gZ3r3td0cL`8rN$i!7BP_}>KBTi?JFyEqr)SSewZ4GYkh5~uq@G306^qCZx$wh;@GAZay!6znp*dGM6 zBrv1$T`OF?N8X>#n^CZ^L=N2Z5~QZcTN>{n;;QyMC_OBEkU2y=Had*?(RPL zBb+B6)Fo}vxE&XSf-~;iT)h0PY73G!7tl$)#k!V6rRBXVTH(?udt4P7H@ZE0{dDuM zFfwq>iyquvGAXg+=7oB%V9fUJ1*9NlMxm_@X#D#o>1*h_A_fX~nVvOxKG@kY)l^Rr zY@eO0P?vuNQZ)4^Pp%DL=DgcE7gDX)`avbJjH!<>{OFNt%!oiDl~0LR?U=ByC~(Tk zdz}LOkWpyIce$QlFUge4;D?M7@j7&J?QTWc=x9eXL=3o+A1XG$XNE9b<6$S2U%q@9 zv@466r$1-N?@>K9jI{+t!Edpa_drwc^MJZxR3gI48C-qT9m4FC1;zMjwy4Dp&OY)~ zh=e+;MJb2FN525;PK;-Y zM!^IrGoSe_jAD_-{HnF7`eaT$1b6o%4cr%_&E=M3*OHoMgkJB&uNgJvT(>zb~F+L`Z1VG<3x)5 zQn*1R(-D}c=#*7b;=t1-FiCmiQE6E621V@Ts86c2L(zHHBx}bx8F8-iW;U=)f+}jt&7l`0?N~)Deqa_qR zziHpV2M@OgR5~I?zom)hdndmSz#(e4S^Jb=kPec8$nG^cI_vp< zKpEtn1iaRS3$p>~={Nu#Oib8sfLKjg-lU33EVptV)TjUfeN6$UoZO9U?ULq){6?>_ z%#U77;sP}h7NX{5&%)@u4Ec$~T88Ijm%z3gkj0bH30j(nlCdCq0ZL18Q%RtmjTPkD zD}qgM&i#=Y@)`M7y8l)!X9=6+WN8c}K}E({x8}Mqd371>_d7c`SyP zDe=)BLyFqk(T!a@^q%Vy6Aiy~5tifyk!b53SrDiDB!1655VZ^^wSpnLGZ z#?>i>(_k?xH@B1Y1E2}4@x;}-!$gGccMljaNknpB4#^Qf044%e41Dy>hXe~O78Iw_ z`G*ke*vx|3d%t@_LnYdfBrn^s_f&Ma{YAMJ3WaGk7QHwrK{XBefL$= zuh$?M`oVjM&Db-eDq2 zw;385QKE0?tI~Qqp5e7~H%9oIX|y z*Qr8sVlO~9urU(Z;VSZDmIr?S4_QMd7kym%dsX$Ib&!KRtvCP~Z{=;x8 z9XJWm3`1_P02sv$_>xhK&Q_Me+}}(=T3fuNzcIj&r}Kw^OJT2Iu>Rbkx?{* z?xmRRnQoP>1`@mW z*xMbq>kzdM)cQ2@CVIh;xlNKtR0=@2rr#tcMrbjkHE_3&sw`h)O+|SlpV2!3y%oDLf9a=xI&9;AyCZD;x(0=DR^k!%Q*10|a@bE|KbZg@GlapIq5^mX`6_6)a4@hYS;>BEhpIH=-8*wpG&Y{u`HC zJ6*5Eg(!oX)lJsDEE-9_Q3W+jSmr%6JsFU}fTMf%S5Il8xG0-KSamqVa6zWC&La|Z z4{qsX;tEGD_Wk7J9 zjcTqfp95u$B`{@OVN@yUeI*PBmof$i1msfn#Vl8rKeF3Q4o)n6%(@u*Q0GieqAQm_rV z{nZF*4;2-E@Cmc(Y>-WOHXC{`!NcF(i6I1*%p|Au5fJ=HBiS(EQYyl|o4^wAk)LJX zlzFs=mtWFRgES&LCT*d{c2bC%Ud8U)$Bc}C^CETXOG?n@!4g(3PcCl75+Y*}lnqky zdZKW>h}~Ba)D}~gmZ$TLOM#3}3jHlx9}5cZ+KP*$&BhaxfO5uh6+nRsxvbt88XkT# z`~9P2nWpLxG7W?i!9&CoKlMz*fEA+!h;)Z7#qpmkRjDGa2XF?1tHo4TWUcK&GH#dBk5`9RH7$0$U4o&|@fz|8R zkT+R>C(#ZEkb?5ZZ&3bR{Fz`Fc{CFNf5$&e+6iJ)ackXAKlh&l4{!Cihxn>X0WN{m zm~}{9;iXo-DLNEy0#E?Kar++b?y;J>QZK$&0es{|=2xI{q(aU+qy zSW%E3m^hXq!Pk3}E(11Ilh=;~ik%y=@jy1PM7sg(rLtC$-p6=$t#+F?vq2OFA0%uP zqdA^|t3k^;pMK7SEK{_=D9>TMj@Mc2194*Oo;JI3*EuK~(@@^{=zYe$<3ND*HwmvP z6WA2AoAA@sZcs*cyb{S$&4Ptx zS=;qH`2`ufjGh+e@kT!QLv(l3A1!*4T~}@Ey1W^gMG@&j#$W z)6*Z-Gea>=?_bV6a^QsdA27TY$Ug*S*JtUwrxIkL6__gCVZibH$do zxFH@9p(1{AU*ug1mxXPAo?H8mA3wD7nb!r$o+e*a3Ii`w)qZ6F!V?`RJc$8jm02W_ zK)G+fl`O)NhMqP#Go~cu8Nq*r^$>{aq$@#($phxLD>tS$;MYOC_AYHojHg;46|9R? z)pabpc0iLKOUA?^!>BQM)HzYS>OW^wkWu6Kdhq*fFc)sw z#5@EdG%hx-LINjYFHTkfz8)#C1v>H{ii@K_AWm1c0$?-)TC!Jy9&H2QTl>o1!92Wo z=?hWyDwv-{c7(^9F&AM1g$C#CH%VYDn&kO%2wq9Wj;8ZOp4(DPKYMkS$YECk3aY&> zlls;xQAg#CFVf3gYusCb`D`AZ-~D-Aa!yoNEbh4BHT@93$L$ZgoP1;i(N)#&c0pIM zFMT;&%OhLcEOvB{4@}&IrCL`CJmiSiHSVbXTo=mev~QvABPMe)XCWf-GpKJ%RSqXP zRr4=jf2<4;RF&Atv;D_{BwRmzpIBgJ<|ML%4?A!S_{wYyFQQftK!`3us)5F(e)XglFjJu_WQ38mpZ9Nypwfe#eeKBb0D*em)yjBstb73&q~7VqpAUu0o5# zyd(Vcy~oYa4^d0IWO>6{PXaPHrGXg7N5tz0Ha>u|l)jy=4~aQYbKMB;3Pm6W|w?UAA(p;M+RvGwiz#9OG&#<`no3)+^Pq)_UzfrBFFh} zZ4m?ESQI=r1nn90!W%=*^k5&(yh^32fBiCpP};`DUw6c|}7tR7taJv?t#K3hLo0^VJvsjJ@8HFlFO@R=M zyEvWzq%XBLZq<*M*}KCN)~1R;RLlY8vYj~$7#F@CGiDrE*Li>p&{tei5|ELZh~|ce zM=e$6XKv6|%qg8*(^c|KBFbgiZ>HW%ELD7zcAX`>>Ipf$76%>?pOx&GuRPaKj^i`e za<5g)%L-trTMkJ2MyfJtCbIo_(fBg<>EF@55c#$ftTj1&^@aupB!LSG^IB{V!abJK z&j8#vghmWP>I5Kkga5SwS1*y08mIpgc~1Dp<${Ra>}5sZu%VBr2!D9e_o6b;31r_r zg0MYs26p)yq^}$>DQ zFK&_UZB2tauFTTF*|VE9lh38GQk4GAVMp?kOmas49d)(ZeF@3?OBNqU<9}{+WO>qS z!0obqV-#Em6?)9`a_U%Wm?SZ5MtO#h%qdu~j&yS>OT#O3_9%e6bTj3s*ru^~7>!9w zk>h^0yeF{NopGfV0~*YO0u?g&{L2#{5E08{u{SgUJx{&TyYy45D3pSOu3fdoBhQK0D%f`105`hqumu|crS(W z<$+n_UV7AGlx_2B>HNuG0K^{}4C&HpJAO z*VxdP%{GGpOPHO1fjBwEs2D}m;n`tv?0vGkU$$=k1q&ps$gLrpb6%cg5S@P`biaj) zoCS+5gV>D=m=!@@Ca?Ixs2+juL`>JN>`9q8@J?E zWHpTUPJhWgLOyq_tIFoz-17Gt6d7*0n>pO({qC-%{8_=tGbXXB=lu#5KY6=0LsXw} z7Q=Yry6KTCQZv8%t=A^ZH1lsMx=8yuaPg(&X9#hOy&3FH_aAfKV-e?^z9k~p^yLZ@hB}270E9n+p#OoIp|$ZU=xAMTv1oBPRL?(TX14bmo4) za6?ay;U-{BT%4^E=B{d?d)`??);3&-r>v$KcXNThTMX_WXt9!pce!F+t;Jxd@-p&} zO7@r(Z6b|XYqijvdvE2;?xWYdPAA2aVxs+=pA!x5*_M2MJPd(58DQgDRJtfzj6$F1xoj#TN%C`skn=I#SCuCL9V!`p zds}((31szB8L#2zcI@*>9_T1j zdp}-H;q-mxa89O)1cFn1svilfi#a$vZnnMGDpI|B6k2}bK)QX5-EL(aav_z?kQKf z`D)l~lRLIS=nf$gDIyl*DTa;9!`rodloVGy$*soHsG8*kmZM|Unx}n(BkJAASz0Nr z)>6Xhs_q90spPykc_fr*jT`v!a^4kWmanoC>kXtlQR)-yOF?gv%*h=CH@=YdrN8-d zU0=eth(RLkp21Ct6V^!Evrju^3QU-U0uLL@=`zhdN!P%kvQv;I?>p}kXE+Xf6?i-1^> z4y;=F4mTPzsQLx~eXs7+)nF9AioR{|Tmt!D48EX5*e;6fDR_@!QcC6(XnT=hUt1h4 zq~^qw(OQa_vtV>2B{g+A>U6it;V5Y>Pc39t?DX#O`~Ie&BG96?m~bx8p#=!PkW~bUqk*BtoZT~7Zg#pN%O988lssZ21XTe6M>pJ4Ld4P z_evv!cqPRtRpZ5YMh6UQY#|FeS{K=PMGBu}_omWznp&y<G%g$XJ|)b5@4CMlaFd_DFuwdD+AlOST!9Zw$|=!UZofLG@!TwP zxF>c421$B|+a`^Qag(5o zvQRN>5H^0DLN4*7Tu&}aOIQWs(EfbM=H-W3kRqvjtmh?WS~)9P3=4Hf0sAQepfnC9 zpir-KaECnCey6Z%Dm~;reNE61@vDvxc2d9sN5(LUED#o$L#_>E-cmlp;*h>$~r*JPHM^6cV z=ZHXjT0Av+QUgM!SI4D#-=F&#**{&@1&t6uVJNbWLDtCu^73+e@A!cFKj$L}%oj}e zMObzz%sO7UfTp+a>%R*`A9FrQLr+5q?<) zZ@_PAqXvl9PIYyv&>!;Nsc;#bdM*a^!Kki3-rJB}v!K)UW`0A1m`;PI+r;j9zcK=( zDb=Rdg15jI%lf#ms#s$so)4gu1DkA49*9dPkPf?Mz+krRCzN~Uiz2i)P8eWw)EPkR zZ$R+`G|dAzts-(=Mn*9S6t^rGznM6Td#q}Zhl{LaShYA(zAcxi&Dhjw{+_51j(7R? z_j$&s>>z68)eMg56U&cWa#!3I0uSAkst&bvV#$TNuM1a#jMguBv$J=PKM=0hzM@R+ zMUlfaF-3rHZQW|vso&w9lbJoWRi`0d~PR*N(C0oFs$0_q}0_Hj}2@M8(rxg3-N`7}G;VT0$? z2dLd-O)^btMfLR&R|#QKT(t|KA`V-E-;_l=8dJpm> zhzG^i&C=LVZ9ke9~d$Mw?;xQ9pz}B!7z03NWu&5pfhS#%BmI< z{jp9qIyWy-`JXyl3TjLL6t2wiV!`#Aez<(d{rNEPiZ6g2~|6X~HiS)I_qB2%gw8^q-IV5dy#zJKE5sY_Sl+9P&%Q=-NN$ApsWEV z>{;*;@9HqZC+<)jGAKmh+dUnuvxL;vF`f|Y_vPbg zV7fMwp3v$>U;7X|c~0oSzUxAiQ{EV<3*A2iETF#P>ZTw}$*TOqaJB&;lAK6z!y#^76-0wj+7W zMs3CM0s|1aUS*n@wOZSX?>wA9;T_Hrg)=~&&(3}8UP@6MK&Vu0>6F*)2mMvKDpbOhV63E0{ONiAY*aO}}B{`R4154?0BTG@&^ z$$@5OxE2$-^^R9Mc?WxO-6H@%n0`?zmD24z-HZ*okUtCd;@`c?O1;sp()p9^C#$X1 zP@cy0%;j9d<22CtJqIdG#mUFgJo^m|?tnC4GBp*7CeLFYaN8R4*X=aOL(qOZ=gYDC z$m;15VzTfTk&~9c7rug7P^65SDCmL4G1NR87 zvw$$Pd!q*2xFQf~GqiGa+ph>xAJ6yB;sMg|@)msd_2@0R@4J1^KJLa$shyfnX(_Fg z3`_$97Q-L!y_%ZQ$f!Nt*nPySEx~-?Y_6agea$*Q^4QZO^+(c#bI$2J}2uRe9uxyP-Wj|8P+6bVEGUEBO61N}~V>W9r52EoErB8stsH#oYl_QzA+G zNVKTlwyUX#N&~bsBLJ%gnw%W&I00~ASn*F3mA1A-KfrmXrC6=1}FESXWmq*JS)(lHu;8s*t z=Apj0>b1?XpB$w(&6Xq>dcO}14Q(!-KGUyv-zmTherZp9m!c(05;PL=$uj+itYbB0 zq5GZ9?1~SaMYiG|LF`jrwp9OzeGotfo(MYFxCA#a80PJf?I$#Qjxl1~4JK0JS6~MW zuML5oWp98zN09qS&`KCxeJqwY{xP=QWiWR5yW}m*!S(m^FXl7%?LfuP?hMi=H z>|nZI_P=(kH9st~L80)-U{eP+EmONh=H)hq`w%6xHW2V7iR46N!n>9rx+`SDa+ zaIw}GZ*MThu4rx1-`i4SKK;NkeOPVy+;y|IuVmYVIQ)Ky8$)GPm5skQJ$bdWz5-qWMLm#VFL*t{5) zR}B+_)YUCMN4Go&OW%#@7nl96;=|T_^(K|}$qFkiK~)YKPciy>FePWEAr`xM z^@+B2NIbFL`RL>$0$!8Jwy^&g&Sav5Fer%WqLG{V7Hmz%Vm(>Iw|b(-VpRU&udqv& zmX`gTMn6l8wY9Znq%)w{tbQ9l4q5w=vV(9?#D5JH@l96!u^>L-NDC`~uE_!p27@fs zB0kUig}b8l0APf7|2N4h{@2_kPy7Q+iItimz={*QMp=&e{2URTmrtI^k?)A{xWP#W z>&+j7b=NPyo(fs+MFq1Z_+xPK!AE6v%lGI-$dm#T@JO6g=D7+!=%_Gko(9UId&yYT zpI{doea=WqI+|fC@%JKBui)!{=ih8;39oos$LU~TrFF1zd2`czcvoP_E*oFt=UB<@ zfic}817>m-N~>ZX&*LwH4do*>QI^S~bM!-%**1|{n3Ovn<+HBQp~mwj_!|1DsljW) z+214)O1SI-fihi=m5+|BgVYmW1O@I_zMMZUcq6kzFF!7GH1Fxjge2i@#Wm5mQQ%Z zK`K#CW2gqSP5J!ZyivC9Rl0L~nXt43;uEltLcGvoevUvQ*mm?fPIrYRKIgyNic=}pz7{X9272YamXbmC8bBu2(;Iq z0pP}qnywRYgJt^Bck?tiR$JW=9#qN9_V#^p3W^S!fxIDN*Pq-4kwy(#t4HUKSf55* z>t80e5kL@M#W$2GE??>+!hG1k-A`c*@CEK(C&o|hgql?NdddANNo)KAs?)>sl27DA ziT?sEhxW7yap5nK6$39-3JUfj!3^Ql8|Q@FNE+GLeBPpBY#`i~uJT<_l!BXEU-umX z^Q6V*l%Pu(mbnx6$_P0sm%mq*FrS*6j(N;R*}lUgxzD(w6N2Qgdh&kv!I$(P*6CZB zn`N7L6t>@oQX|Mo)`M!*7CNw?GHm+EMr|t#0C`#Ur~#hAc#9@ZX(97?DCRBuuNdgG zEblQC1Ernb=%TajAVMMBcPtTtMtiH1({p8qhwe*@izU{4n9A<6{Lrjw97<33oNT+lwNJ)ou zNjDT2jSRG{|p zr^(49a}<<=09(YU$TJYJ=uQh+TJce?KmMgKa(fDybhmtea0hG-GnM!ScWaUpum z?Jz)sJYWc^f=6BYQeb#%loqD0QqOWMT=MLr&tvm+oZpt&ej2#S115g~h>rSRQWbd^bO` zLbgiHzl^x(q0JMmVZo4jsGRKjzFa-xG4~c0IcC0;QJ2}UBgJoYXKsZ!^$nvzbTA45 z^v}|+0ewg}2uEiZaDq{MiR7-UUV@16BPluHIcWT8i7Op~gpuNa=;K~EIl#Xt@wmXss-}h8*Pklz~DI4&>v`6_`)wBFl>ifbiBb#9t_ae z-VrW-AE=$h+E3P$F?uQK>IXLn<{Qdrce?;vi14YmMUu^ zvh=I5_^tFgx>FCqrNi)EcZD|tUJ3m(r}4+9{S&w=LnWFDOEvyE!+k@zLRBfS=8G_; zoD+BA!)SG{u(_D{ezgJ0ozBV%)H^p;Uf3OaSmBMFQ=|I>-)+wkey_b$py{S}F;778E$m0YvDs-}HgfKUsl zY(N8=r-f;f=Y+AoqXHcP8C#WoH~w^&mxvm89RsC1eW0Pa|Fj*4@L!QRaH0T+^c9XW zS?~IegDVxlJ3I0a{Fi(jRl-5pTpUjI0m=rQfkAB6MT8%XCaY|~xon5pDC?-~^g#TV8e8W?yBR90Zs{(wB4l&=n(keBYpU+@2h$HsRA zKpr!{3MT=MjAE*DF+mOr8T*Ij8qh5MDee`x+`4cBpVK%QH@i_3S2cN3;63073H|fI zN~eLuH_sdn*;sBHTVjY5?@E2Dc z$cudfS;4cmNcLZk9NxbD&sW|o2mmRsB60?##W!96VNntuTVQ!tkp|T7?ED-Mg$;^B zAa3Si1o;OT+(tu3iE!B=??*vd!JIY+*3p4U=~Dk}FM!^)1|L4W`6qk`5|#KQOA*wT zEMAK;AhDhdEgFI0sXLR90-%EYuiUE4$1vUipeC6&04t+VcI4!=UA1nQqZ`C1m6CzL zG|cnEP0)|*a#^C%rL=2jhpkwvl0hwE=kMT`GiKjIV16+8@&m^Q;LG9v%>)wgj<&2B z#~=35oI?|bJZrQNxEL9;=f`IO9~{Ympj-qdjo&MvAcN;0GMm5eqXoV!fA{!0ZbAU% zW!twVg)LUuuzVO9Sfu@@gVcFq0GN{rsPAhzOcag)zgHLs;>%(}Lf*t!fe5?ie4w)h zJk1Ibkda6EBZ1)HOpx+Xtl;f!433XWKN31nEV^-*)_C|R!(p_2ct5V#p-pT8ch7gy zc>Xj50L5S^dk?Q}6QiJbrIUn)D}Zi|1l>{uYqf-s#B`Zv_5p&bDuhCi^E|iLmrr%9m}I=3fLP}s|mEcchzUw zF3KcU`KdHG$H#~Ji045cb5_!z8gp-61FLiprS}zfiPm&fZ?P0rDrkiv< zk`Wihm5!xZiY^aLGfK1k3faHY+|5PUBv!Povod;9jkB4E8urnhV*1Y^zV;#{t zCp9FUkZXDAwELL^sSWC2`6iU|n}ZDekd?zA_Y-fBN{VeDx+E~@lat1N_0D1O~ zoa^A;4}If~&)ioV<~b9W+K6?wEB`N08DtxnG(S-Qt%gDI-x!F~ zhNCG0%W0D=nzIB#7kwl*nf$+lJO9J5s;Gd5bvpk3#PQPrMHj4?g3jnxF|4IjMshd) zm(1{Ah&!(-l2=~8iML+{q7%3y5wyue{pth)vyniX9SayL|0jXaacAyz%Ml}(JY;u2 z%CdZw=ooPO9;A1-+GS)`A`{YQot<&|tKHAP7EVdAm-01Cgi#qc?xiqcz7PcXpsK4XPw$qvr4E=7ErDXE)>-1)GBo=C8aQhppOi13S{U zK&_U~NW>7MOKtJ9d4H_J%8N}f`EXkU6ATp1I5&Y*;!JfKKMeJ|1IMSH^Vp--QH$tT zwYs!q29_j1fIGV>g#s78ERkN^<3vF@6XgMhs^dqy4~0T^?JLC#PxEX&jH9v*B$?f? zgr4di2TX_Y+h7`mNTZ=^sa6s*o1;e}RTo|y0Cg{D0Al$cy8Rat53K$IH?U(4;$Jb@mKsG>an&CO154LdK`mB1#)8 z--3l0keIl_f2i^a_Jmz&^iT8a95$WQd-GSfV6+OqOwiD2Me3P=JLw4Wxdei*f2v6T zr|tr=5Xd?A?*tCx7votMszsw)2+vpNsxj*(?7(I)Rblly!8i!vQBSSG(NN`c`ztrT zWb;LR|DUs3GWTRMqz|<77QSoU;;0J6jOe zY-}9^BRH?2EH=?2ipLcwhcecOPg3c>zk7-JkQ4OFP{zSFz3g(>v4zT-YNYoAgR+7$ zBh%Llfs)I+J+j@yiqo}&Kd*VtjH`C>3ox3heG7Vr^E;bVqvJLCO*2z=PQQ|U@+&sc zA`#>)=<<1lHnFImxtY6Q%yFvX4QH%Sa*>Y`No|uPK)BUB_HH(eXh0U`Qv!v5m+HIX z2$kI47-zX`cLs;jWJ?5hA=16hIrjAIZW~aP@h@PyI`9r8^G@S+tmUgSLm!@K{72o? zu6QZCy&<8?u3lXfhc2Q_T5P9wQ62qpjvw(0)Fumghp~^3xh*Sl6D%3cI%uKJ&UY#r zX(Iz)r13ao{?=WCL5#lZClh*IAsTTrC7@Xv>Y>C2if7RDR+ zPE@WN&4w{UkM_qbX9qB;u_O;otLr0zeii0b4X4&kw|Y~-ixV0o5{24~8olb+9f5K% zXm~*%JRDtR)eVUU@*e1&0-lN0pW(F5#0iPmATK)+uomFg3}0Yk`&}|m%>Gnn`#V1*w!L*`{vrspj4?KO#Px>`N_|7g1>2h7<*dy1ZbO>Ibr2RfL4~ z;;X&lQ}LXyB#e3z>}^g@YfUktn{4&=<1#lLMRlp5qgIPuJ4daNh9#pCkde`w!l3CO z4Dg%4<~?&y$D1k%2JTcP=J)6vnAk6#y7X9WM3}pO6AY!zW3cVb1MGa^X%3)kyB%1V zzZUr@l<vaa6E>-f>Z7jV;sNgTKL!_bB`RHRCC3}!fmTsSEBi& zVDe2tzNrmn8C`n4udTU!$G9y!Nz8LTL8ZCzCUH{Uu&-v;34JZR-6lB>#mA6B_ZaA8 z+vG*N#jKhwlMxgy${>;yae!}SRu*sNXH!-4wH7Qqx%M%_G5OdL^wR^=>L&+o@%>`I#N=6o?`QOV*R$wuqw@k@Y8^M(X>3o=8LhH1V3qohmC2_)2aks_I$>!&J45q zDaKWBnV<4Yki*S_y7q+!7-gJ|k_Se>^#a34d1Jl-3e;8aB6PyzZbehf?|JXq@5EUd zFN(uO($6k;uqUCc@Td!$;YRNf6{X^nq%7&v=elXh1um~=XjwVP(vHRk7e3%_4Rk1# z!y*Q=1g;W)bR*f0cvR$hWsElRy7`vp=$2SVmd`xw=1yX);FA2_)?|x}_-lokLDY){ zQr|2_a}eI2kV2i&a}PGFxyJ1=BOx%#qwHKlfr5Omt#@j&NRCHez{>Hx{Tny(&lsOE zxi>)ljJJwP=FyhgJ^)877NJW<3CyZaZ_hqidKM9}hwoA@E@*I($q_pUf8=f-dqn~n zKJo{-vx93O<{>uDf-?FMr$16`y|?l+vK-q)c;vgTE2Yd;om-FoTGXB2rOqjgq>r8o z(104V=lu8|1*@tIn8Mv%nHbA$mUn8eS_M0F_lX5oR+lNW$CSshuTt0+{ruf}v!9!_ z9xuIFbE{yk*lth|&dKj}Qym;#?YI&&71mDi)M=~?(pm`2se_tENv)q%_Sg4q;DDu7 z!}`W`WgN{H{|wvscfjB+H}=OyAqkc0vQEiL%NNG_(JdRPs>?#sFE|F2yes&>TQ`~b zFmt)Mc8U-`OFe4(xUwN@P4ZUNtAgz!KyUaIfA0Xz9I>5!Bz);FgYpGP-?c9u@luH31%lf=_q*%_j168T&CmXybsMI3I6VWCwY_XDzT zW%H%MRR5FyWeC+OZVH{ZA&V4qOr#BdN>s{V5i(flviRV;b7MP=NOW@H23dLQ9uA@( z=SySLtK)X(_WZ>6+Ep8nOy}!7_t0>y-kuFHPeWa)`aq-T?LHm8f4{l!sb4^oh zE5lQ*$rU#dM?>DQpkJB#vC=YOxm;I?lif%ucH0L-6JE-d>x#i!n5~>0VTnLq+ep6| zZ!152d1cHW?vQeqU#aA3#tIqHRGP#rwF%FFD+ox!Nq*vx)|L* zg;nGv+Pi$qyE#W!m@xxY7I4ma%Kc$UxZZ@D2*Eh3PwQi!qme|7dKnP{m;sq(niuqP z4c_t!V<$TLxbJDSW&l4i%UHYyp{Z&(hhP ze%?(d6_2d3@;16>qD*2YfqaeGwfZ`f*0!W@-qp=@!)@o=NbHU?FbgmqsWb6i_9R6I zXKfhoF`RVh8QPgvo;**uv!Xlkqz+Ptb#>j*=sKD0Pg@?Lb$9f%VJyW3IP;NI*P+-^UL#~-gxOE znOM%eP4Rm93>+W4ay6d*=cv?4C&59aO?+yZgm&o+l|uAHeN5@A7Et}a?Vz9-Y(i6S z#9*lDI(ovxv>~++rgw6_QOFv4As^RxUM079pgEofpy_!19b#H^cYy z=zG*peD&vA-PvEClig*fQg@?iv;A^vO+^&jIW_@ZX5J zep*wL9&A_vmy}M_Yp-^A0h_zcBOM!Xw8c5>8`?quCKk{;uv$zWJC6dK;pVy{v?cUmqk$`jDj(!8Ti4!I!Wr#3%BU zmS0}3j*^hPdPS#9QC1Mn7ao1!)X{Nu6+E!5AMLqKGr!q_M5|vG^vpZb6hK6Nrb$J?5D@Awsrn7W@;bCE`#Xr8IT^O|i*THE?1Disp z;yyZn!%2oOI~;)a%mjP;76QZ!#}5Uw@PoduKB>16MQW@Jqu0y>9fXmro$}OjMPV{~ zWhZt~C(m`?)6g?&vfuKVF*kJcEIYG?vYunZjU?duce_2qa2F-y(&)!)T2IE<`w1^^ zacI71%8;Q6Cf8oY-j9Ah@_DsG_iXrS85`_Hlpuk3kf!0{1Fx2X%VMJB%Y4IjNWd^c zSbcXri}nc3=>nNlTNNGtvC=D?#AD6Y{Qb)f+U~FxAq_v$zw>y;Y~I=wgY8on(n^tk z7%C7O=Cqj1ha_=*kgi8ioq@~2KhoXm8W|wbIVmt&GbY1TAVkZ*D56-z>srq}l5F(8 zSv)QBo}_ssu(7rZ`5DM|60uvL!IG`eM zmS8~?5Tpj14bve6d5!h{wzfH?S>J!Tkc5!d4YOsRmb-=2FAF)}w_GHy+^lUnMuh5D zu<1lZ1b}?DBXrs=wv_d@>%bI)z_i#m^+4d@j^X~$^gd>Agm&Zy)o<)%|4 zbpZm0<{~TNC--8Ub!x%`8ChA`$1Z9b= z^9O8UcJQf3K6dSU9Tt$jh%e_CB0x}QlzL#%(h-_0LIsRyfR6=efepBSeW&x4UwB|I z?MiCABJ+DHUB8HQf4gZ{v)&A;O^9v97Y8*zSPRsYyCXPoWdesslAFUezVVk>M#4SG zEcJ*qffP-dn!Z+zq4)JsO)LWYae`OT)!VPl&d%C|TQ_(MHwl^_0(qxg{<_5sfH$2V zoFm;ov|gt+3hY{~^Y6p2)i1m`lD*BmhvajIR%M$wY-m?sOb@QUuo-exP|xidq_Ie< z=oxIciLJ6oq>(G_gZDNP8HG zIef(rN(d}JVWFeU@wtNO7HEIIE7eEUsU(|@B9^;)Oa<61;VFd;1kz=$DvC%bK4$gN zIsLCx!g@W*o&u@&r#2^pFo|o~UJM}glXw;FPQQB4Y^SK~!mmn9Myq$wdSu!?MxjfulA@Tam9{UsO z5|zja`lmwUBx2M)&ngWw;=vyRBqYA+;e__=ee%WsC|)C zKNINhw44~&P4K)tf0yw|?#jyuziCv<0%n!{H9TZyLOB<(8@-sm-jB7oosrsjY%V>+r2o+fvFMVs9z#)jKFgh6sSri|*TO)hg<@jt&u zlD8@w4ST9&#q2)1aL`+?yq7G%RP3~Rf)do;Q*_2dO;{FNMfPKsR$og}+lR^*+d^6W zLd(9_eN=z+g0FGCn`k*TxH>i6Ypi*94|9PKsF(ls03{&JrBRW@S_%cFuLTpxQ-=h| z$H?{uwKtB_5OZ0o`^qP_L%QwuR=*une!b$+JS4N)m8&Q!yWkFW6SG@|nJ{qbgnP1} zTS5|=HizS8^cg&Loas;6kv@mDT1WA#ZV$OORqLf{UBm88s)Sq9SXDA!wx{mLk|ASK zUE_8c@MwY_h_|yppWM;7d-RbHb!@o%y#^C72O)FV13|PzGGBBuBw~r^BCDj8Hj0d+(;{5ES=t|PgMs|Wudu}B=z}rv# z!-)?Y)S0LH+V^{Q?HZr#Gsdo1q?H}~UaO?f7%bqo#PFwssbNhye!Jkpj62m2vePCS zCOcM9S9SO2LW>?Zeye2Q=SSOxKZ~L-Yft}{%p#-trH%{xTr9{LI@L ztbdthXDLeqP%B4{F2D?G%AVO#Su1^uv(aB!d>evZ4sv44!4}d%kM88HNLKg&Uf>4tn6!bU)j(EIECZ7!6u2H z-}kUo@4ho0Ny_73r=H&YHEyT757XQrjD^SdIZE1p^KQTCblEE9HPdhsS7{;MK7>~G z{>ZDW;IioLj6d#-Tb>%W;ZIqtB#RE|t1VmZTh%q|WxLjCxO}krFEK%bZ)Mb7#R2TBsqn~o~@-NTf99G0)iSahZhR&ff~ z_yelD@kJ`FR*|E1{*}W%(Xm$@y}1hg&xpbxarbJ=@?r4-qV(q=di!)R3(&Au;=uVC( z>n4dfhPL;ecaCf(+cI=H_CsbNVOy^gn;;1zu)zaJ40EqKMlHoVep$J5HG&^_n@pKy z0Gsj>q?G`H2E=GwvO=swRr~4NSQ6|7o8h&FSp8Ebv%B3~ha!De*HeT0aeZgm%l5;= z!kFvJnUd}7t=*m(8KR`X%p48YI|ApO@D!Y2=N{Q%tK8uM(wF;saWlu6gCpnl$$brs z&u44J)T}bB8b;wG{Ex&&2VYAxDZn_9BM!qwT;$OY94f`FuD;JWEk6KytS9^Wg{|jK z_0>T+sNJ5_Mhu`9<^GpK86hXe4fZUTF12gN2wCNf<7VwED`g7d*d&vOvFrL7hm%2?#ZG8priGj+wsu-$KNL3DJ@p>l(LX#Z&m^jX|A9}8Y2 zi@qwNo-P;aO5{z>#nfOWw5A^CSIo8oj5S%sItn&=!#&UbU<1wJR%v;al`s|lW?};D z9#26g(%k++Ew!qycece1(?{KF-^T}AS2gbHbXZ&hKtNh&Btd@tu z_TH72uynhUglkXsSGtmj?`A4b-lnW(aaP`u5@oBFtUG$j>HEB(G7HLin(34rq zLq2n7Z%r-~P5RcFu1Pow1HALSq>?xGo&0td7W#F)T`KqmBw|V8mc@)!61!f%w2<#6v?eGsXKDLBklO6B zYeHv=TrQn@Qitk$zrVYxbv>_zahLX9j#}fu?{k!|5}g0vjDsKcAXpqW-eD<|nxZcn zdi!kFcj#)SOeSw#hg$jw0oippn>8NxD0*!+ebHxQmr;7ARTc8bE>(`zw7Mg)t%~EX zXIi~Ru_`!4I1#u}55{b<%>qeT%|uRVu@r(ZoA=UQ8-UEo<=hI`K3FFkQFPFARYl$=Pu zEmO1N^KOgs!lvTPF6x4ZJAr>z`S4KLO^?7LBkJIIJ&$qG2{D@HaFq=oy!?r*I_M>h zSE-hR^C59J}aY!3r+7iS^=rIuo>g&9~j1AOMGtxYk19nQbO%6I+WH53%v zrwa=-GYET@l4BmP;{DEL70b91*Vs$x|b+rU@<%g_$aK(rHuxD@GhQFkaV+ajPe5 zx$A3O2i{>E84U4C^|Tw8NTOBRh6p4xjjS~+Vq`j-{Rh$cQ`T^s}BSdSN!_6RPE?HMd6BLK45nB`&YPHP0|@ zj{?(GQpNJ@*eb$?se8b4xMEEdz4IAY-YrOoPk9awZrpN!J5ryYX*%4~R_1!j{@o|( zWd%WP20BK*OdIlixVn&cHc`;NQSyFj#0Y{|i$Rscz`K3qoJ_mitDA;ixK{7{+vHpU z7R2h!WpScx(XQ#b`>fGf3VantX79-+@I%0J?E%Wa*d?>42V@sFJBxgfT`qfuL6rh4 z=Y90kNlX;B-`?(KPtPSeJW3tBpAm!1)a2;qG#i?dU?G3pf@ZrfZfvFh&ieW0c2Pr_ z=#_&UEc6rtsfVh1G^tm7=0bjC1QD)@m}uWg4X5V5&vG;GRRWq3aK3pfrqw3E`PySx|~95LHCUkW~MS666p zva?#nI8L&h;3Cmg;k0>PeLUgRd&$w*tKm7rQ4xNNWG(pX;-_)GhD`BfU=rhh)bPY4-p+ic0ol)*D(t4z6x-eHC|Oy| zrID5cMmb161XBaEeip(lY>2{A(e8jnXTShZJ+(3TGlEA-m*X@}3D<(cHvEqSO{I*a ztNvxS2G)dtD;k%+Q$I|!WR8GzYy_OBHKJC9EJt_XE05A+9gSScX&u{HGrb(fbbuxo zV*dPLHvASh3kx$iwxmk&TU}_x(QBu^rWe|EmHBj@qKB--i~ z`<<#HipRN}hlG&B^Hy2iNAd+dxY>3tjYv(E`08HM@Rb>xg)sx?4wA4u4wO>8 z96pNWK!bZ8b@lBTy5&PcDM;figV<%SGYlB>R76D7sHYN#j~>L8!BM$KU0KsfI9T06 z!>+fAO+T?IREL{-XK@1NN|LQQ3SCZ?lR4mIDmHs#l>&gDxMlcYqSddBp!3EIp%txQ zBckPlU=r-3E~|QHXmO_4QeW(zad$b(cyd*)$uU*VYxxTl#T6S~Dm!awNzL<6$Kh886(r^{ z66fCWc_Gg9`lCvWYMWg*sg8z@R;`p_N1#8hoP7$UDCJfpUZHgKJB+8gbZq;@4BVVs zSVEOjzYEhk46Lv0JNKvrUZiv_kW@3QD(tGy4r#fFi3Z`_*ux$ybo#Dd=*J1kxKuGp z>@@x!TJdTKf8Z6*lDZbtk&33ShGpnaMb)P=Wmw_w3|QlFe3RvL_ksa+M_X5~k^HJ8 zHYf!dz4~Dx(M_6v{i3mK$4)t?2|B@Zs@Axh-54Zl|C|eLv8yRHL}kaZeWBs~a12UW zf{#h5qsU%uG1GF`s@q!YR5l&gES8d|npbdf!*=eE!s{y5dJMEaWUPpH=PrvSop${8 zu+nH*p+VLXSJR-ipj^)=Nu7E{^!S|sZL7Dt*Fi}6eG6A{F7KccUhABFJ@?`WcdYkJ zFX!=Q^=Dw}7qI2%*jhWsV(Xdw^9=}n;`Ov#_PY0i?hb6S9on)Xd;GhW2;LuIn1j}hN&+#`z?Vmzd zA1N^MX*AYwXZwhWzDe1ByJj+VzQi~(AYYZc?65`>GP-@sNhW-}#rYjs(zeH@JMxN1 z&&+(ndUXILU{R2E$*t@RCU>$WKplo~a@|W7Qfrue zsG5WCH1ep4h!BrJb5N}zq54I(WIY{hc_9Ws7RoiecBhw&8H0@`s&zH4ZIJ{KA?{)d z_3x|V4_SV&{PbjD--~0NzWUYG#QLj(f};HZj6>A`v6pXekt>s;CixCiWgAv_2;4|5 zYka$(kcthJs%!-|s016=|5T;5OCbP&c6Q%_i6_6>!sog7AOs)NOAuV4qg80uW0yIbukqe&EFGbG5Jk2_oefi<~bIj>jT@U1mBbtz#bD}Ga7 zuDRJ7Y+L7wD+N+=ssnMq$U0TBu$-a1w&^hguz5=HgvR*HV0-BOr?V}?sVP+(p~I?! z$4Nden|i!qVCt*}Krn%7#3&GMNX&Lig`EbslVEEI@3-Sw=@(va?ayAI zaOG_WBY!xg1oT9$xx|wDc+Wi6-oG*hT+E)gT}VHxfZz>3MxO61(bqZ-zrWjPDnp(p zQbD;UKYr!G|orq&Z1Ms(1#7CxvBA5E67`_08UZt59FRbK2Fmpvy6IR(qV6iDVg9X*zKc z9UX(z?kRTuDnyfF0*XKE^vd4nf4JgH|LK{C^c?Quk;yx$daGh2sX9{>*>z!sb>m?E zau&NKqgas{S)h)Ku*cu>=%^mfssjBnp@FQ1B{kPXX z9K$g(Xf9{H9-Fhk2)gx4?-vuWi=hB6dIpI8rL;FAb*9)tje#?Z_wFSC#xSUE0#~KM zT`rs0!7e@B9>8$Mfn-yQnOwUoWnV0GrqNSMnVzpnS(H9b?P$744wBqCqWKfg-@aj| zAFe95?T!{&5Q1j2S`IOYiz0!lqf1?y>snL6v5K|h(={yJ)%r$fkN#tR3h}=0!cS;? zY!a4tHOxYxD1ks#xqZuqr@^tU)7?MpR3EB-3Q}&ri&0-mvYL+rkTAhzU5Q>&Y%$aV zf{_rw64HN9dCiI?oj1uju8xPwe z`YR=DBcUr2OWLRH)z<|D?R;Y>K*vO`G14D+dtu0R&uhb}#Zit*Nh z!W3Z?hSZD1oCXw#gt7sz{vettWAn?Mm zI%|nZ!IJZ!#c3}W!Kmpz@8To&u|I<7aPnb!^S-y|sQy&$lirjX7xK?;0fW z(!?jzdRM6@qrQB%@&^*78RQ^M=><8Y<8mZcLtS}EuXubyo9-Fl1D;-ed`fcW&e8*2 z;^YjT)?LSw*$b{KGT#I?eEvyuYgwkF9`1paygG4|(qbi%w^<@sSs@o_Yh_|Ts1^vL zYz*_Em>c(iI8S})Ha*mJbmPJHPCf?;3hS!R(hZg7)l(GBP!tLO*mqsQ&EsI;U~re! zC;+nUM9p$@2O0T|yDeO}{fiyn%vykNdO91r&d>gyoSR&jT$)@q z+JT#F=oGCVh}FR(tfNbR|7Jk} zX$h>5GTa41X|?D009EFmZ_Zw4fY<7(@`?ZUzs%5rBQ+)dc4D&j+WIgNif^3fYBMUx zn6y7Msz0~30x1n$$u3`z{q^dQzBl9+xi-iJUnC=UqlLt_-P5_pB2Y9J83_>~hXXkC zrgizq`(7stw)SGLC|Z%N$TnmL@+T6b6kG4580-Y&E|-_l!E1+jTmC^Y@)<9sDMiIE zz%*dK-P&Egz1mw+;8CZho;n@=L~9y1b;eKPN8K=;-z$P{-DQx|CvA~*N=84_wR)~% zG4l2=zz1eHJFm%(LQ?F}D;Ogg% z{{(UO9wqSvqcxZ=H)dzwJR99f-DuK<4;-GuCzW^+1p{k6VD65{MKgyXDkS0bcMK#i z^R}$t6&Fymi}JKOTIr6lZeYW!n(m^B9>nR4}g=TCg7yszMoAo#(q zJ-x5Hh`{?sGopPMZ1i#5qG=uzG#0dfj74^{S611JDAD5R48V1fV#kbjj z9(oU?CFFO0hC2s~H<;$`U))^yt?RF3_DpO7kETn-{`0g0-BZ?e2G?P8_Ghx%e3xGP z>xLhjNLr6Wx2a}Fb*YlSeNR#ezvYUm_wt7pu&`j5Pt@h?R|V++J5N9AGMd9f+h%5l$h>VEK+vCtryzvX ziSY5Vsn)33&fYO<4o4HAnezrtY-ebw%yibnef!fBr(gp-YboE5DD*efQEMU?RtEj? zg;3e&7`4T>M!*Yhas4P<*>H~J0o-vE)|I+Ne(+MDR42GPbPa#v%<>qA`M-Id^4+Ms zxw;>p+o8=Hs%W3U`v>BFb9VyH5PNb>W4WM8oLd!OYyAuH}P zOFs{PVSB5mvA2V0r<4E|Z7w47uB{(IKbv4oR+O`~s0QoB*p5+KI9?4-hbU5pCfabo zu4;8_Gjm+i`gIvLH?^=11fsDJISJ`lK1{`CdHu#?NAG=uqr_Ri0f0)(}GAkP& zkV6C5SwZCJX{b0Y@c(_2?Ddhttf6S8W`+;Q5AFv1&1>v@Nm;dbXIX&L_&;q;!U+mw zRl1hd5lWnMbVc2iyWAyWsN)QgyU*uO&f&dr!hYu%h+aOtOn(JE0iu_(3q7ig@5FBL zqJ?7(#z)%R{Xw@}$pIXQegbmcpz+hnIm1L7U@!2;<2q2v9V~w20{RqJ?GLx{sK0&t zHK4)NOva-gU?uR#gMcE)VLlnVIbxgTuEy<=5O%6WR?FlE0%6rZ(F?kx0bIvw;rQRNjM4=jV1<8a2XrYfe}?UoA^;lB;r_TA%AU#I;9Yie3eoUuJtkmj z>2%Wx>_0y6jBF|o;EmG1@T7M8zycaSy5ZSWBA<*3EIdDUvUtEIKr-tJDu#Ce>iC|q zv}MC-APbAnda*&nFrg{! zg!IEreJfHCxa1mf0k_JxHXB&jKC6mR`WqoVtk9xvZvYzgp5xk7OXoAJ7eIC*#^TVN5qrp8J6^w!1|H-wTX zJpR%S=*Hhov!WdDx?@nLLe6>rkYQkNbBzfFgYl1|O%j@(1_|M|9?Q(h#-84L3uWrd zKaV~qB?TYF1TQ`m7|;qlSNmHm$(%amxadFLNc)R5ulzqQ9~VnMqi_NMKl@+Y=HcJT zJ>EZT@O6e{$oy~|zH)}TlCiU1#P1tD>h**6lVStnvuno!>{wB*#xUhD6BCVUHDml) zt=PtDVn*uJjtFOs{&`WK+6 zu+d8{Qv`iu{&LMjf~4deGb-;o%2G+DSji$xk$V?9I}W#w0tx!Ju#-}<%`TtR87jQ| zAAkD{?H39;yR&bOt1bu)A|k;C%!j4e$M9car}Vfoicy~zSMRjnO$2(@3eeu7YSsEb z5!NoSb-_73;!-qtCh_$(WwL8UaAE;|Nxb44xhID*#GNT;?L7}*W$t_TGI7u>B;GPNiVdck^3vPrd9Fv_fv(t5g@(Xr!Q6hQwc|KoUpl9*bo(Dd`5ZNMgO()$@C0 zYG#(tGsYT*H%`MHsNh^K0BBojzvEeY{KLfL<>PDe#D|9kAji$adTvfhdvZPr+K+$8 zC1Pj(I2a(;tli9anCNYknn%YrTZ_uGJOb6JynsBW?SKYkZ{m{6Pzf@AI)5)EC%r!& zv`9ExlT+|)n4PodF{!c{s-o-PIN9e~HZIYoO%d_!9Xq_>`6uW8sZ`!2Cw#X(fD#v7q|%}mXYU*aAy4+|;8da_!?d)+RFC0wwsvpsQ78iP!o zgyen99Tg((l7l-Irlu0p3}YY&rv6bM)Rv*g_@d&{#0ei|j%KPlTi8xKP)1)JJjve> zDPCpeSMf%*rI?p5lhIIW(2s5M`?MoRS#@uY3sf7>=1)55F|AcnR!%0}7}fW)v1bi< z`HVskkaGVp(vx`NbOAs3YEKU&xPAMJ*N;9T5tp%e19HHo%6^QbuLB0f%v`wdmu0xL zUCkpyQU$DKO9?^(w%#q#CP+$33>W()gv>@frn;Uv-7fZu`Eig^U^02t6$EKKi1{OadS2mH=U)s06IP6vJC#}x6 zlymack01yA-U4<_(-irIOoT_uZuWy7le6Ua=xQ5bg-5BRS)EHo?#}5EZ1{CD#&it$ zx6P8S`qS#f*`jwzuq&!-zj~TfBBq|?Xiy%znZ1Zid(9-p# zxM#R*^Eg;tQ^Wp>-Dmt_^cbiIgzK_KnifdSN;rp;uEfb+CT{_|+TmZUIk*F9JJEqS zS1ynWzgt@~y2N=sI;)&ftpXAY_1I!-QaP1JKrj|{O!^OQQ6EK&03~InpMyUOC<#88 zW0=attnB;>e}N*<0;rpwXc0!m-5nDJPd-5|2Q`yT;M=J5y}-St_fi0!QlfMw>z1T36~@b>zJcCfRh8!+o302_1c<`m z-$q8|=O2n7$@a`T0`I_Xz1lGzzlgb8LC=!?i%Jfc@XJu$Bo0j`VpvO|(nBWS{I?BL z@X;8BrK&z;y?e@bsgTKhTb&z7NSB9%!rP$~la9v+1G3mj&}6_xexEo`Dh#a|4SLNj z0x&hol<#>3Vzl^vxCx4L)SsOEuvHw(*LFZofT=SG09ul~PmMjR!<&abXqE@r#KG7s zSYygc{GqLI;s1I=#vkvXG<`o>=}bu>S))6whIV$7|22H^_L}BBMnKbECWp%phOfD| z89KKKoo=PG@C!dy@8j115L0er#e15Wj7ORZMZ2?%Fh~XXCyXP z){(hdS-S;<7fd`J>0*$-DHV4LV)*cwW`9CffW zr|E7b1J!5_tKn!`6_EBCI{9Syyur5$^7=yCLfKQuCB6_us!?*RhDy_na zEVQOV<`@fUuU|499&30hh|S18&dJ%_Z_;*S-hWo0co~mJFw!j> zRp+q;u*XgeJ}@VT7MPPW?i#JJWF_7qj{t29?IMin(4G+}V)LE;t45sn2Qm#KA$a+l z+kj$4k?-)*Tw7H0hqIy7gQ798-&6wBP8I(=#SsFD*IC0V<24lo)EpS_{1uEOIv`Y_ zpfkaUUqrRl=TjBiU;d=Y63c)aAW80E*psui85zSC{)MXxGigSl_3*qA6g9vn5#IeO z4W;9aqsC4U*(BRz8&x{&$>6m5{1KDQA#F>bypY%Z(raRQJ}MLhS4YD+g|M;{c<&;r`Y` z(?gP-S*7J@*64t^WJG!iv{C9a4(=zB|9^;5m07OBv{y+fBIpb<1256BNq)HY6S5yD z8+QS$`x@(ND%u2Gy@A}aFJm2SeUcW!@+xuL7-q(B2tNRv` zh31o*xKN%wRRXL=J`C@O@5=tUhV32JwaAl{JLsWP>i5fuy<8O|Rd`dR-ZI?wJa3?{??C zKBsdPZSBgbPt(v$d%RLeBdW;*~*0q>3Q zoCZ);IE@6#P)vSXp?Z=oEMZL;2jKmYEp7eTOjrg36_w^s^u zGomiyuX{pB2_lAM;>gf|1(R>GpJ{t)_se>S1y(;Cqxm#~jyp_e)|6{!((OQ+Hzor# zN8E3tGr7fJV)yKww>Jn*KX_g_*6f!%uIbQVA9am7DyzPFn)K#AjEMO(rO5!&-P-v? zVS(U&xwyERrzef5S?g~$86leg{8rdgnEmsELeYA$kwW`Wh(2RWc;K^wvD(AF3~d`0 z%4-xUU#8{m6S(H*5hjzqsm*x&OZ1&_z=hsBmlwfenQ50-)5S|aLBlaIbM{47oJ_J; zz?pX}$jWDpTod#;GUKYxb(u-rh_iOfC67Tn)#GfV-=ZFU@_(i=S(&#inmYsgF=tt> z>ea5LRd%|TIo={CYLuTwzCwe_8&9m!_g8guupbaIuu218;Xgs+a=}UJu3XUMrBU~a zzx+mMrgZ8qj;*Y%(7~htuSKSJVz2*SIN&j6O6uS^V3Hcj9PpEEO@7gXxTkCW`ER22 zo_b8ka(B4{2BJ z*)*ENr#oUgx_`uaWL!g5bG1gT1})Q4CP~pNb%<+nc9I_-dmxw2>G1;h*aOcPskGO+ zs52YWgzqBuBN2D7ng1ejb21k1*_@Yu$ThM);B!bOR8Cl3r0Hi<(X_A$%&?m8=lmr4 zfBpp`_Rz>HiJ{CJtFQHWS6H+n&oZ7fPxU0+2k$#LmM)&#(h<5sTl>N(BP}i*pJ|#e zEikDhbUg-5a<{oGKkVC9#wQ)meVH8cyy}8mujMl7Ne(3xbidWce4AE$sTzMy`?Z$r z0KLFyOUO>nS9Ue+o3ExswYO94rrs~MC#x)e!u`=2UesL5tG}zGbIysS?ebJxQKgK><3s8V7=xugARhexAwyDZ#uK%kWyp0~g`@s{5uc0D zB+saOx(r&uqrIV>a+w4uW!;0<`-7%lYWzDT7psz5q8(uERnVUawAX#)wKJfv`^3>j zE(ALYA9hG2BRy7|s#gvy_fY%Zd#)48yiC@a5l!EwxqiBEw>(~Z%y7uz5?Axx$#>k_ zJJh37y@OpYd@0F~Y8}7QG3haR-^9+3250WM6~oTHL2{RFcJ-jg4ud?dk#wtlal1oa z|?#V3U~@ zA$$GnW2qbZ4jjpjQeK3Cb=?odUZ*@x@ou(+B0!{1*xTCqIrait)K#h;x{;ihc5l32 zQ%6HB-Xuy^E4Tp`4_HBiKI?ZJiW?lPPjGpci3pR9vt{H+SUp-j5qWa80kpBI6 zzN+a1P~Cg6?mACIa$xoCyU)LEoUH-$Rxz~9d!n}-{i@UxsYAW<+p1JluEg<-?h`<- zq*sgoy>|Q}KG4SW2K}^Loqw<7V(rc{pNNcP`u{oY-f3CI?0G>G<5)Ka$uHu5?+Baz zQf9C;(wpk$?Lu;6Ra0!;Y8-b^w)nGP(XAWZ^SJHwwHGcE39lX8^mTsHjxUG;C)=>l zmjAXG%PKvhBk{ElzfEe_nr&t{8AjLZ%Ds-pH%sHg49T3ye96MeQWM0BH4>L6@>V~7 z=P{{c2F{nKnyPw;LPLdrVX2^0{O&h>vsWNrlEy(oq!Wn|RK?!8SjLBUiRSIgfW9)C zi1Z`r{F)>!8-C4N29}rFM%!zPX}0nWe#8A0O=-0E5>WO5BhDCD>I@y+m%>>NONf=bv6iUVltJN#uRVE&9Y67q5@fybG?3w`=EcNfoL) zSKf6GGn}*jp3uwy4p@JSZN5~f)lXk45&qwQ{_AkPSz%lJ_|LUvT15Lwl6%>^?yhH% zTGmT4ciwa!>r$LnUVdTTtQL>`F8IFkS$d>kmFfqHKuUf|m9zgf+|%3qf@PlL#=ZAs zrM|1AXYW(0{m`Yh;qzkOsaeNN?{ke=Rm~gY46Qw-M6Hm z42n3vxgPs^m+ag=fxoT8kdljq?(#mk+RsoYoZvT}WM*eT_~hT6{K+0)OMep@GR-M` zk{rGF2^ck!y3W%iZ03%~(+cE{twO<2Rg9qU8Aqbk+)=Ca8aoA#Jp3YIz*~KXtMT#8 z4W@h>I0w6yCmg==j;6CRe19GR|CP=+@o$Hl>SmQ@8dBGO-s@6P{OXdd@__m6dzBu6 zO(*pmgPN{h^M~`JxJVW8IRbD6zpGlhbNrrE&4Z$)%ive<9P)??9~RXyrJ6#os7s$ue`X$~dS=?upc#Gn#I5vW`iT3T$y^nsE0f-NC+MPgdfE#r3C>vP^nb z}sQoQv3E9T`_^f!)k?7E1)Ui`hu^p3E&1YDC-0+d}q*l+3Om@Cqrjr~Y)S zKRdH`*vHmR^{LVpH2EixemzuFG)MI~UOPwTKah`q7G)eqM*RC1nlJ5BqQXBlrPexE zZfs7{Y_9kh`d<9AYeWr(N6Sdmfk2es-WKq=Ko1#4_sGw}A{|j4c@Hy~v(89p=6>p! zyctCraKjKANo>F=F;f~E; zE2?39EPkk@oQo8w8@c_}`u~SLy;i zo?&zZyD_W@m$PQT8jt^lH_KU~=8I;z8-HF955z*gtj38;Y1$Jdyf+o`hFAgJ$vOvI zLlo-tXM5OJ^sVqAx6L*R_y0QNuN1sU2&3pdw)QsWXDpNV?$=RLYL#EI<1jop82@gl z;b7yf=H-Q8l_~6{I!Nt>>8>>*A?NTc|NaTQ?x4JqJkNu!fv@%AWBL=excT>8p|}eR zfaJ^{dbF?};xVM8iv9Wcc>=L_HBr}<|L7Glul8x1c3n0%e{U|q1i}D%RxykM|Cv(W z#&iXk!s>ygU--A~1O5c76i)+f_1^x!8Y3Ccl?wo;%IO z;8^(?ZntCF++^VD5FPGP=lEWGf zI4={gb_A<^XbMG(oWuL1q}0_U8LTfz@2aZi7!sYP3w{|hirb5vk|@;NcB@ZSTHdT)rgqFFF@a)mz-vl@-+WBE8*(@h`N=v07FHkei^m zgmTQ<$mM@82c%y{r-zdCv5Z4Gpx^Ggnwn~EF+iS?H7*5i#gYM1V2AIBf0}@iOL7Z$ z2?>}AXqPRe?P;kYvWaEUw>qczGl?xtmMkIl*zPJ-@(a1S=lxIX_dv!HNs+)riD*Q` z!u|sA75at38txanM5^^AYj+*`y!#x|b(^CRO)b=jTws-N)WW_LkMSoe>Y)QkB9P~} z850)P0(6)T%+A!HiqYg}mMgR~YT6I;>Za_I!XX2ou9YG6s8iG4LpM8%x(zT^aW00n z*bBI&2QvVS`A&8&Y9`P&iJ*2wu{zR7Q0ZkF}v z>Sk5<6&sbX9N{y?pmx3R)e(VNn7;N);)?DnDVu@SS!y84or$e4azpA7vF>)i`qtC~ zvd|)_os{jOpu){<*sz&u%+NglQFE&F+)o%yonU|DodjEy`OMNgyt-gGD3^+H+1~1D zSJfU95{Gl|Tn{I7P+CCU_%zpQe#lizB!g(Rs~0aNdUhT2ZRr8FruNnO%hsE3Cj{sT z4Wr%dCe+c_?WT!~Uc8wSJPhyF$R~ww2J~n;zJa@#c!qKgmS?*9ZVkSaWQKS?mS%YG z)qBp?*ywqv;yaT3C2)lOqBJkP9|JE1c|z*AHEj^xJ(T@B6Sm4*Yo$GOGzBX;T2?1X z$Lj_8i`0mEL^N?1spgvaHFpD^;9-epyA70IaEb=1lcFTH0Nbk|)1Ef*`9fkSLBB+SBboH>4(p2#qIlHI4ElL#Wo{uyE0ECW7`fBHHo zj-R?L+##xJJ;!5Md4AGT-rC2HH335hxU5VyqS%4PqW|?Tk%P%Jx)YPVm0_^LV=AzX z*lm52mF_>FP8F9wi&w=LZGK4LPR&qFazsQ>f9&m4*GniDoi3_h6})$9Mzqp9Nk6aS zOZu4RFHs`u5K#iF4w-2jc5RXM$9JR#RtN_VNAZ)F3Nfe?4h%!LI9LQ~FKnstHJVfTzWJNZ z`Ly|BwGlPt&e33?0C`U5UIu}2?oRdnM3BA6E%qbyz)MU{W~T@Vm7xuc(!BnmIVe;;_y&Sa)f2}kijiYh+46w&WB3|tR+33duQKyGD=j&;RBQ9!F^(se@hMUJ0*Nie^cRY z4M6c=Cvi)YIcGa3lS6v9D{Y$QyO(a#NECLGFq+(&Uyc1u^rW#N(d4os7_BuddcLsC-q!&RrF=(t{CpS z!At+lkkyE;{;7v>oneS!(|q4IN9MrGfKXS!Kr|xMxnsw}XiVs@cRPQcO{Gp6#^JoI zt13TrsZfllSKMPa{X{AV`EcWP@&lPoeXPapU|^py&2I}9m07r&>$M`myLQjTp*SvAW5 zCMCspJpVUKPhLoBOpB<_CoKq>w9Dka2=}o4swfdEF<6#y7{LJ6lCKgUY4k`h#EQS{SqAf*mK%F}C1mP7ReTiGCPknt-Y zppnJ#(UTa)0<{PP%`6~hRpP>y0uUq$Uz)#RjnI(P$W?l`laG{UW4)<#;>IN9-hj{7ro?-43yE}-MP z3V9bwdCPhIqy{LO7DJ;~rk(8-%GI24TatiAH?Cj5LeDfGw>mBZw!i9^kwb0`m^b@b z{qD{kKQ0{wR+0fLW{I9S%tH#=J*u=nP68%J@E;eRy9~9PcvuQRE@vp=Z=&Nx*D5e! zq=vD!6zuwdUY6%tW;W|T3H=KOCzmWKyd0|Fvyy7kjDSqOe% zo5(@{$LAf9K0hAfh?^5#eFOyTDrm1jggcW>ut7tSJs8Ym#^jM1MWD>%$<{!c>wKvs zLD$iJAX)EI=Lf|?F7O>*{p8y@BN6VH{8 zVNw;bWXPyOS6wZy@A1v$K%?Kk`A6Ib8^R9OASmR$3~1En)-TooSTSDN z*yF->-DhR9g%bV-%%Ki!l9%Yo4HGe6c5ZP&!ge-jy8VQKDJ)UlR~V=5p{tKjQ$+(3 zI(-E|kA%6TEQAfs=aEO=fYdWi3z?KgI6Y2!N5d#qPSCql$CnhGz<~3kt0>6TPYfuV zBy_PH9B>OJ9M%5;l(;MXB;7%R3c5bT(QunLEP#?VI9Q>zf@?_jmhjh)m97xarPa_; z?(~{CTSas8rSJ7{O!(+23=>054dtZ#5ihcfI?(?oV(v$GB4If1-|2&ovt%|ap}2qKDWYCsqNe)kR~$;1w74knGvT1Pdzu2}SYR{P&3FBrOidS+Rh z>GhP-?d}i8TPwA`ltiqb_g<|ayNhmIc8uS?7#1KXC8dHPV&D(rY+U+|m-4Dy;2uU~ z(4VLQn5l4proh>wDRr#x(2~pXG!YQ{SA`XeXG%=BpJEFE&CH>MYp44CjTH0wCVW)x z@54&T6R@Ubeq%ko%Ud^qt|9g916rhdvSi;Dh5?iKA@od%JK|_OpcGn^G zzpanmS&S?6h|-SER{Tf(vaFz9Y2xZ+;}5dt7fY#%8_^Rg>-f*mbYp3#`qJU2?#g_| zEU};w#H1PRi}UMOY$q?n6xJip+S&16qi3I|@d}Z=_0Wm^?VhDf?ECk+W#sn$5_lfB zKU5_iu=@ld@7lQ%nR@6Z(m_Acj;E@cWGr1h?53_0)h(X-7h=|t%?qYw4h)Fk{7x{% z42XAu-91_I^44{L83O)pFlM~&_k5MAa>d7l5`d}ihJso(zOR=ZNSiL3>j$;c!#DLe zhELg4jqKYOco)(3dFJSX-D}7ZX;CsX0`}rVK3^9V`V|TNarnGC>xcJp+pQ z$(XXCGQc`lZk9sjw@=EJlcIgugWbtFp!F;yzcXliIrS*`oU0}DD!ddKHm>u4ck3#k z8VQVnFQ8h8MUM2uc49v2Hg=$5-d2H@b+eQBD?X!j3g5ljol!p2#3#v{87CL=*O`B- zMAQ{b2q;(a(v+3;)>d5|6L#f2(<;IQxKg>&PwX}MyG4aTK&q)k{K|26L>%4BRy>14#BBf07=MOu%}`Dp^C%=V&k*=2K>k ziTfN|*>e7^v``tGTnL{RyQJIvthvEXxLEdkS_FAfjdYqZidjf_lzWt1ASEVuDLgn} z*wRvCFiTRhZm&H5F1oGVF?k(7EFh&h*>}7T$4!4}{tfoJ*K1;5y7EG0j9~Km0R7zD zTt8J^2h4X^o$Jm!(cJy_@qF1{2*_}4rGtN-8WQ_@GDtZ3O})8YBLQ@~wZc`&T?@!6pV;VbvT2S`!ij(=g{{i_Ht?%X@0cIc;8qtb2DwYBJXST`8 z8j)xD!oymcKK$_N9m6SsAXyS0djEW%ar;I##oQYYFF97cQF3Se^*Athf;2M93S%41 z?IeZ1)8r0wZ>7ow{0AmW7UKN$3=F)Up6jV@(6i;AD|GvssSW1meB(j7qk2wTrxrdz zgzXNR!DOkUfBdtEmxRNXv#Up9{ZV+Ieh)ep4`sBIY?FyK zA{H)-u`W^}K6QLEjmKUfu9zz2EV)wLfN5L-vVX6*xEOxsiJoizP4_Lvhyz#XSQFJ%HFa_LvGL-#J`a{ z8kWvf4<>90IH{SAc++B`y!mj)_v2Vh)ld)#6`MRX3b#Zk7#?eL(=#$$Ec5c9NtAsa zo&)`~mSz5sj5&CUr2y0lgv!HDs8z)=buJW9UE8ab9zpslUg||_bLk`f^2l;fv+nY2 zn8sg|iXP&LA27MPK`GG-D<4Bm_k4%k!S_mekm5PUpZ4>1 z>~V7~fVmk|suRbCkNa|pu)O@zGIT4~)-DWKWUj45z@g;-gSor{P|B|C?=C~3j3m!5-RI~C5l_@l1LMAGrnE%2KH90q|hzzf>rSg zrPl$in!`a*e}kO71^(-kKhMcsEqo6g4PTGEzduzot17#Mke5fhuL>BF*q0f$zB!fU%J5;UdmFa2?1|d&aW<&9 zBNyY~AuAF(HYsTIE}Ugf8hz!6HV8+0p%stDYQw=89U#-jn}qnZZBu?aTpSblc17S1 zo+q}gY?`(e-T-!Lu@qtEzbXFSMn~IFm~+%%l*F7P&&o{jB0cZ?8<1^`d!L}I1o?WY zs;j4;;P|y$u)uO!0l2v_C8j>nNJpbAqrEMVEW?9-g2gu|(<{0j?T};cDFqQ=;O89&b|rMBGLL z{a;79Vb@2(4$#Gs>Kb=|#D(?5GN#qqdaKzvTl(*Z;XDorU6l7K&wg>G6x<#Zs(|EIJ6bEraVKzp*oXm%%g9nNHT?DoF^CqDem5)O{* zF&~dBJ}dE8igGqL#=zLQT2z~!U`D#lDh77(Er7M2+-2kv9lAE^D40vHSf0_I$RU;I5bS+1>cIm>Nf?*3uC@;4_6rG#TDs@@q@ zU&6F&TYjIFu8yU-M+Jog+~z*o^p8LayM!1Iy-|GBw2GFiF68XU{AMHxue z?bO0cqv`~(oRj#mJ$Z5y`Kx^3J`H7VbbxtJR=n!7&iK%WPIez1{#(~9{Az2BVLJGm zKGj~P0T`}K0NI-~ISsjDync6cV4;dv6^Ca?jO*f)Zu*!2sP=7g5LN;xV1+L{TYe`5 zgZvtEzYCwV>{Frz!|0U71wEZ0Pl8a#e>v6DqNI+Xa`=m-_uJD=lz$xQqlqrwJ zHLoL~lia`=$HG%#Q|VMn+jO{m8O8C01NIOlYzTmHBKaU@0qJWdmOkh7_;F32X((SKbz%j(|)HV zNxI&FY!5zezaOFxC*S5MTVFyf;Fdg3{`$uJ%hF5e`=ls$i9%WAsPUCy zWT8Rig?G|D!aJ{=l0}x+jE=x1^>YDqFPE)PBhOYa!*fFIikDc}v;oDGT`qBLv2w!Y zL?t|dMu3g-M6zfI;n#r9p&l?1hd27QAfAdS>8WNShwuJr;>P6juW+#@zaMh*X6$>c ztbFaBxiKh~81W99uX8ij7U!Oa7TnO?L~ z)2V|01A!wOy&$9!+T~l7(f}16_2O}DzrQpF4KU$*5PV1Ay;Ifb=uR%+qe8qvB+kr&#%BET zf1ZEf!ger7YK)l_07^5%4RkYmWak7Zj42Vh;hly1>N%YFv_xgMv(e{SS9R3qOBlKZ zOc|+9{-}XU#UlyDrSyveYFrUpA3>hojgOB{1Fe$$cA^*4u5TMXKT~&hhRY<@!c6c( z1ir5XUfsQ4ecV~Ihdu)IJPeXigJLjVK{doHPxW3Sl54}LrFk~bIGp;p)k{s27Y-Ze zI#AD_q&5cNAo~s5c$4TK8^%>x=A^cRlUIR`KwfjKjP%}lZqr|uhspIFv|6;w z;b{_WaUwhQLHvGYaK61|fSLND@SDvVgjDkf8%}41lrY8D=UjDt8zx2D&{{TWih=vI z+gG`N_aYYBevp1~m=Dk+98jDO`2>Z7*~}~`pdS4S_n}`goOJujs-Zd9Zs?GkF!*Lv*RaFD`@jZwE3H5WYmCkdQ zI^Ro{EC9b7PO3~ZvYTd0-ndwrw~Ico*56cpiOC{!inn#LZ`%Trc@P zeNO|mJzG7(BD3s*&Kx^t!r!=5zL<-`mCB#U(`DbX)9A{?T?O+61{)pjP66K;TcdA$ zwG=J&Bel5GTZ82S+_&@Ui$@n8R&$vA>B`Z0@bR6SG4bxfXdLZaur< z(FJUK@^G!GU7uUqiTxs(l!lQESDbcP8)!z=e`4Ke@8aW$T}p%0g`{uD`y8x_V%2aQ zCxS;?=XoM{BV^~mCIS>kNmRu}O-0VH{*o7cr5)pclwK*h&)JA+&#vCU-@ET~hySCf zkilER$CsjXmCh6q+rJqVO&7JbD36qgHE59&)PRRdZZbzZd4))Mv=@D4#smf`^+!SP zU~P!TR`OyW4_~uBK4P$b24ScZ9_HjnU-O=oM;VsN|7*mG3D=WOj2PZa@-r_`J)ev> z(g}#O1S0-`N1DG!QSGfKNuR(^l!QvQwDb?-dr$!W{O(pzpu_*5RORU4MWV<}-`af}J_~W3x$oJ36)76iEPpv$70iT-v$3oz6x>z6- zJ#@|b=1@i_eO>$sCBKoKQ)mR8s5YFxf7ZN#QENi$+EfA5;%J_NVnk2+L#*#e8_cux zb>rpX_t1lFj;7AM0$~AWHQ=TBB?__-!fwS!B7qveAa`L;^DZ+Fo&ke_dgu%gLyPaO zt*$7%V&m!KAGLi$JvOeT&=z&>u+qHjhA4JrEINT&VILU zc&qS`+sf+`!-tqOh7F(7Y@q+iSZbf*Y{)I9@JlqOh`z<;^aqBMY~SGCXh1CFZk2^L z!w{!h^yDi33nFu)0eSZOdu6U{FOJDG4+i|iJV)mxUu zznpB2>tmYaEUP~5eyUANy(*9;Oa$|P$MK&sRD6lb-mzDVq#@++&q*|A_I0P76ti20 zhkZ4C?WwcV^)HT7ev;;p=cMRv#)pg8j*-jmiJ%3Oz77YH;je~uz2ewvoHCJZb}^$a zyE-n95uzi{zc|KM|N6w;ENUAACn3Q_mBA&a7FTBKZ;Dk-4c44GG9fCP&xIWgqeqGW zht7-G``{UW2bDl|=GYB;+Cabm{?i?Mc0~OH5ca@3E_xInJ@&Z6N>;b)!Ksn_)qr79 z{m<9fSSHsyz9k@Im(m5wFV722Ip{@jSH8G|vEwK_*YE#jgI3;-PaNqcUWZOqZjq57 zTPEMPf13R)rDnhHJv$HLL@P2)#!;dQSi`IDF8-QX3Ij1=0JKLM)B-BCL;Uc;U=5V7 zBB#^;IM~43T@xI9Vln}mHDgaT`Qt=+Wq;-yDB(L2*%xaYit>)mC$y{xq|{=HbWZ-5 zyQcR){@Au+&Vh75J<;Y55fz$h6YS{d$mYZs@9Wp~^TW)U==nD?2Y{t13DB+DuXRst z4%>Y~0y^C8P3E#^AoGO_ZO(PJq9b9~V&(0sRH1f zXaGzF;V`1yMqaK1nT!-fTa3R98t8!%p5yZR-vLV_&F%CXu@F~t9cwnyR!!B?Y3)dV zatT7C+X*cl<{Y%0Lmo!_VWL7nE#2dK1HA4|AqfeEFQ5ku%^dqR`wAdwJ8JzCu+)a@U6 zvE`JeO9eM6Qce`E0s3w^7KG*eXkG6ioy>?cll-G@>7r=e@->Ozc+*=G9v?mRQa?xS zJ~WP$r1mX##6^#M)|#pTEX{^UEmBl!ZNj(a1Or(g7p=Yaq{LOcP=1x3drhKfi)m1f ze+lh@T+1uzqca}aZNm|Z)wA&;Pq7M#6221U;ZBBcMpDKa3?yQMNwb8BeF;ysZ(g`8 zo(b=hQu7+u1Sytm)I;}JLQ*H*R zs?G+qflZ%ci?soT(@*5tEkPDmDnxoXsn55A9dUZ1IwnO$6iP}|GVS`jF_xHw0$7&O zJKJ`2p@}Pu$HxbJWHB`boh)_8(bDp8Wpr0W>{9i|P8R6%U#&aweaj6yG#v5NO8MLu z?{hvB^(_BFR)|REz>u)oiQoH@kHQwSI^(!aQYw3YgBu8IMu+0w_irt~*W?yO6Wl#9 zDRE@yYFBM3LqCRpb2YvJ9L>%qG|~pAIBSqCvp{Q)!TW+u)!=_E$~!pV0-QH-nzD!4 zXy$0s^u3ff<*xi^_RlH>Hv3tpHO8~UNoFm)Z02!Gws(H>r5vK_=RFRL{Yl8j-XTH@I4u8jSUq^n??4)1sG4Zd7HY5gKR{ zB17SdrkOk&9T$~4MFmp6qrfbhg^hdqT%?!7NifJ9n~Hq? zbJ>*w`p)naLv!NZm0HY`*6d_e5<^9Q9oSF5`(|33eI)V)B?v{T-Cj*@B)Qp;BN8nGAk7lHo+TXcWI~k_I>^y^5&{+L)iC&30O2D8r zHw`K$D6`JaXO8;E-9%FfD=QWeVCDcX2uK+T+YBJG22a1UGS!_oH=pSr!*Q1?3pX%h zpfC_K`#Qlb8AwX>w#(?I-gzlE%`t@UELcgRi7QE_!9|Sq?eF9Izd*jKr>duI9IT26 zCG2tQ8qCRE1UU`SHt@PdUvzXd3WxQymvWLDLqKh3fG^PUbSdHq_QJ^O?mrz>S-Xrf zbxanSIk*Ll!FMmwt*cML94#?%w+fuijsWBO@6XdA(!#%O@sK#x8<(r8pvSWM!-kKW zGjJxi%1AW|c!90Q;nIL|TCGDru=%N&p5dU%`gMF%l-^HxoeM9!Xe2KzLNZO(uehVL zv$xw(*Y1U7fS4@A2-f{W<|W6IDyWB_X{f8$-0r!UU!|Q>6G_fkH9fp#$ESX`Rn1gu z`wX;sn)J9J24{Ks!`$Rn#hv^{%y$IOymyzRPG$bN)Kog{?^ROyV2lRpCV-KAw$q}G z6&yMD>u`#a%w@)AOt3KRk#ExBfHyW@oWrN0LKg--S!f2ZTyN0KMbe`0$l&x-BkBo7SM2P(H10b{s;=IlS8TxqJowhcqUFCCz;8PP}BW(4c1c{#AMf z=BU9Sf+Zfa&v!J)z7%QcyzMIA~CJrQLsmLo9%{O@`19 z;cY*kKXj#Sv#5SVJXSbnRH9$wBfC=Zc-j7Wcm2Kr%e;5axm*Xom$=gYx$w4GDFHx% zcwCE|<2&F*lWYYvp=^bF<0bX^19LC?I=%jSNg*u_6o^|y(tr@@+l1)yD;M5o@44x1fQ^b!nZ$I<@G$76Et{Co)=ugJE0BSdM( z8N+eq{afL8F{9&EcSkqzpP8W)`Cp1&jFI@5Rk=>xum4N{{i>z#hO@M^+og}*vKmuq zo85uYWszj(o;fndjMPwNv%ky*Y#I+PDZ}E)J0c=Z)KtlRFrsmVEg3A1c@;l2RG!Xk zBaS&FSPZ`jSpxsO?q<&zNML+^1DDg|9!sRHQkCoFW%!La?D!24CD4waJp)yo=Ibu_ z#N$Sq{H(cWGt!h6+q+*?DoyI|KYO+is;7rycGULZq=bHe>L)ZO0g~JFs!~_F(0Ly_ z>Afn8aEaO0nL#zRI(7u=Bo1eQr}giZxP`gAZ0=V40rt(g(hBz9>iE=fzXo6Kk?l)r zT_SO^I+uT1B=8; zgGqW$I;oREj;@M6q0hU7u7%JAhEog7IHPHrj1*t_d;s3|%Rw-0rmC1ROG!#CF1;Xb zo*%WI%9Vm{tpXR(tYFUE7(RDn+E}mu*(ZZBKF82N48vj+`zm#^b7nx{Dg;&Z!8O!0 zu}faH`$b4n^c@hL_G{IN&$w({QJY#`F>I?H=X&8Ji|(Oa8-LK7QG=&ADSE90!}+A5 zqfl@ z3r&{3La!-x-MGvr22>aNr3tv>R64sIzvlLDjdi{3!qNz9SE!6hL2Y^`-6+6Nak9`O zvJ&5~vC63KdusX5SqM8j|3I8sDBj-=1@OhW2v;^}7eorayCa=jp@jYFaFs|$2OU&t zHF@Xj4}RA19yyAwX&o^>dJu;sO>`Kanz#ox`?+2c zY+ulNW`{-U&vU;hTS+lw@V08C-^dk^KPnG*HjLTxrIL`Q(o*`wURLd!l*qYP1^P)* z3tqn?^1VaUx^g({9|YR28JIudEnB@{jLY~uGZL-Q_tnV#lB#GAA~c?3HH8)JJ@Uz; zYUou8G2Hs6%`SK`lN02Sw|>0CQjT(-XQcye3+fK0FHvCjnjTWwAz|Z8yAGFca1p~y z5nw?-o*;Ov4f_7xrs~#FBv>Su;f9j5-{S?N{8^sF>aSQ>Q;XKDNxW7#r=6FYK6@=( z74hE4stf{U)>9`w%=Mv#|B>sy8}|K3@a_ZG8__N)XFHS?sn0zdS<8%E9^;eARBwmJ zzthT}5c_h#+!$U1GJ0@>R%mwO``Xd!P<7o&UIle(_QVh02G#q%7%+r(Wz#tS6O^2e za4_o=@ufP6Uxvue>0wQs7=+R8~s6LItYheKf_9<5x z24ad|UpTfcyT-O3k&iwAI~CjT%3QN47^C*juY6fwfp7GYh^V5m^b@Dj;S-Hlwyk)v zsM90o>L!@iEJZ!j#`x~z`|1HnF6!L8#cn*ZZjCJ($+iZWVC%>Gd?I2mDBQ8Ri$^Bs zhc+ooz^>70=Y@Wwor6ar1K)F2FGD|0SHM9tf0}1D7cu)y4K|r`!u#8Oxz8cBu4#h8 zCE;QN(x@s9n|P4;2tOfc`df)Q2e*U}8T$O1@;0xBY;) zQTKP9-1C*_&xC8&nL4g^QL#@FxS&4 z2M#!Dovu1LA;wa@kG~n3UOv9$L&r@0q#kX&M4ucNkqk8XXk+zE=i+sKFgqCdG5z&%(k!yP01Q)qWjw%4mpRZY4m}VIz1vUF@a)jSY9JhRzoWf zJFNJ#?d%wC=*1A3zQ0Sdjd*h7@5HP{P$pv)R8gi+W!2#<^=SB&*jwu0^Sj z@TwVsQC{szRN7`AUlK#IF!y}q1Ww&t%WLO?Z`8kRdi(@blFtB5FnS>QN3C^0t4{Nr z+El@6MPRApw*E*@Mh$7;WN9bbWC@|zB>SjKEOiXzO{IF1JN#F0J-rmj3Ut)?yQAL_L`+E!EA%cZtS|FuS zea>p={K`zo>=N)+Xj@daStt?tiAknjt8^ov472o@O>>(z@4MIMP%j_xJ7TqK*1sg4 zJWDzlHgVO!tGj!W1pX1|U)P_7Jg~b1Ckj~!?H+FONS7kb_C*p|3`~g|9phv6{HUIy z#Zw|s0`?wo9sYs-rkzC%$Q-f~s`iw=us3EIG)c+em%YIAdClZcl|gS{rh0F0(Mz&( z71JkIweg^?#(>!gG@Y$7gof+5M;cY5^@tj&LRx=Wlh`+RU6pO^b#~B$^)fLJEqrI0 zYxUuv#lf(5>q}QJSPh%{^?UIW+l%{Pv5)p~qkB)Y0Opq0wb9q(6Njo}=8)inQ!_J% z+i78jf45O_xW5SeTS>7nK9~3vM<3ZS%n~i8C{Gps6)u9^V4D#NgUbR9SG?DYkd7H4jt}HUP#TS80;}N^)`loV&D_`z!WMz5!ona>QW_K^RyM`hFw!( z1il+(&I8`+FB?5+aSeuoBxO&t1sJ1Dw-P~+$RJkjJ3h`v7n&U2R_yJi9d6B4W!*)9 zh$kxeXc&64Ce4YA?^bR9FT43 zM>{NFl1)TPDzlR`AtYJe``SuxL3uaOPuXm#!t@mP~2vglit`?Yq zQap@2e1~re24mk>T}|aaN?DbF-k-j<3HlGP*~n=wSt752qLB`?M=Ch+XahG1qfi9n+S9 zWSiYl(a9J6|C`@-F=%kYOdyuB+T6|UU0`4u7*H5Ew-xZ%LPSwmSa@lOBGPm}w7ozqt5c{ZC4X?I#$ zn_It!Pu0|8&Oq&_@tCh`X=zzsEqHHRVaUGS0EF$8hm3%Um2P&+KJ5xh(&A9+Gb|g5*YM-`<^a*2%h_y*{pJ~ zr~mI?#lWdHmI{vNp$X~J^Ipz+``)f+c&aG}ZdU7-S4~Z&`CkcoTE3!!xi|q2H?h{6 zjIQIy{RJNI=AUNba6u8T@WgiIYK&V}0E8?uLnA6y^MRw$wZL@s=wQr|T5oB&p;! zqTl0$e2qam%Z%a;Lwq(o15G$=uihG2qYg%hKKv4k=w&H5|M^?Hqd!V1=ka7L@nWyL z%w?HCd-CmbtIUvD&8cUTH$WZUJ3vek0)E{oroFv;r8YT&oU~h4D4UH8NvYfslk0aI zew}j<%u!97FU&b6PuAc;b8y{2debPZn^Si5GQCBWMC6f&nBJaxUx3&%>t`g+4bd%u z+zZQ5>ac>`YYfc)kKdlS;GANY4&a^Gznp8Hf;zk;KCQmrL9;tQ{i;dD6h{t|<^;Zu zBQ9popK9@LQ@_@7Bbh+T@zEbk?2g35O)oc1R=kZV0h1xdqKjhEik=UGbkdC8DQfig zg&Kra#PBwA1^>O-bDX_Y#a7r)%q?>38QIxq0*D$=sqU$;DQ>dRkNJX!fERd%Zzj<3 zj=EKl5e%IJ?77u@+$N2BSjy15ky@s`Pf_#s8R=?zRL+Dc)M{P*!8 z{5hSv!s-{LpFfzj+Kq0d%RIcZ73P+mHWbundTT#T2h~$dWzD!b{$q^P*8c9KU3u&( z2K)M;QegjeN(MtrL6M`9b^PAmy`fjHDD3yOlLaq&RxUpU!DwW_BpPgU5D{UaL)0K* z!RFHtMI~$Mwg?^`El zS1U2qjeO+^R)on%b#&=$w=gd@%#ZW^KgPZ~EUK>Sd$0geRHP(DK#&kcN{|pikWN8T zKqQBdQicJPkdP9Q?(UF|Aruf1kdC2Ix?_N$zI_J0pU3Nc@9%ogAI!ixoW1wjd#~DS z{g%iXJ)$z?@=4g!+~XB{*2DrKbfEpnR};Lz#k7O2G8Gl)5`MF@Q5lg}HIr;+>JwLM z^V&+#`NLs$v6{=dYH}#c0am94d@}{{Sm9jzJr;dZ|KggDRkd0Ypw^!|@rr38%5lVt zKd|$aYcN17l!Xj#ecj!x9mJ=vFlk*`=7Tf zT?-Q&PYSD1tBm2@k^g>ujx$r_>Q;cu1_;*&%DD}eXtpgp$*>bFc3E2@c_x!y<*W2l z!ty;mDJ_LE&RdxX-YIQ1YQFg)n=sQ?VOF|3vFI`-bX4sd+%oP!ip^8+PQSi`*;i8p zB!^jATE>7dWy3Y6MChC;B!p6)xuBxYa-VX;s4b{sQ-@S=_#uEshcHiJ7;wB4DQI;V zrCG4_E9Phbow?CGmH?o|{H`^2DCaE0m@Aib6L|PTsr1oWW}8ZPz4TwbadvZ~E*V>u zG)L{XO(Hj6l(*TT@&D9#Rnp|_WYtBkCO~i8Z^bNawmo=jIz?^$!!9@1{wie(CgLD( zIFDCYNH&*|QEjyQ0SLiYEQ=Mmub-2Zbfl`oF)GFN7JaV_2|UVxMK5N~7<$s}PTVYf zif-MVBkSlkD*K=ekjpYF%Y6Y>LzmdLk8{uBjYuk45BLLntdh_YA~i(s-~h0uJ)ybj zZDG#;yf6;Oy~`LT!jUYvnM%dT0QUVsVu8HUs4xN}Q<*%V$U0Wk9tPk0f(o%lbpw5@ zUL@x51U8%n3;l=PFI0)hEBbSH=F@3wwJV3}!>H+!qPEO?Sq?VmbBU%VX^VeNIwxP_ zWBTd8=ibA=-sVK?n8i5Y-`y9n3gH`{e4WNu~DsDC!y zEO8mgCZhpr>j1h`D$vgh{dImHON4r3^_X(juWOJo)_#2J&L)=LqPuacwtAgxO(&#x zqLlbIe19`sdPh#XLUS|5dU+Ydi{hPq&Ch5#T%aV4VAegFRcSkdLVlFF-4XYr3{$O=h8T+dx)E?+-bLjz3$n+^d21c39qTKr zE9>x8_!@j2F54`z>4oMivju?V&~)e@#67M&2<7sFhB1r{F`1)?M|ipJZ5cYRVre(~ z+*BncFZ6T)?XFwnW(KEOTIk-%9+w>UW0<{W(a)l-wV!KS%j(M-%Ua4fX)kwD zSfPiT@Q#t>r+^TE(tA0a+xgK+JI_|%T**j$%M*Qx@1BlF8^P&uY7{HxJR^zx(1I3) zf*alel<5mr|5TMgP9QAJ0)4;irWM`GC(0vKr(Ni0a728`EVjWP{f1KDk>VIiNvt*c5?iK_ zQ730a{r5w4{?YtE!wU+ocu1?4Djbe{*Idv~$wX@^%U_Ml3>BFhS^Kc9Ji#7@ZQqj@ zl5u?YwcCDW5lC!TQwWX<2cCjX|TC`kI9lQaX& zIW4`W?EY(h<9A@_rZ1VcGzE-QTg1zAZGpXtKh6UVBm`uS7`J`A!1#)704U1d@p(;M z+%nvfl&I>`7qf}db1)3Rxqz7rV!kRD7Es@hA-8IUJ*CIllVG_0-X{$U?NGbn8`>Zl zN+r~FC~5GW%`f1?404`*gPqxkZWMWM4Kh(Rqzdp^YTaEqpSH7@-cU4mQ7=2UFon9h72>k-{ng&mxZ}DeK|=rL_jBg~xa^4+EGhhyNjA@XfJu}o zayNH53?Y}keAyc1BjJ5xfP#enB#S4SvWyp&4x*y#UL_$)yBfLNlsMKOUaXp#)HoCx z@y4su`^~rVbC;Gj$Bc~)L-^ke2-6RJV!_=7*3S67ErixwUVimW@>h&LBX&+%OE0L; zz!12smt;Ps;Y;^!k_*j5=R46TNx@p>N;?C`;F@e7f3*={_Et?hc2y9m15)d4L5Rf? zkIojZgcSi$%O!TBEu5`zAiSiHGwRJz`rNa}MiH2^gw&?3l3Twd2ZEJYhbdA3nOt3p zr64H)&8o$V7kiy1P5PL$j;18?!sSE3%3V zSxydZfcv4zLDs1PY+SCTlhy0cnVwf4<7C(gM#;%ZzjI_do{7(Tg<3tA61K>M%T<6O z{I8R2zP)2I+t%24{NPc!F_G|(9*JZCNCCrmKyyz8{DriNJ>Bx*qRiw-K$rF83nCAI zU6}4mIcj~nfDsDH5iozyz`_MUD8XnC61(St_YoK?IfL(351MCZ-Ld4PJ;DdpK1il9 z5J9ebyc!NcnkTe;DrN$Jb@Kzh!zx^?xmx>LPaxN=lc#f&4X(MbYj)?efCE3Tvg+f( z2SJ>p(+{1ud@zWSOzl&`4hG)eg2%)d%~R-1mx8X3TmA(?dHPx zo*pMK46lBvLAIqsko}kl=9mJ7P)fn2gU`kWNbj1K37&|sj)~H%ctG|&tq2$z_0LbC z2Y9=nP-H_Ez!x~l1gS%GV;ScO62=5*k@f~B#n_zlH@TP7Im@^XM!hwnzD9Job?T{4 zxMtN?3$sA0$53Lisl|vvOys%AOjmplJwP^VoVJp6QVa%oq8a=H@u&_Rxeg6hkTj9b zf8LYv0qGS#aH{*kJ2{z@HuAzzbZyhv)c+`MCEe_(y(40Wp5Oz}Ie=l5e+CJg_H)|t z7yaumr6`%z9wFgT#Cv$@Yg#0s)F;#Sei!7S%?e zF{+4gwz=|9XfBS9V$bK&goq`D0uoR7y|f>AGzHT(*$73u9h%`nJ2oORPmh4V5jM(p>n ze?(GV_K(?U1NgU@jP8TpLH8u6$^DJ(zMtgvBoc^Ph&{|T)NkP*e!J9=$(10~Okx-U z6#1FQwSp&pUQ=U#2Oq2T;1j`~SYjOvX(tM@N)alizSLC#tSzbvWqfLqcMVAKdG=lJ zy@FT)5Mp%e^T5j(@skFHl(cJB-<(!+%a|FI9ap z{`2x+4eU;{tJBUC1ZPk1>!k)zd)~Ilbve47#j}6*_CWAc(&fu4#{~J3X~f_jIk`qi zb?$<1;9y;LE$vJ%7FK8Wd;UL-FJ79;l@RlzF&(IG88q+j8<%@o5OR z;`?;9Dub)ZZ%t)$|!mt(Q@)7^P7an@fsaU{7Y1;JK?FMJvz-sljqJ{af4TPJR15z$(uAEC0O@E^|Hj6k`NUwbZ5pomGsfDr z3jq?vV3dCisjBK>BQ%3h&Jw}Xh!$I_#KF9nu>Bw4mPP^%Z+#;r>-eZP99;QY^^H*#~jR8HA5FgauU8d4)DnT+a)IS z{{9^<)kfKL)<~>29;_V)w$N-Ty?P!P-(a)p9)LUrn7eI!FjfZjo2e{SffVPvGtpHF zlk1u=t$)^~uPQLC#33X(Q~M8)-K#7#S-oo4(s4ty45kO_?6{Z7mFOn?n5tzF&5LmV z86i8iFCeMs=KY?rJdo2p`Z)M^Tmcl0oCOJiQ+|Zf?Yr^oFNS)CWIpc{T!3i+PW z&5{eMEmO@lji;Dq_7AnSyEH~9>FZfaP8?^1gV~wzwfO|i4jrg3AO(Xp{n^*L0wD12 za+^WnSKyt3vgTm0cYB^a8tdn`4TNmxH3^iYTb{j#T*@H!&VTsQ-rIN~SK{4@Q@DJQ zm)*ck*MLfAUEK=um7F0c;-(edr;^;ZQspFo4Icv2xT=LukS z<%-fs<}n-kQlfN}J1YM+6GVRbLm!Z)e(UeWq`S(p5Y*Jf3gI)2UD{)+A!OLnk?$Bc zCxDRv?sZkG)C-)c`e}LwUKOrIMK%S}s^p%A4IiW7z*YvjMiumP^T_>wT>3c;!XA|9 zn_OXKz>Nr_L3hjGKnlgBf@j=xA8~GtWR<|&wg2pMfJjFH7L2^@=KH^T1@YpQdqBkl z_tsJ~{Kz~cnycncOKg!xb{qW@!0D=NBsxVCeBq=shyZ>7AL*PsH z8BvVmJYtrgXF@bv3m;ald(3i2?SL}_ij_*T|DwGc4oA=OTUXyVdHYs<0e0Mb4grn+ zM;(XewYb-a)=a({TAyL;`9-gmdlEK`gWw1p3|@us%Ye`r6!II<`mcozG2EkZoB4`M zeqc+CE-O2nf<66f*~6-$qhyrqpMAh$3-8v?r|>hp3ruawH`}s=xzhoypibs8XL{y} zVr|j}{VofRVcm3{JIS-N&&Wet)bH&KyQTkoEDp}5K76RPGc|BDAMQa29tITpQ@ur} zk&6ebJ^>a{fr?2hj7;=pgw+9%n1zCqqr+PO8)k*b`UfaNRdNQlii6Y>c>bWFmlhw# zUqRL{ZT;cyGI74?`0S)){R#b|SwzfD(WlV9~JTeoEqd4YQlKQ{#}Z)u>}OmjJjNorqH)@TZh%R)cCJ@%cF^8Yl6hWF|+LVEN#< z6qdsbc${!YY>K{HJqvk0<8lkg6HNTf+{OccJH!TKCBW(8fHdh`VUTD_Gc%N?tw+7@ zg!P8+ns%qjvri@$8R7X|@3~N^l@hnRC-MvFX~9{Ez{|s?0niKEw(=O zvQB}7L-fhN_UEBJKz0HghUdI|2BU!f?1wZrlW&Gc9gm5pI<<-@0l~_?)intFh19wn zTBOb-&09#Ui5bsZgpt~G(vjY=`NL{)3RsLE(R6cD(+zIYR1r(1uXn5QUh}^K=BkQO z?>lFEysPwJafx036lzN~gduU*pEWOxptXGtK|9PJ_6G1c+vGc^;nlSsi*onVYmrx+ z0rV&qTkm0bTWIoowZp>W%DGwPA#`_goEdDD8>nMq*L_>AWDPn7(j9bK;a+F8yZUK! zxJkwwWCh`#`a{7zt`9)H+)ZP=1k9E6pJI$*FA_7YiPYoIDrtC%9^832u?&MC&FS5& zO&Gm~4__xt;br~Yb7o6HC3vuHataORRTO>kJJ%G>NB`wbk4swQo1yCy_2&uy*<8@p ztF7I912IEE!{x!=4?cN|GfO=M;Y1r2nXd->RXZ_9mOr`aa+w#-6B_sYD4ldQC85)= zlxmT7k7_@~nzOqHN(Ey7rs}Za^~jW$F4uH*IwUb8!bn3rRCsqcTFPTW8he(&rNnpe z$pzigb9Lenb@|eUxRd~PNydRU@B=OTv~7&mCbq|0B}~xfcgYR7JDi8v1RSqUu@@i$ z1*Cq*p)>(9K?NhGeTYVW$? z>-o(zs3_ZuEiaPwf#8Bo--)}c)eU2_CJ7*7uvr^tS`1s;bNvB|% zCjo!{+WTzb36;yT0Z1{t^I}cNFuHhb$JnnLbB?(Z-3!9c3v`b8T5!wYgaH<0r)@?a z>}imp7%|_VgTX7c@k2(K!|{j5{!g*IO6||ZGrOUi%_~+E{y>8LoQRgmoZq^`^Fuf( zP8jC?3+LgF`0X@%(awLzP6ulw_2_KeYG;}}bI*@jy+WJ$nZS(>^|Y$9DK?dMJK-Dk>a{s%4b3UF!*t z*S|I->58hk(?&|r^w=^-soBaRf%_&b5B@m*6siwy@vMr$fIAAclz|b+)#iv1ER(L9 z9###SbEY5qRz`2CAj-Et(9)qe@t5v|bKe2w&}hu|2LYC!iG#jYycpYXY4DxYJrR4( zQ9Zk-jRQq>U(OS@tgU9*X*)d-=T?{?u3f(EXI)$4aCpdDQp!~&=+T|pd_VD6i^j26 z+2_xnvx?k~98I~!LzDWto&YNFaJx3NVf*z;>yK4wENTrI6KUHqb_tda;8Fm9m?+}C zxZ+{RiLsq>Nbbg$S{(A%7(82VxVyyxke4`*y&qqjr~#mBVW#h4dbJLRk5o>_(K0%G z^j6osdpU8;;J^jg^{{t8982S6;_oTc?oB^0yh>LnvaSD#v&JB0Ph3-27BF|*S)vgi z6Lb#C0p@J#)17$&zR+_j!W7kyPqV7tMRJo!Q+S>^L*e&Qyyt5zyQ_1VT1+Mc()8rj`!t$irHV;0`-Qv|C%gndHRjUY-%q5!XmrU#)LPrPXb2wP})B9A~ zpxBd>S^3fD2~`YMwHg}=Z)R~#1zcrs&o2MC-`U%!R#>XVrnUPi^FtiL(DFC(e-=NE z-=vmH;(D36x2Pw`$CShC3TI2LR9H&?JWCVQM@7#^rVK~>DMsJ6eekAu_i65V!oqfE zwkh`Y9D$i6Dp5%dq?m&WO%*?UUAaUw3Y)j_&l14#bazuLt3Xgbz|_$2z2RKjSw{0u z4esT5b@QOG7qGO&ure1ucZO_|UATTdy^ERyR_6+=8d!2b{5_e2pkY~x+Wj%Z#CwK% zC-e_)dC?sSH!8*}b)12@iyVhyisc1*i`#=l2qZXmD^vpYH0d7+PjAPH$OCY>UVv_z z0A;tYezBEk`<#}dpz^vQKs-u1$QY5y>JP}B)BHPfuMObRP| zugR+wnOzRswX0Aw45TJ;5m{f#Nfu+et*;cN!^#e3hw?P>i#7O--qQ{K8idk;_?fn2 z`~A_^HuR&@+pQ6VVRV~Zy2cDw((^8-&NcMCzr*f08GxVhqHVAE%qKo3x%7577EwB~ z9?FU}vbi zT&S$cVM%Vd^j99&-3*jT;Z(b}^*tHyndY}WOT6}60T({_ycb2%EcIHdSOQ;pq#!v^}KrdME z6^cVe0Aj<>0eO-wE$<1o{MRy!}?8iB48NJwVk2?h39j* zcp?rX>us10gdSvqdg+vpM##smkMYra1o7VG4RiJ_m|VFI8-N14pQaTi<7WaOx{M`6 ze5&2b0d=7dU5jvAc#ZE+{+`SHZB#T+<<)`&VPcqLSr22RSzJi2QjAUNSQ=Aw(^g|5 z?_{i)S@H9q%lh_h)5wK&OY=wb`cz`o)@Wgz1u`87AB;1nwafb*nxY%03y1lh7+F_cTI4<8jAZim);mk=$m zRDq%<4u~iBvjs)hr-hZ2EhL}en4vH6*URg@B3JogMKUI>Bm>hbm!FG-uMQ)KR zjf4jzp}pGhO~U!qzEi-$Il06Y{FTdv zaAT_fdR8^-Ije34^YJxPgsS>i@*CCB;UFJmw3x}&F6$~IrcDx!7n16&zj|J=V)sA< zRzL#nTejp70p7=DofxU0E)S3r89laLtobY`Cl)GMB7`$mUimgWD~Q`9&&>NcRnAWLmt8(K?W9%_ z>vqZGzHsmdQDG zR?oQVcnDL;e78>2M{-9m@W|~jta7J6>Yn2(hD_(D|!tXV!WVI2ju9w+Ls;p^iGy^ zNhN3an+_U&rkp3wYDc$ww@WotKSy3SCsApRN7dx8I@WAxvsUt2JrGY7mGPAj|6X+d zQY9Z#h8m(0U25B%2-nN-ReN7&`wxokm>s{E-XHSK$D`hJd&RYoFV4w8mH)A|Me5u`XR z5(c|P4skD*wL2`PtEtVI8-3QDyBCf^HB(lmi(N4zJ)jI<7YLN+>2%m#x^A1AxR z&L$ez1k+iYC0h6Q{6GURX#dTTnC%ZAyDkLLGU{Iy11_STS<5~nuHR!_-JE4j6_=0a$#M%O0>hE7Qb7rMsO24qj# z-L0>tpcV$HY06NnID9&5+%QPRNzN3Blt4nr(_~!dsc8jth zO(71=$xL1y>Nl-KG%2Yqg7`y@3{|n)>m$JlBTozHgsDl$^Tt(*RjHbJ?v_njD_?u4 zJ2RhQh~9sm?X{;Bi@h7YT=MPgUq&`yNbdj&7i?eTQwf8AR z+#)rs35(#=X_T-hmzAk_opO#cwy7};3B1jC+2YMrP{9Ev8;s@of z;^vRU+!@o2_@EykPNcIb+XX13s_Py{&hM8Gzb$P3g#S`o-?^QIb`Yt#WW*tuxy%c5 z-}gx7; zzL!A>2F0+NnjUhy4YKceI3#440#9^IG74iLh|Gw^)C^1K@0Ui=Riiwu_P!8JUUwJ& ziBUvJUl=Ly(5qRw2*LqXyC;aLW*fo^V0CeS=eAN`s$UGGxSH*%G4x24JNXWX+$rxSW8PQ^@sGlnjK&* z`ksS5GTyWnEF}47wL+eMH!G@(xE9(lL!?GIU_LnB=|fe7^fj+Ps=uuBtk&>yo#2%6 zcYVI2jjjS(v!wT7Mk2u8=cSE?o(c7?PrXQC=T#A;{^#Y8LG))Ul*@DT ziC>;k=o~0~z@w#lpK;Gg1ptBpZwWW#c|N1>^6pIB;pHt+toA&@hH>lgkZ*ps?Ly{W z`$Fah%NU4!K)omam!rQ`=%94*XGHlAAcnM$m2Y>|045T9Hj=~#NTgb4_k+~f*y4)Z z06o;p5GYUAh?8@3eDD(#N^AxKu~PWY?avO}t_0XSP_zoNq*CyIj~Z!f9yeOMO|9BY z7aIq`l#ibSVREoC-{!Q8105f&`KmcNd7;>0`UE3m*Q}9IIraSb#B~bA&O8%^ADHGq zC`fCrcZ@!XVmT`b;6wx+Vnc-?jH1-fN7MOh+dC#ZCc92MPP=?Nr8|7PrMr|a93syV zc0SjeVvZ)_VB?&BJP|Y3a5}^=g}ItkLE^IrDyJ8C<~MpB2zqlr+0F|xAio`o<5`{N zmFf-eA|VyzJ=%B(0u`^JRc@LJU(wO^6r@dYjt?sBImM%)^wr?O8@8yn`ERGvETyGm zWlVIjmw&Kpd(AxSQ6!7@%h;5Yj%Bfa%9pG$bCB{(eXZc@Q->aRMSrpn|-2B!81ByghJ z3?4K9OyEoEX)7J`d)0oAs7ZEohAjM4^tY9tz%QS!oLRBntI%SbI)4T=ZyX;Ox0tCY z%7-fIF-msj8|aL271`mN7hEbi`Th?FtFi%DdvLfiq^IrgOMpnb0B)e`@wxIoU+mcE zyAsrd&Wv))FMl7^qe;0)oYFLMjV&`Pr3h@udVZ#ql=g~`&F1=7dgBO{DB|r3Vxuf( z=)=RJdAp)XyMi<>m!!Kn?!K&bzT>^|w<8V)tJ4HNHgJg@yNSTJQ7D=QTsUN6;51Nc za)yr6>_A}@hf~zi0jjItyUuuekuK$=rj2VgVjlUOnV%st?-pIbxos6>8!ICDXr5sY zt2MX9@6W4%0^~pGb)MM7w|aYiXt~I%zV;Ay=$fj;RQ<+`nnd$WLvGg7yGW6Zi8s?X zC#omw(|Uv2O3lsZ%f^01{IdWcQJ%LL;0r9v=`=>+c>=2sW}uX{rj)Qc$3n8mkjjWe z;-jsT?2rs$Y*VX&ADR;om8m=C#2OaD)YA^pj-aV)a;_zK26NT^8S82kjn8{KIAn5m z_PFDd)}7BvIOuieg?|MP-V04TH2g3E0;cQ|DteVydP_%r2=mfBDqbtUt-M!WCR zOrLJYRxX)st-O)liuc;fo{Eukvpn~=(d=7a4(%e8`bcIV>`74J!aFJdGUMnqnYSr| zGiuyNBVVod?MVKSrqIC+m?26c-~j~JaZnWaQYQpCH^KFXT@a}7pzA&)F}<9v7&Bzlx#)k_jC zN{fsSJ@*Q5XNb?bs>SD;e2{$aV5B75r!#{nFM;Fi|CT%8O}~vi7;KytV%FgL?NK>_ z2zQItoQ<*8qp3^)nZTbO`ANPZJ(ne6_#sAp?2{&UNPs9 zF=B_Dy-rJCK*-csNgEfr;7l|H*SSo_mo!cF%yhK)6}U*BeeJB3p@2}}!3DZEG0?ri z^)G|WI8Qh>2Py=EV6FH~I#sFFEGEg5TWZs2iD0G`FR{dSEaUBNDW0q0pU#pY)EkZ3tI;7LhQFtCX!!hL~Mp_syEBnWbU7C(cyB+x!3{{MGBfndVb z?VJ1C4Ug01s{tL^n3f1My_6<+#L$7e)rYu(jMH! z9t0$UVKhMwW^ny>M8ROI#K24ge{jc{UnLB$YpO~lI9|5%D|&*M$ZEzxRmEsP&u?OB z9b*bA0(D+lcxNrnIvT}psWqLh%tUK`oYXScS1*j_rcIm9O6nC|VNb>?EOBv=O?p=@ zWZ3t1Xeg;lWaR12V5prv=SNzu@_txLkK;Pp?NVj%b1t~J4b?gt`^s-^h7Vx1|rpK199RuxOhnz)Wr)E0M2(Jq50R+00fC8lVDDNkBXq zZ3FSNLJ-fHC?Eapt7sM~*z4!hDVNua?Punx^x6F6&+TSv?USxS{?SegoPRW)E{b*?K93HE9DQx*?bCb4LdA~!iAChs*1*=j4?XrW@n^4s$s8>@VR-jjP0zS4%zk}e@ zTe&iigW$oiZk_r6vIr2H97G&<**|Wh4OARgky;J>apR^$ZhFEh1Nw@5GwblBgoIVX z@rgUx{+glnXV&3E5@8fx`ji)xjOP(3Rfet8>ONJO;;bYJDyyd``qHfQ?Nm3)Ksj%P zxyyCHJ?QTdWZ&7~ba}+#N=LJWSNTd+f$Abip(uS?^Lrf0GiRV^=Z$(@ir!1rC#SA9 z-RFdM&R71jUi{umi;Z)~I|n+80@uGtA^`IpRVY$8JLqH2+|;97ef3fOl#b<)G|{j8 zhrXpBa#;;^1wM`@>4V-n{Yv-m@W*FUEi3V*!tbFVw;C(!Lp~93^}rrBxb>&TZER9J z*4#lkowXHGh3n5>e)zL7@$v8MktkHp1~aKJbx7XrLe=sfas6h2@}Y+9nmfcVa7j|H z3LT_r8}(vD$U$%2Z$oZ<@OgZ82)Sp|Yq%I7giZIzk2nZn2One0f9 zg)xz6QEx!(3XsUx;Pb`AjrOf=v0CNiNHAVUvy zcNd_9Lf5~rq6&ryZ>VG)&`@e~(Kl{~t4Xc7OJ5GN9Fkx*X_zOnc%;-%c=H)NVVvj4 z>HQlPmtwQfpsWJ}nVgqjA5#=&HS{mZf0&crDB}F0E?V2pg5*kf9qlil-US}ZmuVNn zq1>5F#h=mJx&xWV3B$KgGilb6FqaBV2FyP03&QZk6YG1 zp%55w{mZfj*~%;pZH8-~E)CKzC`+xqq1>5$Az%kH)pr=FVA$d%wyd>72C07%LIx#N{< zu7x{;K9-9XN?>jOdy1L5j)s^q7qU*1K2K8XKsf(#*RihgT(Fu%pdK68F_iTaP+M zJ#87?cnen-60A1Tw%<@h+E%xt!iFzo)`9sM4pIoS-cJR1YQ zUKQD?hSXInQLgWDtVD$H8N|EK9eO`yy)2freS`Ym3!nU4p3nLI7h2?lkyOx=U7H!I zCx)@8hnNrNXf>8tN1vb1zWM4XBBr-?ek8stDag3BOTFmV!In2epI)YD_^bFcbf23`YB0v?`8_mxM!FsEw?#`= z6f;R_N3Y3}!$X5E%)`GJUDPOh)s3k4w`}QT43wC_pDm5sXqqtLT2C$U!sLFzUwHbx zzy1(Dx~aE%P5Z@;52Las5gx2=r%(}E`RPb;%y}SzCp|jAUt!~zjfU@@MZpPqtfS9p z+%ebohAbAfeM16e?(zw|jBY1+@@XSNEh^2xVRh>Z-xI3?G8-ZPzSg%hTB*HMA9&9f zVr1+(-xhUJy_1)E>$Z{N`Za!ouJ!7&dtcG!_>vcgR;{c;ukPk{!ya?)-Y1cb+Z-d; zUY)POz$M(kI2^hWb=fiDB6}ZS$f~)zqYPCGZDB+Hi`l;Qq*$9AkCVP=!GYS`=+TpU zzg(&B@ z2)g3RTRVz-35~D8ZT0|%TaWFv`D^g2zXnu&ThwlmSAK${X~DnKv^3a;Z_u3P4d&X| zP1g<-+0~)nZ$P&-Vsd}RW$eR{rMzai2VA+F^hN7rD{&)z27lxaR`Lxt3!uVM(qkY^ zdc0WiD+>H7=Cb+}Gvlrzn1ENcj`CWIT9y~vFRR-6B(7(5!L_rdlD{M~ViMhtr zJ2O~zLL$^^Dh)p7zn}9g8tS8rsF*0CxKDs!NmB1ycA52QpX(z{@@N!Ag{?*;c!2%? z(gd+nAN5->hMwo&lRa8*;1MF7PRQiMq{Z3T42gXEA{`0_kdj&a|EsYO-;y~|m zT!g$Hb!PFw$Om9;?2K=KzrxB7s&Cy%yvdZ&5w8cQ@xfec7NTC##!HecgLm_-s8b=T zmbAagEAQc9H?kF}@sn!4t~Dt@gzSW)i`x)kAD`tTWh>atafG7&cS8N3Ai1tV&rK|W z2-#!XMgv0mTcOk7qkjm+ZZ0aOD58ez!uC>rF(4HANU1!!`VXNv|1V9HmOiF@3;IYu zQ^V{v!inL;>r6lwA2;+JE?_*Svg|i>;fFT6R=|T!Jpe2AEwtg|u*l@;TUwR!3#IU( z>6))aciJ$Vze+Q5p^aqu|Jg|T(zA0yK;!@UV(Muz#j6x-Fj7{GXO<5>)K?>8toRm3 z7g4e8n3d3@>irbkTSNZ$UStfRn6FaV1LI>sFSYaz=#iEGkQPuJn!J&a;1yW9Tx1P? zy-4qZ^J|pFQ#`-E>2=CMdsh!?X4gNoHHByJ`54JXt4huVTw`B5WJ(9iK7d@llbdiv zYk&tB8rR3z4_&1xphx9%r8($gGwtnb{VN{H6`3Y>G7^RqVci4iDRF^TDU|jIBOH+eVwz&0SA<*pem?!=@V8 zGf;NQGuo;(4L+P*{baxt(Aa+x8qvd`e;*Nvm*zEdhCY9LZLP1|7k#PDodYL*@_Ki-_v&* z1|y2Gr{;sB^?sQ!qZhTmA(apCutQ7CO?YPc>-%ehdmr6xy5IxFa70*kS=c}sDY3FH z#Li~_ot;Uf1+PAwGMfr6*4W*jb7JbGj;nP(_pA%9QD)Fxx}?4-Oi2#463`vc!~j~^ zbyAQ`I(miIAYGEG00X+dpZa`7e-&^cCB zY4D-!Sp=6{(*DU!*;j+Km51w<;*a7k^#9=f@B5a+IbKn>^1wE#tND(6v$^etV-J&! z{~HSmVPoP+sr*?>mcv6VhX^|zkiMjSvf+N&lkU<*K)dvnnp-V89LbNRO=o3V+_axO zrHR0>5h?Hwm7VeQu=_QCk$gvtgLfu~0d*G(*|ifCcz5pQiJ@Y6<(<64K0BJy7@q3?IYsnhmq!wQdt@CU>BLOHM+IU3)@&C`s=y_^iQG5 zzKR`Q{LNu5;5dwXrwfpjUjY?TvQ9Qlm~n0BC4EQnUge+TGaquSd57w6V4e3m#XBeF z__ly|C5(Ad`{avItK>9zf9ouQGiR8BMCCvzV&p|yvBG@R&|>}>7)&`~484V@cFpf! z*~Qkl{nWoz1gQZRC9VE&8+;tMsWTM;$|MsJAxkAyz~^qg>Gh$#EOW>)_#gdvhQo{-G@`qY5(a)OkcyM^FWx~BZP^H`Mfq$ ze#TJcJs6BY^dK(pKb=WCz{9PLc&zx)HY43fo zZHAN3X=c$|+*mrIYynb|o+SkPyOTY7)Ab$`2Men{d?S4F=$!D*ZOw=ZP9}R6L~1ho zjW%E6yBf-4wcSsc9RlDq&SWt6;I5SkY>iHB`ogy30bTys!WIH$=H?DDNcY(*ts(}3 zxE&}#gxi5n@5NK3T^*;)WS7@JYb%?02W$R3_S|+XfG{>gW~gX*q{t zI8W4f!IjG%b(hYA$s&7Rnb(*YoM1KZ#GEDt!yTT__5LH1+b>wf{W8gs>+0Q|FrbX7NkzQ3+{m_J@W~QD^V!%7GpQ>-f6{^{w<` zg|%y)7Pf5;^zz2Kw-8mXLH#RRj7vl*fLr3a`f{olM@SOTTTf4Z7d>g*c&ZPpl`hYT zH%#}vn7RdsSume~^cO3z*XDsI17d0$kf(#2>yL3aSD(I&X9T*5e;T}JVHUx@I808$ z(^olA8#*|8d-b&;(kNT4pI25={=reZsmF({mEe!+eX=*8_Fvq@3TxGBENojIjOLDg z-a=HkD)z4|CGTGKMV^4V6l~MfifphS;AeseN@%iE9SjlH^V!r!gER$6Pwp?oyOX_o zGc^*Cpo6#Usd?bhpo6Lg6zSq`DjgInY!ikSsx>|qSYIOWw(>}WcY_|Xoqeb({X^5j z=&1d4p$uwuH z&@KH_wh%MzfqsAIAmdW(h=&;?=zw%P^9G{Cb)^s5E-pFrOlAWtS8>$R;NI@79%7Mu zi+pfQOJ+O$fDR&DwNgpB&qN*z6Pyu=!aSp00_p)!IU*)rj7zBz}@8q(9Y z>`WOZfqhhOvuoNt@IN2vFvQrS@4!O=iDi{ z7y4IzVTDIFYewOJ`l9H)lK-kiF@ku{8|oXwM9b;{uLu{8Y`*%?%|b;=e{u1JUAkSF zPQfYnlKv<0tR>cDt8cCB(%_w7+N4377n{Y!Esps8@^e}b^@I9zxa0@Q&h9)YyW3ql z>#!-DvXYuXX~Dp7=OjG`aZWVUQp?skmoYx1BE9vWuE-XZ{ih!Wk1&{jrcbsS&GJr? z;XbFCpbpVl6~07kOSt7chRZ#T+4t9&Ivu z$I1iHSV)D+hM$mxcM(V9RC9;ojHoa`U`>X0h5qK@)PM0VTmUnJ3=pU4S;y{~SZ`@H?|* zi>uSGZu9%a0UUx&9d)cmn!DO#L*!zfQ4H+oIeMOg$`MAdPq*>vz-}Lim#{$oJ zfDY->KOQPO_wqs6jqcKqpg)phca(X+s@1zrdl0ppa-BEg=Ks!^h0SRu`YX^+A?>ovE32Wj(nBW zjxv7gVz12&KfY^T>~%_5zq_lCnK>-rMNxTECGo$sI`OXk_1wLPcCS>EFLE13;W8XR zxHiv>@)JKbVb|7XU(`O+bUi=2r5tX|j-~Tu)9s(slU5RSJ&Lh~326^%b()WH-{Px0 zA6c4fDLKZdJY7uw{9#9T4{xcgt8z&Xe~BzxuZ2GDBp>J&$Jf<&_QY~C_4YUAWB+}Vf?Ml<+QWUi&;F>m6?=iYHH5@a zTUx0#s)O=v;ioXgX|6)+E0nRd_bP_V<-r(7eYnpOQg8@$ThcVOKNhKA8Uv)Yw@2TG zs{+FFC$-BnFbvum$oG7HVRo7PuO==E3(}|A8P2`pj-{s-QGG6x18A9;<T@i*=;W4t`o#$N3n4z@MbgT~ms78bSfZ!T@5YfzUZ z0{RhQ<9r0q5+TLnO%0ZCQ}dA|PZx|(ONAUD%!ZzVU= z%*ynx3kf)k2Y5%+N0L3dtIKg&cMXRh7SWDiroOx);BQ(OKV+Lqa4h|lmePpA_YoQ) zmqSaJCZk=4%byVO>Q<6FezAb8oBv$y0!7o`1S@kY3OCO^nbwKCK}y1HAGZ?5?#F)x zzw}dV_`=2#;&pjtjbXkm`m3XTbU$2FVffrclOye-FqeiU8y~Tyi4fO}-Is{+Zg^cW z{FzW?+d?Tlzg3@jfV#BLAY{$|*Jb-5jy*Z}7BRli{Lj!`*>@i3!S;jOd+b}~<5BOI zn&8*aop9Z<-E!MIp41Bt1$hskwJuN|yEIJyxmG0zSlZF0zLrvD4kOc3U)!`Yd>6E4 ztCl^PNJcQC|J4kz8{H!9jOVTteXrnFWo4q|un;k#8O1R6f3yt$7Wb0)VN+*I-t*nQ z$nAINH}p>iUHVWgYm?6j_%RFPkiTI*mbn zqSj~VoH_0)QCVV^#qOIK!qqzD_wFj^RbtOpm`~DjJM0qQsM#@O%ogIvo>BZ*wYiXw zdU#!4n-$$2Fn#vLqqy&=b;Vy7MAytBi-mK7{+e*HfxB(cYBD&*)%WWPe)+*yW61?$ zH>q5t;zji;bk;9x#e6=ildjM1Pnqq-eNn``wMm|6tQMStmC;vNB10DQQ)9>+uv7~c z@S!vNK2}q<9}h}a3#wNMX^W0jE6n7Z>|)tMd!1aH1P_v2me0PLaNiJfos05am+bC$ zo9uqkE4#-OE`?S6-hV>UjSkI3fx+A)B}rTU#fl3$_=m=CCU`WSpY`&TeVH+zU>l4M z+TU$4E-K!A2Rh2@Od2TKqHpN3bl^j7HOaB}puBVKM?Krr)iV0s4PzE#vv)_%$ey_n z!cAVj8jPz%*;0Ayp_VBdlYMKVnSM#@$@q}y=QqRQi0ziR0J~p#@h(Fm2WOt>TImlb z?A58R%kMeY3as6DQd4uZu$>ondRvbnzC^Ls%s3J`H*9A*U5DKN?Avk^yMN=I&Mt}>=+MPO^KE1SLC|ne*nH5ghxTPg2Oj!cFYxhi`sRywq{I?Z~o$i ziHEIRPT^PZD-XT_7g=f!r`Z^_r>=vEjB8bX!;aP-*+|+YC5DM%@e0S3E-}Q>uiWHQ z`2o^o^ZoPZ@i=o{os*f@>%O1sbzRSUxLl%O@rm5e#THo7!?4!``naoA zYFBMY6!<(tw+(k+s&u#)YI|JSzG_8jc?rX^q!u}f>IBwZYXp3)UyF7$>p7PF-i(LH zlR9x4hv>!NpWn$^uQb%WP)PV&grI|OS>)S*1;oG|&e;_aD#}7iP3!RkDbdL>p1oK$ zh!xsJ?Y#nN@SO?G94ASM7fBZNP!V#rUHO zY8tp3;F^p^S^M;>CjuWXD_SZ*n&VoocCRC1SsDAXY;rUa)GSHDn>QL@ttyro(O99#)PLI3|M5c~V<$6&lODfy-}Y%7vBdQLIO{HP()$A~ zJb$y8uMKtu9}n-{Beqjsygfmt9M<2~116TvcI|KaRy1aQeuIrE_`Eev`_T@T83+f6V5IBGp+ z;26USi-z6x2>0lR9a0`Q)wnD2s5mV>Xpd9Ra=O`X6;^Y@qf(0WtOe3$>gq!X#f@(3 zo?G(K{x6;{^O4FH!W~1Q#tr1kBC5;y9*yXQ@a*Z|-<8kST^ca1{9M&c9#ps{h@1&fd89R&p+ngKsW+t@L0g=n>{3%Pabss6QSwJfl5snF zC!Z)S0=U(}`G~;#L;VJQ1Jh3Uw`49ERQkXozLnz7RL#wt3t=civrOFNwQO{}#hE|c z#ML3{B}p>bPW8Q`mBfEA_~BWn$PanA>dOXkIINiu`Q!kxblj_ms8M|*{9fNYsE_6` z`p!p;;$FKgyk5W{n$ty%{VEpSw^O;pzqovOR&hjOMgkDar2jk`Cz^|U3$Iah0!%?rOtF&wY@^!0b} zFhNY1r)~>Lrx2yuXO8SWxHO-%@_NHRK-V52b8%H6o>=*?#mBY#eW?GthT!cGYA{bz zr0e#q{Va5Oe7x!IEy)6NojhN<;4Q54xURXC_5*<#eKRE?*XX9ufsRb@UKw(+Vx>;( z9mP`t?t6sHyz1XLhJX?QufC5QHl#rACZvh*-|TG}dpB5GWZi9DNkGugFYo}DDN1>x z@HaQ3am5N4P5VcWYo1R|EWg$6Lk?ygi4a7e2|o zxW~UeO410XMnsZ0T*mcW1V@LJ{B}MNLGuog`dPs!t@2z3Uc+B65s|Po? zLRQ`R5`<3EB&ehq{pe$Kl1xuV+)UW)b(9nmR1p}M|tzL89$+~v9Yn`Q6KfoxaJ?e6@8$~ zg4c3FLC8$;-r_k$62_x3GWBJ7tl&+tFfxDf?BdbNm8S8d*bBP6FCpuhyb#j+VAt`1 zfcxvm(!x^$o;Ze?35xWW6y;IRE)(vv?ypuPvf0jY$PxW;DvyLAB4JlwYwf;Yd)~4r zk9&plFZKdR2Jx+;$a5Hs@rU}2C{G;na&AJ!kj8Hcz?ZrmY^x3}UHtT638I5?^qKej z2&&RK?_jMa3bWIOX({2wJqfu+pRf6Hl^*a6PKVi&g%dR{C)-7Jd`$VMggL#2NrlF# zvfUmoR-&`97mxD>Tb_8-KGls}7&@uu@-o~)XEqCejw^7nk05hMtmS~J(dviKqtmfY z<@Dd!K9DQbslFkE7oC86{>zUH(ct3Xvd4$(w~3lsgna62Tf1#mmj5*TXtvNBnzrZXYG6m z#)!$^DPPAVE3@y!ow65>TuNTs*kcLHBR(e-;#@7DPA;e6pV5x_GeZp$=k|>dN0VGd zm!w6sj|Mfmo`x6BruUcRUST;RrQzirMixxf;mK9F1)C*uK2J_G{RkmAn^c8b&eB9X zfBlL7ZUCN6=B$DmSf0y*qy`xn-Sv$!n=If6^L|rx$wqZ4?ux+CHR*UFAtrFz9p* zZ)qnb4IM^d-#gRfHh=kfaAm9bSbSU3Yt`^}>GfFkdyes*-n1pB)GL}%jw@+4XIYWwgzG{mxLG{|9AE#TYle}@6+tpBiWow5>xM#HDwM}t1r$I{?fj+XmPAyuPA zsC->xsH^RH>GnQ8?>+}>gL(~`rTb*x&Hr}VUlbv9Qz+M@wI}T0=zC@-iLx6d&4$<` zTkldJ6wTog(-bo5UW6*TY8;7LN!yn^$coZ|4&fDMU{dmdugA}bFjy0S^X* zx>=5%Q|=cB&ht#(wWbvYzGk&*6X#w;)+Z5G_*!fElJ*y+d@I=~`yJtve;f6!Hb7oc zyhkixVBZKdn74np%RO_f40WX8;qvRr76*oLtar1`eDyBYU;BV{Jl!K#-UeQaF0&z`~dkvaEP#pKRl{WGtlt;sl zt?JLmW!XP#)FOB--LiUF)9IpJR;q8@PQ`ZO1(; z8?@Rj?TKVP+b9q>#u0=`i&li(*HtLbQ4%wO@DKI5b+hN8$NgL?o#7dB?&+6-E+|f# zfnyx_Kv90trzaG2a^~c;4ZesdMJ$DKLCv5^(1Fap5aUd|B7Gy{ubyViLJhSMR-72B zI_2*}!P^_f36_?#c!u)!_uqY8t4jGWQQW}mrlNo*mgI?C{bIsU^zbJvF1%^0McSrG zNQ!Iv92ByBn!mQPz%A>=)v{ibJw*?}TSdqW2}CjJbI^Ba__z+V>R#weC@DW^%|O%t z{N?9Z)K*+~c~ej(3?jF|=v|p$9WfDO@s`A=bNRpZykmBn@6rE!($G=fmQ!2cTayj$ zskPU=VwC=|zF#@E>V$N1ELtAbdp=J)HBqqzBHU>ora@YdMx8j;$5!{rY*|OTd8h#7 zbpwI+PYBUZaloH4P*gHK^)*fY{Ffiets;~6!;LJNaT<{N*8L!=-@MRJ+x;8%4Y`5Y z$tidKN|P#mZipX_A6f}>4tQZdUj%`z1^;5vF4#MJhnngYeLDY6yAA=`>{B4x&lz&~ z0%PD@=`LxJ>eCESo#o$Hr%V(VRn~m=Ej_T~7kfEYap`Kn(rOl#EvQ#xZK*uVum!h& z=SuF=6bXM0v8Q_z#}=?J&M}roEsi@IB4V)&?6Zcf%D)H)D!XYkO#4iI!#0nD$yW*# z_-PQh{-toXy8Vd2f@nEtMeq4x?YgL@pqz}%GdAd)-R!^EO_i~WhG|!FWR7hr78bQ# z#EHh!4w*D5-OhO3fcQZV zco$scCXo3Fyj6p6z(Vn*D)W}Rr8injPues`u&+CdFodWs@~=%fq#+|}4H~chBDU-< z47?Fn1L}T=u&D#*69I}EgaLLHU#dL+`)+9&_A1>aU2~L=o#V@Ug2`(>!Mw(rxrTpp88OEllSv7<=Y(KtKX&&q+G!it64bzv`SG?ah@OnjpBk_>P zhqWxb>3^}I7eD627B7vuE^q`*1h2MF8Y5i&89+N-Hgo6c#v0kR;iVV&E``eONzRe_ zpTO3}v8^`Xac{3NFVhXKaPb$uAI_`mG$e~3DTo0q=ZfNon3dwt*I>=K%M41Ix6|j0 zF)RS{=!hy*L+HVr2p2AU7XHHicL@rFC<1+tNEntgmZ>*)&i05UF#e5e+H7z^-iu7X zFofT4d6-Cz?}%$ZSa+sWZCpt$i9E|I3<|UIfzOW7hfGhJskBY*YyZ`=u=NdH7Xan@ zb?wH-vCKd~Df~f(W6pNZ^>@r-9V=M(#xd2;(T5~cw@Q^`y}t+ubWa(F70p~PZi=B^ z@rb_m*=l4FFYL-c(dmh1`@6xJeVGRvTw}O zFwLjUToiXGqo^WV9TdZx=@b>i(}Zlk-$JX*QmM$SX)oweRGx3BZ@zhud7Qa`1UI-J zonUJusD8#ht>S#H4T#~i4i#0r(lBpA#$`S0YDDqqHzV)IHR+Tm7ysj1nEzs<>6m5r zjr>OU(~VmQZq=}KkGTTxZhP=kRFH_?N@sNM7*#94H^%&(4`FNG%A0h*2l1NHS^QL$ z?c3_(chD8qGmFvvoQMLqYPR1Ve6ifkO82v&t8S&KZA!Be{tR?KKhVpqf|u^teYU!t zbU&Hjn`>K@=>LBo_uEYd5n2uaH zH&4bB2t8SUNiaYLj{mW8?`RAA;v!76OR`Q+NB)Ff=9Qzm=i53FK1&+wYolQq)zv10 za4IZb)}z49-Q8#sXXjk=EigJd`kMOR-jiA6$BJI(0ShjjTEjBmU)d=`e4BddshBYT zEI9FjMcn)p3We%M6VUSV^4-+jr6m`m*A@@m?%(hI=@#YAZ?Z&Sb1!;hGK|7s7E(i9~y-mb`;%BVSmfD@I;= zZ1>)lSkdAq`;2dUu8+rMyn3ZxRaG_C7{Mp$F>i{a{;k3Zy}BH84E**TM5S^LnqqdIuf!^>#r=dA+%%wJ(0q|qv# zJ2D8M6}q2_kbg}%sPY)yj^V(cfheq=@MngnA>^kPs9$K;u5{n)P`Y>8oWQT&XmEDd z0eA|?ui53E)K$7p=Cwq}#AtYVRi3)}`VH||5fX_J-hL(V+$WjHN4M6O#{D4DETN&H zX_~64e)=!knAG~fI4v)K_XPvFtAjF&9!qZu;Nl*4s2(nTWpEM9JcXK`M+yJ7G^zpR z>V8YtxelJGCXBIuhEhIXok)9x$fUE<(k2MSDVfcuc)Z#!WvJ-UrWj)N$3x+el)+FK zL}hp7dE?&SzIT+skMGgkT;SeKh9K61(cA|taoza_BT9A$M%E`0<-Xg{Rv#F9ifmde zrj^9gPV6F&kUqwrS$cO!=O#9u(%asFPJ+yd=Dmm1ApCkP4~5>#a}^#w#ICtJY<^8~EPp#9itmJuy0?cNH9-#W#32{4X5?Cl z9D+e^(Sb%m*ABw@fv?|tP zP6IdBV=qvwRe`HT}+2NgHWyMSvc`N z_hAkAblRjt2NeZ}M=97-lC91Cl0?OG0-HF*yqI(>Cd>^>VD^k`4r|>qPlHw=|RIWl%tUkIZkGNugv+xx)xc3(Z}& zwu~$XxqZj%aKvO!`vf<)nFYCGot8sT#2KDM4J*Ckes_#&H#u?X<31ckL2CTM`Uj+s zR;J==r1PprNy+fHH-2GS$J^}@$GaU|KP=40b#%Cp4pZIT-CIK=t>@P#;Gq)GR51}E6}8f+*wip;*jToKXik0vX{6Nk zjZan5pFqzZ70P)bYD0z?_LUeV7&B?A85XAN22&h3yzZi3yTsOX!jp7-7wKIlpHa1( z`c}}*q9#;=#h#G@N9xm>K{(F}qS97<&r8hYL6wt#Guuro-ZWrlU3sYv@2?IW)sqmj z7o%|m1jG^2Ca=dkvKH*KK2Z)o=FnZ+N0lF~TM!}${sui8fG~ymMH}Ae%`(5TiLA(W z#-FhrCn;`J&XVq;VVd_Vh4`nepwO9p>*Mc+G_PLGht1HT(29sfEu+PW$QK)0RAL4G zTjd4&0EDne?pSxMt~$Um;xQ^ndV^ZgX(@ow-$8x76q!e=*A0v`62lbv0>sa_zo z50RGPjm{n$)szjyQZ*OV5KeIhnS&$8Ir~u^Z#j}yc^}x0m&9Ov^jl-1Wmpd4 z1-BbVwjrh3(M#~pI!K;V7U^M{DdRgug{K^+$uf6`7cXIgC@+eA>o3Qz;f9(*rH4*3 zu!=@?cb_BmGhj2y^VO`|S~%tJ*U4a}PopdEW^b$sV2#x7wROA;iAUZ>xbENoC=p6M5w)0=421z#t?ooLmtlWy%#PT$|aDI zn{{0mHFrVp--6sRAEG@g*sS_aLsBOB$oxKW{;toTTci#-mF;R!E4n}6?ShqOGD27- zh5*8EcD88O(4_dyJ7g0y)2HNOCEvv~0NJjVHP$jQHAdUsy6Ly_{{3X2>1Cwz`<&7u z>Kop8a<30=DE`E6DD)69j>m`w^PO{BlwU54KV??dQ3cS{D+vjS#3nvLR8(NS zma)-^MMfVH!QPC!PE43;Kiv`LU0dfax-4aLvlnwnLnvGoC}d2$bd+IUEok|b9%I@C<=qlRuc^`Xo5?l!*Y z>5G9!&t(-WZL{=jW!?Bl+3OL=dJsQE zG@K2J+atN32g;+3!QwvFFmm0%DtmDCE8dsG$jpJLl)Diw#v@1`OjomQMbBPe+za4Q5<(!h`Fv45xnTmP4$j38v{fMS!^DI2reaGXWeqT)Z zWdA?zOAt40U-FR)K5$)^TBKSlVj*^L*^E5>`pNd}!E@8oSJfpycOY&}YfBlmr*XIu z&Z^t$pkkgS%ZSW1$=GRi8{3tpO|Ih0jLQ$PY}Nx+3tF@qLGAm?BV}+^H@OjvJj$@U z-j(vsX2q}~Dqp3J6^-A&-XkS#jLm3Iim(F z5w};Uq9V=tO6Mhuh?HTsmr`yJV`C?>tSGD`=;O%RNQ18W+I);6moCe-(5s&$M~K1Q z>qJldJ)2ni4t$9KB3VpcUI@2|mdE`L{iuni=mM!Jxh+-smY0g-Kv}1Zq+jfXjsX7= zwNq@qWZC;Z7F<0RtMsHil5=X`CsA}|3v`|qWeWapl)0Ar!>0bRIAd0r)Tx$Jshj@B z#j6}dgu4*-yBuFgCpbraw7h=^Rp9JPqVHy=Qnm+KPZr2`TaX-Fhu9?9ko5#;$u>Pn z`Vk$3SR_e6{v;Jo5XoQm_58eJ{cTtQ7iX*V{)t?C3XE77|1m+{`Smf2)QsJtYBi)m z7TgfufPt-ToZRB@Xt$Mi99y7brdx*^1KV^}bf=X;CK4m(=ox=pZ({r`kqR}=9Kj+< z`?X>VO6H?twVW!Z4SGtv5~tcZI^6o$C_Yu|Ri=~Q>)kDcBJd&Qfy(y{{^xQKM9LW! zcCh1}hO2)>$|>7UkAdazyqlE`#MQe*)aZIVl0EEBcqTRj8}Ahzuv$JJy+B=@RMqa3 zeDjgj)(np&ory8L(X6kCGQWi}I#ychP9Cb?C6?>AK0UIrnG@6-H*z^#&6C|uI1z&|9g1&onW%?y$J3XTb;&fQoo6?EA|OKXB!*xA3hc@}zv(U4Nw7_Q;0g(${bC8I40ai*$~IAY%G4bKv(hmXXEv5^hV?P8iWSY!IM|n&T=?$i1LtAF~XTFYqsk-tyJEMmT zSNEj!pikd01cT)}ys?e{evMR=twDX|%Do-V-HIhit`eljt2mq@QqR2a9>+s{ zJ+S=iSxAUidkXpPbY#F_7$~S6=%_c8U+Sl{^|hY5HRV3-K6w8FO4Uhg!Sz;;N|?)C z0>(MIa+$^0W*_PWcayRfgYnw6`0Z3$n05#L?T#+xZ13&8B@d}bo`EUi*Sm1-q;f}6 z0WY$$Pvuc8o9~5h<+}R%j!8&ld@L9K@lhxW3itJ%+};h-a9wt{9R5EZ$GqurSvWD; zn#TR(a^A(|!t_8QyV8oN0RK?p@t;w4h%->It{v!Ed*kig;T4@%qRN5qbm~?+&Wh3fM=eytAm0WfGiLL7@#L%(4c< z65;NoVRGB#t)U?LyuRyBd-vpIoZ!V=@wQyN&_SI+6WlV?te&4EzkgcBl!av2{(tKZ zmhZLmu;M@F|GQd2-30MLoNaecy3I~5pM`0So~(TtS-5p*rB?egp^?Fg%$-&4nvKM8 zc7TCao22A(-Co_=@ z4FC701pmiPz4;R2cEhj2;m(~%H6Da;M#lJxQ2SS45l;%kQ!Ll^Dc!IUi>=2hbt}`| z8@VN#9?pCUyyIps^=DW0=M`PZ{s4c^<`*Iy9=tD~j+~)85MzGIJ&B`sR}tfHj~S#L zcmw<`fMag@sq~?A`O0mJ>IttIcBTrz^VQWGTaQ6fj~!m%x}+>h7koIgtG2=yvA)Rmsy;nYg|H z-u{;ie7Iu|Ub&XD=&J8#2PVIJ*Y{ZR(-j@vs+<@&?=wHmq&2!*!%Qy`c8Cs~eX#jf zi3P6P9fsQiBHWpkx++Z=g!p%af{45smN%pHPgisgypsK-vG-+R@#`bgHO$6dB4?MRvTH3U@t1 zh%~%G8~lybEVSBn%+zh5gP?^yZH93(>m*#oRz|RBf{nd?rnj`sn4YA{%X=OaR+|{h zCgk$d6*$Rc`E|I2gnxHWQshPIk#4Dz>FKH3+DX|(fnV@}U`p{+ySo-v-0Xv!e5dQ`DUHAS+@OjMfXNN>Ckd|cuHYlTcptR)D=xv zhoG)=CF)-Ks23v0m@_9n>$(-_?#0*(kGY-Z^&iuAGwUE|V1>+(e|xe|b`jQG{QmwI zM$H)VrCuw7&sF^&pR2>Lhuwi$G(K|W*+a{IVuL|;fkW(*gk3^N`|zS1s8DWRoGzGo zd^uH>j*BYLt1_Ri%D}}HSXCug3h|}agJ<01&``}{jDx`IOx0iI2+R5xbLtr7>DaFC@UfGfSuC3`UMmN1hNJ9UI%5^~Ako2f1ox-Nf`1;H8d+XGAVAe-iBpLQpdo09WO+07BqshX;(u|+(9PhD3 z#ITxsDg)qmR%@MBSN-I>fBG}suCr9v0U^!Qi#@c83@{E)HkfB8)VB=~f~4 zf_-p%oQ*{4Xumjbd5V9wr&#TrQ@3<0{tNW^8^F zY!l(>arFOK3QW1mZ<|A&!S{jPskdx$D%Z@+M4z&VMrLN76cQ2P&;@ugZ>fLEi3b;t zTXG$>{G6lTO&?8UfTFlXWkO=iskc)z9#>V#;HZQ+J-`uc1Q(#ICbELIgUL~AMVWnl z{r#ur=H?of!GN>105RTgo@6id(MV2;+&%LiqMp_1_GOOuu1CUw!I?S?RrZa%ISRl z?Wd9V>eJ#r<5)d*Mv7|GHUemWyy2V>Z7I!cGTwArrhbZe@sF!y5yK|rcRX+Qpao-`w~ zk8Ux@cQ6J1Fct&b21e;AGd$u}LtuvEjZydBJ<~0>^LWeu82xQ}Xb`NJh7-(H^-O7le6K58^cTasxOpKA5nxLp?>E1m^)JlT;#T^;&6XR)N3X!(S zUJWt{b;TG35sq_rUi)5OVExykgKpji0P_myjC?IkVUBU@d!g>C{>%qsr?z6WwnMT| zo}YiXpmmAE%VeGvl}8j6wXnw$9ZK1ZveWBC2Fu0*xtb*zofjpZ*uLQ z{9Z@VnFi%hItjGqi|&D}jKG8)FE7m8%&K~fRbLCHSDmgq`+VDORyU{4-(2DsoHJ+$;(i(#6c zUIyywE2eBJBH4FJC<$V87atbbRuyEhYM3oZVijqX=I|}iE69icD{a@!rR4-jF1-6J zIl73=qx#-JwJF%*Ta!g17RX_Urjf9)-mJ%fk8hd~?HfbX`eyWR*reLE)yzh3cw$KiI8-0h3$asM_caZ`sPgCZnzOlDK2)2#h zqNTlF!*4(2Q=u=@iuGtb$!6!mbk$cEpavFXk_mN*4_CV%jj8;4aZm&2rOt0Wr}wT3 zNi>~yWl1l^4My2@LPFkA0)qnlA9hwn30*KU<;y-SYeko;j6Gw9b%UMBhv)gxeSP!E z_;&t7^2=~$&VMs~X6<0>P<@r>6$FS`^dzHMH{7=rwEb5C{6__r#%YZ!fCZhXt04-c z8wccdA8=nC5ilG&Hhv^#C0liRm4!e5~q{AL~U)=S5c=Tw-s_WZCt+61(tKoI2zmvMQLDPd|eLc`}^6r6zHP_HuC*#BS zWLyG<@PceQ3i1QcfwVWE9);3{f=1wa8ZM!8vzDC!=i_{I4_8s4QC~mtUw?99O6Nmw zTvk`p+LI<#6CTdZk5Cx38C?zj4p_7KHhD&{PJUZXl1;3You)^iiS_u$sAWP zHZwzQ_|%E#_hB)&ZhiE*bj}GnB%@OQ4noyV_NLh2CTS2*No!}z6qP!gz>)vRkK(a* z;J4Of(TJ=}Iol&F_?om2+9940eH)t?{USEs8$l&{I|G);JQUMsT$o}?et8Dgd&9GK zcA$Ky3ITwC#8XC9OQ{G){Dpq;Ohusbnp{4Zq~;#^MO4pbnJ;lraOd z$>5N}S}Bcgbcb|=jeiQz8vmeJ*(#Icrw}wfDF91~e;+oosw!@B{hsBD5V;>W$94Yh zm#TjCuOYjaJ5|>R0te#@ENGmRJ9$(#u_U*FO{lu#pXgp@1s;9Tv$4#9ll3{$bI7~4 z01u&WLdM@MmWo!01lG)bPEkAI`8C_lg_`~$4>?p}m+PLu zJqJ9qv+9K}B1zu%;l{8Uwz+!2F#^WJ9WI3V_*!!Zkuw|bJ|Opv(F_00)*3sH(!J?< zYteyFN{f1goA}ey=+fvEkR(ND5P>`sAT=VR1Q_y=S`b@erK_uAN7%PA+4&d&M1qri z=KckzGN%QAE;7!a>B34kik1)B!BFO5g9R-6Vo@k)<8p3!{`(mGxvwiYmzdUI5c;NO zw86np()G4<$JMY>Wy%(*+K1&`61leT^+S6Hu0p5JmFaF{jYaeCr zl@>mbkV7KY;D?xBCKq^Z@;>VC9SRGfLT)ujx2iu=~wK6yM6?~UY!0rCJ*D<wsV=_vB!9^o~!z6LdcCPHCQ6rcPm;MWVeRfz=2fn*lGrDcXdxyfbLQ)LB; zu!KHji$2J!%Dk4tLuN8Q-ES9a@CBH>uCcMgTEA^9SJ&dC5kQXn zS`tbZ%x|!3XhGr>TF**D;#%@5B|$$5R0fYz@V|Ry^P19{^vVB?zA!8J(xCrNcXe3$ z2rNTQp#n%E^7syh@XNa)>%YlFBe>nL3-Z%&+yfM2t3!lR27OQG&VVZb>>{!$P*>x^ zp5v`JwHXdgKY9?%jZzN#D&epqha0M=r<}3V;Dsrcp0}vtNpK4u*2TIL-6HxGr%%)1 zOgq>o8ab%A)?S(>#y>TF27OX=`2y#3B@ac;VfrgMUv{k8kkVD9V@Tb&>;?G0X~0*u z8WO8!mb0l>0R~xlO5SingZCjVPsCK-WQ1%tqTlu|Ah8r=iP!&{CDwdE*1xI5mK>Hd zRD!PK;yQ~t#s81+KY+1Ma$QD)6&ZW;Hf%iZ5L`BWI3*wu4{4)*np@q+vaP{H{k#jl zLhA9*m##aAug_SH7Z9fjx;Jm!K}g;(gZXVnImiaE8QdtFHu$DU!96k$kUInM)G%wn zdV5{Ua7X+g531=4tUN2<03%umnkEQ~DcH)3!59Zv4t9VL(=LX;P;sm7(-OYxXk-aI z>m^2S!Xai_7wo;{WOD;RxB&7NeL*yq78D+oN~}&ZUH&eCc?17v-fOy2HSVa^?rAxX zsoa$l1D(ptz`LPvoc8)Ror2=0F^<i9E4CXcv zPBFKAeaIpr={Q*AxY)=H!a+U47Io*+`?h$Y0hW$cWrP*%YNy&ebywm)F+$8gD8FAY zLN5+B%^c9$V|@9x#xvj8OZ1@X?~)gXb#0SM01!G8&2Ni{Nd%DDxRsTHMp$bq-?67C`W_#W z0jk0Q8O?{>!IqwWK zE3F}5UiS9(`7P6P({nTKw~s0^*C6p?3h6o_5h#;vh4}^L z?H{l(*LsiZvGZKKyvyG*&`zL4l(d=YoUKg#^y+ze_BS`@J%|q8psNy6wPD=ILLl(% zdoHJLrxM-y%qG?GOGNkS;|=O~g)H4xY(6GPq3qlI_Vfkp;-q01+qbdKPBotwAjSLx zHO%(6J0eRY)5-@R`rE1@Ho+@8t2iU|H#?0PyGngxx25Q|RQflN^g&@- zIJ4qE=$3ws)LG2p)#-vS<^sLYtRA!b|I_aREc?4|cuUE?Z~MPBLg0keA1dqr@-zTl z{h2KJw>$$5r~a9k-uACuJl*PF$=e5>c>$%RrA2poBysicO904eRB}6_W~o`kz#!k) zy`u>d+twEmwY3VQ=uN`%U+~W#D|CEOx63|w+w0t=`gob2lC&}DYm*^>TOmvUlK_0K}Eq6xLkmjqqgCanQ zxhZ-zZgcUAJ+eWo1pf_9|GwWzf=na7Juta$&d{lvZhWQU2JcH0%FNtr{D=|SmpmL4 zCSq6+T_40`GKu=uz_A3x?M*;pXAR(5+WtY{8vlR%k>}u=={67yETtTgxhsy8rcMZ?Z?#sl;+<4tg^B)V=V3*6pfpi$wPLI&;dx1 zl3rP9l#q~+=aMk(1O7ayM*kZ-2S+~H;tv3pJgW?FJFY(v$EnHvRVy=-On*S7RhV4( z?vO1tH<6ylOy%kI{7XnQS9`f-x(3F#>R`-Yc(mBwC-9S+eTf{5nZQgB9uyCANOouxy~SLb>OXP=?uK3xp@r0>qT&?FU=Lkfau&x?8KB~=t(G~G&}{J(gDZK zfpbWkeBB1F#&!Oo6wkh4fA`n}+PZububQ8!Mf&ct{Nv_xGy6iO0sfYt5x5G6QeN3y|vfXd&AX`?0li&xTIMM9v?7~(ox?2WDbQ2O2 zTbT>GM}8m8En^S_M&~K*f8ws4+Y15nF$GcTfF{~F7Fd{$X`xt5(>w)<{_3?2!6alL1 z7pG4o#K(1}@vjAg6!dS@$&ZY%HBOKhP)U=s{HNdeQ)~R|{PUOK^8foBa;acyZaV5B zOf#p|ttDQS%K%R$oRw#4JlS2>Pb-K2OXLINWD)yO*$NlD)AbYv`-Qy+onk+^vRe~M zjE(TuVro_rUE)u_ypde7lqa|N`FJJy%ubh4f#vaD>fNuA?Ld)0aKJkdWPsL5hEPQ~ zhcCmQBa{&o=$s0-RPeu+1^g{LKmFFh$M)h&&q)sRf;dBg*cLKG=q$_LN00xU;oo`X zCW9+Y&GQojzp3!q{BMAZ|KR~_b?Z|H?2%Qlw_-w!KD z4clWO`Vsa&`0XDl4`_S&E)4Yvkg#Qq8`UD??F)xn#3|=7=ZAR$rmQ+Yyb9B&4RO9> zaIl>Jq44~L24PFA6s`}0!N{`9*fnr(ki8N9twC~h*!M*$=Ai;B@x?LX ze_pKz_c|em?eC>zS6;Hm&1B?cY76Jsr{|?=YPJX$M;0v!XTQpe$eA(D&d!f($ef_? zaQ0@%#;3ogQ`z zWu*kOTgj^wHS@N#Q*Q1aXVf3@g0bDK%$ev}?XVukxj{8Vwf*K;og~Ad4hLfVB_-wm?W02x-V)MDS(u7;7k_i zd8#l;?B_H6&wJ0TDsva9wll?F!bLdOKE3h+ubG(-^WjVTMlNgF;=Fp~vXr@NY8ua< z(%E=gy9+c;4v+dj7(XZdr^49(`P2-UY#=J0@Ndsb&AH5;Xq=u_tVI(#*dC_Mai2YF zKxj;uoEd9H%Q|vC6L@=qBa zTc5w=R(=YWXY90&7aEn2Yf74ONJqxh-fEQp&!+P@<=Hds>|E!wj9rHoboESzsP&rO zR3=eQDc(Eac!I2)RHt~jM}{d?&8uO=BeJ?3dj%rVx%2UBJ>rqH}Y5npy_Z`F7>q)%gLS>TqeF_Wf@mNkv=n zl#{(v#8R{U=xe!e;x^;@9bdXm4Gd_IJ}VbIsb_Y&!Py@ku7=jT!|5b2^2slbwJG_? zrqM%-bd?D)QojD-F$et zD1d<&3TG*@6XS3r2ti~3jf}tGI??EWM4j9ms#X7jhNElNzCP`?BQmUdEl)HB@%Vz~ z=T&}w>63l&kVD}3#eb2pea~0C*1fVa8C99B=XW4Fg^<>g(WjrY=29-t84e>?>ER+ z3fq^mTAn3fGuN0!zfqF{MAF%RnXZ59Hsr!f?42fGa(cYP?DQN~l6i@nLOeS9Mkd&I ze(lb~JIS3E&ifZtqp>PviR+3tk+?`!})0Udy+SiFe*{T>D@1anW8I>0z^wj8I zy~-(kX3-XTJLXwy!CSP%3kH1c6d29TT)n=KJAHabBn5j7q6NEpAP@9?f!FQK<7$?2 zB}c<$f+5K@OL?4wU+kT^8Z&`+d3kvOsZw{o2|dH^zA{G+(6NaRL*QihY#`?Xhm3V* zUX7v&g(j!bPoF;hK;X?V$J|aONdc(}L_2$Q}=rE!l z;2$Gm$VgGjSGx$l+93yMKtMm&F*xW%w&~_md)5fr0YRD}y?@HuGCxrLf#6pW%Z{(D zxa#U!3}%juAXzqhXwBzE>`+6)WgO|@AH65m)XdwYn9Z`-7eKNAG>Mh+4!e+XJg zwSVtD``X&x)PiYTwp93Q@RgKXogFVdm?hM5H1oUnd?4(`4FfGzt73hnHuUbJQpD%@ z!fC6n5B5;TjmNbOMB{om=n_7;C*@DtyhX({DC5tMH&CO+(d#%pmjy+Z=CmQPvaQn> zGLgZ0x2_1r%$&)K>e-r@^;{kB0Nbj`u$QJNp`bvAXjJYrFnpc4B>LL5YsY8-1Xv_( z`dr||i4(N05yII8Iae9#dn_kl2Ul}P{r^YWcZW5dZSC3-5K)vOMFmliu2MxnK?JD^ z0@78ahR{0%EEJXAr3*rk-V;bDBM2%=4Lua;1V}<}q1+utXFkt8_YCK||IITq581!H z*Is+AcfIRfuH?32F5H2*zjIp=9g1QnnOzD`*z9c?&sLc6*7w@KHUKB1^fwbp@8~Y^ zM)#UT5AQdAYoHOUdU?x-)><`C1-*(1GW>35IYC@O9fTqHb8HLaR2 zWt6dBtdeSmHy2$~E z&C;+-5Kz;qWUd4K!#OhJ>okBk_U-eTsxBbBT#N+@9maZxFtDMeHje%KdNToC?p*4y zqcPys=htkIZmz(9mO(h=`GRH=J3g@EN#%| zsj;(xzz*`WcvTyf#iGwpG}T2UNXH4Pdl7T_;vZZ@$F3A$-79KBrvH{_!I)4h!wn5N z=ns3{Bt`>Ar*~T%yfX4~sv;sHWPJzFma8P_2-t`0v7H6##sZ7Q-k_uLKk};o%xMmE zbPn)$ia0lXbSlu)GUR>Twao@O!gEJ8-b5kvE?DHFCb}4uaek2r!oa3<#r`(a91WhY)AG3lO(UF%1QJR*jAlv|eS3gC6C2 zAy7!IL{5ZY9$pIrw!KIfBDI`||v z<9qDeU46sa{!G%oUm)%Oqxkn1n!Wa+q(nJ88xs6-(kLUp`u_C|TZ-*gvc?kzGOXp4 z&<3R#1maBSMs??JW46gxj)zTZByZs)+-io9m@;VYWsLR&edGLxw^rl#)-w( zbI-ZL7mk6rJaVcK6DkJGzrDBDbcoI&xLS0BInD?@4~HYFcdFuxZ7EGYp2%NCR1Q93 zrK7#Rv$(qWbTBE}VZtsGrhdg}oC`gc7mzcqlXBhCtNCS!O`u)Co|6gPG|55`y)`^v zn1_Q*Zj!Vyt1p}1-mPfNi-Cv_f{a(uRZO9QiPzt*s?5PEk1VL#zx35hS2#4v`v-$u`JmgdzsBf!w|lzF z)_KC(Lggo-9u%u1s@k|+y#!o=tOu6+PQzZa+e5U@T#-Nxbv_-AFhZf$ggAJX$~w9` zmh?ioK>kl+_;Ez8d{k`t^JLJ$AGgM`h$+#Lo|h-a-t;-%b$5PJF<(Z-fy%25+VkFn-WiVx)qRt2L(|DXiFR`O#|Y4nHdiMIC~U_xFSo%fcp z+8JZ9n)g^ff&b#eK>I!m#i!5AGart6?K|nd!R!eu_1*BlL+Y~!N?aiW4ZXm~Y-{T7 zpR7&Ye1QC3S>3V5yumi$a^>`%Ppl#9;xOJA)jcCu=~hM4*>%Nf#+SXnNTQtmG3(Nb zB=|tD(qnd*lnEANb(!~AA%Q(ZbwGOA!|SOpV&ym~{-l_Zt+FcEACWrn*W12fJIz?< zR<41+-rsx{7iB5Kd!-uTeFtfJZ0~=bsSN4LMDg)+)PfS3v{G=!-W002g?(Yqaz?In zY~MA1JB7@(QMJ)WK&$@4(Y7KRvm}F63ipQY-hwGnnyy+QGF|vz+LHc;2|pATun z&2I#Qmzi7HoA=eZcIRn`DC<7m!bs$ij_&db3oWe3d2(E`+*ZafvJ|QcQ}}G^bZ$=;u4c2d z+YM((XZIa+N)Uqb_6FIIHqO3^6ff}0R7;BJ%+b;B1w~SMIrYPb4|kcq5SZT^Bo^B` z-DaFY1Sn6zNv3lJN|Y$*XWhgAJHhdcgvKB+ndo~E<;9h}gK{{;<36+EGcejucMZC0 z8yl74#B7tvB4;bJy%$Q%nj&DN6`ITEK%>>272=ROD2P0H^Be!+x0anhQ3R%2IFMdoJWmA9`0mZk72s-!CV6j}|BOuiOiY8X` zlKXJzYm}+>R0w%-3upw{Kl2pM%7YXZ(0mDLXqdOnMCEm zy)~l;W^whmK^sc>!F;jg*3~;5_0Ig(*49x_EJ9b|PAf+(5RL<)8vW9)KaNt(bQd6x zq>|g907s!n&68NQ`n3KHeOFkN@!oK($K<1WiGBZid))mGT0mNDfZ7SSL8Wa*uKPk3 zraw81-gH3}4`+CgVxG6PK|h!NIra=yp6>@twAiYXr?(6e>jm`tiY+mhW`LC4*g+|! z7cr+p&dJzp$yxxr>Iy%(6uY>we;k0V4a>?LRoiX1ZIJ-hWd7bxtFZhMGz9K8`GDT2 zF1=p@Y8)>Jjx*bcUnS%1RDgs0UGP2v3iN+N0v?B)x3&_<|LbedzpC+9r_{{d<)iH_ zuuy=~=ROzxgBF!%xgYE+D%fTbh5vjP?0o=suDIslAT=SY6wGyF8Sq!ZY9rE!;oO0v zdLL@0bV2QzPq+AK;lo05DI7@X4vo@B&;(d;gEAw)a|aexpql?Ds>Nwj^72=b`w{+fpv)O4%R`Z8m}VymN9J^RI(g}%I~$gsIkaEqrUmawEPLPv2J4$?{YM}9F4C?>Cw`on8)Bf4+RvGHu^$v8Heg5NThgr$V zePKUks}b-CU;BHW!ni3AGtkWGxbD!Z!=VnsZL2p8KG`32EoZdh7CCNge!XB9fR=bG z?S&yA7MYdHDvk;~ey0kc-DI}Py7mZ$q;G62^ALm{YjQw#PWt1p<^DfpP!rSA$n6E{ zeLv2zeFud9BeeU#zXURQGK&?0`_`1Zjkhj5nk9k0ChzT3qvcs;V$W|j8p**M%<>qiZA9l!9fP6>`JD^dXfvRDA=v1~otFo;AWOtAQuy^;V6fg=g zza%{JTqzceu2e?yV|GVr-T}ivPfeE1fxgdTSE3Ow83=s9V+vbyTNt}LBh+gN1ogJJ zNECwl_wN0`ww)x`F#f41{Tjxeda(_-YnKH0gvEB}Q{PydNI0|;8m}*K?8zN1@W6FT z8=JG1H{Wp)gd%L|zZ@TEP{A63qfh$qdQp#wtZ(x#e5}74vOya*`FJjN0rC~3d>q`I zRBN%U>@1U;`y~{RcXy{iCqzZ-+`W1RZ@k^$d=#1eLI2YB_}$S$F#A<1ud7c-hO5x^%tCQCm26Nli5p`wKq^N?`_k!;qKU-(hbc!Vx8%hsZ};Bq|JX-Q%; z6?vj+oTq{xEby6(Q^GZ9Aegr*2j;Do390PCvnyFsQ-b=_=y?RzB5wZ|(^0{Gr@rpf zQo!7#5LHd$kMPc*MyYsRZ*BkC?RKczUn6zt?G32w$W^9)Vvl@6gxwozwP3Avi3D|S zoT<=$2{q*X0c}-mVM0eo7xvE|OWGK9Y#6pGaGa|01z!Rt4F8(Wd-8Lcko8`^gpZn% zjV-BRzcsS0fTH$MCDtQN#4b6UkJ7iUx1(;!cJ@D+)hiAaZYVz=6Hki8nt$vU) z{F9~uIb|pU#R+3?F&bkux&8#>zbQT?3h9d(NKY%)1CcOHwe4gkdw-XIRR7j$X0Ti* zm=gXiF#WyXx&D)U14o>9=S|-k(%cxNLW}45J^rV*d@^_LiH@QL6e4!q2z}SonO_oq zlx@Jw87l(C*wEF}sU`Z`b>Gl2XZ^5pJz!9|2YK|6GEyERIt|7h)4SN~QP(Ne1Ze&x z%Yv&G{8bD5zsi7Mwl0XVCyR@-8^oRPm+)*!DsoY} z95UURItuns@l1vMuc6-=#3G@D^$gmquTKzdPDeRa$nz>W#|7gfnahVOvvClCwB=NM=W;SWox9+b?Y}aOlA0aeA!?CHYZ` z67gfh((dEBqjRM?Z8gV++G=U1zd%vJW+X-S4I?nSKG*N@UMNdX!06Z(W53<(;P9&> z18`L=Dxnl~R6;0`pdftqVT#akHP1LX;>B70gANHcIdEvtw<%A7ptNd+JA0w$zLsZC zMdS~P8{nhj`#ea@&n()Y*wQA+jAE2FnYaLCq!kwn&4L$)v~W1zv~&z=zZS` ze2E&0CIJC0D4u(6hrcH8%pXLzm)+`>mA%?)!kX?9c{{DCn&Et(lfGCLMgQ#~lgIR( z?ERiQMXlnTc&!i{XqheLQLX`hRV+fMVum^suTK^5A>Z%*UfxL#eMO7ArOW{zk~Hq? z4%94uZQVa|i#%dhm3TO_wA9yCTsv`FGe#T-A@L>G1wOea?^a2sQ}j}J?4 z248C!wh&xcqHXCz9{A}K0{fT!Fe`au)!g}hHl1BvjU-}?pgzJnd;smLU$gnu39!%s z7dB?kwD%}Q20A@`Y)~CW&C<%Is9^sq#y?l$yZnmdK1(hCH{#XfcRZu3coXpY2hwgK zEinj!mfEa!iS1>iJe1#wH(c=x*0zSONp$q;WhV>LQW|Qq!W>}GMiz0Aga86 zO7bT#6sTpFjr^b@pgwQPR*lk;MOB6p46*2o{%F2lykA#ey=4|VYj z5T#c8+r92PLFk*D36g6w8Y}X-jIcfYieUhuimhbSUArm1l9YyEKsk?nIq8neh)svX z8#a(_7UaBM9Qm{ z1T`R1SaU>Iq>Dpc=co){AXt;;kPH}Avcdsq1jT=^=}*)D3casSJGC?V}J2%HjDvfY_jzMK7?MT zxsfP#FVEc=6(ztf8&WMZ$$c!4o7&Dv5#z3=r;lG!iEu~1$&hmNk-2|~LPv^U%q)g3 z8Oytwv@+Yi`V3b!9Mzbu2X5j8CAo4eqbPGgY89DGir3$tcK6RuV33I-k<{<&Q4c6F z(#7!UK4F``8F-|idLvs)NijpCiz19nnF*EI?%D8b1#uOk0A|m_ZL$BBU=10Yv7n-$JzJ`Fj+d>F zSrstma)E$H&GDmCuuHoD?q_NyCzXoybb$61T-UC4w-nLa{VX@Tk0aFV^sx!H zyM5+Cx2cc0Vq=LdhB+BM(iFX^#KRcRPnXGc84+K(2mW@X=0Jm;0QZK+Q}Yw#qE~a* zHp@N^6{P(@mnw0;pu>t{CQP~FG7n_f!Xoa`_9keheg_KG*Yg0U3kbHGMV|oFZm+F- z8n|&LWEqd$eZg1p>$Mm3(;K9>BIrE9Am46-E4N}8donNZE>%792o=vrYwyg7yOws< z*K?|BcFTE{9C!>-oq(e95(W<%0bX`RmFOK)epY_|3w#FUtsAZP@dIcbKsqpit(8Nu zqV79et9iB0kEc$%mz5n1aBTnh0W=de0_CU%14Sl&DZVE#n3PZOzI|TsnS5{K*7rgO z_HB~UwU(yI(tSS!*{%cj&98QerA13=AfNAp+rV~pHB<`%sTOK7=zALu2w9Kew=6Sv zQ@v-i+2Q=O8G(m&zEG3;@R%gd@t%ZJM`~+># z1-M6HV7>qrm3a2zEkS)lL#VOl${vvNs4u7Ku3x_%EC(!-vybe9I*C!4c#V(rI^QE+ z+K`TJMJPx^<2vr{x*%%b6GEa<9@*AZS-o8Tl4{b$9M`0XOVY*_uWT0Kt1lSc7q|NdLf_QtF*)xg zx})>+9UNQ)F;(Vn&T|uyr04N?YUNEv=B(y`RYa1epx4&I0n*c_!PR0a#50*7!|3e} z!=?%OqSsP+$eWmTqlx0(h3tXLm`g$SQqw$}9Y$svGI)|utW^LUL)0*?J6 zg^7)~p7x~Uhd32fQ^Fv%(b#L^xJ{q{736WpUhGrxG#wbw5?t4&cK6cy;aFoJtENu0 zDpq%fSg=1{G7fEPU-idHjYi#8RFb~WRNKjJj+t9@TO5lv$Ls$gPY1ZnhKyYt!85Y5 z_*-!zvtXq8<8}v$anxw!tg$&!)F@O!tdaU$M~^Xe2DTa}b!D)BDRX^=e=M0h7Yt$E z19Zi4Ur~{>FQ~39RteA+wmRny`uWa*eB(;Bt3dRIzg?v$Sk_kkxydsDDW4NWS~`1? z&!{8XovbTv#M?jhbuyr*L#cgTU^n$;8OLc+WblQT3JRT%26Q=!WvyDiOz5J{J9_>E3idWvzGw9&inB-0)KB_zSeD+-q2a;;%Wnsa9^p-}SDe*hCu_Gy7S?frI!)#N~i;uhSk@@X1Nz=X`u=w*O zE9eUKwoGhwHH8W>c;0Nhqn{7rx7X!v$kwhe!WvR7GM${jMcOCxlz~n_$^dfJNyu$5 zTzSXY-tGPS_n5Uod!?&~YQ&F&4@g+mITMilIpK4`58H9$unIyyw`+@mfFyP4F%c&z z2R_bY5*{CKNK81aXZLO|{eT+G_YZFCv)v*A8+Y3}^)&Sx3%8Lo$!{hFWF&Pr4jCA5iJih^b9*B5WN zkRYVdO~$Xoe#zUbuW(@ZS3=ZC%nmv|0e0uM)Li`GWmCY`@%Z+^l++8`@A){FF6GTI zec^6=uue^3JF4OLd@S6ZQNWcnz1`WKk*lM+?@RWt(K8xctl>34++4sMb5a!$` zoa}$-*+eU-huo`DayE6hB>DRm70vgjP5FT-OEqg&%%+>`EX}8zx;#V(x;p!G#IcG+ zd1FvHYSXbfi}}vHoPj_)ARy8cuD?{-abs}&`g(L{PgtR1Kr$a%u69{4eST!r(Rs+8 z@ZG6w00x(Vu8qXC3^!lHJYo5Q6`c;B5ATvJ>?D{x<^qdoWo4$@$E+@Q;HD#ar8mGz zn^J)fd*=~7-MG-cLPJ?cq2Lgh#@P6y_2Wp6u4k@ot{HT>)Ub8ZbXsx-VNNE1ypgIuus8 z^T#8b`%AJCUT(Qy&oU(kwE4WGCKDYYJR^7?@X2Yte)*@>w%C}@G!6JOG*)lr`A|f- z2-LzhSH>$|khpfXX6i2%7LMC0GWpEIi!bH+5vVXpUEft)?fdSHTIt~pnXao^Q@dlo zXq0R>43E(1DM*I;CCUElR{gcgeb1(~vU$HL>x*$FN+&r`r}3JOe8_nwHu$v9#WBln zCDDnHhouUAFp75~X0}uk;?1MKi?*+QYU_u7(9o;0%S;Niv)wi@2Gcq;9M{j`c?6#O z@tericBGc#lfPls>)jO=yO7BS?>?GOy>?pvUP>(@@O?B`q`sTMVf>Vz`khx$c3T!* zD{YvStvzMl`=rmcYTzh;H@a3gaNhB9atHR^T;}PF9?!^Qp}WMTKE@!Y(+I|<1vh_vy#&;Ncm0OL| zPzJhhQPE>%2StlJ3^uEi|EnElmAD$Nf2``>3PIBedt=M8E>^j|U^3mYX6yEF_~BzW z!mFJLaX1;6f@`#^*1O~<316`>42|Iz@?cznzzCXno$?iu(K~kblzh2rG?XRrlxCA7 zSYt)cY|HBQutL5k`d_ejro*){%_fgC|M{MD(NJCpP;d-PWo!i3V7^nhZmd}Z=XmXK zA$d(6w$sg*E7^P|Upv*wo5Lk1`xDv`CiPsFAD(|q?T1>XenRv%_LUuV-JE-16YeN= zwe&OP&NQEoob1?UVE*0J>Yfw^Ykb?^%j)shzl#p>OS-)mV`RM{96{cyJ9qTZ_dLcE zRBU%>2AH;}pdfC@)2Gg=}BVJcRO|xD@%cDzY9`@Y*zx>T#o^js?qYPAWWvR1VEjVDC zE5PwwmA>yK(j>awe9f{+Y{#W}fbNaNsx1adaJz(W3v6{NY8rzoQ(r_hX?NwzmrEvJ zkJw$IqY=2B4-ow{l$cA#XnvT7naZXCY85|nYWxy1Bf$(_aYO3L1FuS@(UzfmUQl)& z>HBL>qdtj-BlGw~ifE7#E^O42uYbNFe_iLBqn1A(nth!)4|HN;5jKkAtuk-P2Y>03 z-pur;(<&jE^UskO1Qbh!Q}a9?JZVgm2!92=WUH^oT?9oGyYSZzER?kE9&tcQua3ci zXc0I&d&d0I_fGSeMO>LX8>MZgr%ct_>cUE&dQ0U>q9iIXlKEtRo> z=kiU_30r;N&Oz`{MBD#n5wioX=aEbvsjMkZc-RMd>uc`VcG=g^lISwXNy5QynsEjR zL6KE&$nGKxsP^fy(cS^_c0*)JZs?$h_5JCAzk9Z5_<<5K9kK#w0FAm!>vq zIdAqtn!l4UtCzNpmel`dCNr0hU$0(jSGKga&RzRb3aB+^5=`I1I`roZt!7+EEwOwz#t<=~G~+U_j%U-R2B9t_JCR3NIe zRC}38EQ($G9!FRqJXn{$2@FS&$FoVQ@8rZAD2J`SuGJG3n~V7C3|-2bq?IL%dgeC) zG~e>d3d6ail|K&BrUTvGJHWdIw1btp6%lj5L$(_Z{A=`t_4keBVs!0X(aen3e|Q9c ziEK+*8c$fDT41T316(U(g6N8V&4}-Kz|2BWhQaEOg=SoK zrlaQdGB0XY(cbWpFT+GjUWst2DwTi^NJVH*`{+NxejR`_D0+qFWdrwDGtY%~3`~rT z?@m4|DDb?|%36_IW#>Ec8zI83xdJzGPC9%rxq#y75`O6ut)KuzNSq5s2vKsCdW#Nf z$zVyw%aBAg1KJTO-lkoZH4ApjtJYJTMXF;^US(D5y1aD5&$S+}eBPgR9}6!9p5FmMRN#LiZuJ%is5f$$({(yt&b} zBuC7Uk(WDacm>2KtEU}EG`NMp9T)AAn`8a9GU3@)XP7+L2ZYSeOd7e_qb4SB>q9PE zI_*Qrl9TIJlLOCxcQea#UHZ1BO-I38+&id|H@s#doYuX?7YAI2V7*D+qR_r58#Er_ z1o(tN$~eE&duzi#nUieL|2gOeSMSs*5KO#dR2tfL5vWR3+7irbq8dUI`raZ|mf)#H zUxg(^qdYtj@L*SG)0R9~mQTG=hED@W{E?Uv;IWH3kTCteBG=rt#l}-uwL#Icdq3|3 zsbaM0%AoNe;StJ317K@ z;~7BSUYIvRFE=-}*#DC!6D#ZYm@~v%w{J5?&d<-!4h1p=E+#Xc4By~-Q*=i`p(L%R za#H^GQ+K*iLQl^_(c_a+f*c&n>a4b}OlZS@{U_pZT>zf+=GEBk5(Xii-M1MkR~vp| zv_ig>+H~hRNLH{dpRg>N-Pu}}%crC)`BpiCO|mA7ER?2{5(agL>+AWu!wMJb5s^q$ z{-~Zi`dVm);cYw`wR@2+P~abE+lJFn%8%O0M0Oc$RXBO;I{q=6m{RSqbr9#b71-|$ zKb`@VL9@fgZ5JkBYS(wZiKSB@*m%^wd;M-E%Ji-`-F`f$DCR9_hAO_Q-8r{4rGWe6 z`N3Wv)H$Ecd8axWv2-BceqCy{-w{{VZS?J|aV~dZ_C@=9!pw9d#QW#Zb8rNI{qjYz z>lK6Fe)pEZSBhJwf**lO#P>H(cDHW)F011!*bq~|&y5Tbiwcn%Bk$X{$ezhy$w^G`o9~30WeY-adePbgRTq~9ow^rJ!7LqBR`0o9 zcg)-VD%{>3yK!eB{xrP()1%>v>hRhJ7soN`@FJ-JODr$89U->Sep|c50JnbalEQ{Y z%Sx@I-z3_VHfh;wqDS%ZI?R%DTUAo)t*fOs)>C6>s(;nMU8_KVur4e7vh4*;kjOgccwN=L8S)-E$ z#jixU+|XDtxNJDn9#iGm2o0p!r@wl70xdw8ySE(=MxKpt*Jkou79izD)$F+QWwEFB z5udK0nLJ5fOHsm5-7e`No$jzR%Pk!VV_bdtNWKS&p)WhGO zh>TYUgWA08TBKH=RxEevV`8h9GG<%IQAZQSyq7TRC!ca-_wnf5`|P(h|8T7#4S-1t zaCtG#4j!98CLOES2h;Zns|BI&pYWWH+Isrz&TH5Z_Z#OC>UTwHlwvC|6V@Z|u8g?J zvF5n+GrlW&vKee9M3$e5{%Z?EK^}WcFsl0C!FnBJY^o0NZjN@I^1%Cg3abcXJ{jn9 ze_!B+H-_`FY^^;Mz~Do-jLFX$EhcRb3q_&RXOtrAH-fX~t4o)Mai5Vi>M}8xMFGEY zwXd`Y?X@vjI$rH&yZSv@`T^G9UjA7c%INCI1)`gqzMj6@Y;vY+#>m*=NXv9jlWD!q zXwfQ+5kF-SehjJ!Qh+w?PLKkmtk;V9RY)(;!S~# z;Fh5LX!aSlEJqJe zrdqh^qcZ3EQG}wAgYr#QiXWDeqF|p-NwH8ZeuZ$HQI_-cZ-1DOq#yjdxzyS&?_OBa zpxqy*8vi9W#R2_3Tx!5srMkL0+}PJhUCb?9$Wiv>P=8+wl~*@_E2M$kIr=kX_&dG% z3rE}cpg_Y=*QgHx=HO>g9iD89T@OlrL<~UgB95QLO zz7q+-mfy&ZG|}3FI|j+V9K>j>0Tz1w-lltudrW7pzpq|(WL9b^)KEvXARpb8!Id(j zhCXtq{VB_K&Ci%uZ~c%1c*DF`1wD4(FlcCV1vRBp`G1{)lVksvyRkq1;XR?QDgmGi zsop1Q;Pvj3mhg#=Jod-jP&9hZpgX>l@X?w2spB+%C#j&r-`CEH1B8yM>VwGZ36oCR znaeH9a2KAP5OR{49i_dkZUEMozd?fSW;hd`w2ZO$UY?5V{hB@9eqg3HtCmd7~B6XjUW48{<2%XkJB z8}H3OUsr(-PQL%)qdc7;;flO>W$Wqy`|wA?G&O;xGfb#l#F^g6u_cGTy6&L9!$egS zF@37zK|B*qRWkWuLX!_PW3^3o7pLIKh$h}?2QC2VW9hRSo6g6)tiSlccB7eJYa%?k zcB+sGqk72svYD}6ZX9%@BzoYh>Df*-*MKux_c=kMv;-XN5o?w=3=1hdnl zPGh4AdH-RtTGdj==nC$(ZJ&ejX~TR~tbbsrsgmohhbb;Wr_VL2z_(-J1zpB1{=Vf2 za+NKfMpL|yH{93TVntn@Gep!u!b0^>MNW1;(IvU$kD&L36$@Uwj znv<5+I;+q!!AWHjDjBao)`~8!#FPpk^0-`26A?mDmnP1#yp30s^YmyBvA)qC?y&*< zvC+W8_sn!5!B2_KoZDG(tjblkr)4GwM+`X{Q&LQ*xvACx>8`8+fTKTTWYp1Sx5?keD3; zbxn(()n<0QPn;$QItt1KJ}te+U<3hR_g}wG&T_IK2OwjbXU^Hi&a}6fqzOV3EN^m~ zI_r@>Av(l}3JHHE=H*v?L}5C5tJdnw65%>G4Ls*z$wg8bM|`j0^j4ovpH0-fhk6Ur z?X2+RQVCu{sJm@eYOB-LW!s^st`>J&-I4}nw1#)p%t&?Q>uRXE4}~jK1v^pU;hyv6 zHyje?UiXmfCLOm9dj={$e8a;`r*H7!dc$7|>{pV_1Z`#e7PVEJGx zMQ;8~a~+RjYf{Z*=ZI)HNgA%P^KAys^4Tnd7dm@ZxBT+?YcZ&Ed@4g)@@-pgLrx#J zlNSUncxa$QLX$($IHx?`@7FRJo>mjRmoG@Rx8~xMM@@_$pP6ad4qAmORY^#lUTofZ zO{lFgtbg68671BnQJtx|w(Zft05V_f?LkCw$0yN#r9NlAS?zyB! zYHg`uBG2aFC9aZ=;+z}w7_mY)M~v8(bgX%RbixjYIc%b;bA_$Ts-ouQI0IH?5K(&X z3D(*a09@$B_1C+IZFXq2ra`U711m))WvNj4Q*C{vS>_fK0!K7QVU`e=$%ykiUpUu`J_gBTt$S&W-iO&CGD<=fsj=>-XQCUI)vp(`^F z+Oh6aGJmgY|MKSSx*EN-+{#*=zNRq_Eh7@=Kj?N9Vm0M~LeA!k)cgCAy5P=^aH5Yh z>Q?GZ+yajAtxwC6;R~~tnjg1~`&erh^MkWI*S@<+OR%a^K`?!!k<|zKa4QtN5Ex$c6(nl(xqi%aaE&3y`F?nZdDkv2Ee1XZ&{3KiR_==EJireQnZJ?_n3Jwm93?n_HCg zyW^^VNp;=G=HwP%#-us3euKv9OF#GTP_wxEUe*5ozC8>Jh;R?i|Eh=-Sm8k0KB)EJ z{ef#6RDT|^-j*6C85ADrS)f;{sam$MzZ_V054^+`DXZ!3-9ZQSsR|6EMTz{R79V%FrbeZLqS#Gb`1WY6?L-jW_s(4j+##JwCc!Uoay}NgSIV_-ly}-vq ze;774wjPXez410~sFsE{b;nO4CH2Ms=Cc(-&wk_g&yT`n>gSv%PJzFPuMqdc}E3-g+JlOyQOz{74kR_lnLZ{&V4Oik0x|1Hu~3^Ks&-t zT>7j*Tv4^$QaN>#-4N2=fR~H@oSIc#DgpOFi8VPMeB&3q3~!og=5t+!`4JbwRV}}* z7+QW#l;l*YF1eflk0;veL1`#6*3F6R@f;J%!=7=7xy`zztJ$`SKP%k=n=+_CWec`F zk_HCCPDs9XuWyZZ*j!6f64fcp5^gV%$eVeS*7Or>=F9)$-1f$=iJD$eu;g`e;){vn zPP1$CtC$>=viw$uNwLRB zM&J4IwS(kT8Ow#qSE{;6fyVe#bY}$Hg2<9dd4RSa(hcYLY_^e=%z0b0w#{3U8BssL z4yv4286G6=n4l-tSJRdT9t?peX}7TMwW#jG7~6Wi&z*1f^j@AN_F24SnEr{Xdm*g2 zIi5vb@Qf-$N)EK}_ zT{ySa;z{KriCrpY7z(?GQrg)dR8}ymE@$T1cL(H4q_`H*P`a1de0Is;8eP<}pL_`r zi>w?We65M|u&1|;T8TW4KqWY~)#dPJoy!vguU#^1SsfQJ2NBou@^WUa%msN7QO?to zJ}tb9W%=K=JVFyRjR`Cun<+L!PqWbNR{Ue)C2REXk`{?BXQ?I0@5gSX>=$R^ZxrHJ zkrj|fm{^Hah=U6L|khIJ!F3m(q*+^#Y4J79W=B;PM%1DD$4I{Q#wYi&WOB zb9~h(=Q78W1m4gPK%MBmpm3ajTxO73j5EEBP$~^x;cu24OubjO`ul51PwY3cCnp$c z1noZi6(yHm;k{|>C0DCb@-e&2a^`uWJ+b3q-|}~-3*EY^)FNV>AV^xJZ|Dg?wQL(H z4fWor-z-s0H>@x6*vgTUpgBf{r#vcCwxk$YjwJUucR}D(L}%y?G2oP}thl|z4mx(n z*48l2d*b>ux>)al*JRjmNt$hOC~RTlRs*U#Q_D416?;5U*F`w&dw0;41--``CDL`9 zKbb}5L@5IUr5TIKqC(BzALD=b?f~9Xc-{L7`8MhdHYzwvn$!3c8kxTB*SwrGrJP`b zs(jI#^;){sscMH%3UNanmX)h5?a^L9r21X;DqLpq?yETJ>NNG#rpQsM zc=YRq`V781e*V6-2+^fR6z<3e1q3{~7S-O}9qR)1xtbR^p8+wMmVi9F+~nxoH)mk| zzUrDutID;`3)uGjTCXQY_BydNuKkgsH;M_fcw`JY2p)!7!n%4|qq}ZYUWU4x=u8I| z5&VEhWSe&rcEDfvRLQMiPks0_g{1pL0@Bjo^e*BQ@U(UJzZ<&^u+3oLjPTDNq4UXs zu4tTxn?7^A$RMM)prC@IJUIe)o#q$t*;p-3oil9ke7h*gHWZ!ASB>OCGFHdBsScxi zBlyw0(8T4y+%opmcfvdQ&bY5S9&d=2I*+#!jeMGItRyhvNWxdPdSapY7EB-0W$qwd zcjM0+ssM7VM1;#{6#i^l^EH^*G0*1qq{gnx)E37lh`d##auHWc%de63ng{jda!h5tGkg12SiXx)YRvIi;Ir>{F zNx}UufOGyh(~KIrNy7tB2^MUXJN%u`b#k5x&Dwcr|Du8nrCh^0KTb$I!$URNz7lMy zy3Tak zs!3faJ9{iHSto0MY7{viZIW87oD`1nNJ&_fAohg098+UpIpW}*Iq%t9ehG3yd@;WE zD%H@AHTW|}BjvKtJCurIZFl>baqfyvbZHkV^y8t?epJXLHjaUXEJkzl?{EJ`7ew&RhN-Hg-w zKVbSA#I?Aj<*zRXEKN>#COeDywbSQz!&Rl^uDRZS%S_}KJsf3xxbk6-7XZOs2H+jn z3^oi^Gl~Z4kNA;X(tEpyQ*5`%=|v}@Lg&mm@IJ|hPxqCYwmgiFU-s6{EL*L2#q`op zehxX;GT_29-c!^ijZma&gs{y_e{szOSv7DLc-gFfvI8MxpA0mDyTG2CRytX6oWkfey{BLRR0|UXsdYCa|PeR9|RaFAx?G~?rlq2@5M5!>t`V;Ov-X- zP_ya}e9U;P%E9Hz&V!+tS*30kmx;gSZ*6y zVkF&NWv5gV((aDn2IF#GsuOx2%x^h)fCuj}%U7%mPX^oeB6&Mx&>lj_Dfg`P$(_=Y z@-jw5jJX9d@OZ4MP*L2(49^6jPfRJUm8ZO4o(W~7K^4ED2_ zMkNzx{e52s+;KoA`4uCHHv#=L571AMGWMLvsfx2_z7KIfstNHln=@)|h6gkbVL<%+*xH=$ zeF+O417_-)khp=DGXw%$!+dyFf7+zSSg+^xD&0Ue`Wf#*(fgsZUfz^Dtpe{|&QUY+ zQY8+$jRr4G=MloYF;sG{gKu*3Sy|Y`XkbEk#`_#GPE77Mka%q;7^DS7WY(6dN9f*_d_tulRXZ+x z=F)TYG5J62y?G$i>-#^fjY^gfvZjr)C1YPhDMDH-F%+^y7~9yVjwQQF_E3a6wuWRU zGBeq?vSgUdgeJ>0_I2jD->UOD-_JSE)A#qs^WSg&D#m-c@B7+b*Xz2jV=-anQ7D2B z=%!jn>HV1KTDREm1*Rl980^V6%91in?A2yddz=}(MmCR^3;Cy$gP}4)NhKaqN%&4c zYP||bt!+_`=TlS>hbt7TFJndHxF@@xg;(STm=x(P*Ip*O?oW8RyejsU@Mx{n^AAV{ zieKNpX8LJR?eX!+>4GeHyLEL)w|*jN&6CDbYXPh!rNk)PNrlAkEl}eSL(dnL3qyZH+(TyAnS>h_Vp1x zv8JHU)z9!ma4N^MOb0&g5IDTaHdog$dIk7W=6h^!s=44RWk%fJu?)2=cM}L% z!3#OUxe#*#JqyGHG#it+&!(_ssQFv>-sGZo(>+=v9=u{{2}Sef{j#!>o$7Lxaz(NB zA?xzFa6h^CoOMoZLB>xScN(Mi6S+A_* zD7+}~Qt0|x9(C)@-$a|*_sF!1_OI67c_w7Op3z9We7#GLmpS?5Y`8mUmhFJliQxP` zhT*Zk2-1Q^OL%7K+6Tca?}Y8UaP>GarCnJTe&n|fr@0Kvp8J6F!2DeNmW|EY?aB<7 z=?sHDgFOY+y=k%OeQ!+P+#1?w{`6WoAn?woh0f{H&BzJ``?Ko>!b3?um=Tt}0Ox?nvU&%3RSVQ|Zxw|e z#@p%|j&o}a9HDPasYL7F>tvq_9DQ>szP9$J>#)U?*&f@m%gtwS16Bc&LF>Z=LyHR6 zjTOkeIYZ)6Gd+j<>J>9t{Hv6~w8A_P6{8@XLtf%Db z{#@}st3prmn-18a$EC_eK{giO+NSnKFy5Wv?@OC@-HibNeL6Be7hQPsP)Q2vWbA^A z;Zzypz028qY;^h3ywPJ2l+QnW<+i3`Lb10|-fUxS!9gQ?iUWMrq0(0SlZ@&$%X1F$ zKXNozTL&L5t(KbT?Vla!^i!!|s1_mzzu93LtbDm}%5PY+&Z9bPir2K&tdbY;76JN* zk;D02-}A8pJ{veD3L{=uJ~g0FZ`@Y9wb^d<)o#qSIpN|2od$~G`pV}Iw!J)txP05) z{gilnl#2Wog`>j(VRMouxkcRgmNiHh}KDH8DUt22^CtmjY^OZVR^yfcMR)}ZShoh47qDoRN^RT4U zRnzjjd>Zq6xreuqOugKT|rlJ+W`0KnK#3w=fWsKlmI6}eNgl$ zQsCgh3-?(C|2)z2;FPj*n%uFi_a;#qLzv3)LLpAob6TX$V5PV@7vnFI3euLKfF1k! zI}%GSsR3dhy`MX}u#7Yde`^U9ij-#aahzj~pWxOr1tUOkQJ!}@!CQ^W zUp}!T_>3$4ykYT=f93tiB~lXrG3zMo$fmXgYqD|(E6Wm1Er zbk6#JAGD3n_-OWhx~wbDd#e9dJnkBUzrTN}{atp^$olyV%Jck8|JywP9AbQb`%1O; zAL6$nBVh6}v)BtS|KgHi@q^1g_jm4ph&>*~YC{;fW^-1DFL`h|)|L&?u0-+Mzu!Ew zS>4T2_3uoawy4|ZBj~l02lP!_8?}#Mlr$Zpp{k0PkI_f^Odq-Oh`-#uOr+xzEOx`= zYs=te+~O96B)_TPy3>BI%P9h7kFZU*F{o^kArpbNRM971Qv1j%V5WRwZK}ipc`+g+ z9t#5~!#5DBmvnTK{JS~N4ip-5!_B?Y# zp|SL@&Xe5~TDzc+K>KIkSCem*Kse7yTvzqCN;IM=Y|=o{k`wz}A8Pe)<#qTS2F zsMQYqA7B0LmJ4(tAv@MTQ|n!Cbx;Gp##CzZ$*hv&7f;|X%E^D1ZkZ!k_S_`UX!s^_ zSZ@XD>B{Y&o;&lcuF-nf$MCbt(49n+1>Q?W6zjUK23jLdyee(3&Zy`dI6_cC83BL! zPHt?N_``pCVZZMF6KXmz^x)N?cwKzF5!WO`#S)T5KJ=mw-7|{w7hLyIAMqFvc8N_Qvp0E4Ros)N%6HAc<8th}*s z2kvnuAlh50pIqx*Csego;vcPlJ4hTMVd?aHSN(l>=&3;WqZJX=U;)FUfv);tR!@Ms zlPFI|4d{M-@FP{2p2b$U=>kvk&Kb%6@mlX%E5GS}fQgsaCn|Cm18U}0A=Q6C!N1PQ zPim5hi+0n>;gU5WCI5C5jl6G5-+es#2JKVM5$&$aXqeZqCXD>TeSY9b)*9Og+5P6h z?oJ!QIBz94!4lK(rPoH(c0B=RQ@;N}rvA5jIPo_kBGVeDQY_BHPs{|4BVU}|GL*UC zXAcas)ZtI`*Uev^tt8L}RjPLNF1BtG1SO>Qe7aKj&Tq=6&f}x*=n1KpYK0%r?#F&x zpi`R7&w<6pec`@hnn1#Vim`i%C;F+i-VJFnad8JGdoxXL@1Mn5uugDa7!c3e99QUT zenEfImuWK+>#gWEQfz9&?;VTL!q>aP*y58UOoNYZ~i8$qGKV#~}g6_mtr&kybh@)r9(-T#>i+_)> zn*j8-CE?1@fsy&W1>(1kaan0+npMpAuioYm75g-zC)Fg#ub!&DiK=@D=0lgLu8~5Z zmx=I}cY}kMNsqo$GE(>j?VkJlc!tQ&(PR@{vOGPUWti#GuwC2id|@Bjb+Twa@iZ*( z1$d!1z0vNJ>THb)$7-w9f#t>Lk?&{;hTvv${X@0hRaTLAiUo@nz@hl<&3gB<>`4$c zd_G#Yx~o6&w1+Q{W3z)c8J!jb1ua_+fs>nf-EQQKWxKU>`gl3enEts_)(4LF3E99q zEbdPw4))umJL`%ENm%|K-tvN2>*|V+EN;M0tgo>?PY|7nDLvg>EimOZT5WY=bB8vj z>UD#mSX12$aa^(~u6E@?xa4(owm((XU2+DcHZn_P^CT8BiI(3HOXH zEVuxZeXI9s5c}jkV3_~Bc#y+7q$K?(tO)}N$641w*OU|e9cG|NbHxAnn{uCzAatww zRk->WSo4n(TNz+O99M3*KJ%74n5B6A3G?fGv-|V?Gq-_7Ih3IeN>%cg22KEs2!#6h z?ceoM_H<_+))8Fe~F60>NK4*Z!#`_ zx>i_&W}Pe={`lqlS2Kj6xOP6*n2!di$IyXOYFD})5s}?|HW}>}rc)We?jk|k?1zYS zK=tuSF9pf{tY-f2@?1f{hzP&WHqLEnngGBfAhk;zQ?8@RaD$~~w&E8xM?wuxHq}kk zj^eex{k42XDn%mw7&FoN{*Pe!ihKqxfaUv@z5jn-K45|q5=1Y9z`bee_Lcz=b!0<# zP879$v=0o-R?JV#Nr-Z{rmdQ}%6QlHEtc#}bYekEO%&xU8oF4_6enPtd~$ym4ds&f zIGUb4?7@S3l+xd7-kiK@ynRNb|D&11eE<0E(~4fNz>RYIqTJnT-Q8Tzc-PGnwR14X zv^<-YQz~5hRnP`SJ2T|D-yHd^Di<6AS;}M(I(Ra_Drjlcz8~%RM?d$}?NMdRJ8{BU z5}i)B$gdCM)k*~QC+Wij9?S((si+`pqcp{74GK)RFaXUuQIx)T(MA8~J(Ku>?+^La z9H=aDp|*h9p5IzXFFPP#f+~+J?!xaJj}wyY+;jcPmhhBEszerE%`giM=u!Z=y@IKX zViY`;Zr{CTGty=u3Jyw=NtB@gv-XoO<~2TdswdK%d%^7Hj9Jj_d-{t}6*TuwZS_k6 zAk+6@bfGfj-yB+;{862tAqcb3nub>XE^+5|lfra#+(c2$$LDS+dKAsbOq6>}NKhq_ zHm;CN|3-Z%Ic4H1%M4_(s@5Al8hJSZ~C$8xg7^AdMNtlx_4;1g-h>_6SjgANaMG*<)6M&gg69%av%rM1wAt#Z;^`Y-0M^#)Jpcz&qiAbh ziyuw%K{$3YL)Gtedh!0>A1+`4K@)S&=+c4?+KwG8k{3h5o~SBaGZSU<9Qb(Kb78R6 zt&|KF@jHYM*-VYUH|_GPsk~|?gpM083TS&c=wp%wmM=$S3^bSq&p23#AOSk1KtxQ< zF_%xTPpF7^fj0B@sQ4WTJ9g}F>PV9ty4Q+{y7l4FATB06)K~gv7>gYdi;AUcK#|}} zlpf0-W8QSAhC{+An^zu7P-OgB(E(zujvH~H(g*~YG_0Tcdi~+UtJ}o3`Va_$JUgop zUyEdWu{^LkjJMLH`Wq{2&WtKwzr!jdaH8|htts#(k1Xi7KrFWC@#R~?w4~I5Z{qz$ zwcf5u9K-E;AE4zv3MMHA+Y@HS?h9u+osZzuR3HR;g6Zgd?5+5pVuIrYTMIv)ze^y$0-f&uLNodOw|n6@gy zj;7t(wpCXX9UhvUg9Ax+r-4UeyYJicDygQ`81_FJWK=#2WFaX4!dZw@rALK0F3 zlaBZ2)_S{CaSXTWeSB#<2BwK71aFXp9gx})HHYvXZ6F_!XJ8H}PLdYAV9a~+rdrwp z=*?PMa4pR7a^<%_3wk+?RAu_wFBPOXNJcL6Fr6bs?d%Z{0o7X<1iWGkyl@|H%>Afz zTk6U=p-_A|<;RI5`s!M0HlmpTSaB>)p&4=i{LgK(W$TH#0U=BpKkBy5YHOwT00&iT z=DLUD7k7kfH!lcjJqIw?Ii`X|A*rb<-XM)s?HX)LG!>dDT#Q+Z_7-y+xnA5XU;4=V zo7o`1k4MOd(%7tg$HJy5;5eNJLo-@upFm^TZ?P~L?_SIi-V}4z{0a^m=u2thvkY2p zW^PsKq;U{Wn)jqIXp(e{`?o6dc69TK8fCWcwaHOr0KkMzq2v2&z6{-ph+2_26&(i0 zCyGn#EPFa@wlQ2afzUBfmCwtko?`I|uFr}zLnw5}BD~1Rr3ml(J}=#zwF@`8oX`p1- zu4B(JAn@i_v@Y_PHv|X0OWXg)@4{-p%KXRdF#54vVTIA;2VR*U7^7Ye7) zoyaspsiusnmnxtQ3OZR_m|jtGAbH<^9t+)jkl7;Ze{mxvU`jhAM~meW5?DW%sv2fK zX@yVTHYl9RIKO|^r2>UOxt>|_7-w$yc*XMcRnh*`T5l(C7Ij2ValcXjt=|BaR>s2@ zR&lo`Z+b=_%JX#Y|8}JRrCEi?KouXylyCm!P?g~iAW)h@Rf<=f3aFZsZm24&^fEIm z6!)9z_Bi$?Z1A}9J@l)^-#W&i6UE)eXCYN#_;Ncp!aXAXsrnKq=!Xx06&udDxPNwN zGTeYE$H{)e^R4&co+U6?bP%m&wfYcd?qn`sK% z9f7#`PivAO22JPwqzO&>rCDVYeVdHgg#_j(_j$EZZ?TH=0af$;`WaUk483hK-dmU^ zsi0ZBIBaiD{nmVDi#+=yG7|7nM#|4!*{)v>+`QT9YtG>EUQzCo$f?QYN$%Uy$V`iX z$`#hHUTCQ=MMv5)%$8MR|94AvDGUvYQ^Q8ej~8D=eOn+CEuSd^(bEf9p6P<;-|l!w zjU-yCq#wqw8VIQ+*86u3SM3^Z%U~+uKUvSLe64VY-xSsZ7$F^$MXmm?t zlcCgA5>zJT?I?3lQ04Ly@hEqmH}heU3sltlb=R>akI8-%)y*(JiV!rajK)WLtM#uM z+DW(s{TAhb9sTgR`~4VRi-P$wNCm->R()mqnbGR96an?=yCzC!D-+cK{^UyaIWjjB zUzfnTq_nO71Q99NFkkEaY=5M8-If2P0Lk(3sXw;(UY8PMBp}&&z@A^pvO&c|K=nfv z=FPlus6~c1fxlhyv#X92ttfM0Vm`y~tLFMOS z3{M-6A3xr^vf?Yx3KUsxb|FcMxV`%lz+AOmxg552^8(Y46C|$eV2I@;LG{KN$Q=wX zw4hpJa3VwRHICt%C>T4h>GO!6X?-y~oGtfCz7fyl#e602Z&&#Q{v>259L_g1&zi{DUEyO*e+LJrGqzP@gFfnB z5qjWrRr&osjZIueRRihVvHn|m2bsZm&>x>)UP6*%*XU*y=5ut*+s~KAknW|54$5aY zI@g)PJq98%6JI`DMa+IN4y{q_kUAP)X`vOsC!DHRL2IMo2~I~ir%pNW{_~pOZcqRk zcy=uM0?^7Zb#>z>vf}WKViGd5LP|m&7cbslth0ZX_(VOiSClpYFvvRogGhZxX=ydp zB&lF)qghJnLxBDC-|Rfa4aSH3_*^Q6Y6ZbjZJ~i!g7_U{59oMay(1hZg)1XiiXsKs z@VN5bId85psnM~5{j z4r`Ysz%o2CoAipfY@WP4H@ktgQEv8?GjJ$!Fg9pmEJv$_w*I@5Df?)~_r6s4@oL&@FdgFNq5Gz!Q<; zpM{l9TUlD5mRWS=$7nf1UQ}Bg?qH@+|<%3hl znPvaEnG!jbCxfj-Rxg(UW(uEYMQ9RFL(U3(e3Laf4KNEaxvzMm+@~n~!lM}$yrb<; zmNjP1Lj^vc>Si3N@XOLnqsp26fkhhdl?LQw@N&n+!sx5G|A^3Ea@mf>iFz=%PCW!R z&&gv>2XWzb)JjG(t_VO}Qf~w1U5rgUB6|qEVE`n5c<3$n#gG zqk<)u16+M<1fTo=%dRk{DEdn*LPgO{qpLpgGL4F|+tWsC%GL!7^}?Ix@Lw_$|>4X(O1e4i0|_Q-j81i&LX|tFzWic|{t+ z`uTZ=JOnvU407IMOr?6F#NV^worzwd=Z8+SLEnUg1TqLSKsYaJYosa zLlb=UQ_ns&Kz{$+2$PcwAlv{Gxa`$&as}J%0Gm1$w0dxeEs|lyr5v<%b;8F`{bnnb z2Y@ z`B0N_9Sj*Ku}zVYkrKHwvb312$=P+2PY$uvbpYAP&PTP6EXN`~QJLQ*T zZ16EMr7&7tOia6fz2!yz^$M9gN0;14=w8pL*?tiPvKl9X7YBqeqfu(9D1FbD8CpeU4UquFX8iD_{tEctq>lRW&082;QTV zr(Q9i?HY3>yX9Gp4BVU3XijvF* z%mi$2+F}~pJJJ>M`CThTR8p-=!2~lA5SXg-~Ak+9ZZ(JMD#nXoi7oE&f&>M234 zA#A$J!|f=eb+Ub!4Y!8PB^UK%xR1I2M5@DtTH~(n6U67nMn*x$|p7UBO9f>+8|+hLrVS$U8T+M4Xkkw*o0cCouZTVWtAxC0)00xf(PE zaln$nJwHwk)?i^K-c=! zi4P+q@W2C_4p9jCCHQ1q5t_n>+hU^R2hpfYR>|h{aY~dxnJHtX}G-~xr14<1J2vKCv7|Y4gZPN0iyt;GX z#L6^P@Jo^jI|~z( zz?Hwl1t0Mlz9Y+IY#qEhK5ci+vgnwh%UQ(q+qZYKC7#-1_N?==>5ImW2CvVgqUHJa zLiDlMwz#+`)9_fT!rmR(ucn}mNBxg4fn2%FQX`Xad(`XJbS?(vYcbJi$~x%QY>&CB zvD#u}&&NTmf*PCfE7)pyiy(S6u)~+jw771Z-DbKWV7odyQgg-B)bvxex3kGYIcO~8 z!e>fmRK7Od2W)9BzzkKKKSbHGpHnXC^Ox0i#y zIxckHr+paO9WHb|m^?Ow9;{?Hcqt;XWNr<%q7dQSmLxgE%h!iNhgEg#%yqJ*%P%1b z{!>LR;2++$0RG^|g=GaT4Y?G?GoNfu5$f9Jm91#SkCcem{Gcyg=VhX;1$KT`qVpL> z-NlkY0z1H~KN+>!T<;N6VhyI#l|>(Z%*UaJ6C#@FtcGzjFeqR6VZ~7`QG>t zueIRs0NT>PDw|+8ZPL{YH__g_s+ymyNR~B-WQMBAWc8-#Unh{=)tx_yaFU=E}mwy z&3D4IQ-EYbXe+*Kc3WL+#7QKrGrN-EX`i5)KTPfW+Z~{f)3u?GyK_#x@gk~6-O7oO zi^`vyJ?C(s27s48MV4VJ{MHmkck zjpp9M2K%Tobw}DfiSe`nnBNL?<)9HoMgfDJemq*B&(2^xkOZ0#$xR?X!WJjPm<%Y4 zih>3mSJ6^tItZNtZsX`{pBOgGl&Ji-D7A$5JGW{#rNOi~@rA)@Qi^1*ECTe5leNwYA}oD(q_TB$b%hKOdy zHWFGJY1ZEoA#bdgRn-9!KseICrF$Gdai%GDOiNk%Y)hGGJ^f6?JE$K5c zk($J8hdx%4q?mHULwSuzlVUEh;#}C2gYk)bZ`iuT4KxS4T@Xl))(Yu`W3qP_e7MVw zfy+=-D+zpA>`HO=pAWT0|X| z5IMZ^v!U#^9(d69>H)j$Y*+A7+4Q8?_b4(LADAJ5?e49-J_=(8o4NVX@ni(`bYAr4 zr$J=wCIMdKa|_2q96alUQo!PQO-YlLBs2UT5i1@N@})WGL?Z5o6XYcNGHQxO1@$?; zkDa*3tFO^UiO*zNB_e?~M^ZF6ani-{A?)5^+$RHUR7aSir((A5NJl zhi_x(-UGE<0?-|^VsPN;MYet8+%Kd}85!^Q4GunRGJG=;T18Zeq#Uo$sinA9aZG6z zGD~Qjt?Z!Ag`*dwDKjmSr`;+7XVp~tmOBXvv-Hjz{)^}%PDJ-9t_^HJVCfpnToq`A4$yDvk9%%`B!059_4@TC2Rw&7YiJT{TvBvMHX=E! z_nI~7t-}V8u2;aZNDX;r{0bz)4&5?ELbPMigyKqK6x8j_f#in2XAkVx(1n+)&7Pk8 z9NC}6XVct=ZYAdej??LN*}boAVoXwYd6g=@)TjW4Cl4|kE`UMS4kYn#kr+n`@GTJX zQ6<^?6|e(h3=vdCJ`ON*HD$)=zF2RRjWo+1&HQK?Z&y%x?<5l^4Ek3!!`ZUO4n$;xf;yZwGdIxx@K<1gRDdhYyj!Zp0DJ zan9mNyY0GE5)|*_q+DvnM+0%%yW_Wr9;zfg)ihI9G5$OGT z1!N#?7RN7gjmFz|?3t&8mUvNzNGgtAoLWgu6xHBm8mU(?$~}GZ`{$O|-zXqhcv2Cd zPUlzYV?k9Y+*uiO_n*owGx{o-Y-S=MnE--$)oRff!S??B; zgLtG7RY;r^>x`1A^lCz>=Q@$}&^=GV2}hUkqv8>P026l_srKgMxErScc76%9 z9vK-K>XNQsvGK_rPz-0cb*$obv$c%d9Ihl8_-Si#^g4>Ihx5BP?KzoUgKm z=g^F~=Ldu*P(Dts8O}~r)S7O?!g4oBk1lI#!3MFl#6biMKkC_~CJVlmpDq>`JDG!+ z?o^?C0Q*=A_EAxtokuewsNazowfRXgnH$w9S|TEXol(p~fTu^@I`mEUkw z%%Hnjw@Ip7Us!FXulcXtM>agH|8Aj1c5Y}87cXoWb6d`{P$wh@uPxFR0K<-qwqVPf zoK<=9=8o#4V>F?7jHisjMgEe=0j#Qxl}uL(a`d$6_1S8hrAEJRI1pF?0h?=XdHx=M z?hi%80aM`^#!xZdi0q-2s)`B)6|*&f(L3gC6A6xE3==z?q9T0PFk#@qDO0W;&3p1I z{ig#lSHU8BVqnJ4A@9g9f7{ocGD)EMTuK4V;0Zp*zu|tsEzlWv5Yd|=We>J!esi@LEEXJ1JyY_@=G~6mEy(U1zB!*@*{KZ z%IC#yzmB{B1xi`qnQaPVsbyXRA-vi-55@PIlUQTNZT7{=D8-5DkX$ze01}5BNgNT8 zn4R-opQ&(Q*$vu)i8sTWv(2|XP3S=04^`AKxh)Y99e_zWNTLQ3y}>mnqR^^X3NS>Y zXeNfIw!frKP6h!J{3_foY2$V^`w|^k)|*U$r!&$#bD#KdVFIY;L|M7AJt7lM~Rds1x9*Z);cNy{`OHSOa(AWu?b5o^uny zz5vplZHAT@^$rWSxELl zwxF?7(k}LR2=5gTWFnM7YO*^Dq>En5be_9s3L|5E`NMYk&2Re(`*oit$SG@5#WO=I^P-8H?T^_-c z&Ran0g!RNsRqp5}+gy|Y#ttn%dtM0nT5`l#X33mnScT9XUi|`ebg;*US|&5q0q-q> zT?>;^W@LEv=-1wHtGq|-qSvjne&H@xI32Jw+ZFYdO-SH^tu2XccJqEsVmcSGCVC>| z2ymYO1EP#EbY-7kR?)*q!mSNM(q@k;*iIaj%GhF7o=@)V_=p-xUqdQAtippJ2xy_P z4h0Y$ua7R5ZXZ-s!Y*1yyzAi9AW|$8Fnp)t7SkotpJv8(mre>n zDb}@4WPQTO8IcLjc}Y2O2Cvd8mOV0W&YjUb8pS~TGYoP0WYqTT3ldno*z1!A8k4|_ z*3Pwxn;$WcnM8bS62o3EzkN-f+j;j6AQ0tS0uh6N8GgpmT&By;J$wri=cU>Ad<~{W zafF<~lTpP`kI>jY5Rl*{)r5C38OIzbi4P(rTL&uzumXty);nkrcx33xS7%g(J9f#0 zV+DC^Q3`vn>vvR-gARBDXJyS|x@0;>K{|2>jKRQ|BPR7skg9 zy5@1=up~fazj7z0Y8==$8kSeE4?DsJ3RmSQzVHZ@$)q$-653a)n1QvM3<*U2o#}U2 z#e?gAj^j9q6l&n~5geK@N2&*JMXYj!qF(B#B8mr>Lh5AhImO7(ed4E=!>@>#vs3nU zJX-O#ZfFQ~;}i>Zd(8IQ=7u~TVUKBOia3I!48qD91ZVQ;JtxhJGhhUlFS?a8z=bkx( zcqYnuQeHp@F{1!b3>S4{_NTnuhi(~I8;c#-WtxjXe|Y?T#to&yFH3rUm8%`Z^1HSf zJAND)$uKbGK%t=KoD-!0cFIQj>(ibN(Fp-nO{oIu2M+j(6e2xhf1 z#X(H&*y9OK;OJ#wA^#@fr?kp@EDTa%rxc%9Opof3RZcyq>jHSR`F`5__m0Y{%1Ltj zV&aXSN9N#4C&F4ZNBv3||9 zHkmRMGc2vCbZ%5z{Me|&#;l_tI0XYa3c?rZEBXKzE@*4*AZ$pNJ4xyZgAyP%3%5pE(U`qre&)lLwXrL)cq%aldPTU+vVTHGD~3s>KJXnPk*P zu0mh9FbfZ=29|(k{{`9J_4t{%*#))g2Cw&U4P;ZICvs_!D8ks*MR#L0B>xRG1u6ehI=CkWKh1+2Nm#nM1Rg*jU*Us#1WBaolpN0$@hBD!YNw%$J&m~ zkg8qFpiga0Z|JKzu_m|@HryzWsaQ}CDuD8gCgxHj31>G9iAZhde@O^DP z0la662to^4JWw=^-9-LurFa+M2s@D^nfj7ZlQa0kV27HK!VMuS@NS`h!ONeM0J06T zny;ZEihIGLN7dKgj~N^67S!55+qyKZcE2wXRjX3rX!R{?rI@6WzxgkTtQ1gs-QxAN zdn~TfQ!5a=QbF(4(!DgmB2_jpG|W3f z;)Grb66U2v!S{C<9qpE8hqJ%|>jh@EZ}1^?x|0dyq)*m6D{Aj|br`08*x#||THc3H zcEZ7by>xXXkq)vI`H=YAhk*_tL-0M>XKpyITVp?{(5r6w{y%;D&|<2MD{a=Iu^?l+k7MJj>EU(^JcwSQ9}xTF-Ct zBk87T+k7??qzw9=Y2WV!x!67`30D@ak=VSN2r&QY)$<#5SEMSr2-<cf?qkPZojvz1?lKq3fAZ!L5+)XP>}LLC!T_Y7PD*?oa6 zU2%6Dx$w2e3q%e18Z?rIVZ(xc!(;XD_t>5TcTg@iHuhE*PU#^)5ptuY>lFjk=fb1| zMFI*rJCg-ed?ZEyLyn9@EhGCHXj2V&z=b-HwUt0kd-fw2$snk^Un)^RNHO(|n$OXq z_0bz^G|{EP7sta^B&eyxGK@n^m!889X^|+YoTFEJPxWSP4rS#o*Du$R0q$nKW<)1z z;uvSE9&085~H<(KHJY>L^hS8xXrJKv7fQ@p$W?-!(+6 zEszV9Zg_dY3@%=@LrO+82&)GwuKV4-{iovi>&o=`hevcvCvB_6(&GI>S7d|ZR0P1L zIP`U7Qa*4%Js%Y*0(hqz{dG z4UBJ+K{K_4`UPu(Ue;!!LLW3Lh>WM}2A657am>fvYA&@&MNj*(QLTt-XO3+hm8Vy9 zwrxN>$pw{$c@?r}*1F}|+CW3*)@iX`%eB_aT5WgeEhwp1z5)|QO8Rb4aedYs7;C#O z9Gtnhe(jsr=ePF?eKq!eEvcEUUI=Jd4j^-8t?2Yye$S%MqYM@B?%I3@54KDgm-@|5 zEDWGF2gu^qt9qZxolQ+bg~JxnG3@hewB?4ZVEQsj1CH8U9GW7bSJnZUbZlBp_a5ES zIEDV-Z8H|kltys|IXiCC7HB%I|3x?K;A@N%aap-?D@ z!fQXlNr1zy>f~s`c$?SjK?1v%|z9 z*$AZ^MB*o$r_VHOR-}f9nk_+mx*%lk@*+AuNJ4FC&^C<&+K3yePy^@a@SRUdVokXZ zkDtr$y+N__pZsV`F3-pHC3)wNBZt{?q0Hn~JWWE)>O<+zg$|98jDH5xMrJ3s9EI)^ zt^6Fp^jlCvO`b4FzEYR-3!QRmvpUKkXeVjQBoc(WE4 z2ds_gEAjM{;F*D7$-3n_N=M#0B-Xd){Qx9VcKi>hf%g1~pnmYHAX!xKqzd5zU+TZWNfb;zIpoW_XdaEp{A-yaIsh8l7hNv5DY%$E1jJF)M-4 zioc!vnXiP7KJ$$&1Fx(Lu>LIiYF3w}6Mc0u%Sf2Mg6qoyQka)T-_+7VMM&Fh-c%MVkiecz{9yKfeIw4%bLP<$3{*-!0@# z-<7fZWqyzIBD*-UP6$P-9Igf3#fLnCmysw1xL40vG6?qCXMNr0I}{wUyg|mniB8)b zT90zdK69wP>=Nm<{nRVComjl1r~MQNYrpp7n4^Qa*1G^!;tPlE)Y*@! zt+?FR2(sq=uP$9Tgc-DN3Oq!u+(*6V+ezHi<;YHGx|4gkZ{Y zu-gRd9_fY+kF}bH^_tYKG63)%W3%7^qU(-iD}Sf{_m_f^ArO{N zrs9OVGNb>XJ8DHNb7D4R2x3cgNYSa#FYiYMX8O88_BC@$($|${Yfphs-wHd@&KJC* z%2b&pAi55XaAYglx|W@(0w`jjh77@%E|Lc?vo~1sm1Nn&ui)A~8wdft^D*bbRt&+@ zQdhyC?(~j^jd_e9$7}tpj;I28I_6Ke9UvUIJ$;8GdMgPmufzo?)bOeU(ct{bTXMK>E0gTP5-K_rx;eZPst|bLe$2C0lx2 zc+;Lj046S7PR$ZJ)<6oo_?T=~L3#fjWss&yN}*?Mv}D;K&E7k6MbzxP2rO^dI&~Q* z6d;iiFk5Bb6D%Bxv(q&yd$dEzcYb;%d7Ue1JNASPB8^JZU{fCdxX$)30j0R@e>)&S+?wq~!od-qb z8M?Mk`nGBORS4u(RhV@|P}bJgPy%>uy@9@->J%Ox{^#xUyWQ$n?bq;=>dBI_v**79 zbk~lBU4~vcuPH%!s4H|qc=f$*X$>P(+HX#zY-uXM$&T_=KBC@cgj(x!Sv7u3hi0>6 z9aFy|A<8ER$emsae~5jF?>cC(r^oj@bbf~%Vvhd-C7FTRtY3N3rM60ys>_)Hj=H(?nC{qp%d9m+c zg;eDgK(1I?74rZ3tgz}xeLCpT;X;GfNOSXVc5c@Xa#}`Ct;pB_Q2o6Dz+YQvYuIQr z-ygUUIG$*lN2AfEiy3$nYmZPBu3~+-|U(BV;xAh`awl~jv=0_6O#n$MM zvR{Cb$u<6TEt}w;Kz8KG2tQ0#u6^huN7YKO%??Qwmt?grz>YOLoTxjlZiH77QFp#j zJLyXSZi{RpAB)~x-q5R??+U^Hu!1bKVbr>tY+E%@untMr{MG9dE+=UDf>|HE`Ra== z7g~&hPjB_du+7a2-O}Q<_20v!?F<;Hp7_L@bQ;80lcdM9}7#Fzx2`I{(GO zE+*(bKUn{Zh6BDv{o2P86N#m*?|i-bBZ}Yl736%jZH0B3lOWRseJ*AEivrQnZh}lA z?K2zg5FCLy@Uo$yVP)Ue_qPVNoYnrDXl?zNA*JXa250LRcZdEVi~sizj&4O;Kg{y~ zU8VoO&(blF2h3jCXZEs{sjbTPDT4hg*MFmLZJqZ;ZDvwl%GoT-#raU^jdA-!Mt?lkiaT_SY#b51ZPp0;V*T{XG7x+8UajxoETz=KJ+LLWW^R5?jVQU=tXP zkaBwkW8E&E@%M-N_#tt898BXlZxSf)cRE%$7Xv?>*Naw}i1rYXv&o;xSnI~$}Owrkobgel&x!iK?7RK^vva)4wO z;~-OvS>Yz(14DAbTRZ*ztt~FXhw$xv4^F}F`Yf!pz?6dvUp?qq=&W!2u-Q*@xjBrC zig;~ggO8$$W6``_HkQ%UJN_H#YGQm$XYF#aW@3l`@fkOVuK5KNToTcw1kt|co)*|? z`G<_*Z25KT_j_mSc94sZtj{w?f}AFc-`6ip3?DvRl)#&86Bva!i7nu*u-S-0Nc>p4 zHvaWiTLG?zK5^(vI{)~zUMZKMfXq-!x2GQk){u%(haauJtwKFMLV_W$E^!bokZRnPpDh{)xW zn(1JDvDo-q%qm{Ks)7PakHU}K$Li=@mky(zz`o=y`MErpeUGtc_B##pGN1j&qdr&l zE)T*8f@!sw@L56DuKTf8_9mQaQ$!Ja9aF8S!x?cbZY)*!Jdy?Zt!Hhrf^ik>-1RV z-=5*M-CW$1ktjV-Q|^EM!R8~RB}#d9a>t(+z?zIRw~e;jefe@RF7iKrd|p3q!#CI` zuwgm{b(B*3R_g?+$dmDJM|UP+j;RtC=spZa4FLdc<}MM71|P?AtiwMQFSmQ6}OI~(r*|0 zfBE%o%kn3(De3s|&=$@!BsxOqP$$#HEa{XWjz*=eXo}(M zzK>62-?yoPX|eHwS*od4s2YM~^48sN?27vNCtot8-8+BScx7$5$$fLD-L$p5Brl?<48ed7~a^F#+l3mR(z4FnpNb zUy%T9r$6hHN7JP*@C1E%vM?4O2!n4F4fkTfyfD$|bJltVZj~oT6cwCjjfR(nr43<4 zNNsXea_^HWE_+W4YB^i}DA9JDJ1}_p9cI#1U%E3xX6tr^H`+!XxKFGEitzhK#dm&8 zkWb1Z-?d%TS0em4G;O)Ld>sA*Ru4w!H8yJ&+&WupdheG6o#=SYH*KAuo~Z)=!qa

7cq5ydT+$2f20Jo`y&uwVP5& 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 dgW$4p-iUO^56Vk|r8B@!z?L1GD>m*+{}1Mw literal 0 HcmV?d00001 diff --git a/metricbeat/docs/images/metricbeat-iis-website-overview.png b/metricbeat/docs/images/metricbeat-iis-website-overview.png new file mode 100644 index 0000000000000000000000000000000000000000..ee86a9be9d8c1d82948ad53a7c1f47988f3a4eb6 GIT binary patch literal 349969 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

!11s7!)iL_B+YR$dlo8`3P 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 literal 0 HcmV?d00001 diff --git a/metricbeat/docs/modules_list.asciidoc b/metricbeat/docs/modules_list.asciidoc index bacbcb00906..2382eb508fc 100644 --- a/metricbeat/docs/modules_list.asciidoc +++ b/metricbeat/docs/modules_list.asciidoc @@ -124,7 +124,7 @@ This file is generated! See scripts/mage/docs_collector.go |<> |<> beta[] |image:./images/icon-yes.png[Prebuilt dashboards are available] | .1+| .1+| |<> beta[] -|<> beta[] |image:./images/icon-no.png[No prebuilt dashboards] | +|<> beta[] |image:./images/icon-yes.png[Prebuilt dashboards are available] | .3+| .3+| |<> beta[] |<> beta[] |<> beta[] diff --git a/metricbeat/module/windows/perfmon/data.go b/metricbeat/module/windows/perfmon/data.go index 7db0a338de2..68de068ed05 100644 --- a/metricbeat/module/windows/perfmon/data.go +++ b/metricbeat/module/windows/perfmon/data.go @@ -38,54 +38,52 @@ var processRegexp = regexp.MustCompile(`(.+?)#[1-9]+`) func (re *Reader) groupToEvents(counters map[string][]pdh.CounterValue) []mb.Event { eventMap := make(map[string]*mb.Event) for counterPath, values := range counters { - hasCounter, counter := re.getCounter(counterPath) - for ind, val := range values { - // Some counters, such as rate counters, require two counter values in order to compute a displayable value. In this case we must call PdhCollectQueryData twice before calling PdhGetFormattedCounterValue. - // For more information, see Collecting Performance Data (https://docs.microsoft.com/en-us/windows/desktop/PerfCtrs/collecting-performance-data). - if val.Err != nil && !re.executed { - re.log.Debugw("Ignoring the first measurement because the data isn't ready", - "error", val.Err, logp.Namespace("perfmon"), "query", counterPath) - continue - } - - var eventKey string - if re.config.GroupMeasurements && val.Err == nil { - // Send measurements with the same instance label as part of the same event - eventKey = val.Instance - } else { - // Send every measurement as an individual event - // If a counter contains an error, it will always be sent as an individual event - eventKey = counterPath + strconv.Itoa(ind) - } - - // Create a new event if the key doesn't exist in the map - if _, ok := eventMap[eventKey]; !ok { - eventMap[eventKey] = &mb.Event{ - MetricSetFields: common.MapStr{}, - Error: errors.Wrapf(val.Err, "failed on query=%v", counterPath), + if hasCounter, counter := re.getCounter(counterPath); hasCounter { + for ind, val := range values { + // Some counters, such as rate counters, require two counter values in order to compute a displayable value. In this case we must call PdhCollectQueryData twice before calling PdhGetFormattedCounterValue. + // For more information, see Collecting Performance Data (https://docs.microsoft.com/en-us/windows/desktop/PerfCtrs/collecting-performance-data). + if val.Err != nil && !re.executed { + re.log.Debugw("Ignoring the first measurement because the data isn't ready", + "error", val.Err, logp.Namespace("perfmon"), "query", counterPath) + continue } - if val.Instance != "" && hasCounter { - //will ignore instance counter - if ok, match := matchesParentProcess(val.Instance); ok { - eventMap[eventKey].MetricSetFields.Put(counter.InstanceField, match) - } else { - eventMap[eventKey].MetricSetFields.Put(counter.InstanceField, val.Instance) + var eventKey string + if re.config.GroupMeasurements && val.Err == nil { + // Send measurements with the same instance label as part of the same event + eventKey = val.Instance + } else { + // Send every measurement as an individual event + // If a counter contains an error, it will always be sent as an individual event + eventKey = counterPath + strconv.Itoa(ind) + } + // Create a new event if the key doesn't exist in the map + if _, ok := eventMap[eventKey]; !ok { + eventMap[eventKey] = &mb.Event{ + MetricSetFields: common.MapStr{}, + Error: errors.Wrapf(val.Err, "failed on query=%v", counterPath), + } + if val.Instance != "" { + //will ignore instance counter + if ok, match := matchesParentProcess(val.Instance); ok { + eventMap[eventKey].MetricSetFields.Put(counter.InstanceField, match) + } else { + eventMap[eventKey].MetricSetFields.Put(counter.InstanceField, val.Instance) + } } } - } - event := eventMap[eventKey] - if val.Measurement != nil { - event.MetricSetFields.Put(counter.QueryField, val.Measurement) - } else { - event.MetricSetFields.Put(counter.QueryField, 0) - } - if counter.ObjectField != "" { - event.MetricSetFields.Put(counter.ObjectField, counter.ObjectName) + if val.Measurement != nil { + eventMap[eventKey].MetricSetFields.Put(counter.QueryField, val.Measurement) + } else { + eventMap[eventKey].MetricSetFields.Put(counter.QueryField, 0) + } + if counter.ObjectField != "" { + eventMap[eventKey].MetricSetFields.Put(counter.ObjectField, counter.ObjectName) + } } } } // Write the values into the map. - events := make([]mb.Event, 0, len(eventMap)) + var events []mb.Event for _, val := range eventMap { events = append(events, *val) } @@ -98,30 +96,34 @@ func (re *Reader) groupToSingleEvent(counters map[string][]pdh.CounterValue) mb. } measurements := make(map[string]float64, 0) for counterPath, values := range counters { - _, readerCounter := re.getCounter(counterPath) - for _, val := range values { - // Some counters, such as rate counters, require two counter values in order to compute a displayable value. In this case we must call PdhCollectQueryData twice before calling PdhGetFormattedCounterValue. - // For more information, see Collecting Performance Data (https://docs.microsoft.com/en-us/windows/desktop/PerfCtrs/collecting-performance-data). - if val.Err != nil && !re.executed { - re.log.Debugw("Ignoring the first measurement because the data isn't ready", - "error", val.Err, logp.Namespace("perfmon"), "query", counterPath) - continue - } - var counterVal float64 - switch val.Measurement.(type) { - case int64: - counterVal = float64(val.Measurement.(int64)) - case int: - counterVal = float64(val.Measurement.(int)) - default: - counterVal = val.Measurement.(float64) - } - if _, ok := measurements[readerCounter.QueryField]; !ok { - measurements[readerCounter.QueryField] = counterVal - measurements[readerCounter.QueryField+instanceCountLabel] = 1 - } else { - measurements[readerCounter.QueryField+instanceCountLabel] = measurements[readerCounter.QueryField+instanceCountLabel] + 1 - measurements[readerCounter.QueryField] = measurements[readerCounter.QueryField] + counterVal + if hasCounter, readerCounter := re.getCounter(counterPath); hasCounter { + for _, val := range values { + // Some counters, such as rate counters, require two counter values in order to compute a displayable value. In this case we must call PdhCollectQueryData twice before calling PdhGetFormattedCounterValue. + // For more information, see Collecting Performance Data (https://docs.microsoft.com/en-us/windows/desktop/PerfCtrs/collecting-performance-data). + if val.Err != nil && !re.executed { + re.log.Debugw("Ignoring the first measurement because the data isn't ready", + "error", val.Err, logp.Namespace("perfmon"), "query", counterPath) + continue + } + if val.Measurement == nil { + continue + } + var counterVal float64 + switch val.Measurement.(type) { + case int64: + counterVal = float64(val.Measurement.(int64)) + case int: + counterVal = float64(val.Measurement.(int)) + default: + counterVal = val.Measurement.(float64) + } + if _, ok := measurements[readerCounter.QueryField]; !ok { + measurements[readerCounter.QueryField] = counterVal + measurements[readerCounter.QueryField+instanceCountLabel] = 1 + } else { + measurements[readerCounter.QueryField+instanceCountLabel] = measurements[readerCounter.QueryField+instanceCountLabel] + 1 + measurements[readerCounter.QueryField] = measurements[readerCounter.QueryField] + counterVal + } } } } diff --git a/x-pack/metricbeat/module/iis/_meta/kibana/7/dashboard/Metricbeat-iis-application-pool-overview.json b/x-pack/metricbeat/module/iis/_meta/kibana/7/dashboard/Metricbeat-iis-application-pool-overview.json new file mode 100644 index 00000000000..866b4593489 --- /dev/null +++ b/x-pack/metricbeat/module/iis/_meta/kibana/7/dashboard/Metricbeat-iis-application-pool-overview.json @@ -0,0 +1,1026 @@ +{ + "objects": [ + { + "attributes": { + "description": "This dashboard shows application pools metrics for the IIS server.", + "hits": 0, + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "optionsJSON": { + "hidePanelTitles": false, + "useMargins": true + }, + "panelsJSON": [ + { + "embeddableConfig": { + "title": "" + }, + "gridData": { + "h": 5, + "i": "43790631-73f5-4f5e-824e-aa9d3f4f5664", + "w": 9, + "x": 0, + "y": 0 + }, + "panelIndex": "43790631-73f5-4f5e-824e-aa9d3f4f5664", + "panelRefName": "panel_0", + "version": "7.6.0" + }, + { + "embeddableConfig": { + "title": "Thread Count" + }, + "gridData": { + "h": 11, + "i": "b94f6dac-0f16-4781-ac08-d632154458a6", + "w": 20, + "x": 9, + "y": 0 + }, + "panelIndex": "b94f6dac-0f16-4781-ac08-d632154458a6", + "panelRefName": "panel_1", + "title": "Thread Count", + "version": "7.6.0" + }, + { + "embeddableConfig": { + "title": "Handle Count" + }, + "gridData": { + "h": 11, + "i": "a3c19f11-99d4-4794-8ebb-aa2b3583577b", + "w": 19, + "x": 29, + "y": 0 + }, + "panelIndex": "a3c19f11-99d4-4794-8ebb-aa2b3583577b", + "panelRefName": "panel_2", + "title": "Handle Count", + "version": "7.6.0" + }, + { + "embeddableConfig": { + "title": "Filters" + }, + "gridData": { + "h": 6, + "i": "10fe7306-655b-4bc6-b078-892f91d0d9ea", + "w": 9, + "x": 0, + "y": 5 + }, + "panelIndex": "10fe7306-655b-4bc6-b078-892f91d0d9ea", + "panelRefName": "panel_3", + "title": "Filters", + "version": "7.6.0" + }, + { + "embeddableConfig": { + "title": "IO Write Operations" + }, + "gridData": { + "h": 15, + "i": "4dfc6a98-4749-47a8-a92e-ee955cf20c00", + "w": 24, + "x": 0, + "y": 11 + }, + "panelIndex": "4dfc6a98-4749-47a8-a92e-ee955cf20c00", + "panelRefName": "panel_4", + "title": "IO Write Operations", + "version": "7.6.0" + }, + { + "embeddableConfig": { + "title": "IO Read Operations" + }, + "gridData": { + "h": 15, + "i": "bae63734-a752-46c3-8083-8c12eabca5aa", + "w": 24, + "x": 24, + "y": 11 + }, + "panelIndex": "bae63734-a752-46c3-8083-8c12eabca5aa", + "panelRefName": "panel_5", + "title": "IO Read Operations", + "version": "7.6.0" + }, + { + "embeddableConfig": { + "title": "Working Set" + }, + "gridData": { + "h": 15, + "i": "ce663f89-7cf9-4659-af06-2128f7d2f74b", + "w": 24, + "x": 0, + "y": 26 + }, + "panelIndex": "ce663f89-7cf9-4659-af06-2128f7d2f74b", + "panelRefName": "panel_6", + "title": "Working Set", + "version": "7.6.0" + }, + { + "embeddableConfig": { + "title": "Virtual Bytes" + }, + "gridData": { + "h": 15, + "i": "99da2951-fb14-4a18-8a0c-4647daa9b626", + "w": 24, + "x": 24, + "y": 41 + }, + "panelIndex": "99da2951-fb14-4a18-8a0c-4647daa9b626", + "panelRefName": "panel_7", + "title": "Virtual Bytes", + "version": "7.6.0" + }, + { + "embeddableConfig": { + "title": "Private Bytes" + }, + "gridData": { + "h": 15, + "i": "71985962-b5c9-412e-a99f-b55ed988f4ea", + "w": 24, + "x": 0, + "y": 41 + }, + "panelIndex": "71985962-b5c9-412e-a99f-b55ed988f4ea", + "panelRefName": "panel_8", + "title": "Private Bytes", + "version": "7.6.0" + }, + { + "embeddableConfig": { + "title": "CPU Usage" + }, + "gridData": { + "h": 15, + "i": "347b6893-0ab0-47fc-9e80-efc9bf47f13e", + "w": 24, + "x": 24, + "y": 26 + }, + "panelIndex": "347b6893-0ab0-47fc-9e80-efc9bf47f13e", + "panelRefName": "panel_9", + "title": "CPU Usage", + "version": "7.6.0" + } + ], + "timeRestore": false, + "title": "[Metricbeat IIS] Application Pool Overview", + "version": 1 + }, + "id": "b4108810-861c-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "dashboard": "7.3.0" + }, + "references": [ + { + "id": "dc97bec0-861c-11ea-91bc-ab084c7ec0e7", + "name": "panel_0", + "type": "visualization" + }, + { + "id": "41324ad0-861d-11ea-91bc-ab084c7ec0e7", + "name": "panel_1", + "type": "visualization" + }, + { + "id": "98b90fa0-861d-11ea-91bc-ab084c7ec0e7", + "name": "panel_2", + "type": "visualization" + }, + { + "id": "dd419de0-861d-11ea-91bc-ab084c7ec0e7", + "name": "panel_3", + "type": "visualization" + }, + { + "id": "442a86c0-861e-11ea-91bc-ab084c7ec0e7", + "name": "panel_4", + "type": "visualization" + }, + { + "id": "29a23aa0-861e-11ea-91bc-ab084c7ec0e7", + "name": "panel_5", + "type": "visualization" + }, + { + "id": "34bfec50-8620-11ea-91bc-ab084c7ec0e7", + "name": "panel_6", + "type": "visualization" + }, + { + "id": "14300bf0-8620-11ea-91bc-ab084c7ec0e7", + "name": "panel_7", + "type": "visualization" + }, + { + "id": "f7194cc0-861f-11ea-91bc-ab084c7ec0e7", + "name": "panel_8", + "type": "visualization" + }, + { + "id": "90fe3b30-861f-11ea-91bc-ab084c7ec0e7", + "name": "panel_9", + "type": "visualization" + } + ], + "type": "dashboard", + "updated_at": "2020-04-24T12:19:57.800Z", + "version": "Wzk3MzMsMTNd" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Navigation Application Pool Overview [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "fontSize": 10, + "markdown": "### IIS\n\n[Webserver](#/dashboard/ebc23240-8572-11ea-91bc-ab084c7ec0e7)| [Webserver processes](#/dashboard/2c171500-858b-11ea-91bc-ab084c7ec0e7) | [Websites](#/dashboard/4b975820-85a1-11ea-91bc-ab084c7ec0e7) | [**Application Pools**](#/dashboard/b4108810-861c-11ea-91bc-ab084c7ec0e7) ", + "openLinksInNewTab": false + }, + "title": "Navigation Application Pool Overview [Metricbeat IIS]", + "type": "markdown" + } + }, + "id": "dc97bec0-861c-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T11:54:38.997Z", + "version": "WzkzMTksMTNd" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Application Pool Thread Count [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "background_color": null, + "background_color_rules": [ + { + "id": "71978870-32e4-11ea-af9e-d70582a45bda" + } + ], + "bar_color_rules": [ + { + "id": "f11cfd90-32e5-11ea-af9e-d70582a45bda" + } + ], + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "drilldown_url": "", + "filter": { + "language": "kuery", + "query": "" + }, + "gauge_color_rules": [ + { + "id": "9c09ed50-32e4-11ea-af9e-d70582a45bda" + } + ], + "gauge_inner_color": null, + "gauge_inner_width": "6", + "gauge_style": "circle", + "gauge_width": "10", + "id": "61fb4190-32e4-11ea-b9f8-4d0b340ad993", + "index_pattern": "metricbeat-*", + "interval": "60m", + "isModelInvalid": false, + "pivot_id": "cloud.instance.name", + "pivot_label": "Resource Name", + "pivot_rows": "30", + "pivot_type": "string", + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(22,165,165,1)", + "fill": "1.2", + "filter": { + "language": "kuery", + "query": "" + }, + "formatter": "number", + "id": "61fb4191-32e4-11ea-b9f8-4d0b340ad993", + "label": "Thread Count", + "line_width": 2, + "metrics": [ + { + "agg_with": "avg", + "field": "iis.application_pool.process.thread_count", + "id": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "order": "desc", + "order_by": "@timestamp", + "size": 1, + "type": "top_hit" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "gradient", + "split_mode": "terms", + "stacked": "none", + "terms_field": "iis.application_pool.name", + "terms_order_by": "_count", + "type": "timeseries", + "value_template": "{{value}} " + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "time_range_mode": "entire_time_range", + "type": "top_n" + }, + "title": "Application Pool Thread Count [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "41324ad0-861d-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T11:17:59.421Z", + "version": "WzkwMDMsMTNd" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Application Pool Handle Count [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "background_color": null, + "background_color_rules": [ + { + "id": "71978870-32e4-11ea-af9e-d70582a45bda" + } + ], + "bar_color_rules": [ + { + "id": "f11cfd90-32e5-11ea-af9e-d70582a45bda" + } + ], + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "drilldown_url": "", + "filter": { + "language": "kuery", + "query": "" + }, + "gauge_color_rules": [ + { + "id": "9c09ed50-32e4-11ea-af9e-d70582a45bda" + } + ], + "gauge_inner_color": null, + "gauge_inner_width": "6", + "gauge_style": "circle", + "gauge_width": "10", + "id": "61fb4190-32e4-11ea-b9f8-4d0b340ad993", + "index_pattern": "metricbeat-*", + "interval": "60m", + "isModelInvalid": false, + "pivot_id": "cloud.instance.name", + "pivot_label": "Resource Name", + "pivot_rows": "30", + "pivot_type": "string", + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(101,50,148,1)", + "fill": "1.2", + "filter": { + "language": "kuery", + "query": "" + }, + "formatter": "number", + "id": "61fb4191-32e4-11ea-b9f8-4d0b340ad993", + "label": "Handle Count", + "line_width": 2, + "metrics": [ + { + "agg_with": "avg", + "field": "iis.application_pool.process.handle_count", + "id": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "order": "desc", + "order_by": "@timestamp", + "size": 1, + "type": "top_hit" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "gradient", + "split_mode": "terms", + "stacked": "none", + "terms_field": "iis.application_pool.name", + "terms_order_by": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "type": "timeseries", + "value_template": "{{value}} " + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "time_range_mode": "entire_time_range", + "type": "top_n" + }, + "title": "Application Pool Handle Count [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "98b90fa0-861d-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T11:20:26.266Z", + "version": "WzkwMTgsMTNd" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Application Pool Filters [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "controls": [ + { + "fieldName": "iis.application_pool.name", + "id": "1549397251041", + "indexPatternRefName": "control_0_index_pattern", + "label": "Application Pools", + "options": { + "dynamicOptions": true, + "multiselect": true, + "order": "desc", + "size": 5, + "type": "terms" + }, + "parent": "", + "type": "list" + } + ], + "pinFilters": false, + "updateFiltersOnChange": true, + "useTimeFilter": false + }, + "title": "Application Pool Filters [Metricbeat IIS]", + "type": "input_control_vis" + } + }, + "id": "dd419de0-861d-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [ + { + "id": "metricbeat-*", + "name": "control_0_index_pattern", + "type": "index-pattern" + } + ], + "type": "visualization", + "updated_at": "2020-04-24T11:22:21.246Z", + "version": "WzkwMjksMTNd" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Application Pool IO Write Operations [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "filter": { + "language": "kuery", + "query": "" + }, + "id": "c9fd65d0-32e8-11ea-84f4-e9593f8ba8f6", + "index_pattern": "metricbeat-*", + "interval": "", + "isModelInvalid": false, + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(104,188,0,1)", + "fill": "0", + "formatter": "number", + "id": "c9fd8ce0-32e8-11ea-84f4-e9593f8ba8f6", + "label": "IO Write Operations/s", + "line_width": "2", + "metrics": [ + { + "field": "iis.application_pool.process.io_write_operations_per_sec", + "id": "c9fd8ce1-32e8-11ea-84f4-e9593f8ba8f6", + "type": "avg" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "rainbow", + "split_mode": "terms", + "stacked": "none", + "terms_field": "iis.application_pool.name", + "type": "timeseries", + "value_template": "{{value}}/s" + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "type": "timeseries" + }, + "title": "Application Pool IO Write Operations [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "442a86c0-861e-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T11:25:13.899Z", + "version": "WzkwNDIsMTNd" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Application Pool IO Read Operations [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "filter": { + "language": "kuery", + "query": "" + }, + "id": "c9fd65d0-32e8-11ea-84f4-e9593f8ba8f6", + "index_pattern": "metricbeat-*", + "interval": "", + "isModelInvalid": false, + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(252,196,0,1)", + "fill": "0", + "formatter": "number", + "id": "c9fd8ce0-32e8-11ea-84f4-e9593f8ba8f6", + "label": "IO Read Operations/s", + "line_width": "2", + "metrics": [ + { + "field": "iis.application_pool.process.io_read_operations_per_sec", + "id": "c9fd8ce1-32e8-11ea-84f4-e9593f8ba8f6", + "type": "avg" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "rainbow", + "split_mode": "terms", + "stacked": "none", + "terms_field": "iis.application_pool.name", + "type": "timeseries", + "value_template": "{{value}}/s" + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "type": "timeseries" + }, + "title": "Application Pool IO Read Operations [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "29a23aa0-861e-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T11:26:30.995Z", + "version": "WzkwNjcsMTNd" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Application Pool Working Set [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "filter": { + "language": "kuery", + "query": "" + }, + "id": "c9fd65d0-32e8-11ea-84f4-e9593f8ba8f6", + "index_pattern": "metricbeat-*", + "interval": "", + "isModelInvalid": false, + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(115,216,255,1)", + "fill": "0", + "formatter": "bytes", + "id": "c9fd8ce0-32e8-11ea-84f4-e9593f8ba8f6", + "label": "Working Set", + "line_width": "2", + "metrics": [ + { + "field": "iis.application_pool.process.working_set", + "id": "c9fd8ce1-32e8-11ea-84f4-e9593f8ba8f6", + "type": "avg" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "rainbow", + "split_mode": "terms", + "stacked": "none", + "terms_field": "iis.application_pool.name", + "type": "timeseries", + "value_template": "{{value}}" + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "type": "timeseries" + }, + "title": "Application Pool Working Set [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "34bfec50-8620-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T11:39:22.702Z", + "version": "WzkxMTEsMTNd" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Application Pool Virtual Bytes [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "filter": { + "language": "kuery", + "query": "" + }, + "id": "c9fd65d0-32e8-11ea-84f4-e9593f8ba8f6", + "index_pattern": "metricbeat-*", + "interval": "", + "isModelInvalid": false, + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(0,98,177,1)", + "fill": "0", + "formatter": "bytes", + "id": "c9fd8ce0-32e8-11ea-84f4-e9593f8ba8f6", + "label": "Virtual Bytes", + "line_width": "2", + "metrics": [ + { + "field": "iis.application_pool.process.virtual_bytes", + "id": "c9fd8ce1-32e8-11ea-84f4-e9593f8ba8f6", + "type": "avg" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "rainbow", + "split_mode": "terms", + "stacked": "none", + "terms_field": "iis.application_pool.name", + "type": "timeseries", + "value_template": "{{value}}" + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "type": "timeseries" + }, + "title": "Application Pool Virtual Bytes [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "14300bf0-8620-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T11:38:12.399Z", + "version": "WzkxMDEsMTNd" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Application Pool Private Bytes [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "filter": { + "language": "kuery", + "query": "" + }, + "id": "c9fd65d0-32e8-11ea-84f4-e9593f8ba8f6", + "index_pattern": "metricbeat-*", + "interval": "", + "isModelInvalid": false, + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "#3185FC", + "fill": "0", + "formatter": "bytes", + "id": "c9fd8ce0-32e8-11ea-84f4-e9593f8ba8f6", + "label": "Private Bytes", + "line_width": "2", + "metrics": [ + { + "field": "iis.application_pool.process.private_bytes", + "id": "c9fd8ce1-32e8-11ea-84f4-e9593f8ba8f6", + "type": "avg" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "rainbow", + "split_mode": "terms", + "stacked": "none", + "terms_field": "iis.application_pool.name", + "type": "timeseries", + "value_template": "{{value}}" + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "type": "timeseries" + }, + "title": "Application Pool Private Bytes [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "f7194cc0-861f-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T12:19:46.947Z", + "version": "Wzk3MjQsMTNd" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Application Pool CPU Usage [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "filter": { + "language": "kuery", + "query": "" + }, + "id": "c9fd65d0-32e8-11ea-84f4-e9593f8ba8f6", + "index_pattern": "metricbeat-*", + "interval": "", + "isModelInvalid": false, + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "#3185FC", + "fill": "0", + "formatter": "percent", + "id": "c9fd8ce0-32e8-11ea-84f4-e9593f8ba8f6", + "label": "CPU Usage", + "line_width": "2", + "metrics": [ + { + "field": "iis.application_pool.process.cpu_usage_perc", + "id": "c9fd8ce1-32e8-11ea-84f4-e9593f8ba8f6", + "type": "avg" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "rainbow", + "split_mode": "terms", + "stacked": "none", + "terms_field": "iis.application_pool.name", + "type": "timeseries", + "value_template": "{{value}}" + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "type": "timeseries" + }, + "title": "Application Pool CPU Usage [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "90fe3b30-861f-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T11:34:32.291Z", + "version": "WzkwODEsMTNd" + } + ], + "version": "7.6.0" +} diff --git a/x-pack/metricbeat/module/iis/_meta/kibana/7/dashboard/Metricbeat-iis-webserver-overview.json b/x-pack/metricbeat/module/iis/_meta/kibana/7/dashboard/Metricbeat-iis-webserver-overview.json new file mode 100644 index 00000000000..57fe28b8ea6 --- /dev/null +++ b/x-pack/metricbeat/module/iis/_meta/kibana/7/dashboard/Metricbeat-iis-webserver-overview.json @@ -0,0 +1,1602 @@ +{ + "objects": [ + { + "attributes": { + "description": "This dashboard shows metrics for the IIS server.", + "hits": 0, + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "optionsJSON": { + "hidePanelTitles": false, + "useMargins": true + }, + "panelsJSON": [ + { + "embeddableConfig": { + "title": "" + }, + "gridData": { + "h": 5, + "i": "55b62196-f0fa-4473-a578-c5197ae7581c", + "w": 10, + "x": 0, + "y": 0 + }, + "panelIndex": "55b62196-f0fa-4473-a578-c5197ae7581c", + "panelRefName": "panel_0", + "version": "7.6.2" + }, + { + "embeddableConfig": { + "title": "" + }, + "gridData": { + "h": 9, + "i": "9f1c724a-e57a-4667-8930-b60997efeee5", + "w": 9, + "x": 10, + "y": 0 + }, + "panelIndex": "9f1c724a-e57a-4667-8930-b60997efeee5", + "panelRefName": "panel_1", + "version": "7.6.2" + }, + { + "embeddableConfig": { + "title": "" + }, + "gridData": { + "h": 9, + "i": "5aeeab99-314c-4732-b731-ebd390fa9210", + "w": 10, + "x": 19, + "y": 0 + }, + "panelIndex": "5aeeab99-314c-4732-b731-ebd390fa9210", + "panelRefName": "panel_2", + "version": "7.6.2" + }, + { + "embeddableConfig": { + "title": "" + }, + "gridData": { + "h": 9, + "i": "88113bca-98b0-4877-b768-46f9b1c29751", + "w": 10, + "x": 29, + "y": 0 + }, + "panelIndex": "88113bca-98b0-4877-b768-46f9b1c29751", + "panelRefName": "panel_3", + "version": "7.6.2" + }, + { + "embeddableConfig": { + "title": "" + }, + "gridData": { + "h": 9, + "i": "0add6210-d7d5-4b66-afaa-bcec0bc445d9", + "w": 9, + "x": 39, + "y": 0 + }, + "panelIndex": "0add6210-d7d5-4b66-afaa-bcec0bc445d9", + "panelRefName": "panel_4", + "version": "7.6.2" + }, + { + "embeddableConfig": { + "title": "" + }, + "gridData": { + "h": 8, + "i": "e809e015-29b2-496b-aaf1-f461d59b1568", + "w": 5, + "x": 0, + "y": 5 + }, + "panelIndex": "e809e015-29b2-496b-aaf1-f461d59b1568", + "panelRefName": "panel_5", + "version": "7.6.2" + }, + { + "embeddableConfig": { + "title": "" + }, + "gridData": { + "h": 8, + "i": "6ebec04f-3df0-4995-ae9c-836774f05ea7", + "w": 5, + "x": 5, + "y": 5 + }, + "panelIndex": "6ebec04f-3df0-4995-ae9c-836774f05ea7", + "panelRefName": "panel_6", + "version": "7.6.2" + }, + { + "embeddableConfig": { + "title": "Total Requests" + }, + "gridData": { + "h": 12, + "i": "41de633b-40aa-4456-967a-5737d139a374", + "w": 38, + "x": 10, + "y": 9 + }, + "panelIndex": "41de633b-40aa-4456-967a-5737d139a374", + "panelRefName": "panel_7", + "title": "Total Requests", + "version": "7.6.2" + }, + { + "embeddableConfig": { + "title": "" + }, + "gridData": { + "h": 8, + "i": "3e1276ed-2f8f-4a07-948c-cf5ae0de9fc3", + "w": 5, + "x": 0, + "y": 13 + }, + "panelIndex": "3e1276ed-2f8f-4a07-948c-cf5ae0de9fc3", + "panelRefName": "panel_8", + "version": "7.6.2" + }, + { + "embeddableConfig": { + "title": "" + }, + "gridData": { + "h": 8, + "i": "983e0c36-04f7-4ec0-b6a6-8d2599a63065", + "w": 5, + "x": 5, + "y": 13 + }, + "panelIndex": "983e0c36-04f7-4ec0-b6a6-8d2599a63065", + "panelRefName": "panel_9", + "version": "7.6.2" + }, + { + "embeddableConfig": { + "title": "Total Bytes Transferred" + }, + "gridData": { + "h": 14, + "i": "aa567463-86ec-4bbe-bf0f-b0d4d1d57bb0", + "w": 24, + "x": 24, + "y": 21 + }, + "panelIndex": "aa567463-86ec-4bbe-bf0f-b0d4d1d57bb0", + "panelRefName": "panel_10", + "title": "Total Bytes Transferred", + "version": "7.6.2" + }, + { + "embeddableConfig": { + "title": "Bytes Transferred/sec" + }, + "gridData": { + "h": 14, + "i": "623596fb-3f1f-4f2e-b15b-800d197ce1aa", + "w": 24, + "x": 0, + "y": 21 + }, + "panelIndex": "623596fb-3f1f-4f2e-b15b-800d197ce1aa", + "panelRefName": "panel_11", + "title": "Bytes Transferred/sec", + "version": "7.6.2" + } + ], + "timeRestore": false, + "title": "[Metricbeat IIS] Webserver Overview", + "version": 1 + }, + "id": "ebc23240-8572-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "dashboard": "7.3.0" + }, + "references": [ + { + "id": "40614070-8573-11ea-91bc-ab084c7ec0e7", + "name": "panel_0", + "type": "visualization" + }, + { + "id": "82f1d7d0-858a-11ea-91bc-ab084c7ec0e7", + "name": "panel_1", + "type": "visualization" + }, + { + "id": "c92e0b80-8574-11ea-91bc-ab084c7ec0e7", + "name": "panel_2", + "type": "visualization" + }, + { + "id": "348c4fe0-8575-11ea-91bc-ab084c7ec0e7", + "name": "panel_3", + "type": "visualization" + }, + { + "id": "461a8640-8576-11ea-91bc-ab084c7ec0e7", + "name": "panel_4", + "type": "visualization" + }, + { + "id": "c8e467d0-8d55-11ea-817c-a9b6d42fd8a0", + "name": "panel_5", + "type": "visualization" + }, + { + "id": "df9d0e50-8d55-11ea-817c-a9b6d42fd8a0", + "name": "panel_6", + "type": "visualization" + }, + { + "id": "75812480-857f-11ea-91bc-ab084c7ec0e7", + "name": "panel_7", + "type": "visualization" + }, + { + "id": "14e77b40-8d56-11ea-817c-a9b6d42fd8a0", + "name": "panel_8", + "type": "visualization" + }, + { + "id": "2d802c60-8d56-11ea-817c-a9b6d42fd8a0", + "name": "panel_9", + "type": "visualization" + }, + { + "id": "68a9df20-8581-11ea-91bc-ab084c7ec0e7", + "name": "panel_10", + "type": "visualization" + }, + { + "id": "92acc3e0-8582-11ea-91bc-ab084c7ec0e7", + "name": "panel_11", + "type": "visualization" + } + ], + "type": "dashboard", + "updated_at": "2020-05-03T16:20:16.383Z", + "version": "WzEyNDUsMV0=" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Navigation Webserver Overview [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "fontSize": 10, + "markdown": "### IIS\n\n[**Webserver**](#/dashboard/ebc23240-8572-11ea-91bc-ab084c7ec0e7)| [Webserver processes](#/dashboard/2c171500-858b-11ea-91bc-ab084c7ec0e7) | [Websites](#/dashboard/4b975820-85a1-11ea-91bc-ab084c7ec0e7) | [Application Pools](#/dashboard/b4108810-861c-11ea-91bc-ab084c7ec0e7) \n\n\n\n", + "openLinksInNewTab": false + }, + "title": "Navigation Webserver Overview [Metricbeat IIS]", + "type": "markdown" + } + }, + "id": "40614070-8573-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T13:07:24.547Z", + "version": "WzUwOSwxXQ==" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Webserver Service Uptime [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "background_color": null, + "background_color_rules": [ + { + "id": "71978870-32e4-11ea-af9e-d70582a45bda" + } + ], + "bar_color_rules": [ + { + "id": "f11cfd90-32e5-11ea-af9e-d70582a45bda" + } + ], + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "drilldown_url": "", + "filter": { + "language": "kuery", + "query": "" + }, + "gauge_color_rules": [ + { + "id": "9c09ed50-32e4-11ea-af9e-d70582a45bda" + } + ], + "gauge_inner_color": null, + "gauge_inner_width": "6", + "gauge_style": "circle", + "gauge_width": "10", + "id": "61fb4190-32e4-11ea-b9f8-4d0b340ad993", + "index_pattern": "metricbeat-*", + "interval": "60m", + "isModelInvalid": false, + "pivot_id": "cloud.instance.name", + "pivot_label": "Resource Name", + "pivot_rows": "30", + "pivot_type": "string", + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(211,49,21,1)", + "fill": "1.2", + "filter": { + "language": "kuery", + "query": "" + }, + "formatter": "s,h,", + "id": "61fb4191-32e4-11ea-b9f8-4d0b340ad993", + "label": "Service Uptime", + "line_width": 2, + "metrics": [ + { + "agg_with": "avg", + "field": "iis.webserver.network.service_uptime", + "id": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "order": "desc", + "order_by": "@timestamp", + "size": 1, + "type": "top_hit" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "gradient", + "split_mode": "everything", + "stacked": "none", + "terms_field": "cloud.instance.name", + "terms_order_by": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "type": "timeseries", + "value_template": "{{value}} h" + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "time_range_mode": "entire_time_range", + "type": "gauge" + }, + "title": "Webserver Service Uptime [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "82f1d7d0-858a-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T13:07:24.547Z", + "version": "WzUxMCwxXQ==" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Webserver Current Connections [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "background_color": null, + "background_color_rules": [ + { + "id": "71978870-32e4-11ea-af9e-d70582a45bda" + } + ], + "bar_color_rules": [ + { + "id": "f11cfd90-32e5-11ea-af9e-d70582a45bda" + } + ], + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "drilldown_url": "", + "filter": { + "language": "kuery", + "query": "" + }, + "gauge_color_rules": [ + { + "id": "9c09ed50-32e4-11ea-af9e-d70582a45bda" + } + ], + "gauge_inner_color": null, + "gauge_inner_width": "6", + "gauge_style": "circle", + "gauge_width": "10", + "id": "61fb4190-32e4-11ea-b9f8-4d0b340ad993", + "index_pattern": "metricbeat-*", + "interval": "60m", + "isModelInvalid": false, + "pivot_id": "cloud.instance.name", + "pivot_label": "Resource Name", + "pivot_rows": "30", + "pivot_type": "string", + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(104,188,0,1)", + "fill": "1.2", + "filter": { + "language": "kuery", + "query": "" + }, + "formatter": "number", + "id": "61fb4191-32e4-11ea-b9f8-4d0b340ad993", + "label": "Current Connections", + "line_width": 2, + "metrics": [ + { + "agg_with": "avg", + "field": "iis.webserver.network.current_connections", + "id": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "order": "desc", + "order_by": "@timestamp", + "size": 1, + "type": "top_hit" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "gradient", + "split_mode": "everything", + "stacked": "none", + "terms_field": "cloud.instance.name", + "terms_order_by": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "type": "timeseries", + "value_template": "{{value}}" + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "time_range_mode": "entire_time_range", + "type": "gauge" + }, + "title": "Webserver Current Connections [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "c92e0b80-8574-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T13:07:24.547Z", + "version": "WzUxMSwxXQ==" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Webserver Maximum Connections [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "background_color": null, + "background_color_rules": [ + { + "id": "71978870-32e4-11ea-af9e-d70582a45bda" + } + ], + "bar_color_rules": [ + { + "id": "f11cfd90-32e5-11ea-af9e-d70582a45bda" + } + ], + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "drilldown_url": "", + "filter": { + "language": "kuery", + "query": "" + }, + "gauge_color_rules": [ + { + "id": "9c09ed50-32e4-11ea-af9e-d70582a45bda" + } + ], + "gauge_inner_color": null, + "gauge_inner_width": "6", + "gauge_style": "circle", + "gauge_width": "10", + "id": "61fb4190-32e4-11ea-b9f8-4d0b340ad993", + "index_pattern": "metricbeat-*", + "interval": "60m", + "isModelInvalid": false, + "pivot_id": "cloud.instance.name", + "pivot_label": "Resource Name", + "pivot_rows": "30", + "pivot_type": "string", + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(104,204,202,1)", + "fill": "1.2", + "filter": { + "language": "kuery", + "query": "" + }, + "formatter": "number", + "id": "61fb4191-32e4-11ea-b9f8-4d0b340ad993", + "label": "Maximum Connections", + "line_width": 2, + "metrics": [ + { + "agg_with": "avg", + "field": "iis.webserver.network.maximum_connections", + "id": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "order": "desc", + "order_by": "@timestamp", + "size": 1, + "type": "top_hit" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "gradient", + "split_mode": "everything", + "stacked": "none", + "terms_field": "cloud.instance.name", + "terms_order_by": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "type": "timeseries", + "value_template": "{{value}}" + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "time_range_mode": "entire_time_range", + "type": "gauge" + }, + "title": "Webserver Maximum Connections [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "348c4fe0-8575-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T13:07:24.547Z", + "version": "WzUxMiwxXQ==" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Webserver Total Connection Attempts [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "background_color": null, + "background_color_rules": [ + { + "id": "71978870-32e4-11ea-af9e-d70582a45bda" + } + ], + "bar_color_rules": [ + { + "id": "f11cfd90-32e5-11ea-af9e-d70582a45bda" + } + ], + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "drilldown_url": "", + "filter": { + "language": "kuery", + "query": "" + }, + "gauge_color_rules": [ + { + "id": "9c09ed50-32e4-11ea-af9e-d70582a45bda" + } + ], + "gauge_inner_color": null, + "gauge_inner_width": "6", + "gauge_style": "circle", + "gauge_width": "10", + "id": "61fb4190-32e4-11ea-b9f8-4d0b340ad993", + "index_pattern": "metricbeat-*", + "interval": "60m", + "isModelInvalid": false, + "pivot_id": "cloud.instance.name", + "pivot_label": "Resource Name", + "pivot_rows": "30", + "pivot_type": "string", + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(251,158,0,1)", + "fill": "1.2", + "filter": { + "language": "kuery", + "query": "" + }, + "formatter": "number", + "id": "61fb4191-32e4-11ea-b9f8-4d0b340ad993", + "label": "Total Connection Attempts", + "line_width": 2, + "metrics": [ + { + "agg_with": "avg", + "field": "iis.webserver.network.total_connection_attempts", + "id": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "order": "desc", + "order_by": "@timestamp", + "size": 1, + "type": "top_hit" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "gradient", + "split_mode": "everything", + "stacked": "none", + "terms_field": "cloud.instance.name", + "terms_order_by": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "type": "timeseries", + "value_template": "{{value}}" + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "time_range_mode": "entire_time_range", + "type": "gauge" + }, + "title": "Webserver Total Connection Attempts [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "461a8640-8576-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T13:07:24.547Z", + "version": "WzUxMywxXQ==" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Webserver Overview Current Anonymous Users [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "background_color": null, + "background_color_rules": [ + { + "id": "71978870-32e4-11ea-af9e-d70582a45bda" + } + ], + "bar_color_rules": [ + { + "id": "f11cfd90-32e5-11ea-af9e-d70582a45bda" + } + ], + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "drilldown_url": "", + "filter": { + "language": "kuery", + "query": "" + }, + "gauge_color_rules": [ + { + "id": "9c09ed50-32e4-11ea-af9e-d70582a45bda" + } + ], + "gauge_inner_color": null, + "gauge_inner_width": "6", + "gauge_style": "circle", + "gauge_width": "10", + "id": "61fb4190-32e4-11ea-b9f8-4d0b340ad993", + "index_pattern": "metricbeat-*", + "interval": "60m", + "isModelInvalid": false, + "pivot_id": "cloud.instance.name", + "pivot_label": "Resource Name", + "pivot_rows": "30", + "pivot_type": "string", + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(49,211,21,1)", + "fill": "1.2", + "filter": { + "language": "kuery", + "query": "" + }, + "formatter": "number", + "id": "61fb4191-32e4-11ea-b9f8-4d0b340ad993", + "label": "Current Anonymous Users", + "line_width": 2, + "metrics": [ + { + "agg_with": "avg", + "field": "iis.webserver.network.current_anonymous_users", + "id": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "order": "desc", + "order_by": "@timestamp", + "size": 1, + "type": "top_hit" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "gradient", + "split_mode": "everything", + "stacked": "none", + "terms_field": "cloud.instance.name", + "terms_order_by": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "type": "timeseries", + "value_template": "{{value}} " + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "time_range_mode": "entire_time_range", + "type": "gauge" + }, + "title": "Webserver Overview Current Anonymous Users [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "c8e467d0-8d55-11ea-817c-a9b6d42fd8a0", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-05-03T15:50:17.034Z", + "version": "WzEwNjcsMV0=" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Webserver Overview Total Anonymous Users [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "background_color": null, + "background_color_rules": [ + { + "id": "71978870-32e4-11ea-af9e-d70582a45bda" + } + ], + "bar_color_rules": [ + { + "id": "f11cfd90-32e5-11ea-af9e-d70582a45bda" + } + ], + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "drilldown_url": "", + "filter": { + "language": "kuery", + "query": "" + }, + "gauge_color_rules": [ + { + "id": "9c09ed50-32e4-11ea-af9e-d70582a45bda" + } + ], + "gauge_inner_color": null, + "gauge_inner_width": "6", + "gauge_style": "circle", + "gauge_width": "10", + "id": "61fb4190-32e4-11ea-b9f8-4d0b340ad993", + "index_pattern": "metricbeat-*", + "interval": "60m", + "isModelInvalid": false, + "pivot_id": "cloud.instance.name", + "pivot_label": "Resource Name", + "pivot_rows": "30", + "pivot_type": "string", + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(21,68,211,1)", + "fill": "1.2", + "filter": { + "language": "kuery", + "query": "" + }, + "formatter": "number", + "id": "61fb4191-32e4-11ea-b9f8-4d0b340ad993", + "label": "Total Anonymous Users", + "line_width": 2, + "metrics": [ + { + "agg_with": "avg", + "field": "iis.webserver.network.total_anonymous_users", + "id": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "order": "desc", + "order_by": "@timestamp", + "size": 1, + "type": "top_hit" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "gradient", + "split_mode": "everything", + "stacked": "none", + "terms_field": "cloud.instance.name", + "terms_order_by": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "type": "timeseries", + "value_template": "{{value}} " + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "time_range_mode": "entire_time_range", + "type": "gauge" + }, + "title": "Webserver Overview Total Anonymous Users [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "df9d0e50-8d55-11ea-817c-a9b6d42fd8a0", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-05-03T15:50:55.156Z", + "version": "WzEwNzMsMV0=" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Webserver Total Requests [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "filter": { + "language": "kuery", + "query": "" + }, + "id": "c9fd65d0-32e8-11ea-84f4-e9593f8ba8f6", + "index_pattern": "metricbeat-*", + "interval": "", + "isModelInvalid": false, + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "#3185FC", + "fill": "0", + "formatter": "number", + "id": "c9fd8ce0-32e8-11ea-84f4-e9593f8ba8f6", + "label": "Total Get Requests", + "line_width": "2", + "metrics": [ + { + "field": "iis.webserver.network.total_get_requests", + "id": "c9fd8ce1-32e8-11ea-84f4-e9593f8ba8f6", + "type": "avg" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "rainbow", + "split_mode": "everything", + "stacked": "none", + "terms_field": null, + "type": "timeseries", + "value_template": "{{value}}" + }, + { + "axis_position": "right", + "chart_type": "line", + "color": "#68BC00", + "fill": "0", + "formatter": "number", + "id": "55ef6fb0-857e-11ea-87b6-db4d36ae5839", + "label": "Total Post Requests", + "line_width": "2", + "metrics": [ + { + "field": "iis.webserver.network.total_post_requests", + "id": "55ef6fb1-857e-11ea-87b6-db4d36ae5839", + "type": "avg" + } + ], + "point_size": "0", + "separate_axis": 0, + "split_mode": "everything", + "stacked": "none", + "type": "timeseries" + }, + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(149,0,188,1)", + "fill": "0", + "formatter": "number", + "id": "7501b0c0-857e-11ea-87b6-db4d36ae5839", + "label": "Total Delete Requests", + "line_width": "2", + "metrics": [ + { + "field": "iis.webserver.network.total_delete_requests", + "id": "7501b0c1-857e-11ea-87b6-db4d36ae5839", + "type": "avg" + } + ], + "point_size": "", + "separate_axis": 0, + "split_mode": "everything", + "stacked": "none", + "type": "timeseries" + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "type": "timeseries" + }, + "title": "Webserver Total Requests [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "75812480-857f-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T13:07:24.547Z", + "version": "WzUxNSwxXQ==" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Webserver Overview Current Non Anonymous Users [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "background_color": null, + "background_color_rules": [ + { + "id": "71978870-32e4-11ea-af9e-d70582a45bda" + } + ], + "bar_color_rules": [ + { + "id": "f11cfd90-32e5-11ea-af9e-d70582a45bda" + } + ], + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "drilldown_url": "", + "filter": { + "language": "kuery", + "query": "" + }, + "gauge_color_rules": [ + { + "id": "9c09ed50-32e4-11ea-af9e-d70582a45bda" + } + ], + "gauge_inner_color": null, + "gauge_inner_width": "6", + "gauge_style": "circle", + "gauge_width": "10", + "id": "61fb4190-32e4-11ea-b9f8-4d0b340ad993", + "index_pattern": "metricbeat-*", + "interval": "60m", + "isModelInvalid": false, + "pivot_id": "cloud.instance.name", + "pivot_label": "Resource Name", + "pivot_rows": "30", + "pivot_type": "string", + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(171,21,211,1)", + "fill": "1.2", + "filter": { + "language": "kuery", + "query": "" + }, + "formatter": "number", + "id": "61fb4191-32e4-11ea-b9f8-4d0b340ad993", + "label": "Current Non Anonymous Users", + "line_width": 2, + "metrics": [ + { + "agg_with": "avg", + "field": "iis.webserver.network.current_non_anonymous_users", + "id": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "order": "desc", + "order_by": "@timestamp", + "size": 1, + "type": "top_hit" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "gradient", + "split_mode": "everything", + "stacked": "none", + "terms_field": "cloud.instance.name", + "terms_order_by": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "type": "timeseries", + "value_template": "{{value}} " + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "time_range_mode": "entire_time_range", + "type": "gauge" + }, + "title": "Webserver Overview Current Non Anonymous Users [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "14e77b40-8d56-11ea-817c-a9b6d42fd8a0", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-05-03T15:52:24.564Z", + "version": "WzEwOTcsMV0=" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Webserver Overview Total Non Anonymous Users [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "background_color": null, + "background_color_rules": [ + { + "id": "71978870-32e4-11ea-af9e-d70582a45bda" + } + ], + "bar_color_rules": [ + { + "id": "f11cfd90-32e5-11ea-af9e-d70582a45bda" + } + ], + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "drilldown_url": "", + "filter": { + "language": "kuery", + "query": "" + }, + "gauge_color_rules": [ + { + "id": "9c09ed50-32e4-11ea-af9e-d70582a45bda" + } + ], + "gauge_inner_color": null, + "gauge_inner_width": "6", + "gauge_style": "circle", + "gauge_width": "10", + "id": "61fb4190-32e4-11ea-b9f8-4d0b340ad993", + "index_pattern": "metricbeat-*", + "interval": "60m", + "isModelInvalid": false, + "pivot_id": "cloud.instance.name", + "pivot_label": "Resource Name", + "pivot_rows": "30", + "pivot_type": "string", + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(211,21,105,1)", + "fill": "1.2", + "filter": { + "language": "kuery", + "query": "" + }, + "formatter": "number", + "id": "61fb4191-32e4-11ea-b9f8-4d0b340ad993", + "label": "Total Non Anonymous Users", + "line_width": 2, + "metrics": [ + { + "agg_with": "avg", + "field": "iis.webserver.network.total_non_anonymous_users", + "id": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "order": "desc", + "order_by": "@timestamp", + "size": 1, + "type": "top_hit" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "gradient", + "split_mode": "everything", + "stacked": "none", + "terms_field": "cloud.instance.name", + "terms_order_by": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "type": "timeseries", + "value_template": "{{value}} " + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "time_range_mode": "entire_time_range", + "type": "gauge" + }, + "title": "Webserver Overview Total Non Anonymous Users [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "2d802c60-8d56-11ea-817c-a9b6d42fd8a0", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-05-03T15:53:05.830Z", + "version": "WzExMDQsMV0=" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Webserver Total Bytes Transfered [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "background_color": null, + "background_color_rules": [ + { + "id": "71978870-32e4-11ea-af9e-d70582a45bda" + } + ], + "bar_color_rules": [ + { + "id": "f11cfd90-32e5-11ea-af9e-d70582a45bda" + } + ], + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "drilldown_url": "", + "filter": { + "language": "kuery", + "query": "" + }, + "gauge_color_rules": [ + { + "id": "9c09ed50-32e4-11ea-af9e-d70582a45bda" + } + ], + "gauge_inner_color": null, + "gauge_inner_width": "6", + "gauge_style": "circle", + "gauge_width": "10", + "id": "61fb4190-32e4-11ea-b9f8-4d0b340ad993", + "index_pattern": "metricbeat-*", + "interval": "", + "isModelInvalid": false, + "pivot_id": "cloud.instance.name", + "pivot_label": "Resource Name", + "pivot_rows": "30", + "pivot_type": "string", + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(251,158,0,1)", + "fill": "0", + "filter": { + "language": "kuery", + "query": "" + }, + "formatter": "bytes", + "id": "61fb4191-32e4-11ea-b9f8-4d0b340ad993", + "label": "Total Bytes Sent ", + "line_width": 2, + "metrics": [ + { + "agg_with": "avg", + "field": "iis.webserver.network.total_bytes_sent", + "id": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "order": "desc", + "order_by": "@timestamp", + "size": 1, + "type": "avg" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "gradient", + "split_mode": "everything", + "stacked": "none", + "terms_field": "cloud.instance.name", + "terms_order_by": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "type": "timeseries", + "value_template": "{{value}}" + }, + { + "axis_position": "right", + "chart_type": "line", + "color": "#68BC00", + "fill": "0", + "formatter": "bytes", + "id": "cb6910f0-8580-11ea-8d9f-cf59f8572d31", + "label": "Total Bytes Received", + "line_width": "2", + "metrics": [ + { + "field": "iis.webserver.network.total_bytes_received", + "id": "cb693800-8580-11ea-8d9f-cf59f8572d31", + "type": "avg" + } + ], + "point_size": "", + "separate_axis": 0, + "split_mode": "everything", + "stacked": "none", + "type": "timeseries" + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "time_range_mode": "entire_time_range", + "type": "timeseries" + }, + "title": "Webserver Total Bytes Transfered [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "68a9df20-8581-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T13:07:24.547Z", + "version": "WzUxNywxXQ==" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Webserver Bytes Transfered Per Sec [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "background_color": null, + "background_color_rules": [ + { + "id": "71978870-32e4-11ea-af9e-d70582a45bda" + } + ], + "bar_color_rules": [ + { + "id": "f11cfd90-32e5-11ea-af9e-d70582a45bda" + } + ], + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "drilldown_url": "", + "filter": { + "language": "kuery", + "query": "" + }, + "gauge_color_rules": [ + { + "id": "9c09ed50-32e4-11ea-af9e-d70582a45bda" + } + ], + "gauge_inner_color": null, + "gauge_inner_width": "6", + "gauge_style": "circle", + "gauge_width": "10", + "id": "61fb4190-32e4-11ea-b9f8-4d0b340ad993", + "index_pattern": "metricbeat-*", + "interval": "", + "isModelInvalid": false, + "pivot_id": "cloud.instance.name", + "pivot_label": "Resource Name", + "pivot_rows": "30", + "pivot_type": "string", + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(22,165,165,1)", + "fill": "0", + "filter": { + "language": "kuery", + "query": "" + }, + "formatter": "number", + "id": "61fb4191-32e4-11ea-b9f8-4d0b340ad993", + "label": "Bytes Sent/sec", + "line_width": 2, + "metrics": [ + { + "agg_with": "avg", + "field": "iis.webserver.network.bytes_sent_per_sec", + "id": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "order": "desc", + "order_by": "@timestamp", + "size": 1, + "type": "avg" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "gradient", + "split_mode": "everything", + "stacked": "none", + "terms_field": "cloud.instance.name", + "terms_order_by": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "type": "timeseries", + "value_template": "{{value}}/s" + }, + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(250,40,255,1)", + "fill": "0", + "formatter": "number", + "id": "cb6910f0-8580-11ea-8d9f-cf59f8572d31", + "label": "Bytes Received/sec", + "line_width": "2", + "metrics": [ + { + "field": "iis.webserver.network.bytes_received_per_sec", + "id": "cb693800-8580-11ea-8d9f-cf59f8572d31", + "type": "avg" + } + ], + "point_size": "", + "separate_axis": 0, + "split_mode": "everything", + "stacked": "none", + "type": "timeseries", + "value_template": "{{value}}/s" + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "time_range_mode": "entire_time_range", + "type": "timeseries" + }, + "title": "Webserver Bytes Transfered Per Sec [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "92acc3e0-8582-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T13:07:24.547Z", + "version": "WzUxNiwxXQ==" + } + ], + "version": "7.6.2" +} diff --git a/x-pack/metricbeat/module/iis/_meta/kibana/7/dashboard/Metricbeat-iis-webserver-process-overview.json b/x-pack/metricbeat/module/iis/_meta/kibana/7/dashboard/Metricbeat-iis-webserver-process-overview.json new file mode 100644 index 00000000000..6c294be381f --- /dev/null +++ b/x-pack/metricbeat/module/iis/_meta/kibana/7/dashboard/Metricbeat-iis-webserver-process-overview.json @@ -0,0 +1,1510 @@ +{ + "objects": [ + { + "attributes": { + "description": "This dashboard shows process and cache metrics for the IIS server.", + "hits": 0, + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "optionsJSON": { + "hidePanelTitles": false, + "useMargins": true + }, + "panelsJSON": [ + { + "embeddableConfig": { + "title": "" + }, + "gridData": { + "h": 5, + "i": "8c503a52-2cfb-4922-97db-b91bf343285d", + "w": 10, + "x": 0, + "y": 0 + }, + "panelIndex": "8c503a52-2cfb-4922-97db-b91bf343285d", + "panelRefName": "panel_0", + "version": "7.6.2" + }, + { + "embeddableConfig": { + "title": "" + }, + "gridData": { + "h": 11, + "i": "1bfd458d-84ff-4b0c-b091-9bfd7ac15de1", + "w": 8, + "x": 10, + "y": 0 + }, + "panelIndex": "1bfd458d-84ff-4b0c-b091-9bfd7ac15de1", + "panelRefName": "panel_1", + "version": "7.6.2" + }, + { + "embeddableConfig": { + "title": "" + }, + "gridData": { + "h": 11, + "i": "c0111c35-69be-41b9-a6be-b83f83721aeb", + "w": 10, + "x": 18, + "y": 0 + }, + "panelIndex": "c0111c35-69be-41b9-a6be-b83f83721aeb", + "panelRefName": "panel_2", + "version": "7.6.2" + }, + { + "embeddableConfig": { + "title": "" + }, + "gridData": { + "h": 11, + "i": "b857a52c-865a-4f99-9acb-cc6b64b4508b", + "w": 10, + "x": 28, + "y": 0 + }, + "panelIndex": "b857a52c-865a-4f99-9acb-cc6b64b4508b", + "panelRefName": "panel_3", + "version": "7.6.2" + }, + { + "embeddableConfig": { + "title": "" + }, + "gridData": { + "h": 11, + "i": "dfbef78c-4636-4c5f-ad52-45f931b6a101", + "w": 10, + "x": 38, + "y": 0 + }, + "panelIndex": "dfbef78c-4636-4c5f-ad52-45f931b6a101", + "panelRefName": "panel_4", + "version": "7.6.2" + }, + { + "embeddableConfig": { + "title": "" + }, + "gridData": { + "h": 9, + "i": "552a8182-6705-4faa-95e5-2cde2a3e77c0", + "w": 5, + "x": 0, + "y": 5 + }, + "panelIndex": "552a8182-6705-4faa-95e5-2cde2a3e77c0", + "panelRefName": "panel_5", + "version": "7.6.2" + }, + { + "embeddableConfig": { + "title": "" + }, + "gridData": { + "h": 9, + "i": "1f8299eb-beef-4851-ac58-4416891629c2", + "w": 5, + "x": 5, + "y": 5 + }, + "panelIndex": "1f8299eb-beef-4851-ac58-4416891629c2", + "panelRefName": "panel_6", + "version": "7.6.2" + }, + { + "embeddableConfig": { + "title": "IO Operations" + }, + "gridData": { + "h": 12, + "i": "5b29dd93-bdc2-4539-a08f-023638a8ccdb", + "w": 38, + "x": 10, + "y": 11 + }, + "panelIndex": "5b29dd93-bdc2-4539-a08f-023638a8ccdb", + "panelRefName": "panel_7", + "title": "IO Operations", + "version": "7.6.2" + }, + { + "embeddableConfig": { + "title": "" + }, + "gridData": { + "h": 9, + "i": "c0d58d03-1af7-46d0-a01d-6d9c2d05d703", + "w": 5, + "x": 0, + "y": 14 + }, + "panelIndex": "c0d58d03-1af7-46d0-a01d-6d9c2d05d703", + "panelRefName": "panel_8", + "version": "7.6.2" + }, + { + "embeddableConfig": { + "title": "" + }, + "gridData": { + "h": 9, + "i": "ddc9edcc-d8ee-4953-9f8e-0c24328a90e3", + "w": 5, + "x": 5, + "y": 14 + }, + "panelIndex": "ddc9edcc-d8ee-4953-9f8e-0c24328a90e3", + "panelRefName": "panel_9", + "version": "7.6.2" + }, + { + "embeddableConfig": { + "title": "Memory Usage" + }, + "gridData": { + "h": 15, + "i": "12a8098a-9dfa-464b-b4b7-c08a02829aa1", + "w": 24, + "x": 24, + "y": 23 + }, + "panelIndex": "12a8098a-9dfa-464b-b4b7-c08a02829aa1", + "panelRefName": "panel_10", + "title": "Memory Usage", + "version": "7.6.2" + }, + { + "embeddableConfig": { + "title": "CPU Usage" + }, + "gridData": { + "h": 15, + "i": "4b54bddb-c2e7-47a1-ab7c-71c51e91a931", + "w": 24, + "x": 0, + "y": 23 + }, + "panelIndex": "4b54bddb-c2e7-47a1-ab7c-71c51e91a931", + "panelRefName": "panel_11", + "title": "CPU Usage", + "version": "7.6.2" + } + ], + "timeRestore": false, + "title": "[Metricbeat IIS] Webserver Process Overview", + "version": 1 + }, + "id": "2c171500-858b-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "dashboard": "7.3.0" + }, + "references": [ + { + "id": "e6fab5c0-858b-11ea-91bc-ab084c7ec0e7", + "name": "panel_0", + "type": "visualization" + }, + { + "id": "1084a0e0-8d57-11ea-817c-a9b6d42fd8a0", + "name": "panel_1", + "type": "visualization" + }, + { + "id": "55755550-858c-11ea-91bc-ab084c7ec0e7", + "name": "panel_2", + "type": "visualization" + }, + { + "id": "6c1272a0-858e-11ea-91bc-ab084c7ec0e7", + "name": "panel_3", + "type": "visualization" + }, + { + "id": "92dcde20-858e-11ea-91bc-ab084c7ec0e7", + "name": "panel_4", + "type": "visualization" + }, + { + "id": "d9dc78b0-8d56-11ea-817c-a9b6d42fd8a0", + "name": "panel_5", + "type": "visualization" + }, + { + "id": "b5c6f400-8d56-11ea-817c-a9b6d42fd8a0", + "name": "panel_6", + "type": "visualization" + }, + { + "id": "e26479e0-858d-11ea-91bc-ab084c7ec0e7", + "name": "panel_7", + "type": "visualization" + }, + { + "id": "7d9e1f40-8d56-11ea-817c-a9b6d42fd8a0", + "name": "panel_8", + "type": "visualization" + }, + { + "id": "945f7850-8d56-11ea-817c-a9b6d42fd8a0", + "name": "panel_9", + "type": "visualization" + }, + { + "id": "e4d91170-858f-11ea-91bc-ab084c7ec0e7", + "name": "panel_10", + "type": "visualization" + }, + { + "id": "2dd099f0-858d-11ea-91bc-ab084c7ec0e7", + "name": "panel_11", + "type": "visualization" + } + ], + "type": "dashboard", + "updated_at": "2020-05-03T16:16:09.608Z", + "version": "WzEyMTcsMV0=" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Navigation Webserver Process Overview [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "fontSize": 10, + "markdown": "### IIS\n\n[Webserver](#/dashboard/ebc23240-8572-11ea-91bc-ab084c7ec0e7)| [**Webserver processes**](#/dashboard/2c171500-858b-11ea-91bc-ab084c7ec0e7) | [Websites](#/dashboard/4b975820-85a1-11ea-91bc-ab084c7ec0e7) | [Application Pools](#/dashboard/b4108810-861c-11ea-91bc-ab084c7ec0e7) ", + "openLinksInNewTab": false + }, + "title": "Navigation Webserver Process Overview [Metricbeat IIS]", + "type": "markdown" + } + }, + "id": "e6fab5c0-858b-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T13:07:25.601Z", + "version": "WzUxOSwxXQ==" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Webserver Process Output Cache Current Memory Usage [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "background_color": null, + "background_color_rules": [ + { + "id": "71978870-32e4-11ea-af9e-d70582a45bda" + } + ], + "bar_color_rules": [ + { + "id": "f11cfd90-32e5-11ea-af9e-d70582a45bda" + } + ], + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "drilldown_url": "", + "filter": { + "language": "kuery", + "query": "" + }, + "gauge_color_rules": [ + { + "id": "9c09ed50-32e4-11ea-af9e-d70582a45bda" + } + ], + "gauge_inner_color": null, + "gauge_inner_width": "6", + "gauge_style": "circle", + "gauge_width": "10", + "id": "61fb4190-32e4-11ea-b9f8-4d0b340ad993", + "index_pattern": "metricbeat-*", + "interval": "60m", + "isModelInvalid": false, + "pivot_id": "cloud.instance.name", + "pivot_label": "Resource Name", + "pivot_rows": "30", + "pivot_type": "string", + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(211,181,21,1)", + "fill": "1.2", + "filter": { + "language": "kuery", + "query": "" + }, + "formatter": "bytes", + "id": "61fb4191-32e4-11ea-b9f8-4d0b340ad993", + "label": "Output Cache Current Memory Usage", + "line_width": 2, + "metrics": [ + { + "agg_with": "avg", + "field": "iis.webserver.cache.output_cache_current_memory_usage", + "id": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "order": "desc", + "order_by": "@timestamp", + "size": 1, + "type": "top_hit" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "gradient", + "split_mode": "everything", + "stacked": "none", + "terms_field": "cloud.instance.name", + "terms_order_by": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "type": "timeseries", + "value_template": "{{value}} " + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "time_range_mode": "entire_time_range", + "type": "gauge" + }, + "title": "Webserver Process Output Cache Current Memory Usage [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "1084a0e0-8d57-11ea-817c-a9b6d42fd8a0", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-05-03T16:10:45.498Z", + "version": "WzExOTEsMV0=" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Webserver Process Worker Process Count [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "background_color": null, + "background_color_rules": [ + { + "id": "71978870-32e4-11ea-af9e-d70582a45bda" + } + ], + "bar_color_rules": [ + { + "id": "f11cfd90-32e5-11ea-af9e-d70582a45bda" + } + ], + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "drilldown_url": "", + "filter": { + "language": "kuery", + "query": "" + }, + "gauge_color_rules": [ + { + "id": "9c09ed50-32e4-11ea-af9e-d70582a45bda" + } + ], + "gauge_inner_color": null, + "gauge_inner_width": "6", + "gauge_style": "circle", + "gauge_width": "10", + "id": "61fb4190-32e4-11ea-b9f8-4d0b340ad993", + "index_pattern": "metricbeat-*", + "interval": "60m", + "isModelInvalid": false, + "pivot_id": "cloud.instance.name", + "pivot_label": "Resource Name", + "pivot_rows": "30", + "pivot_type": "string", + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(211,49,21,1)", + "fill": "1.2", + "filter": { + "language": "kuery", + "query": "" + }, + "formatter": "number", + "id": "61fb4191-32e4-11ea-b9f8-4d0b340ad993", + "label": "Worker Processes", + "line_width": 2, + "metrics": [ + { + "agg_with": "avg", + "field": "iis.webserver.process.worker_process_count", + "id": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "order": "desc", + "order_by": "@timestamp", + "size": 1, + "type": "top_hit" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "gradient", + "split_mode": "everything", + "stacked": "none", + "terms_field": "cloud.instance.name", + "terms_order_by": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "type": "timeseries", + "value_template": "{{value}} " + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "time_range_mode": "entire_time_range", + "type": "gauge" + }, + "title": "Webserver Process Worker Process Count [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "55755550-858c-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T13:07:25.601Z", + "version": "WzUyMCwxXQ==" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Webserver Process Thread Count [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "background_color": null, + "background_color_rules": [ + { + "id": "71978870-32e4-11ea-af9e-d70582a45bda" + } + ], + "bar_color_rules": [ + { + "id": "f11cfd90-32e5-11ea-af9e-d70582a45bda" + } + ], + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "drilldown_url": "", + "filter": { + "language": "kuery", + "query": "" + }, + "gauge_color_rules": [ + { + "id": "9c09ed50-32e4-11ea-af9e-d70582a45bda" + } + ], + "gauge_inner_color": null, + "gauge_inner_width": "6", + "gauge_style": "circle", + "gauge_width": "10", + "id": "61fb4190-32e4-11ea-b9f8-4d0b340ad993", + "index_pattern": "metricbeat-*", + "interval": "60m", + "isModelInvalid": false, + "pivot_id": "cloud.instance.name", + "pivot_label": "Resource Name", + "pivot_rows": "30", + "pivot_type": "string", + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(22,165,165,1)", + "fill": "1.2", + "filter": { + "language": "kuery", + "query": "" + }, + "formatter": "number", + "id": "61fb4191-32e4-11ea-b9f8-4d0b340ad993", + "label": "Thread Count", + "line_width": 2, + "metrics": [ + { + "agg_with": "avg", + "field": "iis.webserver.process.thread_count", + "id": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "order": "desc", + "order_by": "@timestamp", + "size": 1, + "type": "top_hit" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "gradient", + "split_mode": "everything", + "stacked": "none", + "terms_field": "cloud.instance.name", + "terms_order_by": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "type": "timeseries", + "value_template": "{{value}} " + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "time_range_mode": "entire_time_range", + "type": "gauge" + }, + "title": "Webserver Process Thread Count [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "6c1272a0-858e-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T13:07:25.601Z", + "version": "WzUyMSwxXQ==" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Webserver Process Handle Count [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "background_color": null, + "background_color_rules": [ + { + "id": "71978870-32e4-11ea-af9e-d70582a45bda" + } + ], + "bar_color_rules": [ + { + "id": "f11cfd90-32e5-11ea-af9e-d70582a45bda" + } + ], + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "drilldown_url": "", + "filter": { + "language": "kuery", + "query": "" + }, + "gauge_color_rules": [ + { + "id": "9c09ed50-32e4-11ea-af9e-d70582a45bda" + } + ], + "gauge_inner_color": null, + "gauge_inner_width": "6", + "gauge_style": "circle", + "gauge_width": "10", + "id": "61fb4190-32e4-11ea-b9f8-4d0b340ad993", + "index_pattern": "metricbeat-*", + "interval": "60m", + "isModelInvalid": false, + "pivot_id": "cloud.instance.name", + "pivot_label": "Resource Name", + "pivot_rows": "30", + "pivot_type": "string", + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(101,50,148,1)", + "fill": "1.2", + "filter": { + "language": "kuery", + "query": "" + }, + "formatter": "number", + "id": "61fb4191-32e4-11ea-b9f8-4d0b340ad993", + "label": "Handle Count", + "line_width": 2, + "metrics": [ + { + "agg_with": "avg", + "field": "iis.webserver.process.handle_count", + "id": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "order": "desc", + "order_by": "@timestamp", + "size": 1, + "type": "top_hit" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "gradient", + "split_mode": "everything", + "stacked": "none", + "terms_field": "cloud.instance.name", + "terms_order_by": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "type": "timeseries", + "value_template": "{{value}} " + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "time_range_mode": "entire_time_range", + "type": "gauge" + }, + "title": "Webserver Process Handle Count [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "92dcde20-858e-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T13:07:25.601Z", + "version": "WzUyMiwxXQ==" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Webserver Process Current Files Cached [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "background_color": null, + "background_color_rules": [ + { + "id": "71978870-32e4-11ea-af9e-d70582a45bda" + } + ], + "bar_color_rules": [ + { + "id": "f11cfd90-32e5-11ea-af9e-d70582a45bda" + } + ], + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "drilldown_url": "", + "filter": { + "language": "kuery", + "query": "" + }, + "gauge_color_rules": [ + { + "id": "9c09ed50-32e4-11ea-af9e-d70582a45bda" + } + ], + "gauge_inner_color": null, + "gauge_inner_width": "6", + "gauge_style": "circle", + "gauge_width": "10", + "id": "61fb4190-32e4-11ea-b9f8-4d0b340ad993", + "index_pattern": "metricbeat-*", + "interval": "60m", + "isModelInvalid": false, + "pivot_id": "cloud.instance.name", + "pivot_label": "Resource Name", + "pivot_rows": "30", + "pivot_type": "string", + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(21,211,162,1)", + "fill": "1.2", + "filter": { + "language": "kuery", + "query": "" + }, + "formatter": "number", + "id": "61fb4191-32e4-11ea-b9f8-4d0b340ad993", + "label": "Current Files Cached", + "line_width": 2, + "metrics": [ + { + "agg_with": "avg", + "field": "iis.webserver.cache.current_files_cached", + "id": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "order": "desc", + "order_by": "@timestamp", + "size": 1, + "type": "top_hit" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "gradient", + "split_mode": "everything", + "stacked": "none", + "terms_field": "cloud.instance.name", + "terms_order_by": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "type": "timeseries", + "value_template": "{{value}} " + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "time_range_mode": "entire_time_range", + "type": "gauge" + }, + "title": "Webserver Process Current Files Cached [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "d9dc78b0-8d56-11ea-817c-a9b6d42fd8a0", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-05-03T15:57:55.003Z", + "version": "WzExMzMsMV0=" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Webserver Process Total Files Cached [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "background_color": null, + "background_color_rules": [ + { + "id": "71978870-32e4-11ea-af9e-d70582a45bda" + } + ], + "bar_color_rules": [ + { + "id": "f11cfd90-32e5-11ea-af9e-d70582a45bda" + } + ], + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "drilldown_url": "", + "filter": { + "language": "kuery", + "query": "" + }, + "gauge_color_rules": [ + { + "id": "9c09ed50-32e4-11ea-af9e-d70582a45bda" + } + ], + "gauge_inner_color": null, + "gauge_inner_width": "6", + "gauge_style": "circle", + "gauge_width": "10", + "id": "61fb4190-32e4-11ea-b9f8-4d0b340ad993", + "index_pattern": "metricbeat-*", + "interval": "60m", + "isModelInvalid": false, + "pivot_id": "cloud.instance.name", + "pivot_label": "Resource Name", + "pivot_rows": "30", + "pivot_type": "string", + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(21,162,211,1)", + "fill": "1.2", + "filter": { + "language": "kuery", + "query": "" + }, + "formatter": "number", + "id": "61fb4191-32e4-11ea-b9f8-4d0b340ad993", + "label": "Total Files Cached", + "line_width": 2, + "metrics": [ + { + "agg_with": "avg", + "field": "iis.webserver.cache.total_files_cached", + "id": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "order": "desc", + "order_by": "@timestamp", + "size": 1, + "type": "top_hit" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "gradient", + "split_mode": "everything", + "stacked": "none", + "terms_field": "cloud.instance.name", + "terms_order_by": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "type": "timeseries", + "value_template": "{{value}} " + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "time_range_mode": "entire_time_range", + "type": "gauge" + }, + "title": "Webserver Process Total Files Cached [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "b5c6f400-8d56-11ea-817c-a9b6d42fd8a0", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-05-03T15:57:12.722Z", + "version": "WzExMjgsMV0=" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Webserver Process IO Operations [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "filter": { + "language": "kuery", + "query": "" + }, + "id": "c9fd65d0-32e8-11ea-84f4-e9593f8ba8f6", + "index_pattern": "metricbeat-*", + "interval": "", + "isModelInvalid": false, + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(252,196,0,1)", + "fill": "0", + "formatter": "number", + "id": "c9fd8ce0-32e8-11ea-84f4-e9593f8ba8f6", + "label": "IO Read Operations/s", + "line_width": "2", + "metrics": [ + { + "field": "iis.webserver.process.io_read_operations_per_sec", + "id": "c9fd8ce1-32e8-11ea-84f4-e9593f8ba8f6", + "type": "avg" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "rainbow", + "split_mode": "everything", + "stacked": "none", + "terms_field": null, + "type": "timeseries", + "value_template": "{{value}}/s" + }, + { + "axis_position": "right", + "chart_type": "line", + "color": "#68BC00", + "fill": "0", + "formatter": "number", + "id": "55ef6fb0-857e-11ea-87b6-db4d36ae5839", + "label": "IO Write Operations/s", + "line_width": "2", + "metrics": [ + { + "field": "iis.webserver.process.io_write_operations_per_sec", + "id": "55ef6fb1-857e-11ea-87b6-db4d36ae5839", + "type": "avg" + } + ], + "point_size": "0", + "separate_axis": 0, + "split_mode": "everything", + "stacked": "none", + "type": "timeseries", + "value_template": "{{value}}/s" + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "type": "timeseries" + }, + "title": "Webserver Process IO Operations [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "e26479e0-858d-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T13:07:25.601Z", + "version": "WzUyNCwxXQ==" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Webserver Current Uris Cached [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "background_color": null, + "background_color_rules": [ + { + "id": "71978870-32e4-11ea-af9e-d70582a45bda" + } + ], + "bar_color_rules": [ + { + "id": "f11cfd90-32e5-11ea-af9e-d70582a45bda" + } + ], + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "drilldown_url": "", + "filter": { + "language": "kuery", + "query": "" + }, + "gauge_color_rules": [ + { + "id": "9c09ed50-32e4-11ea-af9e-d70582a45bda" + } + ], + "gauge_inner_color": null, + "gauge_inner_width": "6", + "gauge_style": "circle", + "gauge_width": "10", + "id": "61fb4190-32e4-11ea-b9f8-4d0b340ad993", + "index_pattern": "metricbeat-*", + "interval": "60m", + "isModelInvalid": false, + "pivot_id": "cloud.instance.name", + "pivot_label": "Resource Name", + "pivot_rows": "30", + "pivot_type": "string", + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(211,21,105,1)", + "fill": "1.2", + "filter": { + "language": "kuery", + "query": "" + }, + "formatter": "number", + "id": "61fb4191-32e4-11ea-b9f8-4d0b340ad993", + "label": "Current Uris Cached", + "line_width": 2, + "metrics": [ + { + "agg_with": "avg", + "field": "iis.webserver.cache.current_uris_cached", + "id": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "order": "desc", + "order_by": "@timestamp", + "size": 1, + "type": "top_hit" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "gradient", + "split_mode": "everything", + "stacked": "none", + "terms_field": "cloud.instance.name", + "terms_order_by": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "type": "timeseries", + "value_template": "{{value}} " + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "time_range_mode": "entire_time_range", + "type": "gauge" + }, + "title": "Webserver Current Uris Cached [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "7d9e1f40-8d56-11ea-817c-a9b6d42fd8a0", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-05-03T15:55:20.244Z", + "version": "WzExMTQsMV0=" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Webserver Total Uris Cached [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "background_color": null, + "background_color_rules": [ + { + "id": "71978870-32e4-11ea-af9e-d70582a45bda" + } + ], + "bar_color_rules": [ + { + "id": "f11cfd90-32e5-11ea-af9e-d70582a45bda" + } + ], + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "drilldown_url": "", + "filter": { + "language": "kuery", + "query": "" + }, + "gauge_color_rules": [ + { + "id": "9c09ed50-32e4-11ea-af9e-d70582a45bda" + } + ], + "gauge_inner_color": null, + "gauge_inner_width": "6", + "gauge_style": "circle", + "gauge_width": "10", + "id": "61fb4190-32e4-11ea-b9f8-4d0b340ad993", + "index_pattern": "metricbeat-*", + "interval": "60m", + "isModelInvalid": false, + "pivot_id": "cloud.instance.name", + "pivot_label": "Resource Name", + "pivot_rows": "30", + "pivot_type": "string", + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(92,21,211,1)", + "fill": "1.2", + "filter": { + "language": "kuery", + "query": "" + }, + "formatter": "number", + "id": "61fb4191-32e4-11ea-b9f8-4d0b340ad993", + "label": "Total Uris Cached", + "line_width": 2, + "metrics": [ + { + "agg_with": "avg", + "field": "iis.webserver.cache.total_uris_cached", + "id": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "order": "desc", + "order_by": "@timestamp", + "size": 1, + "type": "top_hit" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "gradient", + "split_mode": "everything", + "stacked": "none", + "terms_field": "cloud.instance.name", + "terms_order_by": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "type": "timeseries", + "value_template": "{{value}} " + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "time_range_mode": "entire_time_range", + "type": "gauge" + }, + "title": "Webserver Total Uris Cached [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "945f7850-8d56-11ea-817c-a9b6d42fd8a0", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-05-03T15:55:58.421Z", + "version": "WzExMjAsMV0=" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Webserver Process Memory Usage [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "filter": { + "language": "kuery", + "query": "" + }, + "id": "c9fd65d0-32e8-11ea-84f4-e9593f8ba8f6", + "index_pattern": "metricbeat-*", + "interval": "", + "isModelInvalid": false, + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "#3185FC", + "fill": "0", + "formatter": "bytes", + "id": "c9fd8ce0-32e8-11ea-84f4-e9593f8ba8f6", + "label": "Private Bytes", + "line_width": "2", + "metrics": [ + { + "field": "iis.webserver.process.private_bytes", + "id": "c9fd8ce1-32e8-11ea-84f4-e9593f8ba8f6", + "type": "avg" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "rainbow", + "split_mode": "everything", + "stacked": "none", + "terms_field": null, + "type": "timeseries", + "value_template": "{{value}}" + }, + { + "axis_position": "right", + "chart_type": "line", + "color": "#68BC00", + "fill": "0", + "formatter": "bytes", + "id": "55ef6fb0-857e-11ea-87b6-db4d36ae5839", + "label": "Virtual Bytes", + "line_width": "2", + "metrics": [ + { + "field": "iis.webserver.process.virtual_bytes", + "id": "55ef6fb1-857e-11ea-87b6-db4d36ae5839", + "type": "avg" + } + ], + "point_size": "0", + "separate_axis": 0, + "split_mode": "everything", + "stacked": "none", + "type": "timeseries" + }, + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(149,0,188,1)", + "fill": "0", + "formatter": "bytes", + "id": "7501b0c0-857e-11ea-87b6-db4d36ae5839", + "label": "Working Set", + "line_width": "2", + "metrics": [ + { + "field": "iis.webserver.process.working_set", + "id": "7501b0c1-857e-11ea-87b6-db4d36ae5839", + "type": "avg" + } + ], + "point_size": "", + "separate_axis": 0, + "split_mode": "everything", + "stacked": "none", + "type": "timeseries" + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "type": "timeseries" + }, + "title": "Webserver Process Memory Usage [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "e4d91170-858f-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T13:07:25.601Z", + "version": "WzUyNiwxXQ==" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Webserver Process CPU Usage [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "filter": { + "language": "kuery", + "query": "" + }, + "id": "c9fd65d0-32e8-11ea-84f4-e9593f8ba8f6", + "index_pattern": "metricbeat-*", + "interval": "", + "isModelInvalid": false, + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "#3185FC", + "fill": "0", + "formatter": "percent", + "id": "c9fd8ce0-32e8-11ea-84f4-e9593f8ba8f6", + "label": "CPU Usage", + "line_width": "2", + "metrics": [ + { + "field": "iis.webserver.process.cpu_usage_perc", + "id": "c9fd8ce1-32e8-11ea-84f4-e9593f8ba8f6", + "type": "avg" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "rainbow", + "split_mode": "everything", + "stacked": "none", + "terms_field": null, + "type": "timeseries", + "value_template": "{{value}}" + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "type": "timeseries" + }, + "title": "Webserver Process CPU Usage [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "2dd099f0-858d-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T13:07:25.601Z", + "version": "WzUyNSwxXQ==" + } + ], + "version": "7.6.2" +} diff --git a/x-pack/metricbeat/module/iis/_meta/kibana/7/dashboard/Metricbeat-iis-website-overview.json b/x-pack/metricbeat/module/iis/_meta/kibana/7/dashboard/Metricbeat-iis-website-overview.json new file mode 100644 index 00000000000..8f1491f0f1c --- /dev/null +++ b/x-pack/metricbeat/module/iis/_meta/kibana/7/dashboard/Metricbeat-iis-website-overview.json @@ -0,0 +1,1685 @@ +{ + "objects": [ + { + "attributes": { + "description": "This dashboard shows metrics for the websites running on IIS.", + "hits": 0, + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "optionsJSON": { + "hidePanelTitles": false, + "useMargins": true + }, + "panelsJSON": [ + { + "embeddableConfig": { + "title": "" + }, + "gridData": { + "h": 6, + "i": "e8bd7244-57bf-4e16-b096-4fa7cb8cbba8", + "w": 9, + "x": 0, + "y": 0 + }, + "panelIndex": "e8bd7244-57bf-4e16-b096-4fa7cb8cbba8", + "panelRefName": "panel_0", + "version": "7.6.0" + }, + { + "embeddableConfig": { + "title": "Current Connections" + }, + "gridData": { + "h": 12, + "i": "193cca9a-a5c3-40c4-a9af-1020b279b845", + "w": 13, + "x": 9, + "y": 0 + }, + "panelIndex": "193cca9a-a5c3-40c4-a9af-1020b279b845", + "panelRefName": "panel_1", + "title": "Current Connections", + "version": "7.6.0" + }, + { + "embeddableConfig": { + "title": "Maximum Connections" + }, + "gridData": { + "h": 12, + "i": "7b5d1298-0480-443a-bf0e-5b594903c680", + "w": 13, + "x": 22, + "y": 0 + }, + "panelIndex": "7b5d1298-0480-443a-bf0e-5b594903c680", + "panelRefName": "panel_2", + "title": "Maximum Connections", + "version": "7.6.0" + }, + { + "embeddableConfig": { + "title": "Total Connection Attempts" + }, + "gridData": { + "h": 12, + "i": "fd0c5fd7-4df8-44d9-905f-5634f49ae875", + "w": 13, + "x": 35, + "y": 0 + }, + "panelIndex": "fd0c5fd7-4df8-44d9-905f-5634f49ae875", + "panelRefName": "panel_3", + "title": "Total Connection Attempts", + "version": "7.6.0" + }, + { + "embeddableConfig": { + "title": "Filters" + }, + "gridData": { + "h": 6, + "i": "69a127c6-8c4f-403a-aded-7a49dc3d3cd9", + "w": 9, + "x": 0, + "y": 6 + }, + "panelIndex": "69a127c6-8c4f-403a-aded-7a49dc3d3cd9", + "panelRefName": "panel_4", + "title": "Filters", + "version": "7.6.0" + }, + { + "embeddableConfig": { + "title": "Service Uptime" + }, + "gridData": { + "h": 12, + "i": "545b1abe-fd22-455b-8bc6-68bd48c2d384", + "w": 9, + "x": 0, + "y": 12 + }, + "panelIndex": "545b1abe-fd22-455b-8bc6-68bd48c2d384", + "panelRefName": "panel_5", + "title": "Service Uptime", + "version": "7.6.0" + }, + { + "embeddableConfig": { + "title": "Bytes Sent/sec" + }, + "gridData": { + "h": 12, + "i": "c026e461-8b64-4952-be07-744773cb18b9", + "w": 20, + "x": 9, + "y": 12 + }, + "panelIndex": "c026e461-8b64-4952-be07-744773cb18b9", + "panelRefName": "panel_6", + "title": "Bytes Sent/sec", + "version": "7.6.0" + }, + { + "embeddableConfig": { + "title": "Bytes Received/sec" + }, + "gridData": { + "h": 12, + "i": "98b6e727-3343-43df-84e1-12933856f432", + "w": 19, + "x": 29, + "y": 12 + }, + "panelIndex": "98b6e727-3343-43df-84e1-12933856f432", + "panelRefName": "panel_7", + "title": "Bytes Received/sec", + "version": "7.6.0" + }, + { + "embeddableConfig": { + "title": "GET Requests/sec" + }, + "gridData": { + "h": 12, + "i": "d4b32bd4-dee2-44e2-930d-91e4607c0668", + "w": 12, + "x": 0, + "y": 24 + }, + "panelIndex": "d4b32bd4-dee2-44e2-930d-91e4607c0668", + "panelRefName": "panel_8", + "title": "GET Requests/sec", + "version": "7.6.0" + }, + { + "embeddableConfig": { + "title": "POST Requests/sec" + }, + "gridData": { + "h": 12, + "i": "d24b5bb0-6dc3-40ba-91cb-5ca5e48a3afa", + "w": 12, + "x": 12, + "y": 24 + }, + "panelIndex": "d24b5bb0-6dc3-40ba-91cb-5ca5e48a3afa", + "panelRefName": "panel_9", + "title": "POST Requests/sec", + "version": "7.6.0" + }, + { + "embeddableConfig": { + "title": "PUT Requests/sec" + }, + "gridData": { + "h": 12, + "i": "c8cfac9e-ad2a-4ddf-94c9-0454e987b2b8", + "w": 12, + "x": 24, + "y": 24 + }, + "panelIndex": "c8cfac9e-ad2a-4ddf-94c9-0454e987b2b8", + "panelRefName": "panel_10", + "title": "PUT Requests/sec", + "version": "7.6.0" + }, + { + "embeddableConfig": { + "title": "DELETE Requests/sec" + }, + "gridData": { + "h": 12, + "i": "41b4d631-4878-49af-b34b-f04f473599c8", + "w": 12, + "x": 36, + "y": 24 + }, + "panelIndex": "41b4d631-4878-49af-b34b-f04f473599c8", + "panelRefName": "panel_11", + "title": "DELETE Requests/sec", + "version": "7.6.0" + }, + { + "embeddableConfig": { + "title": "Total DELETE Requests" + }, + "gridData": { + "h": 12, + "i": "90d9d209-6ef9-4ab7-b9a2-48a1f420e555", + "w": 12, + "x": 36, + "y": 36 + }, + "panelIndex": "90d9d209-6ef9-4ab7-b9a2-48a1f420e555", + "panelRefName": "panel_12", + "title": "Total DELETE Requests", + "version": "7.6.0" + }, + { + "embeddableConfig": { + "title": "Total GET Requests" + }, + "gridData": { + "h": 12, + "i": "c7379d87-3da3-4be4-a8bd-8db04da66ba4", + "w": 12, + "x": 0, + "y": 36 + }, + "panelIndex": "c7379d87-3da3-4be4-a8bd-8db04da66ba4", + "panelRefName": "panel_13", + "title": "Total GET Requests", + "version": "7.6.0" + }, + { + "embeddableConfig": { + "title": "Total POST Requests" + }, + "gridData": { + "h": 12, + "i": "9a22a36c-5fe8-4cc2-be23-4d9db2d7beda", + "w": 12, + "x": 12, + "y": 36 + }, + "panelIndex": "9a22a36c-5fe8-4cc2-be23-4d9db2d7beda", + "panelRefName": "panel_14", + "title": "Total POST Requests", + "version": "7.6.0" + }, + { + "embeddableConfig": { + "title": "Total PUT Requests " + }, + "gridData": { + "h": 12, + "i": "6cf940a0-eb04-447b-8f7e-d4561f8838ac", + "w": 12, + "x": 24, + "y": 36 + }, + "panelIndex": "6cf940a0-eb04-447b-8f7e-d4561f8838ac", + "panelRefName": "panel_15", + "title": "Total PUT Requests ", + "version": "7.6.0" + } + ], + "timeRestore": false, + "title": "[Metricbeat IIS] Website Overview", + "version": 1 + }, + "id": "4b975820-85a1-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "dashboard": "7.3.0" + }, + "references": [ + { + "id": "f9723710-8602-11ea-91bc-ab084c7ec0e7", + "name": "panel_0", + "type": "visualization" + }, + { + "id": "57d914d0-860e-11ea-91bc-ab084c7ec0e7", + "name": "panel_1", + "type": "visualization" + }, + { + "id": "6db58c20-860e-11ea-91bc-ab084c7ec0e7", + "name": "panel_2", + "type": "visualization" + }, + { + "id": "e3ee4990-860e-11ea-91bc-ab084c7ec0e7", + "name": "panel_3", + "type": "visualization" + }, + { + "id": "b7230190-8603-11ea-91bc-ab084c7ec0e7", + "name": "panel_4", + "type": "visualization" + }, + { + "id": "4557d670-860e-11ea-91bc-ab084c7ec0e7", + "name": "panel_5", + "type": "visualization" + }, + { + "id": "c784f9b0-8614-11ea-91bc-ab084c7ec0e7", + "name": "panel_6", + "type": "visualization" + }, + { + "id": "96fe7d70-8614-11ea-91bc-ab084c7ec0e7", + "name": "panel_7", + "type": "visualization" + }, + { + "id": "4921d5c0-8619-11ea-91bc-ab084c7ec0e7", + "name": "panel_8", + "type": "visualization" + }, + { + "id": "7dabd8e0-8619-11ea-91bc-ab084c7ec0e7", + "name": "panel_9", + "type": "visualization" + }, + { + "id": "a9427270-8619-11ea-91bc-ab084c7ec0e7", + "name": "panel_10", + "type": "visualization" + }, + { + "id": "7453b910-8624-11ea-91bc-ab084c7ec0e7", + "name": "panel_11", + "type": "visualization" + }, + { + "id": "8ee988d0-861b-11ea-91bc-ab084c7ec0e7", + "name": "panel_12", + "type": "visualization" + }, + { + "id": "1b4f8790-861a-11ea-91bc-ab084c7ec0e7", + "name": "panel_13", + "type": "visualization" + }, + { + "id": "31ed84b0-861b-11ea-91bc-ab084c7ec0e7", + "name": "panel_14", + "type": "visualization" + }, + { + "id": "54038fe0-861b-11ea-91bc-ab084c7ec0e7", + "name": "panel_15", + "type": "visualization" + } + ], + "type": "dashboard", + "updated_at": "2020-04-24T12:15:15.363Z", + "version": "Wzk1OTcsMTNd" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Navigation Website Overview [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "fontSize": 10, + "markdown": "### IIS\n\n[Webserver](#/dashboard/ebc23240-8572-11ea-91bc-ab084c7ec0e7)| [Webserver processes](#/dashboard/2c171500-858b-11ea-91bc-ab084c7ec0e7) | [**Websites**](#/dashboard/4b975820-85a1-11ea-91bc-ab084c7ec0e7) | [Application Pools](#/dashboard/b4108810-861c-11ea-91bc-ab084c7ec0e7) \n\n\n\n", + "openLinksInNewTab": false + }, + "title": "Navigation Website Overview [Metricbeat IIS]", + "type": "markdown" + } + }, + "id": "f9723710-8602-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T12:15:06.609Z", + "version": "Wzk1ODIsMTNd" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Website Current Connections [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "background_color": null, + "background_color_rules": [ + { + "id": "71978870-32e4-11ea-af9e-d70582a45bda" + } + ], + "bar_color_rules": [ + { + "id": "f11cfd90-32e5-11ea-af9e-d70582a45bda" + } + ], + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "drilldown_url": "", + "filter": { + "language": "kuery", + "query": "" + }, + "gauge_color_rules": [ + { + "id": "9c09ed50-32e4-11ea-af9e-d70582a45bda" + } + ], + "gauge_inner_color": null, + "gauge_inner_width": "6", + "gauge_style": "circle", + "gauge_width": "10", + "id": "61fb4190-32e4-11ea-b9f8-4d0b340ad993", + "index_pattern": "metricbeat-*", + "interval": "60m", + "isModelInvalid": false, + "pivot_id": "cloud.instance.name", + "pivot_label": "Resource Name", + "pivot_rows": "30", + "pivot_type": "string", + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(104,188,0,1)", + "fill": "1.2", + "filter": { + "language": "kuery", + "query": "" + }, + "formatter": "number", + "id": "61fb4191-32e4-11ea-b9f8-4d0b340ad993", + "label": "Current Connections", + "line_width": 2, + "metrics": [ + { + "agg_with": "avg", + "field": "iis.website.network.current_connections", + "id": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "order": "desc", + "order_by": "@timestamp", + "size": 1, + "type": "top_hit" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "gradient", + "split_mode": "terms", + "stacked": "none", + "terms_field": "iis.website.name", + "terms_order_by": "_count", + "type": "timeseries", + "value_template": "{{value}}" + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "time_range_mode": "entire_time_range", + "type": "top_n" + }, + "title": "Website Current Connections [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "57d914d0-860e-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T10:09:05.853Z", + "version": "Wzg3NTksMTNd" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Website Maximum Connections [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "background_color": null, + "background_color_rules": [ + { + "id": "71978870-32e4-11ea-af9e-d70582a45bda" + } + ], + "bar_color_rules": [ + { + "id": "f11cfd90-32e5-11ea-af9e-d70582a45bda" + } + ], + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "drilldown_url": "", + "filter": { + "language": "kuery", + "query": "" + }, + "gauge_color_rules": [ + { + "id": "9c09ed50-32e4-11ea-af9e-d70582a45bda" + } + ], + "gauge_inner_color": null, + "gauge_inner_width": "6", + "gauge_style": "circle", + "gauge_width": "10", + "id": "61fb4190-32e4-11ea-b9f8-4d0b340ad993", + "index_pattern": "metricbeat-*", + "interval": "60m", + "isModelInvalid": false, + "pivot_id": "cloud.instance.name", + "pivot_label": "Resource Name", + "pivot_rows": "30", + "pivot_type": "string", + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(104,204,202,1)", + "fill": "1.2", + "filter": { + "language": "kuery", + "query": "" + }, + "formatter": "number", + "id": "61fb4191-32e4-11ea-b9f8-4d0b340ad993", + "label": "Maximum Connections", + "line_width": 2, + "metrics": [ + { + "agg_with": "avg", + "field": "iis.website.network.maximum_connections", + "id": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "order": "desc", + "order_by": "@timestamp", + "size": 1, + "type": "top_hit" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "gradient", + "split_mode": "terms", + "stacked": "none", + "terms_field": "iis.website.name", + "terms_order_by": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "type": "timeseries", + "value_template": "{{value}}" + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "time_range_mode": "entire_time_range", + "type": "top_n" + }, + "title": "Website Maximum Connections [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "6db58c20-860e-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T10:07:57.207Z", + "version": "Wzg3NDMsMTNd" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Website Total Connection Attempts [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "background_color": null, + "background_color_rules": [ + { + "id": "71978870-32e4-11ea-af9e-d70582a45bda" + } + ], + "bar_color_rules": [ + { + "id": "f11cfd90-32e5-11ea-af9e-d70582a45bda" + } + ], + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "drilldown_url": "", + "filter": { + "language": "kuery", + "query": "" + }, + "gauge_color_rules": [ + { + "id": "9c09ed50-32e4-11ea-af9e-d70582a45bda" + } + ], + "gauge_inner_color": null, + "gauge_inner_width": "6", + "gauge_style": "circle", + "gauge_width": "10", + "id": "61fb4190-32e4-11ea-b9f8-4d0b340ad993", + "index_pattern": "metricbeat-*", + "interval": "60m", + "isModelInvalid": false, + "pivot_id": "cloud.instance.name", + "pivot_label": "Resource Name", + "pivot_rows": "30", + "pivot_type": "string", + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(251,158,0,1)", + "fill": "1.2", + "filter": { + "language": "kuery", + "query": "" + }, + "formatter": "number", + "id": "61fb4191-32e4-11ea-b9f8-4d0b340ad993", + "label": "Total Connection Attempts", + "line_width": 2, + "metrics": [ + { + "agg_with": "avg", + "field": "iis.website.network.total_connection_attempts", + "id": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "order": "desc", + "order_by": "@timestamp", + "size": 1, + "type": "top_hit" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "gradient", + "split_mode": "terms", + "stacked": "none", + "terms_field": "iis.website.name", + "terms_order_by": "_count", + "type": "timeseries", + "value_template": "{{value}}" + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "time_range_mode": "entire_time_range", + "type": "top_n" + }, + "title": "Website Total Connection Attempts [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "e3ee4990-860e-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T10:08:45.818Z", + "version": "Wzg3NTMsMTNd" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Website Filters [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "controls": [ + { + "fieldName": "iis.website.name", + "id": "1549397251041", + "indexPatternRefName": "control_0_index_pattern", + "label": "Website", + "options": { + "dynamicOptions": true, + "multiselect": true, + "order": "desc", + "size": 5, + "type": "terms" + }, + "parent": "", + "type": "list" + } + ], + "pinFilters": false, + "updateFiltersOnChange": true, + "useTimeFilter": false + }, + "title": "Website Filters [Metricbeat IIS]", + "type": "input_control_vis" + } + }, + "id": "b7230190-8603-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [ + { + "id": "metricbeat-*", + "name": "control_0_index_pattern", + "type": "index-pattern" + } + ], + "type": "visualization", + "updated_at": "2020-04-24T09:47:17.191Z", + "version": "Wzg2NDUsMTNd" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Website Service Uptime [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "background_color": null, + "background_color_rules": [ + { + "id": "71978870-32e4-11ea-af9e-d70582a45bda" + } + ], + "bar_color_rules": [ + { + "id": "f11cfd90-32e5-11ea-af9e-d70582a45bda" + } + ], + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "drilldown_url": "", + "filter": { + "language": "kuery", + "query": "" + }, + "gauge_color_rules": [ + { + "id": "9c09ed50-32e4-11ea-af9e-d70582a45bda" + } + ], + "gauge_inner_color": null, + "gauge_inner_width": "6", + "gauge_style": "circle", + "gauge_width": "10", + "id": "61fb4190-32e4-11ea-b9f8-4d0b340ad993", + "index_pattern": "metricbeat-*", + "interval": "60m", + "isModelInvalid": false, + "pivot_id": "cloud.instance.name", + "pivot_label": "Resource Name", + "pivot_rows": "30", + "pivot_type": "string", + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(211,49,21,1)", + "fill": "1.2", + "filter": { + "language": "kuery", + "query": "" + }, + "formatter": "s,d,", + "id": "61fb4191-32e4-11ea-b9f8-4d0b340ad993", + "label": "Service Uptime", + "line_width": 2, + "metrics": [ + { + "agg_with": "avg", + "field": "iis.website.network.service_uptime", + "id": "61fb4192-32e4-11ea-b9f8-4d0b340ad993", + "order": "desc", + "order_by": "@timestamp", + "size": 1, + "type": "top_hit" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "gradient", + "split_mode": "terms", + "stacked": "none", + "terms_field": "iis.website.name", + "terms_order_by": "_count", + "terms_size": "10", + "type": "timeseries", + "value_template": "{{value}} d" + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "time_range_mode": "entire_time_range", + "type": "top_n" + }, + "title": "Website Service Uptime [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "4557d670-860e-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T10:06:35.877Z", + "version": "Wzg3MjksMTNd" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Website Bytes Sent/s [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "filter": { + "language": "kuery", + "query": "" + }, + "id": "c9fd65d0-32e8-11ea-84f4-e9593f8ba8f6", + "index_pattern": "metricbeat-*", + "interval": "", + "isModelInvalid": false, + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(252,196,0,1)", + "fill": "0", + "formatter": "bytes", + "id": "c9fd8ce0-32e8-11ea-84f4-e9593f8ba8f6", + "label": "Bytes Sent", + "line_width": "2", + "metrics": [ + { + "field": "iis.website.network.bytes_sent_per_sec", + "id": "c9fd8ce1-32e8-11ea-84f4-e9593f8ba8f6", + "type": "avg" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "rainbow", + "split_mode": "terms", + "stacked": "none", + "terms_field": "iis.website.name", + "type": "timeseries", + "value_template": "{{value}}/s" + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "type": "timeseries" + }, + "title": "Website Bytes Sent/s [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "c784f9b0-8614-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T10:17:19.307Z", + "version": "Wzg3ODgsMTNd" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Website Bytes Received/s [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "filter": { + "language": "kuery", + "query": "" + }, + "id": "c9fd65d0-32e8-11ea-84f4-e9593f8ba8f6", + "index_pattern": "metricbeat-*", + "interval": "", + "isModelInvalid": false, + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(252,196,0,1)", + "fill": "0", + "formatter": "bytes", + "id": "c9fd8ce0-32e8-11ea-84f4-e9593f8ba8f6", + "label": "Bytes Received", + "line_width": "2", + "metrics": [ + { + "field": "iis.website.network.bytes_received_per_sec", + "id": "c9fd8ce1-32e8-11ea-84f4-e9593f8ba8f6", + "type": "avg" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "rainbow", + "split_mode": "terms", + "stacked": "none", + "terms_field": "iis.website.name", + "type": "timeseries", + "value_template": "{{value}}/s" + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "type": "timeseries" + }, + "title": "Website Bytes Received/s [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "96fe7d70-8614-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T10:18:51.070Z", + "version": "Wzg3OTksMTNd" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Website GET Requests/s [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "filter": { + "language": "kuery", + "query": "" + }, + "id": "c9fd65d0-32e8-11ea-84f4-e9593f8ba8f6", + "index_pattern": "metricbeat-*", + "interval": "", + "isModelInvalid": false, + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(252,196,0,1)", + "fill": "0", + "formatter": "number", + "id": "c9fd8ce0-32e8-11ea-84f4-e9593f8ba8f6", + "label": "GET Requests ", + "line_width": "2", + "metrics": [ + { + "field": "iis.website.network.get_requests_per_sec", + "id": "c9fd8ce1-32e8-11ea-84f4-e9593f8ba8f6", + "type": "avg" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "rainbow", + "split_mode": "terms", + "stacked": "none", + "terms_field": "iis.website.name", + "type": "timeseries", + "value_template": "{{value}}/s" + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "type": "timeseries" + }, + "title": "Website GET Requests/s [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "4921d5c0-8619-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T10:49:34.748Z", + "version": "Wzg4MjQsMTNd" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Website POST Requests/s [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "filter": { + "language": "kuery", + "query": "" + }, + "id": "c9fd65d0-32e8-11ea-84f4-e9593f8ba8f6", + "index_pattern": "metricbeat-*", + "interval": "", + "isModelInvalid": false, + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(252,196,0,1)", + "fill": "0", + "formatter": "number", + "id": "c9fd8ce0-32e8-11ea-84f4-e9593f8ba8f6", + "label": "POST Requests ", + "line_width": "2", + "metrics": [ + { + "field": "iis.website.network.post_requests_per_sec", + "id": "c9fd8ce1-32e8-11ea-84f4-e9593f8ba8f6", + "type": "avg" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "rainbow", + "split_mode": "terms", + "stacked": "none", + "terms_field": "iis.website.name", + "type": "timeseries", + "value_template": "{{value}}/s" + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "type": "timeseries" + }, + "title": "Website POST Requests/s [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "7dabd8e0-8619-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T10:51:02.893Z", + "version": "Wzg4MjksMTNd" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Website PUT Requests/s [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "filter": { + "language": "kuery", + "query": "" + }, + "id": "c9fd65d0-32e8-11ea-84f4-e9593f8ba8f6", + "index_pattern": "metricbeat-*", + "interval": "", + "isModelInvalid": false, + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(211,49,21,1)", + "fill": "0", + "formatter": "number", + "id": "c9fd8ce0-32e8-11ea-84f4-e9593f8ba8f6", + "label": "PUT Requests ", + "line_width": "2", + "metrics": [ + { + "field": "iis.website.network.put_requests_per_sec", + "id": "c9fd8ce1-32e8-11ea-84f4-e9593f8ba8f6", + "type": "avg" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "rainbow", + "split_mode": "terms", + "stacked": "none", + "terms_field": "iis.website.name", + "type": "timeseries", + "value_template": "{{value}}/s" + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "type": "timeseries" + }, + "title": "Website PUT Requests/s [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "a9427270-8619-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T10:52:16.023Z", + "version": "Wzg4MzUsMTNd" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Website DELETE Requests/s [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "filter": { + "language": "kuery", + "query": "" + }, + "id": "c9fd65d0-32e8-11ea-84f4-e9593f8ba8f6", + "index_pattern": "metricbeat-*", + "interval": "", + "isModelInvalid": false, + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(25,77,51,1)", + "fill": "0", + "formatter": "number", + "id": "c9fd8ce0-32e8-11ea-84f4-e9593f8ba8f6", + "label": "DELETE Requests ", + "line_width": "2", + "metrics": [ + { + "field": "iis.website.network.delete_requests_per_sec", + "id": "c9fd8ce1-32e8-11ea-84f4-e9593f8ba8f6", + "type": "avg" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "rainbow", + "split_mode": "terms", + "stacked": "none", + "terms_field": "iis.website.name", + "type": "timeseries", + "value_template": "{{value}}/s" + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "type": "timeseries" + }, + "title": "Website DELETE Requests/s [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "7453b910-8624-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T12:09:31.681Z", + "version": "Wzk1MjEsMTNd" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Website Total DELETE Requests [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "filter": { + "language": "kuery", + "query": "" + }, + "id": "c9fd65d0-32e8-11ea-84f4-e9593f8ba8f6", + "index_pattern": "metricbeat-*", + "interval": "", + "isModelInvalid": false, + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(244,78,59,1)", + "fill": "0", + "formatter": "number", + "id": "c9fd8ce0-32e8-11ea-84f4-e9593f8ba8f6", + "label": "Total DELETE Requests ", + "line_width": "2", + "metrics": [ + { + "field": "iis.website.network.total_delete_requests", + "id": "c9fd8ce1-32e8-11ea-84f4-e9593f8ba8f6", + "type": "avg" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "rainbow", + "split_mode": "terms", + "stacked": "none", + "terms_field": "iis.website.name", + "type": "timeseries", + "value_template": "{{value}}" + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "type": "timeseries" + }, + "title": "Website Total DELETE Requests [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "8ee988d0-861b-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T11:05:50.813Z", + "version": "Wzg4ODUsMTNd" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Website Total GET Requests [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "filter": { + "language": "kuery", + "query": "" + }, + "id": "c9fd65d0-32e8-11ea-84f4-e9593f8ba8f6", + "index_pattern": "metricbeat-*", + "interval": "", + "isModelInvalid": false, + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(104,188,0,1)", + "fill": "0", + "formatter": "number", + "id": "c9fd8ce0-32e8-11ea-84f4-e9593f8ba8f6", + "label": "Total GET Requests ", + "line_width": "2", + "metrics": [ + { + "field": "iis.website.network.total_get_requests", + "id": "c9fd8ce1-32e8-11ea-84f4-e9593f8ba8f6", + "type": "avg" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "rainbow", + "split_mode": "terms", + "stacked": "none", + "terms_field": "iis.website.name", + "type": "timeseries", + "value_template": "{{value}}" + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "type": "timeseries" + }, + "title": "Website Total GET Requests [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "1b4f8790-861a-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T11:02:23.731Z", + "version": "Wzg4NjQsMTNd" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Website Total POST Requests [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "filter": { + "language": "kuery", + "query": "" + }, + "id": "c9fd65d0-32e8-11ea-84f4-e9593f8ba8f6", + "index_pattern": "metricbeat-*", + "interval": "", + "isModelInvalid": false, + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(104,188,0,1)", + "fill": "0", + "formatter": "number", + "id": "c9fd8ce0-32e8-11ea-84f4-e9593f8ba8f6", + "label": "Total POST Requests ", + "line_width": "2", + "metrics": [ + { + "field": "iis.website.network.total_post_requests", + "id": "c9fd8ce1-32e8-11ea-84f4-e9593f8ba8f6", + "type": "avg" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "rainbow", + "split_mode": "terms", + "stacked": "none", + "terms_field": "iis.website.name", + "type": "timeseries", + "value_template": "{{value}}" + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "type": "timeseries" + }, + "title": "Website Total POST Requests [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "31ed84b0-861b-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T11:03:14.811Z", + "version": "Wzg4NjgsMTNd" + }, + { + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "title": "Website Total PUT Requests [Metricbeat IIS]", + "uiStateJSON": {}, + "version": 1, + "visState": { + "aggs": [], + "params": { + "axis_formatter": "number", + "axis_min": 0, + "axis_position": "left", + "axis_scale": "normal", + "default_index_pattern": "metricbeat-*", + "default_timefield": "@timestamp", + "filter": { + "language": "kuery", + "query": "" + }, + "id": "c9fd65d0-32e8-11ea-84f4-e9593f8ba8f6", + "index_pattern": "metricbeat-*", + "interval": "", + "isModelInvalid": false, + "series": [ + { + "axis_position": "right", + "chart_type": "line", + "color": "rgba(22,165,165,1)", + "fill": "0", + "formatter": "number", + "id": "c9fd8ce0-32e8-11ea-84f4-e9593f8ba8f6", + "label": "Total PUT Requests ", + "line_width": "2", + "metrics": [ + { + "field": "iis.website.network.total_put_requests", + "id": "c9fd8ce1-32e8-11ea-84f4-e9593f8ba8f6", + "type": "avg" + } + ], + "point_size": 0, + "separate_axis": 0, + "split_color_mode": "rainbow", + "split_mode": "terms", + "stacked": "none", + "terms_field": "iis.website.name", + "type": "timeseries", + "value_template": "{{value}}" + } + ], + "show_grid": 1, + "show_legend": 1, + "time_field": "@timestamp", + "type": "timeseries" + }, + "title": "Website Total PUT Requests [Metricbeat IIS]", + "type": "metrics" + } + }, + "id": "54038fe0-861b-11ea-91bc-ab084c7ec0e7", + "migrationVersion": { + "visualization": "7.4.2" + }, + "references": [], + "type": "visualization", + "updated_at": "2020-04-24T11:04:11.998Z", + "version": "Wzg4NzQsMTNd" + } + ], + "version": "7.6.0" +} diff --git a/x-pack/metricbeat/module/iis/application_pool/_meta/docs.asciidoc b/x-pack/metricbeat/module/iis/application_pool/_meta/docs.asciidoc index e85da0ee7e8..bb4c752b844 100644 --- a/x-pack/metricbeat/module/iis/application_pool/_meta/docs.asciidoc +++ b/x-pack/metricbeat/module/iis/application_pool/_meta/docs.asciidoc @@ -9,6 +9,12 @@ The `process` object contains System/Process counters like the the overall serve The `net_clr` object which returns ASP.NET error rate counter values. Users can specify the application pools they would like to monitor using the configuration option `application_pool.name`, else, all application pools are considered. +[float] +==== Dashboard + +image::./images/metricbeat-iis-application-pool-overview.png[] + + diff --git a/x-pack/metricbeat/module/iis/application_pool/reader.go b/x-pack/metricbeat/module/iis/application_pool/reader.go index 518d7231dca..2c137c42d65 100644 --- a/x-pack/metricbeat/module/iis/application_pool/reader.go +++ b/x-pack/metricbeat/module/iis/application_pool/reader.go @@ -49,7 +49,7 @@ var appPoolCounters = map[string]string{ "process.handle_count": "\\Process(w3wp*)\\Handle Count", "process.thread_count": "\\Process(w3wp*)\\Thread Count", "process.working_set": "\\Process(w3wp*)\\Working Set", - "process.private_byte": "\\Process(w3wp*)\\Private Bytes", + "process.private_bytes": "\\Process(w3wp*)\\Private Bytes", "process.virtual_bytes": "\\Process(w3wp*)\\Virtual Bytes", "process.page_faults_per_sec": "\\Process(w3wp*)\\Page Faults/sec", "process.io_read_operations_per_sec": "\\Process(w3wp*)\\IO Read Operations/sec", diff --git a/x-pack/metricbeat/module/iis/webserver/_meta/docs.asciidoc b/x-pack/metricbeat/module/iis/webserver/_meta/docs.asciidoc index 9b15b923985..9c77eb7de43 100644 --- a/x-pack/metricbeat/module/iis/webserver/_meta/docs.asciidoc +++ b/x-pack/metricbeat/module/iis/webserver/_meta/docs.asciidoc @@ -15,7 +15,12 @@ The `cache` object contains metrics from the user mode cache and output cache. The `asp_net` and `asp_net_application` contain asp.net related performance counter values. +[float] +==== Dashboard +image::./images/metricbeat-iis-webserver-overview.png[] + +image::./images/metricbeat-iis-webserver-process.png[] diff --git a/x-pack/metricbeat/module/iis/webserver/manifest.yml b/x-pack/metricbeat/module/iis/webserver/manifest.yml index 32da4b384b6..39648f054d8 100644 --- a/x-pack/metricbeat/module/iis/webserver/manifest.yml +++ b/x-pack/metricbeat/module/iis/webserver/manifest.yml @@ -33,8 +33,7 @@ input: - name: "Anonymous Users/sec" - name: "Total NonAnonymous Users" #asp.net - - object: "ASP.NET Applications" - instance: "__Total__" + - object: "ASP.NET" namespace: "asp_net" counters: - name: "Application Restarts" @@ -76,6 +75,7 @@ input: instance: "w3wp*" counters: - name: "% Processor Time" + field: "cpu_usage_perc" - name: "Handle Count" - name: "Thread Count" - name: "Working Set" diff --git a/x-pack/metricbeat/module/iis/website/_meta/docs.asciidoc b/x-pack/metricbeat/module/iis/website/_meta/docs.asciidoc index e4e6a5e9dc9..808afe381cf 100644 --- a/x-pack/metricbeat/module/iis/website/_meta/docs.asciidoc +++ b/x-pack/metricbeat/module/iis/website/_meta/docs.asciidoc @@ -7,6 +7,9 @@ The metrics contain the IIS Performance counter values like: Web Service: Current Connections (through experience with their apps app, users can identify what is a normal value for this) and others. +[float] +==== Dashboard +image::./images/metricbeat-iis-website-overview.png[] diff --git a/x-pack/metricbeat/module/iis/website/manifest.yml b/x-pack/metricbeat/module/iis/website/manifest.yml index 62ebfd27ed8..8492b584948 100644 --- a/x-pack/metricbeat/module/iis/website/manifest.yml +++ b/x-pack/metricbeat/module/iis/website/manifest.yml @@ -6,18 +6,18 @@ input: perfmon.group_measurements_by_instance: true perfmon.ignore_non_existent_counters: true perfmon.queries: - - object: 'Web Service' + - object: "Web Service" instance: "*" namespace : "network" counters: - - name: 'Total Bytes Received' - - name: 'Total Bytes Sent' + - name: "Total Bytes Received" + - name: "Total Bytes Sent" - name: "Bytes Sent/sec" - name: "Bytes Received/sec" - name: "Current Connections" - name: "Maximum Connections" - name: "Total Connection Attempts (all instances)" - field: total_connection_attempts + field: "total_connection_attempts" - name: "Total Get Requests" - name: "Get Requests/sec" - name: "Total Post Requests" @@ -30,7 +30,7 @@ input: processors: - drop_event.when.equals: - iis.website.name: '_Total' + iis.website.instance: '_Total' - drop_fields: fields: "iis.website.object" - rename: From 428ee729e6877d595294ba64caaf86b557b6fa8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mi=20V=C3=A1nyi?= Date: Mon, 4 May 2020 13:56:51 +0200 Subject: [PATCH 084/116] Add Kerberos-aware Elasticsearch and integration test to ES output (#18127) This PR adds an integration test to the Elasticsearch output to check Kerberos authentication. Furthermore, it adds a new element to our testing environment, a Kerberos-aware Elasticsearch instance named `elasticsearch_kerberos.elastic`. --- libbeat/docker-compose.yml | 47 +++++++++-- libbeat/esleg/eslegtest/util.go | 5 ++ .../elasticsearch/client_integration_test.go | 62 ++++++++++++++- .../outputs/elasticsearch/testdata/krb5.conf | 43 ++++++++++ .../docker/elasticsearch/kerberos/init.sh | 10 +++ .../elasticsearch/kerberos/installkdc.sh | 73 +++++++++++++++++ .../docker/elasticsearch_kerberos/Dockerfile | 15 ++++ .../config/kdc.conf.template | 34 ++++++++ .../elasticsearch_kerberos/config/krb5.conf | 25 ++++++ .../config/krb5.conf.template | 43 ++++++++++ .../elasticsearch_kerberos/healthcheck.sh | 11 +++ .../docker/elasticsearch_kerberos/init.sh | 0 .../scripts/addprinc.sh | 62 +++++++++++++++ .../scripts/addprincs.sh | 7 ++ .../scripts/installkdc.sh | 78 +++++++++++++++++++ .../docker/elasticsearch_kerberos/start.sh | 8 ++ .../docker/kerberos_kdc/Dockerfile | 15 ++++ 17 files changed, 528 insertions(+), 10 deletions(-) create mode 100644 libbeat/outputs/elasticsearch/testdata/krb5.conf create mode 100644 testing/environments/docker/elasticsearch/kerberos/init.sh create mode 100644 testing/environments/docker/elasticsearch/kerberos/installkdc.sh create mode 100644 testing/environments/docker/elasticsearch_kerberos/Dockerfile create mode 100644 testing/environments/docker/elasticsearch_kerberos/config/kdc.conf.template create mode 100644 testing/environments/docker/elasticsearch_kerberos/config/krb5.conf create mode 100644 testing/environments/docker/elasticsearch_kerberos/config/krb5.conf.template create mode 100755 testing/environments/docker/elasticsearch_kerberos/healthcheck.sh create mode 100755 testing/environments/docker/elasticsearch_kerberos/init.sh create mode 100755 testing/environments/docker/elasticsearch_kerberos/scripts/addprinc.sh create mode 100755 testing/environments/docker/elasticsearch_kerberos/scripts/addprincs.sh create mode 100755 testing/environments/docker/elasticsearch_kerberos/scripts/installkdc.sh create mode 100755 testing/environments/docker/elasticsearch_kerberos/start.sh create mode 100644 testing/environments/docker/kerberos_kdc/Dockerfile diff --git a/libbeat/docker-compose.yml b/libbeat/docker-compose.yml index f922e4b6e42..be4c0be7dfa 100644 --- a/libbeat/docker-compose.yml +++ b/libbeat/docker-compose.yml @@ -26,6 +26,7 @@ services: - ES_MONITORING_HOST=elasticsearch_monitoring - ES_MONITORING_PORT=9200 - ES_HOST_SSL=elasticsearchssl + - ES_KERBEROS_HOST=elasticsearch_kerberos.elastic - ES_PORT_SSL=9200 - ES_SUPERUSER_USER=admin - ES_SUPERUSER_PASS=changeme @@ -41,14 +42,15 @@ services: proxy_dep: image: busybox depends_on: - elasticsearch: { condition: service_healthy } - elasticsearch_monitoring: { condition: service_healthy } - elasticsearchssl: { condition: service_healthy } - logstash: { condition: service_healthy } - kafka: { condition: service_healthy } - redis: { condition: service_healthy } - sredis: { condition: service_healthy } - kibana: { condition: service_healthy } + elasticsearch: { condition: service_healthy } + elasticsearch_kerberos.elastic: { condition: service_healthy } + elasticsearch_monitoring: { condition: service_healthy } + elasticsearchssl: { condition: service_healthy } + logstash: { condition: service_healthy } + kafka: { condition: service_healthy } + redis: { condition: service_healthy } + sredis: { condition: service_healthy } + kibana: { condition: service_healthy } healthcheck: interval: 1s retries: 1200 @@ -127,6 +129,35 @@ services: environment: - ADVERTISED_HOST=kafka + elasticsearch_kerberos.elastic: + build: ${ES_BEATS}/testing/environments/docker/elasticsearch_kerberos + healthcheck: + test: bash -c "/healthcheck.sh" + retries: 1200 + interval: 5s + start_period: 60s + environment: + - "TERM=linux" + - "ELASTIC_PASSWORD=changeme" + - "ES_JAVA_OPTS=-Xms512m -Xmx512m -Djava.security.krb5.conf=/etc/krb5.conf" + - "network.host=" + - "transport.host=127.0.0.1" + - "http.host=0.0.0.0" + - "xpack.security.enabled=true" + - "indices.id_field_data.enabled=true" + - "xpack.license.self_generated.type=trial" + - "xpack.security.authc.realms.kerberos.ELASTIC.order=1" + - "xpack.security.authc.realms.kerberos.ELASTIC.keytab.path=/usr/share/elasticsearch/config/HTTP_elasticsearch_kerberos.elastic.keytab" + hostname: elasticsearch_kerberos.elastic + volumes: + # This is needed otherwise there won't be enough entropy to generate a new kerberos realm + - /dev/urandom:/dev/random + ports: + - 1088 + - 1749 + - 9200 + command: bash -c "/start.sh" + kibana: extends: file: ${ES_BEATS}/testing/environments/${TESTING_ENVIRONMENT}.yml diff --git a/libbeat/esleg/eslegtest/util.go b/libbeat/esleg/eslegtest/util.go index 8da334dc3a4..28f33fde2dc 100644 --- a/libbeat/esleg/eslegtest/util.go +++ b/libbeat/esleg/eslegtest/util.go @@ -64,6 +64,11 @@ func GetEsHost() string { return getEnv("ES_HOST", ElasticsearchDefaultHost) } +// GetEsKerberosHost returns the Elasticsearch testing host. +func GetEsKerberosHost() string { + return getEnv("ES_KERBEROS_HOST", ElasticsearchDefaultHost) +} + // getEsPort returns the Elasticsearch testing port. func getEsPort() string { return getEnv("ES_PORT", ElasticsearchDefaultPort) diff --git a/libbeat/outputs/elasticsearch/client_integration_test.go b/libbeat/outputs/elasticsearch/client_integration_test.go index 1e01b757da0..009b1edd833 100644 --- a/libbeat/outputs/elasticsearch/client_integration_test.go +++ b/libbeat/outputs/elasticsearch/client_integration_test.go @@ -21,6 +21,7 @@ package elasticsearch import ( "context" + "fmt" "io/ioutil" "math/rand" "net/http" @@ -43,9 +44,39 @@ import ( func TestClientPublishEvent(t *testing.T) { index := "beat-int-pub-single-event" - output, client := connectTestEs(t, map[string]interface{}{ + cfg := map[string]interface{}{ "index": index, - }) + } + + testPublishEvent(t, index, cfg) +} + +func TestClientPublishEventKerberosAware(t *testing.T) { + err := setupRoleMapping(t, eslegtest.GetEsKerberosHost()) + if err != nil { + t.Fatal(err) + } + + index := "beat-int-pub-single-event-behind-kerb" + cfg := map[string]interface{}{ + "hosts": eslegtest.GetEsKerberosHost(), + "index": index, + "username": "", + "password": "", + "kerberos": map[string]interface{}{ + "auth_type": "password", + "config_path": "testdata/krb5.conf", + "username": eslegtest.GetUser(), + "password": eslegtest.GetPass(), + "realm": "ELASTIC", + }, + } + + testPublishEvent(t, index, cfg) +} + +func testPublishEvent(t *testing.T, index string, cfg map[string]interface{}) { + output, client := connectTestEs(t, cfg) // drop old index preparing test client.conn.Delete(index, "", "", nil) @@ -281,6 +312,33 @@ func connectTestEs(t *testing.T, cfg interface{}) (outputs.Client, *Client) { return client, client } +// setupRoleMapping sets up role mapping for the Kerberos user beats@ELASTIC +func setupRoleMapping(t *testing.T, host string) error { + _, client := connectTestEs(t, map[string]interface{}{ + "hosts": host, + "username": "elastic", + "password": "changeme", + }) + + roleMappingURL := client.conn.URL + "/_security/role_mapping/kerbrolemapping" + + status, _, err := client.conn.RequestURL("POST", roleMappingURL, map[string]interface{}{ + "roles": []string{"superuser"}, + "enabled": true, + "rules": map[string]interface{}{ + "field": map[string]interface{}{ + "username": "beats@ELASTIC", + }, + }, + }) + + if status >= 300 { + return fmt.Errorf("non-2xx return code: %d", status) + } + + return err +} + func randomClient(grp outputs.Group) outputs.NetworkClient { L := len(grp.Clients) if L == 0 { diff --git a/libbeat/outputs/elasticsearch/testdata/krb5.conf b/libbeat/outputs/elasticsearch/testdata/krb5.conf new file mode 100644 index 00000000000..355145f315f --- /dev/null +++ b/libbeat/outputs/elasticsearch/testdata/krb5.conf @@ -0,0 +1,43 @@ +# Licensed to Elasticsearch under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +[libdefaults] + default_realm = ELASTIC + dns_canonicalize_hostname = false + dns_lookup_kdc = false + dns_lookup_realm = false + dns_uri_lookup = false + forwardable = true + ignore_acceptor_hostname = true + rdns = false + default_tgs_enctypes = aes128-cts-hmac-sha1-96 + default_tkt_enctypes = aes128-cts-hmac-sha1-96 + permitted_enctypes = aes128-cts-hmac-sha1-96 + udp_preference_limit = 1 + kdc_timeout = 3000 + +[realms] + ELASTIC = { + kdc = elasticsearch_kerberos.elastic:1088 + admin_server = elasticsearch_kerberos.elastic:1749 + default_domain = elastic + } + +[domain_realm] + .elastic = ELASTIC + elastic = ELASTIC + diff --git a/testing/environments/docker/elasticsearch/kerberos/init.sh b/testing/environments/docker/elasticsearch/kerberos/init.sh new file mode 100644 index 00000000000..ac7fe70fa69 --- /dev/null +++ b/testing/environments/docker/elasticsearch/kerberos/init.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +# setup Keberos +echo elasticsearch_kerberos.elastic > /etc/hostname && echo "127.0.0.1 elasticsearch_kerberos.elastic" >> /etc/hosts + +/scripts/installkdc.sh +/scripts/addprincs.sh + +# add test user +bin/elasticsearch-users useradd beats -r superuser -p testing | /usr/local/bin/docker-entrypoint.sh eswrapper diff --git a/testing/environments/docker/elasticsearch/kerberos/installkdc.sh b/testing/environments/docker/elasticsearch/kerberos/installkdc.sh new file mode 100644 index 00000000000..f35848d004c --- /dev/null +++ b/testing/environments/docker/elasticsearch/kerberos/installkdc.sh @@ -0,0 +1,73 @@ +#!/bin/bash + +# Licensed to Elasticsearch under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +set -e + +# KDC installation steps and considerations based on https://web.mit.edu/kerberos/krb5-latest/doc/admin/install_kdc.html +# and helpful input from https://help.ubuntu.com/community/Kerberos + +LOCALSTATEDIR=/etc +LOGDIR=/var/log/krb5 + +#MARKER_FILE=/etc/marker + +# Transfer and interpolate krb5.conf +cp /config/krb5.conf.template $LOCALSTATEDIR/krb5.conf +sed -i 's/${REALM_NAME}/'$REALM_NAME'/g' $LOCALSTATEDIR/krb5.conf +sed -i 's/${KDC_NAME}/'$KDC_NAME'/g' $LOCALSTATEDIR/krb5.conf +sed -i 's/${BUILD_ZONE}/'$BUILD_ZONE'/g' $LOCALSTATEDIR/krb5.conf +sed -i 's/${ELASTIC_ZONE}/'$ELASTIC_ZONE'/g' $LOCALSTATEDIR/krb5.conf + + +# Transfer and interpolate the kdc.conf +mkdir -p $LOCALSTATEDIR/krb5kdc +cp /config/kdc.conf.template $LOCALSTATEDIR/krb5kdc/kdc.conf +sed -i 's/${REALM_NAME}/'$REALM_NAME'/g' $LOCALSTATEDIR/krb5kdc/kdc.conf +sed -i 's/${KDC_NAME}/'$KDC_NAME'/g' $LOCALSTATEDIR/krb5kdc/kdc.conf +sed -i 's/${BUILD_ZONE}/'$BUILD_ZONE'/g' $LOCALSTATEDIR/krb5kdc/kdc.conf +sed -i 's/${ELASTIC_ZONE}/'$ELASTIC_ZONE'/g' $LOCALSTATEDIR/krb5.conf + +# Touch logging locations +mkdir -p $LOGDIR +touch $LOGDIR/kadmin.log +touch $LOGDIR/krb5kdc.log +touch $LOGDIR/krb5lib.log + +# Update package manager +yum update -qqy + +# Install krb5 packages +yum install -qqy krb5-{server,libs,workstation} + +# Create kerberos database with stash file and garbage password +kdb5_util create -s -r $REALM_NAME -P zyxwvutsrpqonmlk9876 + +# Set up admin acls +cat << EOF > /etc/krb5kdc/kadm5.acl +*/admin@$REALM_NAME * +*@$REALM_NAME * +*/*@$REALM_NAME i +EOF + +# Create admin principal +kadmin.local -q "addprinc -pw elastic admin/admin@$REALM_NAME" +kadmin.local -q "ktadd -k /etc/admin.keytab admin/admin@$REALM_NAME" + +# Create a link so addprinc.sh is on path +ln -s /scripts/addprinc.sh /usr/bin/ diff --git a/testing/environments/docker/elasticsearch_kerberos/Dockerfile b/testing/environments/docker/elasticsearch_kerberos/Dockerfile new file mode 100644 index 00000000000..59e5de735ad --- /dev/null +++ b/testing/environments/docker/elasticsearch_kerberos/Dockerfile @@ -0,0 +1,15 @@ +FROM docker.elastic.co/elasticsearch/elasticsearch:8.0.0-SNAPSHOT + +ADD scripts /scripts +ADD config /config +ADD healthcheck.sh /healthcheck.sh +ADD start.sh /start.sh + +ENV REALM_NAME ELASTIC +ENV KDC_NAME elasticsearch_kerberos.elastic +ENV BUILD_ZONE elastic +ENV ELASTIC_ZONE $BUILD_ZONE + +USER root +RUN /scripts/installkdc.sh && /scripts/addprincs.sh +USER elasticsearch diff --git a/testing/environments/docker/elasticsearch_kerberos/config/kdc.conf.template b/testing/environments/docker/elasticsearch_kerberos/config/kdc.conf.template new file mode 100644 index 00000000000..0d32b8d411f --- /dev/null +++ b/testing/environments/docker/elasticsearch_kerberos/config/kdc.conf.template @@ -0,0 +1,34 @@ +# Licensed to Elasticsearch under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +[kdcdefaults] + kdc_listen = 1088 + kdc_tcp_listen = 1088 + +[realms] + ${REALM_NAME} = { + kadmind_port = 1749 + max_life = 12h 0m 0s + max_renewable_life = 7d 0h 0m 0s + master_key_type = aes256-cts + supported_enctypes = aes128-cts:normal des3-hmac-sha1:normal arcfour-hmac:normal des-hmac-sha1:normal des-cbc-md5:normal des-cbc-crc:normal + } + +[logging] + kdc = FILE:/var/log/krb5/krb5kdc.log + admin_server = FILE:/var/log/krb5/kadmin.log + default = FILE:/var/log/krb5/krb5lib.log diff --git a/testing/environments/docker/elasticsearch_kerberos/config/krb5.conf b/testing/environments/docker/elasticsearch_kerberos/config/krb5.conf new file mode 100644 index 00000000000..1b34299558c --- /dev/null +++ b/testing/environments/docker/elasticsearch_kerberos/config/krb5.conf @@ -0,0 +1,25 @@ +[libdefaults] + default_realm = ELASTIC + dns_canonicalize_hostname = false + dns_lookup_kdc = false + dns_lookup_realm = false + dns_uri_lookup = false + forwardable = true + ignore_acceptor_hostname = true + rdns = false + default_tgs_enctypes = aes128-cts-hmac-sha1-96 + default_tkt_enctypes = aes128-cts-hmac-sha1-96 + permitted_enctypes = aes128-cts-hmac-sha1-96 + kdc_timeout = 3000 + +[realms] + ELASTIC = { + kdc = elasticsearch_kerberos.elastic:88 + admin_server = elasticsearch_kerberos.elastic:749 + default_domain = elastic + } + +[domain_realm] + .elastic = ELASTIC + elastic = ELASTIC + diff --git a/testing/environments/docker/elasticsearch_kerberos/config/krb5.conf.template b/testing/environments/docker/elasticsearch_kerberos/config/krb5.conf.template new file mode 100644 index 00000000000..75245ab7733 --- /dev/null +++ b/testing/environments/docker/elasticsearch_kerberos/config/krb5.conf.template @@ -0,0 +1,43 @@ +# Licensed to Elasticsearch under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +[libdefaults] + default_realm = ${REALM_NAME} + dns_canonicalize_hostname = false + dns_lookup_kdc = false + dns_lookup_realm = false + dns_uri_lookup = false + forwardable = true + ignore_acceptor_hostname = true + rdns = false + default_tgs_enctypes = aes128-cts-hmac-sha1-96 + default_tkt_enctypes = aes128-cts-hmac-sha1-96 + permitted_enctypes = aes128-cts-hmac-sha1-96 + udp_preference_limit = 1 + kdc_timeout = 3000 + +[realms] + ${REALM_NAME} = { + kdc = localhost:1088 + admin_server = localhost:1749 + default_domain = ${BUILD_ZONE} + } + +[domain_realm] + .${ELASTIC_ZONE} = ${REALM_NAME} + ${ELASTIC_ZONE} = ${REALM_NAME} + diff --git a/testing/environments/docker/elasticsearch_kerberos/healthcheck.sh b/testing/environments/docker/elasticsearch_kerberos/healthcheck.sh new file mode 100755 index 00000000000..a0932afaa94 --- /dev/null +++ b/testing/environments/docker/elasticsearch_kerberos/healthcheck.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +# check if service principal is OK +KRB5_CONFIG=/etc/krb5.conf \ + kinit -k -t /etc/HTTP_elasticsearch_kerberos.elastic.keytab HTTP/elasticsearch_kerberos.elastic@ELASTIC + + +# check if beats user can connect +echo testing | KRB5_CONFIG=/etc/krb5.conf kinit beats@ELASTIC +klist +curl --negotiate -u : -XGET http://elasticsearch_kerberos.elastic:9200/ diff --git a/testing/environments/docker/elasticsearch_kerberos/init.sh b/testing/environments/docker/elasticsearch_kerberos/init.sh new file mode 100755 index 00000000000..e69de29bb2d diff --git a/testing/environments/docker/elasticsearch_kerberos/scripts/addprinc.sh b/testing/environments/docker/elasticsearch_kerberos/scripts/addprinc.sh new file mode 100755 index 00000000000..97493df7c51 --- /dev/null +++ b/testing/environments/docker/elasticsearch_kerberos/scripts/addprinc.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +# Licensed to Elasticsearch under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +set -e + +if [[ $# -lt 1 ]]; then + echo 'Usage: addprinc.sh principalName [password]' + echo ' principalName user principal name without realm' + echo ' password If provided then will set password for user else it will provision user with keytab' + exit 1 +fi + +PRINC="$1" +PASSWD="$2" +USER=$(echo $PRINC | tr "/" "_") +REALM=ELASTIC + +VDIR=/usr/share/kerberos +BUILD_DIR=/var/build +LOCALSTATEDIR=/etc +LOGDIR=/var/log/krb5 + +ADMIN_PRIN=admin/admin@$REALM +ADMIN_KTAB=$LOCALSTATEDIR/admin.keytab + +USER_PRIN=$PRINC@$REALM +USER_KTAB=$LOCALSTATEDIR/$USER.keytab + +if [ -f $USER_KTAB ] && [ -z "$PASSWD" ]; then + echo "Principal '${PRINC}@${REALM}' already exists. Re-copying keytab..." + sudo cp $USER_KTAB $KEYTAB_DIR/$USER.keytab +else + if [ -z "$PASSWD" ]; then + echo "Provisioning '${PRINC}@${REALM}' principal and keytab..." + sudo kadmin -p $ADMIN_PRIN -kt $ADMIN_KTAB -q "addprinc -randkey $USER_PRIN" + sudo kadmin -p $ADMIN_PRIN -kt $ADMIN_KTAB -q "ktadd -k $USER_KTAB $USER_PRIN" + sudo chmod 777 $USER_KTAB + sudo cp $USER_KTAB /usr/share/elasticsearch/config + sudo chown elasticsearch:elasticsearch /usr/share/elasticsearch/config/$USER.keytab + else + echo "Provisioning '${PRINC}@${REALM}' principal with password..." + sudo kadmin -p $ADMIN_PRIN -kt $ADMIN_KTAB -q "addprinc -pw $PASSWD $PRINC" + fi +fi + +echo "Done provisioning $USER" diff --git a/testing/environments/docker/elasticsearch_kerberos/scripts/addprincs.sh b/testing/environments/docker/elasticsearch_kerberos/scripts/addprincs.sh new file mode 100755 index 00000000000..7ee85889f0d --- /dev/null +++ b/testing/environments/docker/elasticsearch_kerberos/scripts/addprincs.sh @@ -0,0 +1,7 @@ +set -e + +krb5kdc +kadmind + +addprinc.sh HTTP/elasticsearch_kerberos.elastic +addprinc.sh beats testing diff --git a/testing/environments/docker/elasticsearch_kerberos/scripts/installkdc.sh b/testing/environments/docker/elasticsearch_kerberos/scripts/installkdc.sh new file mode 100755 index 00000000000..50ab0ff0a6a --- /dev/null +++ b/testing/environments/docker/elasticsearch_kerberos/scripts/installkdc.sh @@ -0,0 +1,78 @@ +#!/bin/bash + +# Licensed to Elasticsearch under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +set -e + +LOCALSTATEDIR=/etc +KDC_CONFIG=/var/kerberos +LOGDIR=/var/log/krb5 + +#MARKER_FILE=/etc/marker + +# Transfer and interpolate krb5.conf +cp /config/krb5.conf.template $LOCALSTATEDIR/krb5.conf +sed -i 's/${REALM_NAME}/'$REALM_NAME'/g' $LOCALSTATEDIR/krb5.conf +sed -i 's/${KDC_NAME}/'$KDC_NAME'/g' $LOCALSTATEDIR/krb5.conf +sed -i 's/${BUILD_ZONE}/'$BUILD_ZONE'/g' $LOCALSTATEDIR/krb5.conf +sed -i 's/${ELASTIC_ZONE}/'$ELASTIC_ZONE'/g' $LOCALSTATEDIR/krb5.conf + + +# Transfer and interpolate the kdc.conf +mkdir -p $KDC_CONFIG/krb5kdc +cp /config/kdc.conf.template $KDC_CONFIG/krb5kdc/kdc.conf +sed -i 's/${REALM_NAME}/'$REALM_NAME'/g' $KDC_CONFIG/krb5kdc/kdc.conf +sed -i 's/${KDC_NAME}/'$KDC_NAME'/g' $KDC_CONFIG/krb5kdc/kdc.conf +sed -i 's/${BUILD_ZONE}/'$BUILD_ZONE'/g' $KDC_CONFIG/krb5kdc/kdc.conf +sed -i 's/${ELASTIC_ZONE}/'$ELASTIC_ZONE'/g' $LOCALSTATEDIR/krb5.conf + +# Touch logging locations +mkdir -p $LOGDIR +touch $LOGDIR/kadmin.log +touch $LOGDIR/krb5kdc.log +touch $LOGDIR/krb5lib.log + +# Update package manager +yum update -qqy + +# Install krb5 packages +yum install -qqy krb5-{server,libs,workstation} sudo + +# Create kerberos database with stash file and garbage password +kdb5_util create -s -r $REALM_NAME -P zyxwvutsrpqonmlk9876 + +# Set up admin acls +cat << EOF > /var/kerberos/krb5kdc/kadm5.acl +*/admin@$REALM_NAME * +*@$REALM_NAME * +*/*@$REALM_NAME i +EOF + +# Create admin principal +kadmin.local -q "addprinc -pw elastic admin/admin@$REALM_NAME" +kadmin.local -q "ktadd -k /etc/admin.keytab admin/admin@$REALM_NAME" + +# set ownership for ES +chown -R elasticsearch:elasticsearch $LOGDIR +chown -R elasticsearch:elasticsearch $KDC_CONFIG +chown -R elasticsearch:elasticsearch $LOCALSTATEDIR/krb5.conf +chown -R elasticsearch:elasticsearch $LOCALSTATEDIR/admin.keytab + + +# Create a link so addprinc.sh is on path +ln -s /scripts/addprinc.sh /usr/bin/ diff --git a/testing/environments/docker/elasticsearch_kerberos/start.sh b/testing/environments/docker/elasticsearch_kerberos/start.sh new file mode 100755 index 00000000000..522f6c20474 --- /dev/null +++ b/testing/environments/docker/elasticsearch_kerberos/start.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +# start Kerberos services +krb5kdc +kadmind + +# start ES +/usr/local/bin/docker-entrypoint.sh eswrapper diff --git a/testing/environments/docker/kerberos_kdc/Dockerfile b/testing/environments/docker/kerberos_kdc/Dockerfile new file mode 100644 index 00000000000..629fbaebcd5 --- /dev/null +++ b/testing/environments/docker/kerberos_kdc/Dockerfile @@ -0,0 +1,15 @@ +FROM ubuntu:14.04 +ADD scripts /scripts + +ENV REALM_NAME ELASTIC +ENV KDC_NAME kerberos_kdc +ENV BUILD_ZONE elastic +ENV ELASTIC_ZONE $BUILD_ZONE + +RUN echo kerberos_kdc.elastic > /etc/hostname && echo "127.0.0.1 kerberos_kdc.elastic" >> /etc/hosts +RUN bash /scripts/installkdc.sh + +EXPOSE 88 +EXPOSE 749 + +CMD sleep infinity From 2889e6592774575939317257dc2d4774fbaa9a49 Mon Sep 17 00:00:00 2001 From: Michal Pristas Date: Mon, 4 May 2020 14:02:18 +0200 Subject: [PATCH 085/116] [Elastic-Agent] Configurable log level (#18083) * configurable log level * changelog * fmt * error message updated * windows passing * type --- x-pack/elastic-agent/CHANGELOG.asciidoc | 1 + x-pack/elastic-agent/_meta/common.p2.yml | 4 + .../_meta/common.reference.p2.yml | 4 + .../_meta/elastic-agent.docker.yml | 4 + x-pack/elastic-agent/_meta/elastic-agent.yml | 4 + .../elastic-agent_configuration_example.yml | 4 + x-pack/elastic-agent/elastic-agent.docker.yml | 4 + .../elastic-agent/elastic-agent.reference.yml | 4 + x-pack/elastic-agent/elastic-agent.yml | 4 + .../pkg/agent/application/local_mode.go | 2 +- .../pkg/agent/operation/config/config.go | 3 + .../pkg/agent/operation/operator.go | 15 +++- .../elastic-agent/pkg/core/logger/logger.go | 73 +++++++++++++++++-- .../elastic-agent/pkg/core/plugin/app/app.go | 4 +- .../pkg/core/plugin/app/start.go | 22 ++++++ 15 files changed, 142 insertions(+), 10 deletions(-) diff --git a/x-pack/elastic-agent/CHANGELOG.asciidoc b/x-pack/elastic-agent/CHANGELOG.asciidoc index 998d310c74d..13f15a9f5b0 100644 --- a/x-pack/elastic-agent/CHANGELOG.asciidoc +++ b/x-pack/elastic-agent/CHANGELOG.asciidoc @@ -43,6 +43,7 @@ - Pack ECS metadata to request payload send to fleet {pull}17894[17894] - Allow CLI overrides of paths {pull}17781[17781] - Enable Filebeat input: S3, Azureeventhub, cloudfoundry, httpjson, netflow, o365audit. {pull}17909[17909] +- Configurable log level {pull}18083[18083] - Use data subfolder as default for process logs {pull}17960[17960] - Do not require unnecessary configuration {pull}18003[18003] - Enable debug log level for Metricbeat and Filebeat when run under the Elastic Agent. {pull}17935[17935] diff --git a/x-pack/elastic-agent/_meta/common.p2.yml b/x-pack/elastic-agent/_meta/common.p2.yml index 6ea3b10c033..15582908fe7 100644 --- a/x-pack/elastic-agent/_meta/common.p2.yml +++ b/x-pack/elastic-agent/_meta/common.p2.yml @@ -119,3 +119,7 @@ datasources: # logs: false # # enables metrics monitoring # metrics: false + +# Sets log level. The default log level is info. +# Available log levels are: error, warning, info, debug +#logging.level: trace diff --git a/x-pack/elastic-agent/_meta/common.reference.p2.yml b/x-pack/elastic-agent/_meta/common.reference.p2.yml index 6ea3b10c033..15582908fe7 100644 --- a/x-pack/elastic-agent/_meta/common.reference.p2.yml +++ b/x-pack/elastic-agent/_meta/common.reference.p2.yml @@ -119,3 +119,7 @@ datasources: # logs: false # # enables metrics monitoring # metrics: false + +# Sets log level. The default log level is info. +# Available log levels are: error, warning, info, debug +#logging.level: trace diff --git a/x-pack/elastic-agent/_meta/elastic-agent.docker.yml b/x-pack/elastic-agent/_meta/elastic-agent.docker.yml index bccefc9b92d..fc7edf73413 100644 --- a/x-pack/elastic-agent/_meta/elastic-agent.docker.yml +++ b/x-pack/elastic-agent/_meta/elastic-agent.docker.yml @@ -119,3 +119,7 @@ datasources: # logs: false # # enables metrics monitoring # metrics: false + +# Sets log level. The default log level is info. +# Available log levels are: error, warning, info, debug +#logging.level: trace diff --git a/x-pack/elastic-agent/_meta/elastic-agent.yml b/x-pack/elastic-agent/_meta/elastic-agent.yml index 6ea3b10c033..15582908fe7 100644 --- a/x-pack/elastic-agent/_meta/elastic-agent.yml +++ b/x-pack/elastic-agent/_meta/elastic-agent.yml @@ -119,3 +119,7 @@ datasources: # logs: false # # enables metrics monitoring # metrics: false + +# Sets log level. The default log level is info. +# Available log levels are: error, warning, info, debug +#logging.level: trace 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 08b92363921..e91363998fb 100644 --- a/x-pack/elastic-agent/docs/elastic-agent_configuration_example.yml +++ b/x-pack/elastic-agent/docs/elastic-agent_configuration_example.yml @@ -25,6 +25,10 @@ outputs: settings.monitoring: use_output: monitoring +# Sets log level. The default log level is info. +# Available log levels are: error, warning, info, debug +#logging.level: trace + datasources: # use the nginx package - id?: nginx-x1 diff --git a/x-pack/elastic-agent/elastic-agent.docker.yml b/x-pack/elastic-agent/elastic-agent.docker.yml index bccefc9b92d..fc7edf73413 100644 --- a/x-pack/elastic-agent/elastic-agent.docker.yml +++ b/x-pack/elastic-agent/elastic-agent.docker.yml @@ -119,3 +119,7 @@ datasources: # logs: false # # enables metrics monitoring # metrics: false + +# Sets log level. The default log level is info. +# Available log levels are: error, warning, info, debug +#logging.level: trace diff --git a/x-pack/elastic-agent/elastic-agent.reference.yml b/x-pack/elastic-agent/elastic-agent.reference.yml index 1db6a77cca9..547053af6b2 100644 --- a/x-pack/elastic-agent/elastic-agent.reference.yml +++ b/x-pack/elastic-agent/elastic-agent.reference.yml @@ -124,3 +124,7 @@ datasources: # logs: false # # enables metrics monitoring # metrics: false + +# Sets log level. The default log level is info. +# Available log levels are: error, warning, info, debug +#logging.level: trace diff --git a/x-pack/elastic-agent/elastic-agent.yml b/x-pack/elastic-agent/elastic-agent.yml index 1db6a77cca9..547053af6b2 100644 --- a/x-pack/elastic-agent/elastic-agent.yml +++ b/x-pack/elastic-agent/elastic-agent.yml @@ -124,3 +124,7 @@ datasources: # logs: false # # enables metrics monitoring # metrics: false + +# Sets log level. The default log level is info. +# Available log levels are: error, warning, info, debug +#logging.level: trace diff --git a/x-pack/elastic-agent/pkg/agent/application/local_mode.go b/x-pack/elastic-agent/pkg/agent/application/local_mode.go index 65a4927a55d..4ad8f27c7f0 100644 --- a/x-pack/elastic-agent/pkg/agent/application/local_mode.go +++ b/x-pack/elastic-agent/pkg/agent/application/local_mode.go @@ -55,7 +55,7 @@ func newLocal( ) (*Local, error) { var err error if log == nil { - log, err = logger.New() + log, err = logger.NewFromConfig(rawConfig) if err != nil { return nil, err } diff --git a/x-pack/elastic-agent/pkg/agent/operation/config/config.go b/x-pack/elastic-agent/pkg/agent/operation/config/config.go index f54737151c9..d96f4c5125e 100644 --- a/x-pack/elastic-agent/pkg/agent/operation/config/config.go +++ b/x-pack/elastic-agent/pkg/agent/operation/config/config.go @@ -6,6 +6,7 @@ package config import ( "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/artifact" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/core/logger" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/core/plugin/process" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/core/plugin/retry" ) @@ -16,6 +17,7 @@ type Config struct { RetryConfig *retry.Config `yaml:"retry" config:"retry"` DownloadConfig *artifact.Config `yaml:"download" config:"download"` + LoggingConfig *logger.Config `yaml:"logging,omitempty" config:"logging,omitempty"` } // DefaultConfig creates a config with pre-set default values. @@ -24,5 +26,6 @@ func DefaultConfig() *Config { ProcessConfig: process.DefaultConfig(), RetryConfig: retry.DefaultConfig(), DownloadConfig: artifact.DefaultConfig(), + LoggingConfig: logger.DefaultLoggingConfig(), } } diff --git a/x-pack/elastic-agent/pkg/agent/operation/operator.go b/x-pack/elastic-agent/pkg/agent/operation/operator.go index c6c47d444ef..89734e38fe7 100644 --- a/x-pack/elastic-agent/pkg/agent/operation/operator.go +++ b/x-pack/elastic-agent/pkg/agent/operation/operator.go @@ -239,7 +239,20 @@ func (o *Operator) getApp(p Descriptor) (Application, error) { return nil, fmt.Errorf("descriptor is not an app.Specifier") } - a, err := app.NewApplication(o.bgContext, p.ID(), p.BinaryName(), o.pipelineID, specifier, factory, o.config, o.logger, o.eventProcessor.OnFailing, o.monitor) + // TODO: (michal) join args into more compact options version + a, err := app.NewApplication( + o.bgContext, + p.ID(), + p.BinaryName(), + o.pipelineID, + o.config.LoggingConfig.Level.String(), + specifier, + factory, + o.config, + o.logger, + o.eventProcessor.OnFailing, + o.monitor) + if err != nil { return nil, err } diff --git a/x-pack/elastic-agent/pkg/core/logger/logger.go b/x-pack/elastic-agent/pkg/core/logger/logger.go index 3f23feb782e..5c7e2aa9527 100644 --- a/x-pack/elastic-agent/pkg/core/logger/logger.go +++ b/x-pack/elastic-agent/pkg/core/logger/logger.go @@ -5,6 +5,8 @@ package logger import ( + "fmt" + "github.com/urso/ecslog" "github.com/urso/ecslog/backend" "github.com/urso/ecslog/backend/appender" @@ -16,21 +18,78 @@ import ( // Logger alias ecslog.Logger with Logger. type Logger = ecslog.Logger +// Config is a configuration of logging. +type Config struct { + Level loggingLevel `config:"level"` +} + +// DefaultLoggingConfig creates a default logging configuration. +func DefaultLoggingConfig() *Config { + return &Config{ + Level: loggingLevel(backend.Trace), + } +} + // New returns a configured ECS Logger func New() (*Logger, error) { - backend, err := createJSONBackend() + return new(backend.Trace) +} + +func createJSONBackend(lvl backend.Level) (backend.Backend, error) { + return appender.Console(lvl, layout.Text(true)) +} + +//NewFromConfig takes the user configuration and generate the right logger. +// TODO: Finish implementation, need support on the library that we use. +func NewFromConfig(cfg *config.Config) (*Logger, error) { + wrappedConfig := &struct { + Logging *Config `config:"logging"` + }{ + Logging: DefaultLoggingConfig(), + } + + if err := cfg.Unpack(&wrappedConfig); err != nil { + return nil, err + } + + return new(backend.Level(wrappedConfig.Logging.Level)) +} + +func new(lvl backend.Level) (*Logger, error) { + backend, err := createJSONBackend(lvl) if err != nil { return nil, err } return ecslog.New(backend), nil } -func createJSONBackend() (backend.Backend, error) { - return appender.Console(backend.Trace, layout.Text(true)) +type loggingLevel backend.Level + +var loggingLevelMap = map[string]loggingLevel{ + "trace": loggingLevel(backend.Trace), + "debug": loggingLevel(backend.Debug), + "info": loggingLevel(backend.Info), + "error": loggingLevel(backend.Error), } -//NewFromConfig takes the user configuration and generate the right logger. -// TODO: Finish implementation, need support on the library that we use. -func NewFromConfig(_ *config.Config) (*Logger, error) { - return New() +func (m *loggingLevel) Unpack(v string) error { + mgt, ok := loggingLevelMap[v] + if !ok { + return fmt.Errorf( + "unknown logging level mode, received '%s' and valid values are 'trace', 'debug', 'info' or 'error'", + v, + ) + } + *m = mgt + return nil +} + +func (m *loggingLevel) String() string { + for s, v := range loggingLevelMap { + if v == *m { + return s + } + } + + return "unknown" } diff --git a/x-pack/elastic-agent/pkg/core/plugin/app/app.go b/x-pack/elastic-agent/pkg/core/plugin/app/app.go index be361354a5e..0c343793b55 100644 --- a/x-pack/elastic-agent/pkg/core/plugin/app/app.go +++ b/x-pack/elastic-agent/pkg/core/plugin/app/app.go @@ -42,6 +42,7 @@ type Application struct { id string name string pipelineID string + logLevel string spec Specifier state state.State grpcClient remoteconfig.Client @@ -70,7 +71,7 @@ type ArgsDecorator func([]string) []string // the application. func NewApplication( ctx context.Context, - id, appName, pipelineID string, + id, appName, pipelineID, logLevel string, spec Specifier, factory remoteconfig.ConnectionCreator, cfg *config.Config, @@ -90,6 +91,7 @@ func NewApplication( id: id, name: appName, pipelineID: pipelineID, + logLevel: logLevel, spec: spec, clientFactory: factory, processConfig: cfg.ProcessConfig, diff --git a/x-pack/elastic-agent/pkg/core/plugin/app/start.go b/x-pack/elastic-agent/pkg/core/plugin/app/start.go index 68c9df887d4..e13e137699e 100644 --- a/x-pack/elastic-agent/pkg/core/plugin/app/start.go +++ b/x-pack/elastic-agent/pkg/core/plugin/app/start.go @@ -81,6 +81,7 @@ func (a *Application) Start(ctx context.Context, cfg map[string]interface{}) (er a.limiter.Add() } + spec.Args = injectLogLevel(a.logLevel, spec.Args) spec.Args = a.monitor.EnrichArgs(a.name, a.pipelineID, spec.Args) // specify beat name to avoid data lock conflicts @@ -114,6 +115,27 @@ func (a *Application) Start(ctx context.Context, cfg map[string]interface{}) (er return nil } +func injectLogLevel(logLevel string, args []string) []string { + var level string + // Translate to level beat understands + switch logLevel { + case "trace": + level = "debug" + case "info": + level = "info" + case "debug": + level = "debug" + case "error": + level = "error" + } + + if args == nil || level == "" { + return args + } + + return append(args, "-E", "logging.level="+level) +} + func (a *Application) waitForGrpc(spec ProcessSpec, ca *authority.CertificateAuthority) error { const ( rounds int = 3 From 2d9ac92c5abaeaf431fbf7b69c21606493adcd70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20=C3=81lvarez?= Date: Mon, 4 May 2020 14:29:19 +0200 Subject: [PATCH 086/116] [APM] Instrument beat pipeline (#17938) This allows beat users to instrument the publishing pipeline by setting ELASTIC_APM_ACTIVE=true in the environment. Co-authored-by: Gil Raphaelli Co-authored-by: Andrew Wilkins --- CHANGELOG-developer.next.asciidoc | 1 + CHANGELOG.next.asciidoc | 1 + NOTICE.txt | 181 +++ go.mod | 3 + go.sum | 15 + libbeat/cmd/instance/beat.go | 10 + .../breaking/breaking-7.8.asciidoc | 25 + .../release-notes/breaking/breaking.asciidoc | 3 + libbeat/esleg/eslegclient/bulkapi.go | 8 + .../eslegclient/bulkapi_integration_test.go | 7 +- .../esleg/eslegclient/bulkapi_mock_test.go | 7 +- libbeat/esleg/eslegclient/connection.go | 8 +- .../monitoring/report/elasticsearch/client.go | 12 +- libbeat/outputs/backoff.go | 5 +- libbeat/outputs/console/console.go | 3 +- libbeat/outputs/console/console_test.go | 3 +- libbeat/outputs/elasticsearch/client.go | 26 +- .../elasticsearch/client_integration_test.go | 48 +- libbeat/outputs/elasticsearch/client_test.go | 3 +- libbeat/outputs/failover.go | 5 +- libbeat/outputs/fileout/file.go | 5 +- libbeat/outputs/kafka/client.go | 3 +- .../outputs/kafka/kafka_integration_test.go | 3 +- libbeat/outputs/logstash/async.go | 3 +- libbeat/outputs/logstash/async_test.go | 3 +- .../logstash/logstash_integration_test.go | 7 +- libbeat/outputs/logstash/logstash_test.go | 3 +- libbeat/outputs/logstash/sync.go | 3 +- libbeat/outputs/logstash/sync_test.go | 3 +- libbeat/outputs/outputs.go | 5 +- libbeat/outputs/redis/backoff.go | 5 +- libbeat/outputs/redis/client.go | 3 +- .../outputs/redis/redis_integration_test.go | 3 +- libbeat/publisher/pipeline/controller.go | 2 +- libbeat/publisher/pipeline/module.go | 3 + libbeat/publisher/pipeline/nilpipeline.go | 4 +- libbeat/publisher/pipeline/output.go | 38 +- libbeat/publisher/pipeline/output_test.go | 57 +- libbeat/publisher/pipeline/stress/out.go | 3 +- libbeat/publisher/pipeline/testing.go | 3 +- vendor/github.com/armon/go-radix/.gitignore | 22 + vendor/github.com/armon/go-radix/.travis.yml | 3 + vendor/github.com/armon/go-radix/LICENSE | 20 + vendor/github.com/armon/go-radix/README.md | 38 + vendor/github.com/armon/go-radix/go.mod | 1 + vendor/github.com/armon/go-radix/radix.go | 540 +++++++ .../santhosh-tekuri/jsonschema/.travis.yml | 10 + .../santhosh-tekuri/jsonschema/LICENSE | 27 + .../santhosh-tekuri/jsonschema/README.md | 148 ++ .../santhosh-tekuri/jsonschema/compiler.go | 534 +++++++ .../jsonschema/decoders/decoders.go | 32 + .../santhosh-tekuri/jsonschema/doc.go | 77 + .../santhosh-tekuri/jsonschema/draft4.go | 172 +++ .../santhosh-tekuri/jsonschema/draft6.go | 170 +++ .../santhosh-tekuri/jsonschema/draft7.go | 196 +++ .../santhosh-tekuri/jsonschema/errors.go | 122 ++ .../jsonschema/formats/formats.go | 295 ++++ .../santhosh-tekuri/jsonschema/go.mod | 1 + .../santhosh-tekuri/jsonschema/go.test.sh | 12 + .../jsonschema/loader/loader.go | 105 ++ .../jsonschema/mediatypes/mediatypes.go | 39 + .../santhosh-tekuri/jsonschema/resource.go | 236 +++ .../santhosh-tekuri/jsonschema/schema.go | 558 +++++++ .../github.com/stretchr/testify/suite/doc.go | 65 + .../stretchr/testify/suite/interfaces.go | 46 + .../stretchr/testify/suite/suite.go | 166 +++ vendor/go.elastic.co/apm/.dockerignore | 2 + vendor/go.elastic.co/apm/.gitignore | 5 + vendor/go.elastic.co/apm/.jenkins-edge.yml | 2 + vendor/go.elastic.co/apm/.jenkins.yml | 8 + vendor/go.elastic.co/apm/CHANGELOG.asciidoc | 262 ++++ vendor/go.elastic.co/apm/CHANGELOG.md | 1 + vendor/go.elastic.co/apm/CODE_OF_CONDUCT.md | 3 + vendor/go.elastic.co/apm/CONTRIBUTING.md | 91 ++ vendor/go.elastic.co/apm/Jenkinsfile | 294 ++++ vendor/go.elastic.co/apm/LICENSE | 201 +++ vendor/go.elastic.co/apm/Makefile | 81 + vendor/go.elastic.co/apm/NOTICE | 84 ++ vendor/go.elastic.co/apm/README.md | 41 + vendor/go.elastic.co/apm/apmconfig/doc.go | 20 + vendor/go.elastic.co/apm/apmconfig/watcher.go | 54 + .../apm/apmtest/configwatcher.go | 32 + vendor/go.elastic.co/apm/apmtest/discard.go | 52 + vendor/go.elastic.co/apm/apmtest/httpsuite.go | 137 ++ vendor/go.elastic.co/apm/apmtest/recorder.go | 69 + .../go.elastic.co/apm/apmtest/recordlogger.go | 60 + .../go.elastic.co/apm/apmtest/testlogger.go | 45 + .../apm/apmtest/withtransaction.go | 39 + vendor/go.elastic.co/apm/breakdown.go | 365 +++++ vendor/go.elastic.co/apm/builtin_metrics.go | 164 +++ vendor/go.elastic.co/apm/capturebody.go | 198 +++ vendor/go.elastic.co/apm/config.go | 448 ++++++ vendor/go.elastic.co/apm/context.go | 256 ++++ vendor/go.elastic.co/apm/doc.go | 21 + vendor/go.elastic.co/apm/error.go | 696 +++++++++ vendor/go.elastic.co/apm/error_unix.go | 30 + vendor/go.elastic.co/apm/error_windows.go | 27 + vendor/go.elastic.co/apm/fmt.go | 85 ++ vendor/go.elastic.co/apm/fnv.go | 42 + vendor/go.elastic.co/apm/go.mod | 17 + vendor/go.elastic.co/apm/go.sum | 53 + vendor/go.elastic.co/apm/gocontext.go | 138 ++ vendor/go.elastic.co/apm/gofuzz.go | 270 ++++ .../apm/internal/apmcontext/context.go | 78 + .../apm/internal/apmhostutil/container.go | 34 + .../internal/apmhostutil/container_linux.go | 156 ++ .../apmhostutil/container_nonlinux.go | 36 + .../apm/internal/apmhttputil/forwarded.go | 74 + .../apm/internal/apmhttputil/remoteaddr.go | 60 + .../apm/internal/apmhttputil/url.go | 113 ++ .../apm/internal/apmlog/logger.go | 173 +++ .../apm/internal/apmschema/schema.go | 69 + .../apm/internal/apmschema/update.sh | 37 + .../apm/internal/apmstrings/truncate.go | 31 + .../apm/internal/apmversion/version.go | 23 + .../apm/internal/configutil/duration.go | 73 + .../apm/internal/configutil/env.go | 95 ++ .../apm/internal/configutil/list.go | 34 + .../apm/internal/configutil/size.go | 105 ++ .../apm/internal/configutil/wildcards.go | 62 + .../go.elastic.co/apm/internal/iochan/doc.go | 19 + .../apm/internal/iochan/reader.go | 110 ++ .../apm/internal/pkgerrorsutil/pkgerrors.go | 60 + .../apm/internal/ringbuffer/buffer.go | 142 ++ .../apm/internal/ringbuffer/doc.go | 22 + .../apm/internal/wildcard/doc.go | 19 + .../apm/internal/wildcard/matcher.go | 142 ++ .../apm/internal/wildcard/matchers.go | 31 + vendor/go.elastic.co/apm/logger.go | 54 + vendor/go.elastic.co/apm/metrics.go | 161 ++ vendor/go.elastic.co/apm/model/doc.go | 21 + vendor/go.elastic.co/apm/model/generate.sh | 4 + vendor/go.elastic.co/apm/model/gofuzz.go | 82 ++ vendor/go.elastic.co/apm/model/maps.go | 48 + vendor/go.elastic.co/apm/model/marshal.go | 639 ++++++++ .../apm/model/marshal_fastjson.go | 1297 +++++++++++++++++ vendor/go.elastic.co/apm/model/model.go | 671 +++++++++ vendor/go.elastic.co/apm/modelwriter.go | 267 ++++ .../apm/module/apmelasticsearch/LICENSE | 201 +++ .../apm/module/apmelasticsearch/client.go | 239 +++ .../apm/module/apmelasticsearch/doc.go | 20 + .../apm/module/apmelasticsearch/go.mod | 14 + .../apm/module/apmelasticsearch/go.sum | 62 + .../module/apmelasticsearch/requestname.go | 39 + .../apmelasticsearch/requestname_go19.go | 30 + .../go.elastic.co/apm/module/apmhttp/LICENSE | 201 +++ .../apm/module/apmhttp/client.go | 200 +++ .../apm/module/apmhttp/context.go | 40 + .../go.elastic.co/apm/module/apmhttp/doc.go | 20 + .../go.elastic.co/apm/module/apmhttp/go.mod | 13 + .../go.elastic.co/apm/module/apmhttp/go.sum | 62 + .../apm/module/apmhttp/handler.go | 330 +++++ .../apm/module/apmhttp/ignorer.go | 81 + .../apm/module/apmhttp/recovery.go | 60 + .../apm/module/apmhttp/requestname.go | 56 + .../apm/module/apmhttp/requestname_go19.go | 46 + .../apm/module/apmhttp/traceheaders.go | 168 +++ vendor/go.elastic.co/apm/profiling.go | 164 +++ vendor/go.elastic.co/apm/sampler.go | 66 + vendor/go.elastic.co/apm/sanitizer.go | 66 + vendor/go.elastic.co/apm/span.go | 415 ++++++ vendor/go.elastic.co/apm/spancontext.go | 193 +++ vendor/go.elastic.co/apm/stacktrace.go | 52 + .../go.elastic.co/apm/stacktrace/context.go | 100 ++ vendor/go.elastic.co/apm/stacktrace/doc.go | 20 + vendor/go.elastic.co/apm/stacktrace/frame.go | 34 + .../apm/stacktrace/generate_library.bash | 77 + .../go.elastic.co/apm/stacktrace/library.go | 253 ++++ .../apm/stacktrace/stacktrace.go | 162 ++ vendor/go.elastic.co/apm/tracecontext.go | 263 ++++ vendor/go.elastic.co/apm/tracer.go | 1170 +++++++++++++++ vendor/go.elastic.co/apm/tracer_stats.go | 52 + vendor/go.elastic.co/apm/transaction.go | 324 ++++ vendor/go.elastic.co/apm/transport/api.go | 33 + vendor/go.elastic.co/apm/transport/default.go | 54 + vendor/go.elastic.co/apm/transport/discard.go | 31 + vendor/go.elastic.co/apm/transport/doc.go | 20 + vendor/go.elastic.co/apm/transport/http.go | 638 ++++++++ .../apm/transport/transporttest/doc.go | 20 + .../apm/transport/transporttest/err.go | 54 + .../apm/transport/transporttest/recorder.go | 203 +++ vendor/go.elastic.co/apm/utils.go | 242 +++ vendor/go.elastic.co/apm/utils_linux.go | 40 + vendor/go.elastic.co/apm/utils_other.go | 38 + vendor/go.elastic.co/apm/version.go | 23 + vendor/go.elastic.co/fastjson/.travis.yml | 9 + vendor/go.elastic.co/fastjson/LICENSE | 23 + vendor/go.elastic.co/fastjson/README.md | 134 ++ vendor/go.elastic.co/fastjson/doc.go | 23 + vendor/go.elastic.co/fastjson/go.mod | 3 + vendor/go.elastic.co/fastjson/go.sum | 2 + vendor/go.elastic.co/fastjson/marshaler.go | 151 ++ vendor/go.elastic.co/fastjson/writer.go | 181 +++ vendor/modules.txt | 35 + 194 files changed, 20792 insertions(+), 61 deletions(-) create mode 100644 libbeat/docs/release-notes/breaking/breaking-7.8.asciidoc create mode 100644 vendor/github.com/armon/go-radix/.gitignore create mode 100644 vendor/github.com/armon/go-radix/.travis.yml create mode 100644 vendor/github.com/armon/go-radix/LICENSE create mode 100644 vendor/github.com/armon/go-radix/README.md create mode 100644 vendor/github.com/armon/go-radix/go.mod create mode 100644 vendor/github.com/armon/go-radix/radix.go create mode 100644 vendor/github.com/santhosh-tekuri/jsonschema/.travis.yml create mode 100644 vendor/github.com/santhosh-tekuri/jsonschema/LICENSE create mode 100644 vendor/github.com/santhosh-tekuri/jsonschema/README.md create mode 100644 vendor/github.com/santhosh-tekuri/jsonschema/compiler.go create mode 100644 vendor/github.com/santhosh-tekuri/jsonschema/decoders/decoders.go create mode 100644 vendor/github.com/santhosh-tekuri/jsonschema/doc.go create mode 100644 vendor/github.com/santhosh-tekuri/jsonschema/draft4.go create mode 100644 vendor/github.com/santhosh-tekuri/jsonschema/draft6.go create mode 100644 vendor/github.com/santhosh-tekuri/jsonschema/draft7.go create mode 100644 vendor/github.com/santhosh-tekuri/jsonschema/errors.go create mode 100644 vendor/github.com/santhosh-tekuri/jsonschema/formats/formats.go create mode 100644 vendor/github.com/santhosh-tekuri/jsonschema/go.mod create mode 100644 vendor/github.com/santhosh-tekuri/jsonschema/go.test.sh create mode 100644 vendor/github.com/santhosh-tekuri/jsonschema/loader/loader.go create mode 100644 vendor/github.com/santhosh-tekuri/jsonschema/mediatypes/mediatypes.go create mode 100644 vendor/github.com/santhosh-tekuri/jsonschema/resource.go create mode 100644 vendor/github.com/santhosh-tekuri/jsonschema/schema.go create mode 100644 vendor/github.com/stretchr/testify/suite/doc.go create mode 100644 vendor/github.com/stretchr/testify/suite/interfaces.go create mode 100644 vendor/github.com/stretchr/testify/suite/suite.go create mode 100644 vendor/go.elastic.co/apm/.dockerignore create mode 100644 vendor/go.elastic.co/apm/.gitignore create mode 100644 vendor/go.elastic.co/apm/.jenkins-edge.yml create mode 100644 vendor/go.elastic.co/apm/.jenkins.yml create mode 100644 vendor/go.elastic.co/apm/CHANGELOG.asciidoc create mode 100644 vendor/go.elastic.co/apm/CHANGELOG.md create mode 100644 vendor/go.elastic.co/apm/CODE_OF_CONDUCT.md create mode 100644 vendor/go.elastic.co/apm/CONTRIBUTING.md create mode 100644 vendor/go.elastic.co/apm/Jenkinsfile create mode 100644 vendor/go.elastic.co/apm/LICENSE create mode 100644 vendor/go.elastic.co/apm/Makefile create mode 100644 vendor/go.elastic.co/apm/NOTICE create mode 100644 vendor/go.elastic.co/apm/README.md create mode 100644 vendor/go.elastic.co/apm/apmconfig/doc.go create mode 100644 vendor/go.elastic.co/apm/apmconfig/watcher.go create mode 100644 vendor/go.elastic.co/apm/apmtest/configwatcher.go create mode 100644 vendor/go.elastic.co/apm/apmtest/discard.go create mode 100644 vendor/go.elastic.co/apm/apmtest/httpsuite.go create mode 100644 vendor/go.elastic.co/apm/apmtest/recorder.go create mode 100644 vendor/go.elastic.co/apm/apmtest/recordlogger.go create mode 100644 vendor/go.elastic.co/apm/apmtest/testlogger.go create mode 100644 vendor/go.elastic.co/apm/apmtest/withtransaction.go create mode 100644 vendor/go.elastic.co/apm/breakdown.go create mode 100644 vendor/go.elastic.co/apm/builtin_metrics.go create mode 100644 vendor/go.elastic.co/apm/capturebody.go create mode 100644 vendor/go.elastic.co/apm/config.go create mode 100644 vendor/go.elastic.co/apm/context.go create mode 100644 vendor/go.elastic.co/apm/doc.go create mode 100644 vendor/go.elastic.co/apm/error.go create mode 100644 vendor/go.elastic.co/apm/error_unix.go create mode 100644 vendor/go.elastic.co/apm/error_windows.go create mode 100644 vendor/go.elastic.co/apm/fmt.go create mode 100644 vendor/go.elastic.co/apm/fnv.go create mode 100644 vendor/go.elastic.co/apm/go.mod create mode 100644 vendor/go.elastic.co/apm/go.sum create mode 100644 vendor/go.elastic.co/apm/gocontext.go create mode 100644 vendor/go.elastic.co/apm/gofuzz.go create mode 100644 vendor/go.elastic.co/apm/internal/apmcontext/context.go create mode 100644 vendor/go.elastic.co/apm/internal/apmhostutil/container.go create mode 100644 vendor/go.elastic.co/apm/internal/apmhostutil/container_linux.go create mode 100644 vendor/go.elastic.co/apm/internal/apmhostutil/container_nonlinux.go create mode 100644 vendor/go.elastic.co/apm/internal/apmhttputil/forwarded.go create mode 100644 vendor/go.elastic.co/apm/internal/apmhttputil/remoteaddr.go create mode 100644 vendor/go.elastic.co/apm/internal/apmhttputil/url.go create mode 100644 vendor/go.elastic.co/apm/internal/apmlog/logger.go create mode 100644 vendor/go.elastic.co/apm/internal/apmschema/schema.go create mode 100644 vendor/go.elastic.co/apm/internal/apmschema/update.sh create mode 100644 vendor/go.elastic.co/apm/internal/apmstrings/truncate.go create mode 100644 vendor/go.elastic.co/apm/internal/apmversion/version.go create mode 100644 vendor/go.elastic.co/apm/internal/configutil/duration.go create mode 100644 vendor/go.elastic.co/apm/internal/configutil/env.go create mode 100644 vendor/go.elastic.co/apm/internal/configutil/list.go create mode 100644 vendor/go.elastic.co/apm/internal/configutil/size.go create mode 100644 vendor/go.elastic.co/apm/internal/configutil/wildcards.go create mode 100644 vendor/go.elastic.co/apm/internal/iochan/doc.go create mode 100644 vendor/go.elastic.co/apm/internal/iochan/reader.go create mode 100644 vendor/go.elastic.co/apm/internal/pkgerrorsutil/pkgerrors.go create mode 100644 vendor/go.elastic.co/apm/internal/ringbuffer/buffer.go create mode 100644 vendor/go.elastic.co/apm/internal/ringbuffer/doc.go create mode 100644 vendor/go.elastic.co/apm/internal/wildcard/doc.go create mode 100644 vendor/go.elastic.co/apm/internal/wildcard/matcher.go create mode 100644 vendor/go.elastic.co/apm/internal/wildcard/matchers.go create mode 100644 vendor/go.elastic.co/apm/logger.go create mode 100644 vendor/go.elastic.co/apm/metrics.go create mode 100644 vendor/go.elastic.co/apm/model/doc.go create mode 100644 vendor/go.elastic.co/apm/model/generate.sh create mode 100644 vendor/go.elastic.co/apm/model/gofuzz.go create mode 100644 vendor/go.elastic.co/apm/model/maps.go create mode 100644 vendor/go.elastic.co/apm/model/marshal.go create mode 100644 vendor/go.elastic.co/apm/model/marshal_fastjson.go create mode 100644 vendor/go.elastic.co/apm/model/model.go create mode 100644 vendor/go.elastic.co/apm/modelwriter.go create mode 100644 vendor/go.elastic.co/apm/module/apmelasticsearch/LICENSE create mode 100644 vendor/go.elastic.co/apm/module/apmelasticsearch/client.go create mode 100644 vendor/go.elastic.co/apm/module/apmelasticsearch/doc.go create mode 100644 vendor/go.elastic.co/apm/module/apmelasticsearch/go.mod create mode 100644 vendor/go.elastic.co/apm/module/apmelasticsearch/go.sum create mode 100644 vendor/go.elastic.co/apm/module/apmelasticsearch/requestname.go create mode 100644 vendor/go.elastic.co/apm/module/apmelasticsearch/requestname_go19.go create mode 100644 vendor/go.elastic.co/apm/module/apmhttp/LICENSE create mode 100644 vendor/go.elastic.co/apm/module/apmhttp/client.go create mode 100644 vendor/go.elastic.co/apm/module/apmhttp/context.go create mode 100644 vendor/go.elastic.co/apm/module/apmhttp/doc.go create mode 100644 vendor/go.elastic.co/apm/module/apmhttp/go.mod create mode 100644 vendor/go.elastic.co/apm/module/apmhttp/go.sum create mode 100644 vendor/go.elastic.co/apm/module/apmhttp/handler.go create mode 100644 vendor/go.elastic.co/apm/module/apmhttp/ignorer.go create mode 100644 vendor/go.elastic.co/apm/module/apmhttp/recovery.go create mode 100644 vendor/go.elastic.co/apm/module/apmhttp/requestname.go create mode 100644 vendor/go.elastic.co/apm/module/apmhttp/requestname_go19.go create mode 100644 vendor/go.elastic.co/apm/module/apmhttp/traceheaders.go create mode 100644 vendor/go.elastic.co/apm/profiling.go create mode 100644 vendor/go.elastic.co/apm/sampler.go create mode 100644 vendor/go.elastic.co/apm/sanitizer.go create mode 100644 vendor/go.elastic.co/apm/span.go create mode 100644 vendor/go.elastic.co/apm/spancontext.go create mode 100644 vendor/go.elastic.co/apm/stacktrace.go create mode 100644 vendor/go.elastic.co/apm/stacktrace/context.go create mode 100644 vendor/go.elastic.co/apm/stacktrace/doc.go create mode 100644 vendor/go.elastic.co/apm/stacktrace/frame.go create mode 100644 vendor/go.elastic.co/apm/stacktrace/generate_library.bash create mode 100644 vendor/go.elastic.co/apm/stacktrace/library.go create mode 100644 vendor/go.elastic.co/apm/stacktrace/stacktrace.go create mode 100644 vendor/go.elastic.co/apm/tracecontext.go create mode 100644 vendor/go.elastic.co/apm/tracer.go create mode 100644 vendor/go.elastic.co/apm/tracer_stats.go create mode 100644 vendor/go.elastic.co/apm/transaction.go create mode 100644 vendor/go.elastic.co/apm/transport/api.go create mode 100644 vendor/go.elastic.co/apm/transport/default.go create mode 100644 vendor/go.elastic.co/apm/transport/discard.go create mode 100644 vendor/go.elastic.co/apm/transport/doc.go create mode 100644 vendor/go.elastic.co/apm/transport/http.go create mode 100644 vendor/go.elastic.co/apm/transport/transporttest/doc.go create mode 100644 vendor/go.elastic.co/apm/transport/transporttest/err.go create mode 100644 vendor/go.elastic.co/apm/transport/transporttest/recorder.go create mode 100644 vendor/go.elastic.co/apm/utils.go create mode 100644 vendor/go.elastic.co/apm/utils_linux.go create mode 100644 vendor/go.elastic.co/apm/utils_other.go create mode 100644 vendor/go.elastic.co/apm/version.go create mode 100644 vendor/go.elastic.co/fastjson/.travis.yml create mode 100644 vendor/go.elastic.co/fastjson/LICENSE create mode 100644 vendor/go.elastic.co/fastjson/README.md create mode 100644 vendor/go.elastic.co/fastjson/doc.go create mode 100644 vendor/go.elastic.co/fastjson/go.mod create mode 100644 vendor/go.elastic.co/fastjson/go.sum create mode 100644 vendor/go.elastic.co/fastjson/marshaler.go create mode 100644 vendor/go.elastic.co/fastjson/writer.go diff --git a/CHANGELOG-developer.next.asciidoc b/CHANGELOG-developer.next.asciidoc index 42d45bb80b6..f2313ccdb94 100644 --- a/CHANGELOG-developer.next.asciidoc +++ b/CHANGELOG-developer.next.asciidoc @@ -40,6 +40,7 @@ The list below covers the major changes between 7.0.0-rc2 and master only. - Extract Elasticsearch client logic from `outputs/elasticsearch` package into new `esclientleg` package. {pull}16150[16150] - Rename `queue.BufferConfig.Events` to `queue.BufferConfig.MaxEvents`. {pull}17622[17622] - Remove `queue.Feature` and replace `queue.RegisterType` with `queue.RegisterQueueType`. {pull}17666[17666] +- Introduce APM libbeat instrumentation. `Publish` method on `Client` interface now takes a Context as first argument. {pull}17938[17938] ==== Bugfixes diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 74ecf4c2232..29e90199083 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -14,6 +14,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Variable substitution from environment variables is not longer supported. {pull}15937{15937} - Change aws_elb autodiscover provider field name from elb_listener.* to aws.elb.*. {issue}16219[16219] {pull}16402{16402} - Remove `AddDockerMetadata` and `AddKubernetesMetadata` processors from the `script` processor. They can still be used as normal processors in the configuration. {issue}16349[16349] {pull}16514[16514] +- Introduce APM libbeat instrumentation, active when running the beat with ELASTIC_APM_ACTIVE=true. {pull}17938[17938] *Auditbeat* diff --git a/NOTICE.txt b/NOTICE.txt index abea1705477..57b366327b6 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -405,6 +405,33 @@ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +-------------------------------------------------------------------- +Dependency: github.com/armon/go-radix +Version: v1.0.0 +License type (autodetected): MIT +./vendor/github.com/armon/go-radix/LICENSE: +-------------------------------------------------------------------- +The MIT License (MIT) + +Copyright (c) 2014 Armon Dadgar + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + -------------------------------------------------------------------- Dependency: github.com/armon/go-socks5 Revision: e75332964ef5 @@ -6886,6 +6913,39 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-------------------------------------------------------------------- +Dependency: github.com/santhosh-tekuri/jsonschema +Version: v1.2.4 +License type (autodetected): BSD-3-Clause +./vendor/github.com/santhosh-tekuri/jsonschema/LICENSE: +-------------------------------------------------------------------- +Copyright (c) 2017 Santhosh Kumar Tekuri. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------- Dependency: github.com/shirou/gopsutil Version: v2.19.11 @@ -7357,6 +7417,127 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +-------------------------------------------------------------------- +Dependency: go.elastic.co/apm +Version: v1.7.2 +License type (autodetected): Apache-2.0 +./vendor/go.elastic.co/apm/LICENSE: +-------------------------------------------------------------------- +Apache License 2.0 + +-------NOTICE----- +Elastic APM Go Agent +Copyright 2018-2019 Elasticsearch B.V. + +This product includes software developed at Elasticsearch, B.V. (https://www.elastic.co/). + +========================================= +Third party code included by the Go Agent +========================================= + +------------------------------------------------------------------------------------ +This project copies code from the Go standard library (https://github.com/golang/go) +------------------------------------------------------------------------------------ + +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------------- +This project copies code from Gorilla Mux (https://github.com/gorilla/mux) +-------------------------------------------------------------------------- + +Copyright (c) 2012-2018 The Gorilla Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +------------------------------------------------------------ +This project copies code from pq (https://github.com/lib/pq) +------------------------------------------------------------ + +Copyright (c) 2011-2013, 'pq' Contributors Portions Copyright (C) 2011 Blake Mizerany + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +-------------------------------------------------------------------- +Dependency: go.elastic.co/apm/module/apmelasticsearch +Version: v1.7.2 +License type (autodetected): Apache-2.0 +./vendor/go.elastic.co/apm/module/apmelasticsearch/LICENSE: +-------------------------------------------------------------------- +Apache License 2.0 + + +-------------------------------------------------------------------- +Dependency: go.elastic.co/apm/module/apmhttp +Version: v1.7.2 +License type (autodetected): Apache-2.0 +./vendor/go.elastic.co/apm/module/apmhttp/LICENSE: +-------------------------------------------------------------------- +Apache License 2.0 + + +-------------------------------------------------------------------- +Dependency: go.elastic.co/fastjson +Version: v1.0.0 +License type (autodetected): Apache-2.0 +./vendor/go.elastic.co/fastjson/LICENSE: +-------------------------------------------------------------------- +Apache License 2.0 + + -------------------------------------------------------------------- Dependency: go.opencensus.io Version: v0.22.2 diff --git a/go.mod b/go.mod index 6dc10d0b15f..4cd1af71ef4 100644 --- a/go.mod +++ b/go.mod @@ -142,6 +142,9 @@ require ( github.com/vmware/govmomi v0.0.0-20170802214208-2cad15190b41 github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c github.com/yuin/gopher-lua v0.0.0-20170403160031-b402f3114ec7 // indirect + go.elastic.co/apm v1.7.2 + go.elastic.co/apm/module/apmelasticsearch v1.7.2 + go.elastic.co/apm/module/apmhttp v1.7.2 go.uber.org/atomic v1.3.1 go.uber.org/multierr v1.1.1-0.20170829224307-fb7d312c2c04 go.uber.org/zap v1.7.1 diff --git a/go.sum b/go.sum index c018b305485..c064c9cf1b9 100644 --- a/go.sum +++ b/go.sum @@ -113,6 +113,8 @@ github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYU github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= github.com/antlr/antlr4 v0.0.0-20200225173536-225249fdaef5 h1:nkZ9axP+MvUFCu8JRN/MCY+DmTfs6lY7hE0QnJbxSdI= github.com/antlr/antlr4 v0.0.0-20200225173536-225249fdaef5/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y= +github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aws/aws-lambda-go v1.6.0 h1:T+u/g79zPKw1oJM7xYhvpq7i4Sjc0iVsXZUaqRVVSOg= @@ -173,6 +175,8 @@ github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+ github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea h1:n2Ltr3SrfQlf/9nOna1DoGKxLx3qTSI8Ttl6Xrqp6mw= github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/cucumber/godog v0.8.1 h1:lVb+X41I4YDreE+ibZ50bdXmySxgRviYFgKY6Aw4XE8= +github.com/cucumber/godog v0.8.1/go.mod h1:vSh3r/lM+psC1BPXvdkSEuNjmXfpVqrMGYAElF6hxnA= github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -240,6 +244,7 @@ github.com/elastic/go-seccomp-bpf v1.1.0 h1:jUzzDc6LyCtdolZdvL/26dad6rZ9vsc7xZ2e github.com/elastic/go-seccomp-bpf v1.1.0/go.mod h1:l+89Vy5BzjVcaX8USZRMOwmwwDScE+vxCFzzvQwN7T8= github.com/elastic/go-structform v0.0.6 h1:wqeK4LwD2NNDOoRGTImE24S6pkCDVr8+oUSIkmChzLk= github.com/elastic/go-structform v0.0.6/go.mod h1:QrMyP3oM9Sjk92EVGLgRaL2lKt0Qx7ZNDRWDxB6khVs= +github.com/elastic/go-sysinfo v1.1.1/go.mod h1:i1ZYdU10oLNfRzq4vq62BEwD2fH8KaWh6eh0ikPT9F0= github.com/elastic/go-sysinfo v1.3.0 h1:eb2XFGTMlSwG/yyU9Y8jVAYLIzU2sFzWXwo2gmetyrE= github.com/elastic/go-sysinfo v1.3.0/go.mod h1:i1ZYdU10oLNfRzq4vq62BEwD2fH8KaWh6eh0ikPT9F0= github.com/elastic/go-txfile v0.0.7 h1:Yn28gclW7X0Qy09nSMSsx0uOAvAGMsp6XHydbiLVe2s= @@ -596,6 +601,8 @@ github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b/go.mod h1:8458kAa github.com/sanathkr/yaml v0.0.0-20170819201035-0056894fa522/go.mod h1:tQTYKOQgxoH3v6dEmdHiz4JG+nbxWwM5fgPQUpSZqVQ= github.com/sanathkr/yaml v1.0.1-0.20170819201035-0056894fa522 h1:39BJIaZIhIBmXATIhdlTBlTQpAiGXHnz17CrO7vF2Ss= github.com/sanathkr/yaml v1.0.1-0.20170819201035-0056894fa522/go.mod h1:tQTYKOQgxoH3v6dEmdHiz4JG+nbxWwM5fgPQUpSZqVQ= +github.com/santhosh-tekuri/jsonschema v1.2.4 h1:hNhW8e7t+H1vgY+1QeEQpveR6D4+OwKPXCfD2aieJis= +github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shirou/gopsutil v2.19.11+incompatible h1:lJHR0foqAjI4exXqWsU3DbH7bX1xvdhGdnXTIARA9W4= github.com/shirou/gopsutil v2.19.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= @@ -669,6 +676,14 @@ github.com/xeipuuv/gojsonschema v0.0.0-20181112162635-ac52e6811b56 h1:yhqBHs09Sm github.com/xeipuuv/gojsonschema v0.0.0-20181112162635-ac52e6811b56/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= github.com/yuin/gopher-lua v0.0.0-20170403160031-b402f3114ec7 h1:0gYLpmzecnaDCoeWxSfEJ7J1b6B/67+NV++4HKQXx+Y= github.com/yuin/gopher-lua v0.0.0-20170403160031-b402f3114ec7/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU= +go.elastic.co/apm v1.7.2 h1:0nwzVIPp4PDBXSYYtN19+1W5V+sj+C25UjqxDVoKcA8= +go.elastic.co/apm v1.7.2/go.mod h1:tCw6CkOJgkWnzEthFN9HUP1uL3Gjc/Ur6m7gRPLaoH0= +go.elastic.co/apm/module/apmelasticsearch v1.7.2 h1:5STGHLZLSeAzxordMc+dFVKiyVtMmxADOV+TgRaXXJg= +go.elastic.co/apm/module/apmelasticsearch v1.7.2/go.mod h1:ZyNFuyWdt42GBZkz0SogoLzDBrBGj4orxpiUuxYeYq8= +go.elastic.co/apm/module/apmhttp v1.7.2 h1:2mRh7SwBuEVLmJlX+hsMdcSg9xaielCLElaPn/+i34w= +go.elastic.co/apm/module/apmhttp v1.7.2/go.mod h1:sTFWiWejnhSdZv6+dMgxGec2Nxe/ZKfHfz/xtRM+cRY= +go.elastic.co/fastjson v1.0.0 h1:ooXV/ABvf+tBul26jcVViPT3sBir0PvXgibYB1IQQzg= +go.elastic.co/fastjson v1.0.0/go.mod h1:PmeUOMMtLHQr9ZS9J9owrAVg0FkaZDRZJEFTTGHtchs= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= diff --git a/libbeat/cmd/instance/beat.go b/libbeat/cmd/instance/beat.go index e2b4d7d6630..5439e311301 100644 --- a/libbeat/cmd/instance/beat.go +++ b/libbeat/cmd/instance/beat.go @@ -33,6 +33,8 @@ import ( "strings" "time" + "go.elastic.co/apm" + "github.com/gofrs/uuid" errw "github.com/pkg/errors" "go.uber.org/zap" @@ -121,6 +123,8 @@ var debugf = logp.MakeDebug("beat") func init() { initRand() + // we need to close the default tracer to prevent the beat sending events to localhost:8200 + apm.DefaultTracer.Close() } // initRand initializes the runtime random number generator seed using @@ -331,11 +335,17 @@ func (b *Beat) createBeater(bt beat.Creator) (beat.Beater, error) { } } + tracer, err := apm.NewTracer(b.Info.Beat, b.Info.Version) + if err != nil { + return nil, err + } + pipeline, err := pipeline.Load(b.Info, pipeline.Monitors{ Metrics: reg, Telemetry: monitoring.GetNamespace("state").GetRegistry(), Logger: logp.L().Named("publisher"), + Tracer: tracer, }, b.Config.Pipeline, b.processing, diff --git a/libbeat/docs/release-notes/breaking/breaking-7.8.asciidoc b/libbeat/docs/release-notes/breaking/breaking-7.8.asciidoc new file mode 100644 index 00000000000..c94a45b7603 --- /dev/null +++ b/libbeat/docs/release-notes/breaking/breaking-7.8.asciidoc @@ -0,0 +1,25 @@ +[[breaking-changes-7.8]] + +=== Breaking changes in 7.8 +++++ +7.8 +++++ + +{see-relnotes} + +//NOTE: The notable-breaking-changes tagged regions are re-used in the +//Installation and Upgrade Guide + +//tag::notable-breaking-changes[] +[float] + +==== APM Instrumentation + +Libbeat includes the Elastic APM Agent for instrumenting the publishing pipeline. +Currently the Elasticsearch output is instrumented. APM can be enabled simply with +setting the `ELASTIC_APM_ACTIVE` environment variable to `true` when starting the beat. +To make tracing possible, the `Publish` method of the `Client` interface takes a +`Context` object as first argument. That `Context` is intended for propagating +request-scoped values, not for cancellation. + +// end::notable-breaking-changes[] diff --git a/libbeat/docs/release-notes/breaking/breaking.asciidoc b/libbeat/docs/release-notes/breaking/breaking.asciidoc index 51e58e6ba9d..5bd5d2ad03d 100644 --- a/libbeat/docs/release-notes/breaking/breaking.asciidoc +++ b/libbeat/docs/release-notes/breaking/breaking.asciidoc @@ -10,6 +10,7 @@ changes, but there are breaking changes between major versions (e.g. 6.x to 7.x) is not recommended. See the following topics for a description of breaking changes: +* <> * <> @@ -27,6 +28,8 @@ See the following topics for a description of breaking changes: * <> +include::breaking-7.8.asciidoc[] + include::breaking-7.7.asciidoc[] include::breaking-7.6.asciidoc[] diff --git a/libbeat/esleg/eslegclient/bulkapi.go b/libbeat/esleg/eslegclient/bulkapi.go index 86b518eeea1..ae7ea92f8ba 100644 --- a/libbeat/esleg/eslegclient/bulkapi.go +++ b/libbeat/esleg/eslegclient/bulkapi.go @@ -19,6 +19,7 @@ package eslegclient import ( "bytes" + "context" "encoding/json" "errors" "io" @@ -26,6 +27,9 @@ import ( "net/http" "strings" + "go.elastic.co/apm" + "go.elastic.co/apm/module/apmhttp" + "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/libbeat/logp" ) @@ -59,6 +63,7 @@ type BulkResult json.RawMessage // Bulk performs many index/delete operations in a single API call. // Implements: http://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html func (conn *Connection) Bulk( + ctx context.Context, index, docType string, params map[string]string, body []interface{}, ) (int, BulkResult, error) { @@ -69,13 +74,16 @@ func (conn *Connection) Bulk( enc := conn.Encoder enc.Reset() if err := bulkEncode(conn.log, enc, body); err != nil { + apm.CaptureError(ctx, err).Send() return 0, nil, err } requ, err := newBulkRequest(conn.URL, index, docType, params, enc) if err != nil { + apm.CaptureError(ctx, err).Send() return 0, nil, err } + requ.requ = apmhttp.RequestWithContext(ctx, requ.requ) return conn.sendBulkRequest(requ) } diff --git a/libbeat/esleg/eslegclient/bulkapi_integration_test.go b/libbeat/esleg/eslegclient/bulkapi_integration_test.go index 49bb7497d56..d2201b6e8bd 100644 --- a/libbeat/esleg/eslegclient/bulkapi_integration_test.go +++ b/libbeat/esleg/eslegclient/bulkapi_integration_test.go @@ -20,6 +20,7 @@ package eslegclient import ( + "context" "fmt" "os" "testing" @@ -53,7 +54,7 @@ func TestBulk(t *testing.T) { params := map[string]string{ "refresh": "true", } - _, _, err := client.Bulk(index, "", params, body) + _, _, err := client.Bulk(context.Background(), index, "", params, body) if err != nil { t.Fatalf("Bulk() returned error: %s", err) } @@ -86,7 +87,7 @@ func TestEmptyBulk(t *testing.T) { params := map[string]string{ "refresh": "true", } - _, resp, err := client.Bulk(index, "", params, body) + _, resp, err := client.Bulk(context.Background(), index, "", params, body) if err != nil { t.Fatalf("Bulk() returned error: %s", err) } @@ -150,7 +151,7 @@ func TestBulkMoreOperations(t *testing.T) { params := map[string]string{ "refresh": "true", } - _, resp, err := client.Bulk(index, "", params, body) + _, resp, err := client.Bulk(context.Background(), index, "", params, body) if err != nil { t.Fatalf("Bulk() returned error: %s [%s]", err, resp) } diff --git a/libbeat/esleg/eslegclient/bulkapi_mock_test.go b/libbeat/esleg/eslegclient/bulkapi_mock_test.go index 1fbd53d9425..ded3e53c95c 100644 --- a/libbeat/esleg/eslegclient/bulkapi_mock_test.go +++ b/libbeat/esleg/eslegclient/bulkapi_mock_test.go @@ -20,6 +20,7 @@ package eslegclient import ( + "context" "fmt" "net/http" "os" @@ -60,7 +61,7 @@ func TestOneHostSuccessResp_Bulk(t *testing.T) { params := map[string]string{ "refresh": "true", } - _, _, err := client.Bulk(index, "type1", params, body) + _, _, err := client.Bulk(context.Background(), index, "type1", params, body) if err != nil { t.Errorf("Bulk() returns error: %s", err) } @@ -96,7 +97,7 @@ func TestOneHost500Resp_Bulk(t *testing.T) { params := map[string]string{ "refresh": "true", } - _, _, err := client.Bulk(index, "type1", params, body) + _, _, err := client.Bulk(context.Background(), index, "type1", params, body) if err == nil { t.Errorf("Bulk() should return error.") } @@ -136,7 +137,7 @@ func TestOneHost503Resp_Bulk(t *testing.T) { params := map[string]string{ "refresh": "true", } - _, _, err := client.Bulk(index, "type1", params, body) + _, _, err := client.Bulk(context.Background(), index, "type1", params, body) if err == nil { t.Errorf("Bulk() should return error.") } diff --git a/libbeat/esleg/eslegclient/connection.go b/libbeat/esleg/eslegclient/connection.go index 7001d2e453d..138c9ab3c83 100644 --- a/libbeat/esleg/eslegclient/connection.go +++ b/libbeat/esleg/eslegclient/connection.go @@ -26,6 +26,8 @@ import ( "net/url" "time" + "go.elastic.co/apm/module/apmelasticsearch" + "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/libbeat/common/transport" "github.com/elastic/beats/v7/libbeat/common/transport/kerberos" @@ -128,14 +130,16 @@ func NewConnection(s ConnectionSettings) (*Connection, error) { } var httpClient esHTTPClient + // when dropping the legacy client in favour of the official Go client, it should be instrumented + // eg, like in https://github.com/elastic/apm-server/blob/7.7/elasticsearch/client.go httpClient = &http.Client{ - Transport: &http.Transport{ + Transport: apmelasticsearch.WrapRoundTripper(&http.Transport{ Dial: dialer.Dial, DialTLS: tlsDialer.Dial, TLSClientConfig: s.TLS.ToConfig(), Proxy: proxy, IdleConnTimeout: s.IdleConnTimeout, - }, + }), Timeout: s.Timeout, } diff --git a/libbeat/monitoring/report/elasticsearch/client.go b/libbeat/monitoring/report/elasticsearch/client.go index 9e8469ab547..fb83a2e636b 100644 --- a/libbeat/monitoring/report/elasticsearch/client.go +++ b/libbeat/monitoring/report/elasticsearch/client.go @@ -18,11 +18,14 @@ package elasticsearch import ( + "context" "encoding/json" "fmt" "net/http" "time" + "go.elastic.co/apm" + "github.com/pkg/errors" "github.com/elastic/beats/v7/libbeat/common" @@ -103,7 +106,7 @@ func (c *publishClient) Close() error { return c.es.Close() } -func (c *publishClient) Publish(batch publisher.Batch) error { +func (c *publishClient) Publish(ctx context.Context, batch publisher.Batch) error { events := batch.Events() var failed []publisher.Event var reason error @@ -141,7 +144,7 @@ func (c *publishClient) Publish(batch publisher.Batch) error { case report.FormatXPackMonitoringBulk: err = c.publishXPackBulk(params, event, typ) case report.FormatBulk: - err = c.publishBulk(event, typ) + err = c.publishBulk(ctx, event, typ) } if err != nil { @@ -186,7 +189,7 @@ func (c *publishClient) publishXPackBulk(params map[string]string, event publish return err } -func (c *publishClient) publishBulk(event publisher.Event, typ string) error { +func (c *publishClient) publishBulk(ctx context.Context, event publisher.Event, typ string) error { meta := common.MapStr{ "_index": getMonitoringIndexName(), "_routing": nil, @@ -233,8 +236,9 @@ func (c *publishClient) publishBulk(event publisher.Event, typ string) error { // Currently one request per event is sent. Reason is that each event can contain different // interval params and X-Pack requires to send the interval param. - _, result, err := c.es.Bulk(getMonitoringIndexName(), "", nil, bulk[:]) + _, result, err := c.es.Bulk(ctx, getMonitoringIndexName(), "", nil, bulk[:]) if err != nil { + apm.CaptureError(ctx, fmt.Errorf("failed to perform any bulk index operations: %w", err)).Send() return err } diff --git a/libbeat/outputs/backoff.go b/libbeat/outputs/backoff.go index 256b8029b09..5c1ece2e5db 100644 --- a/libbeat/outputs/backoff.go +++ b/libbeat/outputs/backoff.go @@ -18,6 +18,7 @@ package outputs import ( + "context" "errors" "time" @@ -56,8 +57,8 @@ func (b *backoffClient) Close() error { return err } -func (b *backoffClient) Publish(batch publisher.Batch) error { - err := b.client.Publish(batch) +func (b *backoffClient) Publish(ctx context.Context, batch publisher.Batch) error { + err := b.client.Publish(ctx, batch) if err != nil { b.client.Close() } diff --git a/libbeat/outputs/console/console.go b/libbeat/outputs/console/console.go index 79aee6957d6..bbce8f449a9 100644 --- a/libbeat/outputs/console/console.go +++ b/libbeat/outputs/console/console.go @@ -19,6 +19,7 @@ package console import ( "bufio" + "context" "fmt" "os" "runtime" @@ -102,7 +103,7 @@ func newConsole(index string, observer outputs.Observer, codec codec.Codec) (*co } func (c *console) Close() error { return nil } -func (c *console) Publish(batch publisher.Batch) error { +func (c *console) Publish(_ context.Context, batch publisher.Batch) error { st := c.observer events := batch.Events() st.NewBatch(len(events)) diff --git a/libbeat/outputs/console/console_test.go b/libbeat/outputs/console/console_test.go index 29201beee54..a8e85601a89 100644 --- a/libbeat/outputs/console/console_test.go +++ b/libbeat/outputs/console/console_test.go @@ -21,6 +21,7 @@ package console import ( "bytes" + "context" "io" "os" "testing" @@ -130,7 +131,7 @@ func run(codec codec.Codec, batches ...publisher.Batch) (string, error) { return withStdout(func() { c, _ := newConsole("test", outputs.NewNilObserver(), codec) for _, b := range batches { - c.Publish(b) + c.Publish(context.Background(), b) } }) } diff --git a/libbeat/outputs/elasticsearch/client.go b/libbeat/outputs/elasticsearch/client.go index bee2769cb9e..4e9c8875b31 100644 --- a/libbeat/outputs/elasticsearch/client.go +++ b/libbeat/outputs/elasticsearch/client.go @@ -18,12 +18,15 @@ package elasticsearch import ( + "context" "encoding/base64" "errors" "fmt" "net/http" "time" + "go.elastic.co/apm" + "github.com/elastic/beats/v7/libbeat/testing" "github.com/elastic/beats/v7/libbeat/beat" @@ -171,9 +174,9 @@ func (client *Client) Clone() *Client { return c } -func (client *Client) Publish(batch publisher.Batch) error { +func (client *Client) Publish(ctx context.Context, batch publisher.Batch) error { events := batch.Events() - rest, err := client.publishEvents(events) + rest, err := client.publishEvents(ctx, events) if len(rest) == 0 { batch.ACK() } else { @@ -185,9 +188,9 @@ func (client *Client) Publish(batch publisher.Batch) error { // PublishEvents sends all events to elasticsearch. On error a slice with all // events not published or confirmed to be processed by elasticsearch will be // returned. The input slice backing memory will be reused by return the value. -func (client *Client) publishEvents( - data []publisher.Event, -) ([]publisher.Event, error) { +func (client *Client) publishEvents(ctx context.Context, data []publisher.Event) ([]publisher.Event, error) { + span, ctx := apm.StartSpan(ctx, "publishEvents", "output") + defer span.End() begin := time.Now() st := client.observer @@ -202,8 +205,10 @@ func (client *Client) publishEvents( // encode events into bulk request buffer, dropping failed elements from // events slice origCount := len(data) + span.Context.SetLabel("events_original", origCount) data, bulkItems := bulkEncodePublishRequest(client.log, client.conn.GetVersion(), client.index, client.pipeline, data) newCount := len(data) + span.Context.SetLabel("events_encoded", newCount) if st != nil && origCount > newCount { st.Dropped(origCount - newCount) } @@ -211,14 +216,18 @@ func (client *Client) publishEvents( return nil, nil } - status, result, sendErr := client.conn.Bulk("", "", nil, bulkItems) + status, result, sendErr := client.conn.Bulk(ctx, "", "", nil, bulkItems) if sendErr != nil { - client.log.Errorf("Failed to perform any bulk index operations: %s", sendErr) + err := apm.CaptureError(ctx, fmt.Errorf("failed to perform any bulk index operations: %w", sendErr)) + err.Send() + client.log.Error(err) return data, sendErr } + pubCount := len(data) + span.Context.SetLabel("events_published", pubCount) client.log.Debugf("PublishEvents: %d events have been published to elasticsearch in %v.", - len(data), + pubCount, time.Now().Sub(begin)) // check response for transient errors @@ -232,6 +241,7 @@ func (client *Client) publishEvents( } failed := len(failedEvents) + span.Context.SetLabel("events_failed", failed) if st := client.observer; st != nil { dropped := stats.nonIndexable duplicates := stats.duplicates diff --git a/libbeat/outputs/elasticsearch/client_integration_test.go b/libbeat/outputs/elasticsearch/client_integration_test.go index 009b1edd833..9abbbe39873 100644 --- a/libbeat/outputs/elasticsearch/client_integration_test.go +++ b/libbeat/outputs/elasticsearch/client_integration_test.go @@ -30,6 +30,8 @@ import ( "testing" "time" + "go.elastic.co/apm/apmtest" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -89,7 +91,7 @@ func testPublishEvent(t *testing.T, index string, cfg map[string]interface{}) { }, }) - err := output.Publish(batch) + err := output.Publish(context.Background(), batch) if err != nil { t.Fatal(err) } @@ -127,7 +129,7 @@ func TestClientPublishEventWithPipeline(t *testing.T) { } publish := func(event beat.Event) { - err := output.Publish(outest.NewBatch(event)) + err := output.Publish(context.Background(), outest.NewBatch(event)) if err != nil { t.Fatal(err) } @@ -208,7 +210,7 @@ func TestClientBulkPublishEventsWithPipeline(t *testing.T) { } publish := func(events ...beat.Event) { - err := output.Publish(outest.NewBatch(events...)) + err := output.Publish(context.Background(), outest.NewBatch(events...)) if err != nil { t.Fatal(err) } @@ -272,6 +274,46 @@ func TestClientBulkPublishEventsWithPipeline(t *testing.T) { assert.Equal(t, 1, getCount("testfield:0")) // no pipeline } +func TestClientPublishTracer(t *testing.T) { + index := "beat-apm-tracer-test" + output, client := connectTestEs(t, map[string]interface{}{ + "index": index, + }) + + client.conn.Delete(index, "", "", nil) + + batch := outest.NewBatch(beat.Event{ + Timestamp: time.Now(), + Fields: common.MapStr{ + "message": "Hello world", + }, + }) + + tx, spans, _ := apmtest.WithTransaction(func(ctx context.Context) { + err := output.Publish(ctx, batch) + if err != nil { + t.Fatal(err) + } + }) + require.Len(t, spans, 2) + + // get spans in reverse order + firstSpan := spans[1] + + assert.Equal(t, "publishEvents", firstSpan.Name) + assert.Equal(t, "output", firstSpan.Type) + assert.Equal(t, [8]byte(firstSpan.TransactionID), [8]byte(tx.ID)) + assert.True(t, len(firstSpan.Context.Tags) > 0, "no tags found") + + secondSpan := spans[0] + assert.Contains(t, secondSpan.Name, "POST") + assert.Equal(t, "db", secondSpan.Type) + assert.Equal(t, "elasticsearch", secondSpan.Subtype) + assert.Equal(t, [8]byte(secondSpan.ParentID), [8]byte(firstSpan.ID)) + assert.Equal(t, [8]byte(secondSpan.TransactionID), [8]byte(tx.ID)) + assert.Equal(t, "/_bulk", secondSpan.Context.HTTP.URL.Path) +} + func connectTestEs(t *testing.T, cfg interface{}) (outputs.Client, *Client) { config, err := common.NewConfigFrom(map[string]interface{}{ "hosts": eslegtest.GetEsHost(), diff --git a/libbeat/outputs/elasticsearch/client_test.go b/libbeat/outputs/elasticsearch/client_test.go index d69849dabab..5219d052987 100644 --- a/libbeat/outputs/elasticsearch/client_test.go +++ b/libbeat/outputs/elasticsearch/client_test.go @@ -20,6 +20,7 @@ package elasticsearch import ( + "context" "fmt" "net/http" "net/http/httptest" @@ -242,7 +243,7 @@ func TestClientWithHeaders(t *testing.T) { }} batch := outest.NewBatch(event, event, event) - err = client.Publish(batch) + err = client.Publish(context.Background(), batch) assert.NoError(t, err) assert.Equal(t, 2, requestCount) } diff --git a/libbeat/outputs/failover.go b/libbeat/outputs/failover.go index b388a58a61f..f64720a7895 100644 --- a/libbeat/outputs/failover.go +++ b/libbeat/outputs/failover.go @@ -18,6 +18,7 @@ package outputs import ( + "context" "errors" "fmt" "math/rand" @@ -91,12 +92,12 @@ func (f *failoverClient) Close() error { return f.clients[f.active].Close() } -func (f *failoverClient) Publish(batch publisher.Batch) error { +func (f *failoverClient) Publish(ctx context.Context, batch publisher.Batch) error { if f.active < 0 { batch.Retry() return errNoActiveConnection } - return f.clients[f.active].Publish(batch) + return f.clients[f.active].Publish(ctx, batch) } func (f *failoverClient) Test(d testing.Driver) { diff --git a/libbeat/outputs/fileout/file.go b/libbeat/outputs/fileout/file.go index c3f5d3c5e4e..2c2f5216294 100644 --- a/libbeat/outputs/fileout/file.go +++ b/libbeat/outputs/fileout/file.go @@ -18,6 +18,7 @@ package fileout import ( + "context" "os" "path/filepath" @@ -109,9 +110,7 @@ func (out *fileOutput) Close() error { return out.rotator.Close() } -func (out *fileOutput) Publish( - batch publisher.Batch, -) error { +func (out *fileOutput) Publish(_ context.Context, batch publisher.Batch) error { defer batch.ACK() st := out.observer diff --git a/libbeat/outputs/kafka/client.go b/libbeat/outputs/kafka/client.go index 99e41bd1a8b..c785f9e729f 100644 --- a/libbeat/outputs/kafka/client.go +++ b/libbeat/outputs/kafka/client.go @@ -18,6 +18,7 @@ package kafka import ( + "context" "errors" "fmt" "strings" @@ -126,7 +127,7 @@ func (c *client) Close() error { return nil } -func (c *client) Publish(batch publisher.Batch) error { +func (c *client) Publish(_ context.Context, batch publisher.Batch) error { events := batch.Events() c.observer.NewBatch(len(events)) diff --git a/libbeat/outputs/kafka/kafka_integration_test.go b/libbeat/outputs/kafka/kafka_integration_test.go index 58d03d1c1e7..af46aa65c73 100644 --- a/libbeat/outputs/kafka/kafka_integration_test.go +++ b/libbeat/outputs/kafka/kafka_integration_test.go @@ -20,6 +20,7 @@ package kafka import ( + "context" "encoding/json" "fmt" "math/rand" @@ -220,7 +221,7 @@ func TestKafkaPublish(t *testing.T) { } wg.Add(1) - output.Publish(batch) + output.Publish(context.Background(), batch) } // wait for all published batches to be ACKed diff --git a/libbeat/outputs/logstash/async.go b/libbeat/outputs/logstash/async.go index bcbbbdbc428..f196d137b88 100644 --- a/libbeat/outputs/logstash/async.go +++ b/libbeat/outputs/logstash/async.go @@ -18,6 +18,7 @@ package logstash import ( + "context" "errors" "net" "sync" @@ -134,7 +135,7 @@ func (c *asyncClient) Close() error { return c.Client.Close() } -func (c *asyncClient) Publish(batch publisher.Batch) error { +func (c *asyncClient) Publish(_ context.Context, batch publisher.Batch) error { st := c.observer events := batch.Events() st.NewBatch(len(events)) diff --git a/libbeat/outputs/logstash/async_test.go b/libbeat/outputs/logstash/async_test.go index 5e6e416a0b4..04d97d8c40b 100644 --- a/libbeat/outputs/logstash/async_test.go +++ b/libbeat/outputs/logstash/async_test.go @@ -20,6 +20,7 @@ package logstash import ( + "context" "sync" "testing" "time" @@ -85,7 +86,7 @@ func newAsyncTestDriver(client outputs.NetworkClient) *testAsyncDriver { case driverCmdClose: driver.client.Close() case driverCmdPublish: - err := driver.client.Publish(cmd.batch) + err := driver.client.Publish(context.Background(), cmd.batch) driver.returns = append(driver.returns, testClientReturn{cmd.batch, err}) } } diff --git a/libbeat/outputs/logstash/logstash_integration_test.go b/libbeat/outputs/logstash/logstash_integration_test.go index 6e7e3693dc3..c0969358dfb 100644 --- a/libbeat/outputs/logstash/logstash_integration_test.go +++ b/libbeat/outputs/logstash/logstash_integration_test.go @@ -20,6 +20,7 @@ package logstash import ( + "context" "encoding/json" "fmt" "os" @@ -301,7 +302,7 @@ func testSendMessageViaLogstash(t *testing.T, name string, tls bool) { }, }, ) - ls.Publish(batch) + ls.Publish(context.Background(), batch) // wait for logstash event flush + elasticsearch waitUntilTrue(5*time.Second, checkIndex(ls, 1)) @@ -546,7 +547,7 @@ func checkEvent(t *testing.T, ls, es map[string]interface{}) { } func (t *testOutputer) PublishEvent(event beat.Event) { - t.Publish(outest.NewBatch(event)) + t.Publish(context.Background(), outest.NewBatch(event)) } func (t *testOutputer) BulkPublish(events []beat.Event) bool { @@ -560,7 +561,7 @@ func (t *testOutputer) BulkPublish(events []beat.Event) bool { wg.Done() } - t.Publish(batch) + t.Publish(context.Background(), batch) wg.Wait() return ok } diff --git a/libbeat/outputs/logstash/logstash_test.go b/libbeat/outputs/logstash/logstash_test.go index 06d15567ec5..7b8adeb8f43 100644 --- a/libbeat/outputs/logstash/logstash_test.go +++ b/libbeat/outputs/logstash/logstash_test.go @@ -18,6 +18,7 @@ package logstash import ( + "context" "fmt" "os" "testing" @@ -126,7 +127,7 @@ func testConnectionType( batch.OnSignal = func(_ outest.BatchSignal) { close(sig) } - err = output.Publish(batch) + err = output.Publish(context.Background(), batch) t.Log("wait signal") <-sig diff --git a/libbeat/outputs/logstash/sync.go b/libbeat/outputs/logstash/sync.go index d13740d37f8..22e133db906 100644 --- a/libbeat/outputs/logstash/sync.go +++ b/libbeat/outputs/logstash/sync.go @@ -18,6 +18,7 @@ package logstash import ( + "context" "time" "github.com/elastic/beats/v7/libbeat/beat" @@ -101,7 +102,7 @@ func (c *syncClient) reconnect() error { return c.Client.Connect() } -func (c *syncClient) Publish(batch publisher.Batch) error { +func (c *syncClient) Publish(_ context.Context, batch publisher.Batch) error { events := batch.Events() st := c.observer diff --git a/libbeat/outputs/logstash/sync_test.go b/libbeat/outputs/logstash/sync_test.go index af90cfa130d..3ba9e682232 100644 --- a/libbeat/outputs/logstash/sync_test.go +++ b/libbeat/outputs/logstash/sync_test.go @@ -20,6 +20,7 @@ package logstash import ( + "context" "sync" "testing" "time" @@ -99,7 +100,7 @@ func newClientTestDriver(client outputs.NetworkClient) *testSyncDriver { case driverCmdClose: driver.client.Close() case driverCmdPublish: - err := driver.client.Publish(cmd.batch) + err := driver.client.Publish(context.Background(), cmd.batch) driver.returns = append(driver.returns, testClientReturn{cmd.batch, err}) } } diff --git a/libbeat/outputs/outputs.go b/libbeat/outputs/outputs.go index c6808321ce7..0fdf4d9407b 100644 --- a/libbeat/outputs/outputs.go +++ b/libbeat/outputs/outputs.go @@ -21,6 +21,8 @@ package outputs import ( + "context" + "github.com/elastic/beats/v7/libbeat/publisher" ) @@ -34,7 +36,8 @@ type Client interface { // Using Retry/Cancelled a client can return a batch of unprocessed events to // the publisher pipeline. The publisher pipeline (if configured by the output // factory) will take care of retrying/dropping events. - Publish(publisher.Batch) error + // Context is intended for carrying request-scoped values, not for cancellation. + Publish(context.Context, publisher.Batch) error // String identifies the client type and endpoint. String() string diff --git a/libbeat/outputs/redis/backoff.go b/libbeat/outputs/redis/backoff.go index 30107df90fa..41f448ca318 100644 --- a/libbeat/outputs/redis/backoff.go +++ b/libbeat/outputs/redis/backoff.go @@ -18,6 +18,7 @@ package redis import ( + "context" "time" "github.com/garyburd/redigo/redis" @@ -78,8 +79,8 @@ func (b *backoffClient) Close() error { return err } -func (b *backoffClient) Publish(batch publisher.Batch) error { - err := b.client.Publish(batch) +func (b *backoffClient) Publish(ctx context.Context, batch publisher.Batch) error { + err := b.client.Publish(ctx, batch) if err != nil { b.client.Close() b.updateFailReason(err) diff --git a/libbeat/outputs/redis/client.go b/libbeat/outputs/redis/client.go index fbaa40f4d3e..70e316cba3f 100644 --- a/libbeat/outputs/redis/client.go +++ b/libbeat/outputs/redis/client.go @@ -18,6 +18,7 @@ package redis import ( + "context" "errors" "regexp" "strconv" @@ -134,7 +135,7 @@ func (c *client) Close() error { return c.Client.Close() } -func (c *client) Publish(batch publisher.Batch) error { +func (c *client) Publish(_ context.Context, batch publisher.Batch) error { if c == nil { panic("no client") } diff --git a/libbeat/outputs/redis/redis_integration_test.go b/libbeat/outputs/redis/redis_integration_test.go index 66c3375246a..25189fa9008 100644 --- a/libbeat/outputs/redis/redis_integration_test.go +++ b/libbeat/outputs/redis/redis_integration_test.go @@ -20,6 +20,7 @@ package redis import ( + "context" "encoding/json" "fmt" "os" @@ -348,7 +349,7 @@ func sendTestEvents(out outputs.Client, batches, N int) error { } batch := outest.NewBatch(events...) - err := out.Publish(batch) + err := out.Publish(context.Background(), batch) if err != nil { return err } diff --git a/libbeat/publisher/pipeline/controller.go b/libbeat/publisher/pipeline/controller.go index 837a70eab77..f703f28f685 100644 --- a/libbeat/publisher/pipeline/controller.go +++ b/libbeat/publisher/pipeline/controller.go @@ -105,7 +105,7 @@ func (c *outputController) Set(outGrp outputs.Group) { clients := outGrp.Clients worker := make([]outputWorker, len(clients)) for i, client := range clients { - worker[i] = makeClientWorker(c.observer, c.workQueue, client) + worker[i] = makeClientWorker(c.observer, c.workQueue, client, c.monitors.Tracer) } grp := &outputGroup{ workQueue: c.workQueue, diff --git a/libbeat/publisher/pipeline/module.go b/libbeat/publisher/pipeline/module.go index 6522bb72120..591d6d9a278 100644 --- a/libbeat/publisher/pipeline/module.go +++ b/libbeat/publisher/pipeline/module.go @@ -21,6 +21,8 @@ import ( "flag" "fmt" + "go.elastic.co/apm" + "github.com/elastic/beats/v7/libbeat/beat" "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/libbeat/logp" @@ -43,6 +45,7 @@ type Monitors struct { Metrics *monitoring.Registry Telemetry *monitoring.Registry Logger *logp.Logger + Tracer *apm.Tracer } // OutputFactory is used by the publisher pipeline to create an output instance. diff --git a/libbeat/publisher/pipeline/nilpipeline.go b/libbeat/publisher/pipeline/nilpipeline.go index f32785a8d22..cf1b276db91 100644 --- a/libbeat/publisher/pipeline/nilpipeline.go +++ b/libbeat/publisher/pipeline/nilpipeline.go @@ -17,7 +17,9 @@ package pipeline -import "github.com/elastic/beats/v7/libbeat/beat" +import ( + "github.com/elastic/beats/v7/libbeat/beat" +) type nilPipeline struct{} diff --git a/libbeat/publisher/pipeline/output.go b/libbeat/publisher/pipeline/output.go index fa2ce73a28c..ffc5acfa6ad 100644 --- a/libbeat/publisher/pipeline/output.go +++ b/libbeat/publisher/pipeline/output.go @@ -18,6 +18,13 @@ package pipeline import ( + "context" + "fmt" + + "github.com/elastic/beats/v7/libbeat/publisher" + + "go.elastic.co/apm" + "github.com/elastic/beats/v7/libbeat/logp" "github.com/elastic/beats/v7/libbeat/outputs" ) @@ -43,9 +50,11 @@ type netClientWorker struct { batchSize int batchSizer func() int logger *logp.Logger + + tracer *apm.Tracer } -func makeClientWorker(observer outputObserver, qu workQueue, client outputs.Client) outputWorker { +func makeClientWorker(observer outputObserver, qu workQueue, client outputs.Client, tracer *apm.Tracer) outputWorker { w := worker{ observer: observer, qu: qu, @@ -62,6 +71,7 @@ func makeClientWorker(observer outputObserver, qu workQueue, client outputs.Clie worker: w, client: nc, logger: logp.NewLogger("publisher_pipeline_output"), + tracer: tracer, } } else { c = &clientWorker{worker: w, client: client} @@ -94,8 +104,7 @@ func (w *clientWorker) run() { continue } w.observer.outBatchSend(len(batch.Events())) - - if err := w.client.Publish(batch); err != nil { + if err := w.client.Publish(context.TODO(), batch); err != nil { return } } @@ -150,11 +159,28 @@ func (w *netClientWorker) run() { continue } - if err := w.client.Publish(batch); err != nil { - w.logger.Errorf("Failed to publish events: %v", err) - // on error return to connect loop + if err := w.publishBatch(batch); err != nil { connected = false } } } } + +func (w *netClientWorker) publishBatch(batch publisher.Batch) error { + ctx := context.Background() + if w.tracer != nil { + tx := w.tracer.StartTransaction("publish", "output") + defer tx.End() + tx.Context.SetLabel("worker", "netclient") + ctx = apm.ContextWithTransaction(ctx, tx) + } + err := w.client.Publish(ctx, batch) + if err != nil { + err = fmt.Errorf("failed to publish events: %w", err) + apm.CaptureError(ctx, err).Send() + w.logger.Error(err) + // on error return to connect loop + return err + } + return nil +} diff --git a/libbeat/publisher/pipeline/output_test.go b/libbeat/publisher/pipeline/output_test.go index 5f471ddf396..36c138a6d01 100644 --- a/libbeat/publisher/pipeline/output_test.go +++ b/libbeat/publisher/pipeline/output_test.go @@ -24,6 +24,8 @@ import ( "testing/quick" "time" + "go.elastic.co/apm/apmtest" + "github.com/stretchr/testify/require" "github.com/elastic/beats/v7/libbeat/common/atomic" @@ -58,7 +60,7 @@ func TestMakeClientWorker(t *testing.T) { client := ctor(publishFn) - worker := makeClientWorker(nilObserver, wqu, client) + worker := makeClientWorker(nilObserver, wqu, client, nil) defer worker.Close() for i := uint(0); i < numBatches; i++ { @@ -137,7 +139,7 @@ func TestReplaceClientWorker(t *testing.T) { } client := ctor(blockingPublishFn) - worker := makeClientWorker(nilObserver, wqu, client) + worker := makeClientWorker(nilObserver, wqu, client, nil) // Allow the worker to make *some* progress before we close it timeout := 10 * time.Second @@ -162,7 +164,7 @@ func TestReplaceClientWorker(t *testing.T) { } client = ctor(countingPublishFn) - makeClientWorker(nilObserver, wqu, client) + makeClientWorker(nilObserver, wqu, client, nil) wg.Wait() // Make sure that all events have eventually been published @@ -178,3 +180,52 @@ func TestReplaceClientWorker(t *testing.T) { }) } } + +func TestMakeClientTracer(t *testing.T) { + seedPRNG(t) + + numBatches := 10 + numEvents := atomic.MakeUint(0) + + wqu := makeWorkQueue() + retryer := newRetryer(logp.NewLogger("test"), nilObserver, wqu, nil) + defer retryer.close() + + var published atomic.Uint + publishFn := func(batch publisher.Batch) error { + published.Add(uint(len(batch.Events()))) + return nil + } + + client := newMockNetworkClient(publishFn) + + recorder := apmtest.NewRecordingTracer() + defer recorder.Close() + + worker := makeClientWorker(nilObserver, wqu, client, recorder.Tracer) + defer worker.Close() + + for i := 0; i < numBatches; i++ { + batch := randomBatch(10, 15).withRetryer(retryer) + numEvents.Add(uint(len(batch.Events()))) + wqu <- batch + } + + // Give some time for events to be published + timeout := 10 * time.Second + + // Make sure that all events have eventually been published + matches := waitUntilTrue(timeout, func() bool { + return numEvents == published + }) + if !matches { + t.Errorf("expected %d events, got %d", numEvents, published) + } + recorder.Flush(nil) + + apmEvents := recorder.Payloads() + transactions := apmEvents.Transactions + if len(transactions) != numBatches { + t.Errorf("expected %d traces, got %d", numBatches, len(transactions)) + } +} diff --git a/libbeat/publisher/pipeline/stress/out.go b/libbeat/publisher/pipeline/stress/out.go index 692d62f98ab..00afb1ac74e 100644 --- a/libbeat/publisher/pipeline/stress/out.go +++ b/libbeat/publisher/pipeline/stress/out.go @@ -18,6 +18,7 @@ package stress import ( + "context" "math/rand" "time" @@ -70,7 +71,7 @@ func makeTestOutput(_ outputs.IndexManager, beat beat.Info, observer outputs.Obs func (*testOutput) Close() error { return nil } -func (t *testOutput) Publish(batch publisher.Batch) error { +func (t *testOutput) Publish(_ context.Context, batch publisher.Batch) error { config := &t.config n := len(batch.Events()) diff --git a/libbeat/publisher/pipeline/testing.go b/libbeat/publisher/pipeline/testing.go index 0db2780ba56..5534c0ce3b4 100644 --- a/libbeat/publisher/pipeline/testing.go +++ b/libbeat/publisher/pipeline/testing.go @@ -18,6 +18,7 @@ package pipeline import ( + "context" "flag" "math/rand" "sync" @@ -45,7 +46,7 @@ type mockClient struct { func (c *mockClient) String() string { return "mock_client" } func (c *mockClient) Close() error { return nil } -func (c *mockClient) Publish(batch publisher.Batch) error { +func (c *mockClient) Publish(_ context.Context, batch publisher.Batch) error { return c.publishFn(batch) } diff --git a/vendor/github.com/armon/go-radix/.gitignore b/vendor/github.com/armon/go-radix/.gitignore new file mode 100644 index 00000000000..00268614f04 --- /dev/null +++ b/vendor/github.com/armon/go-radix/.gitignore @@ -0,0 +1,22 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe diff --git a/vendor/github.com/armon/go-radix/.travis.yml b/vendor/github.com/armon/go-radix/.travis.yml new file mode 100644 index 00000000000..1a0bbea6c77 --- /dev/null +++ b/vendor/github.com/armon/go-radix/.travis.yml @@ -0,0 +1,3 @@ +language: go +go: + - tip diff --git a/vendor/github.com/armon/go-radix/LICENSE b/vendor/github.com/armon/go-radix/LICENSE new file mode 100644 index 00000000000..a5df10e675d --- /dev/null +++ b/vendor/github.com/armon/go-radix/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014 Armon Dadgar + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/armon/go-radix/README.md b/vendor/github.com/armon/go-radix/README.md new file mode 100644 index 00000000000..26f42a2837c --- /dev/null +++ b/vendor/github.com/armon/go-radix/README.md @@ -0,0 +1,38 @@ +go-radix [![Build Status](https://travis-ci.org/armon/go-radix.png)](https://travis-ci.org/armon/go-radix) +========= + +Provides the `radix` package that implements a [radix tree](http://en.wikipedia.org/wiki/Radix_tree). +The package only provides a single `Tree` implementation, optimized for sparse nodes. + +As a radix tree, it provides the following: + * O(k) operations. In many cases, this can be faster than a hash table since + the hash function is an O(k) operation, and hash tables have very poor cache locality. + * Minimum / Maximum value lookups + * Ordered iteration + +For an immutable variant, see [go-immutable-radix](https://github.com/hashicorp/go-immutable-radix). + +Documentation +============= + +The full documentation is available on [Godoc](http://godoc.org/github.com/armon/go-radix). + +Example +======= + +Below is a simple example of usage + +```go +// Create a tree +r := radix.New() +r.Insert("foo", 1) +r.Insert("bar", 2) +r.Insert("foobar", 2) + +// Find the longest prefix match +m, _, _ := r.LongestPrefix("foozip") +if m != "foo" { + panic("should be foo") +} +``` + diff --git a/vendor/github.com/armon/go-radix/go.mod b/vendor/github.com/armon/go-radix/go.mod new file mode 100644 index 00000000000..4336aa29ea2 --- /dev/null +++ b/vendor/github.com/armon/go-radix/go.mod @@ -0,0 +1 @@ +module github.com/armon/go-radix diff --git a/vendor/github.com/armon/go-radix/radix.go b/vendor/github.com/armon/go-radix/radix.go new file mode 100644 index 00000000000..e2bb22eb91d --- /dev/null +++ b/vendor/github.com/armon/go-radix/radix.go @@ -0,0 +1,540 @@ +package radix + +import ( + "sort" + "strings" +) + +// WalkFn is used when walking the tree. Takes a +// key and value, returning if iteration should +// be terminated. +type WalkFn func(s string, v interface{}) bool + +// leafNode is used to represent a value +type leafNode struct { + key string + val interface{} +} + +// edge is used to represent an edge node +type edge struct { + label byte + node *node +} + +type node struct { + // leaf is used to store possible leaf + leaf *leafNode + + // prefix is the common prefix we ignore + prefix string + + // Edges should be stored in-order for iteration. + // We avoid a fully materialized slice to save memory, + // since in most cases we expect to be sparse + edges edges +} + +func (n *node) isLeaf() bool { + return n.leaf != nil +} + +func (n *node) addEdge(e edge) { + n.edges = append(n.edges, e) + n.edges.Sort() +} + +func (n *node) updateEdge(label byte, node *node) { + num := len(n.edges) + idx := sort.Search(num, func(i int) bool { + return n.edges[i].label >= label + }) + if idx < num && n.edges[idx].label == label { + n.edges[idx].node = node + return + } + panic("replacing missing edge") +} + +func (n *node) getEdge(label byte) *node { + num := len(n.edges) + idx := sort.Search(num, func(i int) bool { + return n.edges[i].label >= label + }) + if idx < num && n.edges[idx].label == label { + return n.edges[idx].node + } + return nil +} + +func (n *node) delEdge(label byte) { + num := len(n.edges) + idx := sort.Search(num, func(i int) bool { + return n.edges[i].label >= label + }) + if idx < num && n.edges[idx].label == label { + copy(n.edges[idx:], n.edges[idx+1:]) + n.edges[len(n.edges)-1] = edge{} + n.edges = n.edges[:len(n.edges)-1] + } +} + +type edges []edge + +func (e edges) Len() int { + return len(e) +} + +func (e edges) Less(i, j int) bool { + return e[i].label < e[j].label +} + +func (e edges) Swap(i, j int) { + e[i], e[j] = e[j], e[i] +} + +func (e edges) Sort() { + sort.Sort(e) +} + +// Tree implements a radix tree. This can be treated as a +// Dictionary abstract data type. The main advantage over +// a standard hash map is prefix-based lookups and +// ordered iteration, +type Tree struct { + root *node + size int +} + +// New returns an empty Tree +func New() *Tree { + return NewFromMap(nil) +} + +// NewFromMap returns a new tree containing the keys +// from an existing map +func NewFromMap(m map[string]interface{}) *Tree { + t := &Tree{root: &node{}} + for k, v := range m { + t.Insert(k, v) + } + return t +} + +// Len is used to return the number of elements in the tree +func (t *Tree) Len() int { + return t.size +} + +// longestPrefix finds the length of the shared prefix +// of two strings +func longestPrefix(k1, k2 string) int { + max := len(k1) + if l := len(k2); l < max { + max = l + } + var i int + for i = 0; i < max; i++ { + if k1[i] != k2[i] { + break + } + } + return i +} + +// Insert is used to add a newentry or update +// an existing entry. Returns if updated. +func (t *Tree) Insert(s string, v interface{}) (interface{}, bool) { + var parent *node + n := t.root + search := s + for { + // Handle key exhaution + if len(search) == 0 { + if n.isLeaf() { + old := n.leaf.val + n.leaf.val = v + return old, true + } + + n.leaf = &leafNode{ + key: s, + val: v, + } + t.size++ + return nil, false + } + + // Look for the edge + parent = n + n = n.getEdge(search[0]) + + // No edge, create one + if n == nil { + e := edge{ + label: search[0], + node: &node{ + leaf: &leafNode{ + key: s, + val: v, + }, + prefix: search, + }, + } + parent.addEdge(e) + t.size++ + return nil, false + } + + // Determine longest prefix of the search key on match + commonPrefix := longestPrefix(search, n.prefix) + if commonPrefix == len(n.prefix) { + search = search[commonPrefix:] + continue + } + + // Split the node + t.size++ + child := &node{ + prefix: search[:commonPrefix], + } + parent.updateEdge(search[0], child) + + // Restore the existing node + child.addEdge(edge{ + label: n.prefix[commonPrefix], + node: n, + }) + n.prefix = n.prefix[commonPrefix:] + + // Create a new leaf node + leaf := &leafNode{ + key: s, + val: v, + } + + // If the new key is a subset, add to to this node + search = search[commonPrefix:] + if len(search) == 0 { + child.leaf = leaf + return nil, false + } + + // Create a new edge for the node + child.addEdge(edge{ + label: search[0], + node: &node{ + leaf: leaf, + prefix: search, + }, + }) + return nil, false + } +} + +// Delete is used to delete a key, returning the previous +// value and if it was deleted +func (t *Tree) Delete(s string) (interface{}, bool) { + var parent *node + var label byte + n := t.root + search := s + for { + // Check for key exhaution + if len(search) == 0 { + if !n.isLeaf() { + break + } + goto DELETE + } + + // Look for an edge + parent = n + label = search[0] + n = n.getEdge(label) + if n == nil { + break + } + + // Consume the search prefix + if strings.HasPrefix(search, n.prefix) { + search = search[len(n.prefix):] + } else { + break + } + } + return nil, false + +DELETE: + // Delete the leaf + leaf := n.leaf + n.leaf = nil + t.size-- + + // Check if we should delete this node from the parent + if parent != nil && len(n.edges) == 0 { + parent.delEdge(label) + } + + // Check if we should merge this node + if n != t.root && len(n.edges) == 1 { + n.mergeChild() + } + + // Check if we should merge the parent's other child + if parent != nil && parent != t.root && len(parent.edges) == 1 && !parent.isLeaf() { + parent.mergeChild() + } + + return leaf.val, true +} + +// DeletePrefix is used to delete the subtree under a prefix +// Returns how many nodes were deleted +// Use this to delete large subtrees efficiently +func (t *Tree) DeletePrefix(s string) int { + return t.deletePrefix(nil, t.root, s) +} + +// delete does a recursive deletion +func (t *Tree) deletePrefix(parent, n *node, prefix string) int { + // Check for key exhaustion + if len(prefix) == 0 { + // Remove the leaf node + subTreeSize := 0 + //recursively walk from all edges of the node to be deleted + recursiveWalk(n, func(s string, v interface{}) bool { + subTreeSize++ + return false + }) + if n.isLeaf() { + n.leaf = nil + } + n.edges = nil // deletes the entire subtree + + // Check if we should merge the parent's other child + if parent != nil && parent != t.root && len(parent.edges) == 1 && !parent.isLeaf() { + parent.mergeChild() + } + t.size -= subTreeSize + return subTreeSize + } + + // Look for an edge + label := prefix[0] + child := n.getEdge(label) + if child == nil || (!strings.HasPrefix(child.prefix, prefix) && !strings.HasPrefix(prefix, child.prefix)) { + return 0 + } + + // Consume the search prefix + if len(child.prefix) > len(prefix) { + prefix = prefix[len(prefix):] + } else { + prefix = prefix[len(child.prefix):] + } + return t.deletePrefix(n, child, prefix) +} + +func (n *node) mergeChild() { + e := n.edges[0] + child := e.node + n.prefix = n.prefix + child.prefix + n.leaf = child.leaf + n.edges = child.edges +} + +// Get is used to lookup a specific key, returning +// the value and if it was found +func (t *Tree) Get(s string) (interface{}, bool) { + n := t.root + search := s + for { + // Check for key exhaution + if len(search) == 0 { + if n.isLeaf() { + return n.leaf.val, true + } + break + } + + // Look for an edge + n = n.getEdge(search[0]) + if n == nil { + break + } + + // Consume the search prefix + if strings.HasPrefix(search, n.prefix) { + search = search[len(n.prefix):] + } else { + break + } + } + return nil, false +} + +// LongestPrefix is like Get, but instead of an +// exact match, it will return the longest prefix match. +func (t *Tree) LongestPrefix(s string) (string, interface{}, bool) { + var last *leafNode + n := t.root + search := s + for { + // Look for a leaf node + if n.isLeaf() { + last = n.leaf + } + + // Check for key exhaution + if len(search) == 0 { + break + } + + // Look for an edge + n = n.getEdge(search[0]) + if n == nil { + break + } + + // Consume the search prefix + if strings.HasPrefix(search, n.prefix) { + search = search[len(n.prefix):] + } else { + break + } + } + if last != nil { + return last.key, last.val, true + } + return "", nil, false +} + +// Minimum is used to return the minimum value in the tree +func (t *Tree) Minimum() (string, interface{}, bool) { + n := t.root + for { + if n.isLeaf() { + return n.leaf.key, n.leaf.val, true + } + if len(n.edges) > 0 { + n = n.edges[0].node + } else { + break + } + } + return "", nil, false +} + +// Maximum is used to return the maximum value in the tree +func (t *Tree) Maximum() (string, interface{}, bool) { + n := t.root + for { + if num := len(n.edges); num > 0 { + n = n.edges[num-1].node + continue + } + if n.isLeaf() { + return n.leaf.key, n.leaf.val, true + } + break + } + return "", nil, false +} + +// Walk is used to walk the tree +func (t *Tree) Walk(fn WalkFn) { + recursiveWalk(t.root, fn) +} + +// WalkPrefix is used to walk the tree under a prefix +func (t *Tree) WalkPrefix(prefix string, fn WalkFn) { + n := t.root + search := prefix + for { + // Check for key exhaution + if len(search) == 0 { + recursiveWalk(n, fn) + return + } + + // Look for an edge + n = n.getEdge(search[0]) + if n == nil { + break + } + + // Consume the search prefix + if strings.HasPrefix(search, n.prefix) { + search = search[len(n.prefix):] + + } else if strings.HasPrefix(n.prefix, search) { + // Child may be under our search prefix + recursiveWalk(n, fn) + return + } else { + break + } + } + +} + +// WalkPath is used to walk the tree, but only visiting nodes +// from the root down to a given leaf. Where WalkPrefix walks +// all the entries *under* the given prefix, this walks the +// entries *above* the given prefix. +func (t *Tree) WalkPath(path string, fn WalkFn) { + n := t.root + search := path + for { + // Visit the leaf values if any + if n.leaf != nil && fn(n.leaf.key, n.leaf.val) { + return + } + + // Check for key exhaution + if len(search) == 0 { + return + } + + // Look for an edge + n = n.getEdge(search[0]) + if n == nil { + return + } + + // Consume the search prefix + if strings.HasPrefix(search, n.prefix) { + search = search[len(n.prefix):] + } else { + break + } + } +} + +// recursiveWalk is used to do a pre-order walk of a node +// recursively. Returns true if the walk should be aborted +func recursiveWalk(n *node, fn WalkFn) bool { + // Visit the leaf values if any + if n.leaf != nil && fn(n.leaf.key, n.leaf.val) { + return true + } + + // Recurse on the children + for _, e := range n.edges { + if recursiveWalk(e.node, fn) { + return true + } + } + return false +} + +// ToMap is used to walk the tree and convert it into a map +func (t *Tree) ToMap() map[string]interface{} { + out := make(map[string]interface{}, t.size) + t.Walk(func(k string, v interface{}) bool { + out[k] = v + return false + }) + return out +} diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/.travis.yml b/vendor/github.com/santhosh-tekuri/jsonschema/.travis.yml new file mode 100644 index 00000000000..1ab35ab1b1f --- /dev/null +++ b/vendor/github.com/santhosh-tekuri/jsonschema/.travis.yml @@ -0,0 +1,10 @@ +language: go + +go: + - 1.8.1 + +script: + - ./go.test.sh + +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/LICENSE b/vendor/github.com/santhosh-tekuri/jsonschema/LICENSE new file mode 100644 index 00000000000..65cd403ab0e --- /dev/null +++ b/vendor/github.com/santhosh-tekuri/jsonschema/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2017 Santhosh Kumar Tekuri. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/README.md b/vendor/github.com/santhosh-tekuri/jsonschema/README.md new file mode 100644 index 00000000000..3d369d71d74 --- /dev/null +++ b/vendor/github.com/santhosh-tekuri/jsonschema/README.md @@ -0,0 +1,148 @@ +# jsonschema + +[![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) +[![GoDoc](https://godoc.org/github.com/santhosh-tekuri/jsonschema?status.svg)](https://godoc.org/github.com/santhosh-tekuri/jsonschema) +[![Go Report Card](https://goreportcard.com/badge/github.com/santhosh-tekuri/jsonschema)](https://goreportcard.com/report/github.com/santhosh-tekuri/jsonschema) +[![Build Status](https://travis-ci.org/santhosh-tekuri/jsonschema.svg?branch=master)](https://travis-ci.org/santhosh-tekuri/jsonschema) +[![codecov.io](https://codecov.io/github/santhosh-tekuri/jsonschema/coverage.svg?branch=master)](https://codecov.io/github/santhosh-tekuri/jsonschema?branch=master) + +Package jsonschema provides json-schema compilation and validation. + +This implementation of JSON Schema, supports draft4, draft6 and draft7. + +Passes all tests(including optional) in https://github.com/json-schema/JSON-Schema-Test-Suite + +An example of using this package: + +```go +schema, err := jsonschema.Compile("schemas/purchaseOrder.json") +if err != nil { + return err +} +f, err := os.Open("purchaseOrder.json") +if err != nil { + return err +} +defer f.Close() +if err = schema.Validate(f); err != nil { + return err +} +``` + +The schema is compiled against the version specified in `$schema` property. +If `$schema` property is missing, it uses latest draft which currently is draft7. +You can force to use draft4 when `$schema` is missing, as follows: + +```go +compiler := jsonschema.NewCompiler() +compler.Draft = jsonschema.Draft4 +``` + +you can also validate go value using `schema.ValidateInterface(interface{})` method. +but the argument should not be user-defined struct. + + +This package supports loading json-schema from filePath and fileURL. + +To load json-schema from HTTPURL, add following import: + +```go +import _ "github.com/santhosh-tekuri/jsonschema/httploader" +``` + +Loading from urls for other schemes (such as ftp), can be plugged in. see package jsonschema/httploader +for an example + +To load json-schema from in-memory: + +```go +data := `{"type": "string"}` +url := "sch.json" +compiler := jsonschema.NewCompiler() +if err := compiler.AddResource(url, strings.NewReader(data)); err != nil { + return err +} +schema, err := compiler.Compile(url) +if err != nil { + return err +} +f, err := os.Open("doc.json") +if err != nil { + return err +} +defer f.Close() +if err = schema.Validate(f); err != nil { + return err +} +``` + +This package supports json string formats: +- date-time +- date +- time +- hostname +- email +- ip-address +- ipv4 +- ipv6 +- uri +- uriref/uri-reference +- regex +- format +- json-pointer +- relative-json-pointer +- uri-template (limited validation) + +Developers can register their own formats using package "github.com/santhosh-tekuri/jsonschema/formats". + +"base64" contentEncoding is supported. Custom decoders can be registered using package "github.com/santhosh-tekuri/jsonschema/decoders". + +"application/json" contentMediaType is supported. Custom mediatypes can be registered using package "github.com/santhosh-tekuri/jsonschema/mediatypes". + +## ValidationError + +The ValidationError returned by Validate method contains detailed context to understand why and where the error is. + +schema.json: +```json +{ + "$ref": "t.json#/definitions/employee" +} +``` + +t.json: +```json +{ + "definitions": { + "employee": { + "type": "string" + } + } +} +``` + +doc.json: +```json +1 +``` + +Validating `doc.json` with `schema.json`, gives following ValidationError: +``` +I[#] S[#] doesn't validate with "schema.json#" + I[#] S[#/$ref] doesn't valide with "t.json#/definitions/employee" + I[#] S[#/definitions/employee/type] expected string, but got number +``` + +Here `I` stands for instance document and `S` stands for schema document. +The json-fragments that caused error in instance and schema documents are represented using json-pointer notation. +Nested causes are printed with indent. + +## CLI + +```bash +jv []... +``` + +if no `` arguments are passed, it simply validates the ``. + +exit-code is 1, if there are any validation errors diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/compiler.go b/vendor/github.com/santhosh-tekuri/jsonschema/compiler.go new file mode 100644 index 00000000000..9ffb715ab05 --- /dev/null +++ b/vendor/github.com/santhosh-tekuri/jsonschema/compiler.go @@ -0,0 +1,534 @@ +// Copyright 2017 Santhosh Kumar Tekuri. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package jsonschema + +import ( + "encoding/json" + "fmt" + "io" + "math/big" + "regexp" + "strings" + + "github.com/santhosh-tekuri/jsonschema/decoders" + "github.com/santhosh-tekuri/jsonschema/formats" + "github.com/santhosh-tekuri/jsonschema/loader" + "github.com/santhosh-tekuri/jsonschema/mediatypes" +) + +func init() { + formats.Register("encoding", func(s string) bool { + _, ok := decoders.Get(s) + return ok + }) + formats.Register("mediatype", func(s string) bool { + _, ok := mediatypes.Get(s) + return ok + }) +} + +// A Draft represents json-schema draft +type Draft struct { + meta *Schema + id string // property name used to represent schema id. + version int +} + +var latest = Draft7 + +func (draft *Draft) validateSchema(url, ptr string, v interface{}) error { + if meta := draft.meta; meta != nil { + if err := meta.validate(v); err != nil { + addContext(ptr, "", err) + finishSchemaContext(err, meta) + finishInstanceContext(err) + var instancePtr string + if ptr == "" { + instancePtr = "#" + } else { + instancePtr = "#/" + ptr + } + return &SchemaError{ + url, + &ValidationError{ + Message: fmt.Sprintf("doesn't validate with %q", meta.URL+meta.Ptr), + InstancePtr: instancePtr, + SchemaURL: meta.URL, + SchemaPtr: "#", + Causes: []*ValidationError{err.(*ValidationError)}, + }, + } + } + } + return nil +} + +// A Compiler represents a json-schema compiler. +// +// Currently draft4, draft6 and draft7 are supported +type Compiler struct { + // Draft represents the draft used when '$schema' attribute is missing. + // + // This defaults to latest draft (currently draft7). + Draft *Draft + resources map[string]*resource + + // ExtractAnnotations tells whether schema annotations has to be extracted + // in compiled Schema or not. + ExtractAnnotations bool +} + +// NewCompiler returns a draft7 json-schema Compiler object. +func NewCompiler() *Compiler { + return &Compiler{Draft: latest, resources: make(map[string]*resource)} +} + +// AddResource adds in-memory resource to the compiler. +// +// Note that url must not have fragment +func (c *Compiler) AddResource(url string, r io.Reader) error { + res, err := newResource(url, r) + if err != nil { + return err + } + c.resources[res.url] = res + return nil +} + +// MustCompile is like Compile but panics if the url cannot be compiled to *Schema. +// It simplifies safe initialization of global variables holding compiled Schemas. +func (c *Compiler) MustCompile(url string) *Schema { + s, err := c.Compile(url) + if err != nil { + panic(fmt.Sprintf("jsonschema: Compile(%q): %s", url, err)) + } + return s +} + +// Compile parses json-schema at given url returns, if successful, +// a Schema object that can be used to match against json. +func (c *Compiler) Compile(url string) (*Schema, error) { + base, fragment := split(url) + if _, ok := c.resources[base]; !ok { + r, err := loader.Load(base) + if err != nil { + return nil, err + } + defer r.Close() + if err := c.AddResource(base, r); err != nil { + return nil, err + } + } + r := c.resources[base] + if r.draft == nil { + if m, ok := r.doc.(map[string]interface{}); ok { + if url, ok := m["$schema"]; ok { + switch url { + case "http://json-schema.org/schema#": + r.draft = latest + case "http://json-schema.org/draft-07/schema#": + r.draft = Draft7 + case "http://json-schema.org/draft-06/schema#": + r.draft = Draft6 + case "http://json-schema.org/draft-04/schema#": + r.draft = Draft4 + default: + return nil, fmt.Errorf("unknown $schema %q", url) + } + } + } + if r.draft == nil { + r.draft = c.Draft + } + } + return c.compileRef(r, r.url, fragment) +} + +func (c Compiler) compileRef(r *resource, base, ref string) (*Schema, error) { + var err error + if rootFragment(ref) { + if _, ok := r.schemas["#"]; !ok { + if err := r.draft.validateSchema(r.url, "", r.doc); err != nil { + return nil, err + } + s := &Schema{URL: r.url, Ptr: "#"} + r.schemas["#"] = s + if m, ok := r.doc.(map[string]interface{}); ok { + if _, err := c.compile(r, s, base, m); err != nil { + return nil, err + } + } else { + if _, err := c.compile(r, s, base, r.doc); err != nil { + return nil, err + } + } + } + return r.schemas["#"], nil + } + + if strings.HasPrefix(ref, "#/") { + if _, ok := r.schemas[ref]; !ok { + ptrBase, doc, err := r.resolvePtr(ref) + if err != nil { + return nil, err + } + if err := r.draft.validateSchema(r.url, strings.TrimPrefix(ref, "#/"), doc); err != nil { + return nil, err + } + r.schemas[ref] = &Schema{URL: base, Ptr: ref} + if _, err := c.compile(r, r.schemas[ref], ptrBase, doc); err != nil { + return nil, err + } + } + return r.schemas[ref], nil + } + + refURL, err := resolveURL(base, ref) + if err != nil { + return nil, err + } + if rs, ok := r.schemas[refURL]; ok { + return rs, nil + } + + ids := make(map[string]map[string]interface{}) + if err := resolveIDs(r.draft, r.url, r.doc, ids); err != nil { + return nil, err + } + if v, ok := ids[refURL]; ok { + if err := r.draft.validateSchema(r.url, "", v); err != nil { + return nil, err + } + u, f := split(refURL) + s := &Schema{URL: u, Ptr: f} + r.schemas[refURL] = s + if err := c.compileMap(r, s, refURL, v); err != nil { + return nil, err + } + return s, nil + } + + base, _ = split(refURL) + if base == r.url { + return nil, fmt.Errorf("invalid ref: %q", refURL) + } + return c.Compile(refURL) +} + +func (c Compiler) compile(r *resource, s *Schema, base string, m interface{}) (*Schema, error) { + if s == nil { + s = new(Schema) + s.URL, _ = split(base) + } + switch m := m.(type) { + case bool: + s.Always = &m + return s, nil + default: + return s, c.compileMap(r, s, base, m.(map[string]interface{})) + } +} + +func (c Compiler) compileMap(r *resource, s *Schema, base string, m map[string]interface{}) error { + var err error + + if id, ok := m[r.draft.id]; ok { + if base, err = resolveURL(base, id.(string)); err != nil { + return err + } + } + + if ref, ok := m["$ref"]; ok { + b, _ := split(base) + s.Ref, err = c.compileRef(r, b, ref.(string)) + if err != nil { + return err + } + // All other properties in a "$ref" object MUST be ignored + return nil + } + + if t, ok := m["type"]; ok { + switch t := t.(type) { + case string: + s.Types = []string{t} + case []interface{}: + s.Types = toStrings(t) + } + } + + if e, ok := m["enum"]; ok { + s.Enum = e.([]interface{}) + allPrimitives := true + for _, item := range s.Enum { + switch jsonType(item) { + case "object", "array": + allPrimitives = false + break + } + } + s.enumError = "enum failed" + if allPrimitives { + if len(s.Enum) == 1 { + s.enumError = fmt.Sprintf("value must be %#v", s.Enum[0]) + } else { + strEnum := make([]string, len(s.Enum)) + for i, item := range s.Enum { + strEnum[i] = fmt.Sprintf("%#v", item) + } + s.enumError = fmt.Sprintf("value must be one of %s", strings.Join(strEnum, ", ")) + } + } + } + + loadSchema := func(pname string) (*Schema, error) { + if pvalue, ok := m[pname]; ok { + return c.compile(r, nil, base, pvalue) + } + return nil, nil + } + + if s.Not, err = loadSchema("not"); err != nil { + return err + } + + loadSchemas := func(pname string) ([]*Schema, error) { + if pvalue, ok := m[pname]; ok { + pvalue := pvalue.([]interface{}) + schemas := make([]*Schema, len(pvalue)) + for i, v := range pvalue { + sch, err := c.compile(r, nil, base, v) + if err != nil { + return nil, err + } + schemas[i] = sch + } + return schemas, nil + } + return nil, nil + } + if s.AllOf, err = loadSchemas("allOf"); err != nil { + return err + } + if s.AnyOf, err = loadSchemas("anyOf"); err != nil { + return err + } + if s.OneOf, err = loadSchemas("oneOf"); err != nil { + return err + } + + loadInt := func(pname string) int { + if num, ok := m[pname]; ok { + i, _ := num.(json.Number).Int64() + return int(i) + } + return -1 + } + s.MinProperties, s.MaxProperties = loadInt("minProperties"), loadInt("maxProperties") + + if req, ok := m["required"]; ok { + s.Required = toStrings(req.([]interface{})) + } + + if props, ok := m["properties"]; ok { + props := props.(map[string]interface{}) + s.Properties = make(map[string]*Schema, len(props)) + for pname, pmap := range props { + s.Properties[pname], err = c.compile(r, nil, base, pmap) + if err != nil { + return err + } + } + } + + if regexProps, ok := m["regexProperties"]; ok { + s.RegexProperties = regexProps.(bool) + } + + if patternProps, ok := m["patternProperties"]; ok { + patternProps := patternProps.(map[string]interface{}) + s.PatternProperties = make(map[*regexp.Regexp]*Schema, len(patternProps)) + for pattern, pmap := range patternProps { + s.PatternProperties[regexp.MustCompile(pattern)], err = c.compile(r, nil, base, pmap) + if err != nil { + return err + } + } + } + + if additionalProps, ok := m["additionalProperties"]; ok { + switch additionalProps := additionalProps.(type) { + case bool: + if !additionalProps { + s.AdditionalProperties = false + } + case map[string]interface{}: + s.AdditionalProperties, err = c.compile(r, nil, base, additionalProps) + if err != nil { + return err + } + } + } + + if deps, ok := m["dependencies"]; ok { + deps := deps.(map[string]interface{}) + s.Dependencies = make(map[string]interface{}, len(deps)) + for pname, pvalue := range deps { + switch pvalue := pvalue.(type) { + case []interface{}: + s.Dependencies[pname] = toStrings(pvalue) + default: + s.Dependencies[pname], err = c.compile(r, nil, base, pvalue) + if err != nil { + return err + } + } + } + } + + s.MinItems, s.MaxItems = loadInt("minItems"), loadInt("maxItems") + + if unique, ok := m["uniqueItems"]; ok { + s.UniqueItems = unique.(bool) + } + + if items, ok := m["items"]; ok { + switch items := items.(type) { + case []interface{}: + s.Items, err = loadSchemas("items") + if err != nil { + return err + } + if additionalItems, ok := m["additionalItems"]; ok { + switch additionalItems := additionalItems.(type) { + case bool: + s.AdditionalItems = additionalItems + case map[string]interface{}: + s.AdditionalItems, err = c.compile(r, nil, base, additionalItems) + if err != nil { + return err + } + } + } else { + s.AdditionalItems = true + } + default: + s.Items, err = c.compile(r, nil, base, items) + if err != nil { + return err + } + } + } + + s.MinLength, s.MaxLength = loadInt("minLength"), loadInt("maxLength") + + if pattern, ok := m["pattern"]; ok { + s.Pattern = regexp.MustCompile(pattern.(string)) + } + + if format, ok := m["format"]; ok { + s.FormatName = format.(string) + s.Format, _ = formats.Get(s.FormatName) + } + + loadFloat := func(pname string) *big.Float { + if num, ok := m[pname]; ok { + r, _ := new(big.Float).SetString(string(num.(json.Number))) + return r + } + return nil + } + + s.Minimum = loadFloat("minimum") + if exclusive, ok := m["exclusiveMinimum"]; ok { + if exclusive, ok := exclusive.(bool); ok { + if exclusive { + s.Minimum, s.ExclusiveMinimum = nil, s.Minimum + } + } else { + s.ExclusiveMinimum = loadFloat("exclusiveMinimum") + } + } + + s.Maximum = loadFloat("maximum") + if exclusive, ok := m["exclusiveMaximum"]; ok { + if exclusive, ok := exclusive.(bool); ok { + if exclusive { + s.Maximum, s.ExclusiveMaximum = nil, s.Maximum + } + } else { + s.ExclusiveMaximum = loadFloat("exclusiveMaximum") + } + } + + s.MultipleOf = loadFloat("multipleOf") + + if c.ExtractAnnotations { + if title, ok := m["title"]; ok { + s.Title = title.(string) + } + if description, ok := m["description"]; ok { + s.Description = description.(string) + } + s.Default = m["default"] + } + + if r.draft.version >= 6 { + if c, ok := m["const"]; ok { + s.Constant = []interface{}{c} + } + if s.PropertyNames, err = loadSchema("propertyNames"); err != nil { + return err + } + if s.Contains, err = loadSchema("contains"); err != nil { + return err + } + } + + if r.draft.version >= 7 { + if m["if"] != nil && (m["then"] != nil || m["else"] != nil) { + if s.If, err = loadSchema("if"); err != nil { + return err + } + if s.Then, err = loadSchema("then"); err != nil { + return err + } + if s.Else, err = loadSchema("else"); err != nil { + return err + } + + if c.ExtractAnnotations { + if readOnly, ok := m["readOnly"]; ok { + s.ReadOnly = readOnly.(bool) + } + if writeOnly, ok := m["writeOnly"]; ok { + s.WriteOnly = writeOnly.(bool) + } + if examples, ok := m["examples"]; ok { + s.Examples = examples.([]interface{}) + } + } + } + + if encoding, ok := m["contentEncoding"]; ok { + s.ContentEncoding = encoding.(string) + s.Decoder, _ = decoders.Get(s.ContentEncoding) + } + if mediaType, ok := m["contentMediaType"]; ok { + s.ContentMediaType = mediaType.(string) + s.MediaType, _ = mediatypes.Get(s.ContentMediaType) + } + } + + return nil +} + +func toStrings(arr []interface{}) []string { + s := make([]string, len(arr)) + for i, v := range arr { + s[i] = v.(string) + } + return s +} diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/decoders/decoders.go b/vendor/github.com/santhosh-tekuri/jsonschema/decoders/decoders.go new file mode 100644 index 00000000000..4a1edc43c57 --- /dev/null +++ b/vendor/github.com/santhosh-tekuri/jsonschema/decoders/decoders.go @@ -0,0 +1,32 @@ +// Copyright 2017 Santhosh Kumar Tekuri. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package decoders provides functions to decode encoded-string. +// +// It allows developers to register custom encodings, that can be used +// in json-schema for validation. +package decoders + +import ( + "encoding/base64" +) + +// The Decoder type is a function, that returns +// the bytes represented by encoded string. +type Decoder func(string) ([]byte, error) + +var decoders = map[string]Decoder{ + "base64": base64.StdEncoding.DecodeString, +} + +// Register registers Decoder object for given encoding. +func Register(name string, d Decoder) { + decoders[name] = d +} + +// Get returns Decoder object for given encoding, if found. +func Get(name string) (Decoder, bool) { + d, ok := decoders[name] + return d, ok +} diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/doc.go b/vendor/github.com/santhosh-tekuri/jsonschema/doc.go new file mode 100644 index 00000000000..ea444425313 --- /dev/null +++ b/vendor/github.com/santhosh-tekuri/jsonschema/doc.go @@ -0,0 +1,77 @@ +// Copyright 2017 Santhosh Kumar Tekuri. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Package jsonschema provides json-schema compilation and validation. + +This implementation of JSON Schema, supports draft4, draft6 and draft7. +Passes all tests(including optional) in https://github.com/json-schema/JSON-Schema-Test-Suite + +An example of using this package: + + schema, err := jsonschema.Compile("schemas/purchaseOrder.json") + if err != nil { + return err + } + f, err := os.Open("purchaseOrder.json") + if err != nil { + return err + } + defer f.Close() + if err = schema.Validate(f); err != nil { + return err + } + +The schema is compiled against the version specified in `$schema` property. +If `$schema` property is missing, it uses latest draft which currently is draft7. +You can force to use draft4 when `$schema` is missing, as follows: + + compiler := jsonschema.NewCompiler() + compler.Draft = jsonschema.Draft4 + +you can also validate go value using schema.ValidateInterface(interface{}) method. +but the argument should not be user-defined struct. + +This package supports loading json-schema from filePath and fileURL. + +To load json-schema from HTTPURL, add following import: + + import _ "github.com/santhosh-tekuri/jsonschema/httploader" + +Loading from urls for other schemes (such as ftp), can be plugged in. see package jsonschema/httploader +for an example + +To load json-schema from in-memory: + + data := `{"type": "string"}` + url := "sch.json" + compiler := jsonschema.NewCompiler() + if err := compiler.AddResource(url, strings.NewReader(data)); err != nil { + return err + } + schema, err := compiler.Compile(url) + if err != nil { + return err + } + f, err := os.Open("doc.json") + if err != nil { + return err + } + defer f.Close() + if err = schema.Validate(f); err != nil { + return err + } + +This package supports json string formats: date-time, date, time, hostname, email, ip-address, ipv4, ipv6, uri, uriref, regex, +format, json-pointer, relative-json-pointer, uri-template (limited validation). Developers can register their own formats using +package "github.com/santhosh-tekuri/jsonschema/formats". + +"base64" contentEncoding is supported. Custom decoders can be registered using package "github.com/santhosh-tekuri/jsonschema/decoders". + +"application/json" contentMediaType is supported. Custom mediatypes can be registered using package "github.com/santhosh-tekuri/jsonschema/mediatypes". + +The ValidationError returned by Validate method contains detailed context to understand why and where the error is. + +*/ +package jsonschema diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/draft4.go b/vendor/github.com/santhosh-tekuri/jsonschema/draft4.go new file mode 100644 index 00000000000..6c787bc1a76 --- /dev/null +++ b/vendor/github.com/santhosh-tekuri/jsonschema/draft4.go @@ -0,0 +1,172 @@ +// Copyright 2017 Santhosh Kumar Tekuri. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package jsonschema + +import "strings" + +// Draft4 respresents http://json-schema.org/specification-links.html#draft-4 +var Draft4 = &Draft{id: "id", version: 4} + +func init() { + c := NewCompiler() + url := "http://json-schema.org/draft-04/schema" + err := c.AddResource(url, strings.NewReader(`{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#" } + }, + "positiveInteger": { + "type": "integer", + "minimum": 0 + }, + "positiveIntegerDefault0": { + "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ] + }, + "simpleTypes": { + "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "minItems": 1, + "uniqueItems": true + } + }, + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uriref" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": {}, + "multipleOf": { + "type": "number", + "minimum": 0, + "exclusiveMinimum": true + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "boolean", + "default": false + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "boolean", + "default": false + }, + "maxLength": { "$ref": "#/definitions/positiveInteger" }, + "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { + "anyOf": [ + { "type": "boolean" }, + { "$ref": "#" } + ], + "default": {} + }, + "items": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/schemaArray" } + ], + "default": {} + }, + "maxItems": { "$ref": "#/definitions/positiveInteger" }, + "minItems": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "maxProperties": { "$ref": "#/definitions/positiveInteger" }, + "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "required": { "$ref": "#/definitions/stringArray" }, + "additionalProperties": { + "anyOf": [ + { "type": "boolean" }, + { "$ref": "#" } + ], + "default": {} + }, + "definitions": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "properties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "regexProperties": true, + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "regexProperties": { "type": "boolean" }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/stringArray" } + ] + } + }, + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + }, + "type": { + "anyOf": [ + { "$ref": "#/definitions/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/definitions/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "allOf": { "$ref": "#/definitions/schemaArray" }, + "anyOf": { "$ref": "#/definitions/schemaArray" }, + "oneOf": { "$ref": "#/definitions/schemaArray" }, + "not": { "$ref": "#" }, + "format": { "type": "string", "format": "format" }, + "$ref": { "type": "string" } + }, + "dependencies": { + "exclusiveMaximum": [ "maximum" ], + "exclusiveMinimum": [ "minimum" ] + }, + "default": {} + }`)) + if err != nil { + panic(err) + } + Draft4.meta = c.MustCompile(url) +} diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/draft6.go b/vendor/github.com/santhosh-tekuri/jsonschema/draft6.go new file mode 100644 index 00000000000..310245d1974 --- /dev/null +++ b/vendor/github.com/santhosh-tekuri/jsonschema/draft6.go @@ -0,0 +1,170 @@ +// Copyright 2017 Santhosh Kumar Tekuri. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package jsonschema + +import "strings" + +// Draft6 respresents http://json-schema.org/specification-links.html#draft-6 +var Draft6 = &Draft{id: "$id", version: 6} + +func init() { + c := NewCompiler() + url := "http://json-schema.org/draft-06/schema" + err := c.AddResource(url, strings.NewReader(`{ + "$schema": "http://json-schema.org/draft-06/schema#", + "$id": "http://json-schema.org/draft-06/schema#", + "title": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#" } + }, + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "allOf": [ + { "$ref": "#/definitions/nonNegativeInteger" }, + { "default": 0 } + ] + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + "default": [] + } + }, + "type": ["object", "boolean"], + "properties": { + "$id": { + "type": "string", + "format": "uri-reference" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": {}, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": { "$ref": "#/definitions/nonNegativeInteger" }, + "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { "$ref": "#" }, + "items": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/schemaArray" } + ], + "default": {} + }, + "maxItems": { "$ref": "#/definitions/nonNegativeInteger" }, + "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "contains": { "$ref": "#" }, + "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" }, + "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "required": { "$ref": "#/definitions/stringArray" }, + "additionalProperties": { "$ref": "#" }, + "definitions": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "properties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "regexProperties": true, + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/stringArray" } + ] + } + }, + "propertyNames": { "$ref": "#" }, + "const": {}, + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + }, + "type": { + "anyOf": [ + { "$ref": "#/definitions/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/definitions/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "format": { "type": "string", "format": "format" }, + "allOf": { "$ref": "#/definitions/schemaArray" }, + "anyOf": { "$ref": "#/definitions/schemaArray" }, + "oneOf": { "$ref": "#/definitions/schemaArray" }, + "not": { "$ref": "#" } + }, + "default": {} + }`)) + if err != nil { + panic(err) + } + Draft6.meta = c.MustCompile(url) +} diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/draft7.go b/vendor/github.com/santhosh-tekuri/jsonschema/draft7.go new file mode 100644 index 00000000000..68c88f0a08f --- /dev/null +++ b/vendor/github.com/santhosh-tekuri/jsonschema/draft7.go @@ -0,0 +1,196 @@ +// Copyright 2017 Santhosh Kumar Tekuri. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package jsonschema + +import "strings" + +// Draft7 respresents http://json-schema.org/specification-links.html#draft-7 +var Draft7 = &Draft{id: "$id", version: 7} + +func init() { + c := NewCompiler() + url := "http://json-schema.org/draft-07/schema" + err := c.AddResource(url, strings.NewReader(`{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://json-schema.org/draft-07/schema#", + "title": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#" } + }, + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "allOf": [ + { "$ref": "#/definitions/nonNegativeInteger" }, + { "default": 0 } + ] + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + "default": [] + } + }, + "type": ["object", "boolean"], + "properties": { + "$id": { + "type": "string", + "format": "uri-reference" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "$comment": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": true, + "readOnly": { + "type": "boolean", + "default": false + }, + "examples": { + "type": "array", + "items": true + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": { "$ref": "#/definitions/nonNegativeInteger" }, + "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { "$ref": "#" }, + "items": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/schemaArray" } + ], + "default": true + }, + "maxItems": { "$ref": "#/definitions/nonNegativeInteger" }, + "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "contains": { "$ref": "#" }, + "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" }, + "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "required": { "$ref": "#/definitions/stringArray" }, + "additionalProperties": { "$ref": "#" }, + "definitions": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "properties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "propertyNames": { "format": "regex" }, + "default": {} + }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/stringArray" } + ] + } + }, + "propertyNames": { "$ref": "#" }, + "const": true, + "enum": { + "type": "array", + "items": true, + "minItems": 1, + "uniqueItems": true + }, + "type": { + "anyOf": [ + { "$ref": "#/definitions/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/definitions/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "format": { + "type": "string", + "format": "format" + }, + "contentMediaType": { + "type": "string", + "format": "mediatype" + }, + "contentEncoding": { + "type": "string", + "format": "encoding" + }, + "if": {"$ref": "#"}, + "then": {"$ref": "#"}, + "else": {"$ref": "#"}, + "allOf": { "$ref": "#/definitions/schemaArray" }, + "anyOf": { "$ref": "#/definitions/schemaArray" }, + "oneOf": { "$ref": "#/definitions/schemaArray" }, + "not": { "$ref": "#" } + }, + "default": true + }`)) + if err != nil { + panic(err) + } + Draft7.meta = c.MustCompile(url) +} diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/errors.go b/vendor/github.com/santhosh-tekuri/jsonschema/errors.go new file mode 100644 index 00000000000..4bb61a925a5 --- /dev/null +++ b/vendor/github.com/santhosh-tekuri/jsonschema/errors.go @@ -0,0 +1,122 @@ +// Copyright 2017 Santhosh Kumar Tekuri. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package jsonschema + +import ( + "fmt" + "strings" +) + +// InvalidJSONTypeError is the error type returned by ValidateInteface. +// this tells that specified go object is not valid jsonType. +type InvalidJSONTypeError string + +func (e InvalidJSONTypeError) Error() string { + return fmt.Sprintf("invalid jsonType: %s", string(e)) +} + +// SchemaError is the error type returned by Compile. +type SchemaError struct { + // SchemaURL is the url to json-schema that filed to compile. + // This is helpful, if your schema refers to external schemas + SchemaURL string + + // Err is the error that occurred during compilation. + // It could be ValidationError, because compilation validates + // given schema against the json meta-schema + Err error +} + +func (se *SchemaError) Error() string { + return fmt.Sprintf("json-schema %q compilation failed. Reason:\n%s", se.SchemaURL, se.Err) +} + +// ValidationError is the error type returned by Validate. +type ValidationError struct { + // Message describes error + Message string + + // InstancePtr is json-pointer which refers to json-fragment in json instance + // that is not valid + InstancePtr string + + // SchemaURL is the url to json-schema against which validation failed. + // This is helpful, if your schema refers to external schemas + SchemaURL string + + // SchemaPtr is json-pointer which refers to json-fragment in json schema + // that failed to satisfy + SchemaPtr string + + // Causes details the nested validation errors + Causes []*ValidationError +} + +func (ve *ValidationError) add(causes ...error) error { + for _, cause := range causes { + addContext(ve.InstancePtr, ve.SchemaPtr, cause) + ve.Causes = append(ve.Causes, cause.(*ValidationError)) + } + return ve +} + +func (ve *ValidationError) Error() string { + msg := fmt.Sprintf("I[%s] S[%s] %s", ve.InstancePtr, ve.SchemaPtr, ve.Message) + for _, c := range ve.Causes { + for _, line := range strings.Split(c.Error(), "\n") { + msg += "\n " + line + } + } + return msg +} + +func validationError(schemaPtr string, format string, a ...interface{}) *ValidationError { + return &ValidationError{fmt.Sprintf(format, a...), "", "", schemaPtr, nil} +} + +func addContext(instancePtr, schemaPtr string, err error) error { + ve := err.(*ValidationError) + ve.InstancePtr = joinPtr(instancePtr, ve.InstancePtr) + if len(ve.SchemaURL) == 0 { + ve.SchemaPtr = joinPtr(schemaPtr, ve.SchemaPtr) + } + for _, cause := range ve.Causes { + addContext(instancePtr, schemaPtr, cause) + } + return ve +} + +func finishSchemaContext(err error, s *Schema) { + ve := err.(*ValidationError) + if len(ve.SchemaURL) == 0 { + ve.SchemaURL = s.URL + ve.SchemaPtr = s.Ptr + "/" + ve.SchemaPtr + for _, cause := range ve.Causes { + finishSchemaContext(cause, s) + } + } +} + +func finishInstanceContext(err error) { + ve := err.(*ValidationError) + if len(ve.InstancePtr) == 0 { + ve.InstancePtr = "#" + } else { + ve.InstancePtr = "#/" + ve.InstancePtr + } + for _, cause := range ve.Causes { + finishInstanceContext(cause) + } +} + +func joinPtr(ptr1, ptr2 string) string { + if len(ptr1) == 0 { + return ptr2 + } + if len(ptr2) == 0 { + return ptr1 + } + return ptr1 + "/" + ptr2 +} diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/formats/formats.go b/vendor/github.com/santhosh-tekuri/jsonschema/formats/formats.go new file mode 100644 index 00000000000..03efa2bc46f --- /dev/null +++ b/vendor/github.com/santhosh-tekuri/jsonschema/formats/formats.go @@ -0,0 +1,295 @@ +// Copyright 2017 Santhosh Kumar Tekuri. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package formats provides functions to check string against format. +// +// It allows developers to register custom formats, that can be used +// in json-schema for validation. +package formats + +import ( + "net" + "net/mail" + "net/url" + "regexp" + "strconv" + "strings" + "time" +) + +// The Format type is a function, to check +// whether given string is in valid format. +type Format func(string) bool + +var formats = map[string]Format{ + "date-time": IsDateTime, + "date": IsDate, + "time": IsTime, + "hostname": IsHostname, + "email": IsEmail, + "ip-address": IsIPV4, + "ipv4": IsIPV4, + "ipv6": IsIPV6, + "uri": IsURI, + "iri": IsURI, + "uri-reference": IsURIReference, + "uriref": IsURIReference, + "iri-reference": IsURIReference, + "uri-template": IsURITemplate, + "regex": IsRegex, + "json-pointer": IsJSONPointer, + "relative-json-pointer": IsRelativeJSONPointer, +} + +func init() { + formats["format"] = IsFormat +} + +// Register registers Format object for given format name. +func Register(name string, f Format) { + formats[name] = f +} + +// Get returns Format object for given format name, if found. +func Get(name string) (Format, bool) { + f, ok := formats[name] + return f, ok +} + +// IsFormat tells whether given string is a valid format that is registered. +func IsFormat(s string) bool { + _, ok := formats[s] + return ok +} + +// IsDateTime tells whether given string is a valid date representation +// as defined by RFC 3339, section 5.6. +// +// Note: this is unable to parse UTC leap seconds. See https://github.com/golang/go/issues/8728. +func IsDateTime(s string) bool { + if _, err := time.Parse(time.RFC3339, s); err == nil { + return true + } + if _, err := time.Parse(time.RFC3339Nano, s); err == nil { + return true + } + return false +} + +// IsDate tells whether given string is a valid full-date production +// as defined by RFC 3339, section 5.6. +func IsDate(s string) bool { + _, err := time.Parse("2006-01-02", s) + return err == nil +} + +// IsTime tells whether given string is a valid full-time production +// as defined by RFC 3339, section 5.6. +func IsTime(s string) bool { + if _, err := time.Parse("15:04:05Z07:00", s); err == nil { + return true + } + if _, err := time.Parse("15:04:05.999999999Z07:00", s); err == nil { + return true + } + return false +} + +// IsHostname tells whether given string is a valid representation +// for an Internet host name, as defined by RFC 1034, section 3.1. +// +// See https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_host_names, for details. +func IsHostname(s string) bool { + // entire hostname (including the delimiting dots but not a trailing dot) has a maximum of 253 ASCII characters + s = strings.TrimSuffix(s, ".") + if len(s) > 253 { + return false + } + + // Hostnames are composed of series of labels concatenated with dots, as are all domain names + for _, label := range strings.Split(s, ".") { + // Each label must be from 1 to 63 characters long + if labelLen := len(label); labelLen < 1 || labelLen > 63 { + return false + } + + // labels could not start with a digit or with a hyphen + if first := s[0]; (first >= '0' && first <= '9') || (first == '-') { + return false + } + + // must not end with a hyphen + if label[len(label)-1] == '-' { + return false + } + + // labels may contain only the ASCII letters 'a' through 'z' (in a case-insensitive manner), + // the digits '0' through '9', and the hyphen ('-') + for _, c := range label { + if valid := (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || (c == '-'); !valid { + return false + } + } + } + + return true +} + +// IsEmail tells whether given string is a valid Internet email address +// as defined by RFC 5322, section 3.4.1. +// +// See https://en.wikipedia.org/wiki/Email_address, for details. +func IsEmail(s string) bool { + // entire email address to be no more than 254 characters long + if len(s) > 254 { + return false + } + + // email address is generally recognized as having two parts joined with an at-sign + at := strings.LastIndexByte(s, '@') + if at == -1 { + return false + } + local := s[0:at] + domain := s[at+1:] + + // local part may be up to 64 characters long + if len(local) > 64 { + return false + } + + // domain must match the requirements for a hostname + if !IsHostname(domain) { + return false + } + + _, err := mail.ParseAddress(s) + return err == nil +} + +// IsIPV4 tells whether given string is a valid representation of an IPv4 address +// according to the "dotted-quad" ABNF syntax as defined in RFC 2673, section 3.2. +func IsIPV4(s string) bool { + groups := strings.Split(s, ".") + if len(groups) != 4 { + return false + } + for _, group := range groups { + n, err := strconv.Atoi(group) + if err != nil { + return false + } + if n < 0 || n > 255 { + return false + } + } + return true +} + +// IsIPV6 tells whether given string is a valid representation of an IPv6 address +// as defined in RFC 2373, section 2.2. +func IsIPV6(s string) bool { + if !strings.Contains(s, ":") { + return false + } + return net.ParseIP(s) != nil +} + +// IsURI tells whether given string is valid URI, according to RFC 3986. +func IsURI(s string) bool { + u, err := url.Parse(s) + return err == nil && u.IsAbs() +} + +// IsURIReference tells whether given string is a valid URI Reference +// (either a URI or a relative-reference), according to RFC 3986. +func IsURIReference(s string) bool { + _, err := url.Parse(s) + return err == nil +} + +// IsURITemplate tells whether given string is a valid URI Template +// according to RFC6570. +// +// Current implementation does minimal validation. +func IsURITemplate(s string) bool { + u, err := url.Parse(s) + if err != nil { + return false + } + for _, item := range strings.Split(u.RawPath, "/") { + depth := 0 + for _, ch := range item { + switch ch { + case '{': + depth++ + if depth != 1 { + return false + } + case '}': + depth-- + if depth != 0 { + return false + } + } + } + if depth != 0 { + return false + } + } + return true +} + +// IsRegex tells whether given string is a valid regular expression, +// according to the ECMA 262 regular expression dialect. +// +// The implementation uses go-lang regexp package. +func IsRegex(s string) bool { + _, err := regexp.Compile(s) + return err == nil +} + +// IsJSONPointer tells whether given string is a valid JSON Pointer. +// +// Note: It returns false for JSON Pointer URI fragments. +func IsJSONPointer(s string) bool { + if s != "" && !strings.HasPrefix(s, "/") { + return false + } + for _, item := range strings.Split(s, "/") { + for i := 0; i < len(item); i++ { + if item[i] == '~' { + if i == len(item)-1 { + return false + } + switch item[i+1] { + case '~', '0', '1': + // valid + default: + return false + } + } + } + } + return true +} + +// IsRelativeJSONPointer tells whether given string is a valid Relative JSON Pointer. +// +// see https://tools.ietf.org/html/draft-handrews-relative-json-pointer-01#section-3 +func IsRelativeJSONPointer(s string) bool { + if s == "" { + return false + } + if s[0] == '0' { + s = s[1:] + } else if s[0] >= '0' && s[0] <= '9' { + for s != "" && s[0] >= '0' && s[0] <= '9' { + s = s[1:] + } + } else { + return false + } + return s == "#" || IsJSONPointer(s) +} diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/go.mod b/vendor/github.com/santhosh-tekuri/jsonschema/go.mod new file mode 100644 index 00000000000..89a74866100 --- /dev/null +++ b/vendor/github.com/santhosh-tekuri/jsonschema/go.mod @@ -0,0 +1 @@ +module github.com/santhosh-tekuri/jsonschema diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/go.test.sh b/vendor/github.com/santhosh-tekuri/jsonschema/go.test.sh new file mode 100644 index 00000000000..88c4e8b6e7d --- /dev/null +++ b/vendor/github.com/santhosh-tekuri/jsonschema/go.test.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -e +echo "" > coverage.txt + +for d in $(go list ./... | grep -v vendor); do + go test -v -race -coverprofile=profile.out -covermode=atomic $d + if [ -f profile.out ]; then + cat profile.out >> coverage.txt + rm profile.out + fi +done diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/loader/loader.go b/vendor/github.com/santhosh-tekuri/jsonschema/loader/loader.go new file mode 100644 index 00000000000..6ae19fb13b9 --- /dev/null +++ b/vendor/github.com/santhosh-tekuri/jsonschema/loader/loader.go @@ -0,0 +1,105 @@ +// Copyright 2017 Santhosh Kumar Tekuri. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package loader abstracts the reading document at given url. +// +// It allows developers to register loaders for different uri +// schemes. +package loader + +import ( + "fmt" + "io" + "net/url" + "os" + "path/filepath" + "runtime" + "strings" + "sync" +) + +// Loader is the interface that wraps the basic Load method. +// +// Load loads the document at given url and returns []byte, +// if successful. +type Loader interface { + Load(url string) (io.ReadCloser, error) +} + +type filePathLoader struct{} + +func (filePathLoader) Load(path string) (io.ReadCloser, error) { + return os.Open(path) +} + +type fileURLLoader struct{} + +func (fileURLLoader) Load(s string) (io.ReadCloser, error) { + u, err := url.Parse(s) + if err != nil { + return nil, err + } + f := u.Path + if runtime.GOOS == "windows" { + f = strings.TrimPrefix(f, "/") + f = filepath.FromSlash(f) + } + return os.Open(f) +} + +var registry = make(map[string]Loader) +var mutex = sync.RWMutex{} + +// SchemeNotRegisteredError is the error type returned by Load function. +// It tells that no Loader is registered for that URL Scheme. +type SchemeNotRegisteredError string + +func (s SchemeNotRegisteredError) Error() string { + return fmt.Sprintf("no Loader registered for scheme %s", string(s)) +} + +// Register registers given Loader for given URI Scheme. +func Register(scheme string, loader Loader) { + mutex.Lock() + defer mutex.Unlock() + registry[scheme] = loader +} + +// UnRegister unregisters the registered loader(if any) for given URI Scheme. +func UnRegister(scheme string) { + mutex.Lock() + defer mutex.Unlock() + delete(registry, scheme) +} + +func get(s string) (Loader, error) { + mutex.RLock() + defer mutex.RUnlock() + u, err := url.Parse(s) + if err != nil { + return nil, err + } + if loader, ok := registry[u.Scheme]; ok { + return loader, nil + } + return nil, SchemeNotRegisteredError(u.Scheme) +} + +// Load loads the document at given url and returns []byte, +// if successful. +// +// If no Loader is registered against the URI Scheme, then it +// returns *SchemeNotRegisteredError +var Load = func(url string) (io.ReadCloser, error) { + loader, err := get(url) + if err != nil { + return nil, err + } + return loader.Load(url) +} + +func init() { + Register("", filePathLoader{}) + Register("file", fileURLLoader{}) +} diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/mediatypes/mediatypes.go b/vendor/github.com/santhosh-tekuri/jsonschema/mediatypes/mediatypes.go new file mode 100644 index 00000000000..3c4ec3f53bf --- /dev/null +++ b/vendor/github.com/santhosh-tekuri/jsonschema/mediatypes/mediatypes.go @@ -0,0 +1,39 @@ +// Copyright 2017 Santhosh Kumar Tekuri. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package mediatypes provides functions to validate data against mediatype. +// +// It allows developers to register custom mediatypes, that can be used +// in json-schema for validation. +package mediatypes + +import ( + "bytes" + "encoding/json" +) + +// The MediaType type is a function, that validates +// whether the bytes represent data of given mediaType. +type MediaType func([]byte) error + +var mediaTypes = map[string]MediaType{ + "application/json": validateJSON, +} + +// Register registers MediaType object for given mediaType. +func Register(name string, mt MediaType) { + mediaTypes[name] = mt +} + +// Get returns MediaType object for given mediaType, if found. +func Get(name string) (MediaType, bool) { + mt, ok := mediaTypes[name] + return mt, ok +} + +func validateJSON(b []byte) error { + decoder := json.NewDecoder(bytes.NewReader(b)) + var v interface{} + return decoder.Decode(&v) +} diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/resource.go b/vendor/github.com/santhosh-tekuri/jsonschema/resource.go new file mode 100644 index 00000000000..9f52cf3acd9 --- /dev/null +++ b/vendor/github.com/santhosh-tekuri/jsonschema/resource.go @@ -0,0 +1,236 @@ +// Copyright 2017 Santhosh Kumar Tekuri. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package jsonschema + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "net/url" + "path/filepath" + "strconv" + "strings" +) + +type resource struct { + url string + doc interface{} + draft *Draft + schemas map[string]*Schema +} + +// DecodeJSON decodes json document from r. +// +// Note that number is decoded into json.Number instead of as a float64 +func DecodeJSON(r io.Reader) (interface{}, error) { + decoder := json.NewDecoder(r) + decoder.UseNumber() + var doc interface{} + if err := decoder.Decode(&doc); err != nil { + return nil, err + } + if t, _ := decoder.Token(); t != nil { + return nil, fmt.Errorf("invalid character %v after top-level value", t) + } + return doc, nil +} + +func newResource(base string, r io.Reader) (*resource, error) { + if strings.IndexByte(base, '#') != -1 { + panic(fmt.Sprintf("BUG: newResource(%q)", base)) + } + doc, err := DecodeJSON(r) + if err != nil { + return nil, fmt.Errorf("parsing %q failed. Reason: %v", base, err) + } + return &resource{ + url: base, + doc: doc, + schemas: make(map[string]*Schema)}, nil +} + +func resolveURL(base, ref string) (string, error) { + if ref == "" { + return base, nil + } + + refURL, err := url.Parse(ref) + if err != nil { + return "", err + } + if refURL.IsAbs() { + return normalize(ref), nil + } + + baseURL, err := url.Parse(base) + if err != nil { + return "", err + } + if baseURL.IsAbs() { + return normalize(baseURL.ResolveReference(refURL).String()), nil + } + + // filepath resolving + base, _ = split(base) + ref, fragment := split(ref) + if ref == "" { + return base + fragment, nil + } + dir, _ := filepath.Split(base) + return filepath.Join(dir, ref) + fragment, nil +} + +func (r *resource) resolvePtr(ptr string) (string, interface{}, error) { + if !strings.HasPrefix(ptr, "#/") { + panic(fmt.Sprintf("BUG: resolvePtr(%q)", ptr)) + } + base := r.url + p := strings.TrimPrefix(ptr, "#/") + doc := r.doc + for _, item := range strings.Split(p, "/") { + item = strings.Replace(item, "~1", "/", -1) + item = strings.Replace(item, "~0", "~", -1) + item, err := url.PathUnescape(item) + if err != nil { + return "", nil, errors.New("unable to url unscape: " + item) + } + switch d := doc.(type) { + case map[string]interface{}: + if id, ok := d[r.draft.id]; ok { + if id, ok := id.(string); ok { + if base, err = resolveURL(base, id); err != nil { + return "", nil, err + } + } + } + doc = d[item] + case []interface{}: + index, err := strconv.Atoi(item) + if err != nil { + return "", nil, fmt.Errorf("invalid $ref %q, reason: %s", ptr, err) + } + if index < 0 || index >= len(d) { + return "", nil, fmt.Errorf("invalid $ref %q, reason: array index outofrange", ptr) + } + doc = d[index] + default: + return "", nil, errors.New("invalid $ref " + ptr) + } + } + return base, doc, nil +} + +func split(uri string) (string, string) { + hash := strings.IndexByte(uri, '#') + if hash == -1 { + return uri, "#" + } + return uri[0:hash], uri[hash:] +} + +func normalize(url string) string { + base, fragment := split(url) + if rootFragment(fragment) { + fragment = "#" + } + return base + fragment +} + +func rootFragment(fragment string) bool { + return fragment == "" || fragment == "#" || fragment == "#/" +} + +func resolveIDs(draft *Draft, base string, v interface{}, ids map[string]map[string]interface{}) error { + m, ok := v.(map[string]interface{}) + if !ok { + return nil + } + if id, ok := m[draft.id]; ok { + b, err := resolveURL(base, id.(string)) + if err != nil { + return err + } + base = b + ids[base] = m + } + + for _, pname := range []string{"not", "additionalProperties"} { + if m, ok := m[pname]; ok { + if err := resolveIDs(draft, base, m, ids); err != nil { + return err + } + } + } + + for _, pname := range []string{"allOf", "anyOf", "oneOf"} { + if arr, ok := m[pname]; ok { + for _, m := range arr.([]interface{}) { + if err := resolveIDs(draft, base, m, ids); err != nil { + return err + } + } + } + } + + for _, pname := range []string{"definitions", "properties", "patternProperties", "dependencies"} { + if props, ok := m[pname]; ok { + for _, m := range props.(map[string]interface{}) { + if err := resolveIDs(draft, base, m, ids); err != nil { + return err + } + } + } + } + + if items, ok := m["items"]; ok { + switch items := items.(type) { + case map[string]interface{}: + if err := resolveIDs(draft, base, items, ids); err != nil { + return err + } + case []interface{}: + for _, item := range items { + if err := resolveIDs(draft, base, item, ids); err != nil { + return err + } + } + } + if additionalItems, ok := m["additionalItems"]; ok { + if additionalItems, ok := additionalItems.(map[string]interface{}); ok { + if err := resolveIDs(draft, base, additionalItems, ids); err != nil { + return err + } + } + } + } + + if draft.version >= 6 { + for _, pname := range []string{"propertyNames", "contains"} { + if m, ok := m[pname]; ok { + if err := resolveIDs(draft, base, m, ids); err != nil { + return err + } + } + } + } + + if draft.version >= 7 { + if iff, ok := m["if"]; ok { + if err := resolveIDs(draft, base, iff, ids); err != nil { + return err + } + for _, pname := range []string{"then", "else"} { + if m, ok := m[pname]; ok { + if err := resolveIDs(draft, base, m, ids); err != nil { + return err + } + } + } + } + } + + return nil +} diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/schema.go b/vendor/github.com/santhosh-tekuri/jsonschema/schema.go new file mode 100644 index 00000000000..8ee1638547f --- /dev/null +++ b/vendor/github.com/santhosh-tekuri/jsonschema/schema.go @@ -0,0 +1,558 @@ +// Copyright 2017 Santhosh Kumar Tekuri. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package jsonschema + +import ( + "encoding/json" + "fmt" + "io" + "math/big" + "net/url" + "regexp" + "strconv" + "strings" + "unicode/utf8" + + "github.com/santhosh-tekuri/jsonschema/decoders" + "github.com/santhosh-tekuri/jsonschema/formats" + "github.com/santhosh-tekuri/jsonschema/mediatypes" +) + +// A Schema represents compiled version of json-schema. +type Schema struct { + URL string // absolute url of the resource. + Ptr string // json-pointer to schema. always starts with `#`. + + // type agnostic validations + Always *bool // always pass/fail. used when booleans are used as schemas in draft-07. + Ref *Schema // reference to actual schema. if not nil, all the remaining fields are ignored. + Types []string // allowed types. + Constant []interface{} // first element in slice is constant value. note: slice is used to capture nil constant. + Enum []interface{} // allowed values. + enumError string // error message for enum fail. captured here to avoid constructing error message every time. + Not *Schema + AllOf []*Schema + AnyOf []*Schema + OneOf []*Schema + If *Schema + Then *Schema // nil, when If is nil. + Else *Schema // nil, when If is nil. + + // object validations + MinProperties int // -1 if not specified. + MaxProperties int // -1 if not specified. + Required []string // list of required properties. + Properties map[string]*Schema + PropertyNames *Schema + RegexProperties bool // property names must be valid regex. used only in draft4 as workaround in metaschema. + PatternProperties map[*regexp.Regexp]*Schema + AdditionalProperties interface{} // nil or false or *Schema. + Dependencies map[string]interface{} // value is *Schema or []string. + + // array validations + MinItems int // -1 if not specified. + MaxItems int // -1 if not specified. + UniqueItems bool + Items interface{} // nil or *Schema or []*Schema + AdditionalItems interface{} // nil or bool or *Schema. + Contains *Schema + + // string validations + MinLength int // -1 if not specified. + MaxLength int // -1 if not specified. + Pattern *regexp.Regexp + Format formats.Format + FormatName string + ContentEncoding string + Decoder decoders.Decoder + ContentMediaType string + MediaType mediatypes.MediaType + + // number validators + Minimum *big.Float + ExclusiveMinimum *big.Float + Maximum *big.Float + ExclusiveMaximum *big.Float + MultipleOf *big.Float + + // annotations. captured only when Compiler.ExtractAnnotations is true. + Title string + Description string + Default interface{} + ReadOnly bool + WriteOnly bool + Examples []interface{} +} + +// Compile parses json-schema at given url returns, if successful, +// a Schema object that can be used to match against json. +// +// The json-schema is validated with draft4 specification. +// Returned error can be *SchemaError +func Compile(url string) (*Schema, error) { + return NewCompiler().Compile(url) +} + +// MustCompile is like Compile but panics if the url cannot be compiled to *Schema. +// It simplifies safe initialization of global variables holding compiled Schemas. +func MustCompile(url string) *Schema { + return NewCompiler().MustCompile(url) +} + +// Validate validates the given json data, against the json-schema. +// +// Returned error can be *ValidationError. +func (s *Schema) Validate(r io.Reader) error { + doc, err := DecodeJSON(r) + if err != nil { + return err + } + return s.ValidateInterface(doc) +} + +// ValidateInterface validates given doc, against the json-schema. +// +// the doc must be the value decoded by json package using interface{} type. +// we recommend to use jsonschema.DecodeJSON(io.Reader) to decode JSON. +func (s *Schema) ValidateInterface(doc interface{}) (err error) { + defer func() { + if r := recover(); r != nil { + if _, ok := r.(InvalidJSONTypeError); ok { + err = r.(InvalidJSONTypeError) + } else { + panic(r) + } + } + }() + if err := s.validate(doc); err != nil { + finishSchemaContext(err, s) + finishInstanceContext(err) + return &ValidationError{ + Message: fmt.Sprintf("doesn't validate with %q", s.URL+s.Ptr), + InstancePtr: "#", + SchemaURL: s.URL, + SchemaPtr: s.Ptr, + Causes: []*ValidationError{err.(*ValidationError)}, + } + } + return nil +} + +// validate validates given value v with this schema. +func (s *Schema) validate(v interface{}) error { + if s.Always != nil { + if !*s.Always { + return validationError("", "always fail") + } + return nil + } + + if s.Ref != nil { + if err := s.Ref.validate(v); err != nil { + finishSchemaContext(err, s.Ref) + var refURL string + if s.URL == s.Ref.URL { + refURL = s.Ref.Ptr + } else { + refURL = s.Ref.URL + s.Ref.Ptr + } + return validationError("$ref", "doesn't validate with %q", refURL).add(err) + } + + // All other properties in a "$ref" object MUST be ignored + return nil + } + + if len(s.Types) > 0 { + vType := jsonType(v) + matched := false + for _, t := range s.Types { + if vType == t { + matched = true + break + } else if t == "integer" && vType == "number" { + if _, ok := new(big.Int).SetString(fmt.Sprint(v), 10); ok { + matched = true + break + } + } + } + if !matched { + return validationError("type", "expected %s, but got %s", strings.Join(s.Types, " or "), vType) + } + } + + if len(s.Constant) > 0 { + if !equals(v, s.Constant[0]) { + switch jsonType(s.Constant[0]) { + case "object", "array": + return validationError("const", "const failed") + default: + return validationError("const", "value must be %#v", s.Constant[0]) + } + } + } + + if len(s.Enum) > 0 { + matched := false + for _, item := range s.Enum { + if equals(v, item) { + matched = true + break + } + } + if !matched { + return validationError("enum", s.enumError) + } + } + + if s.Not != nil && s.Not.validate(v) == nil { + return validationError("not", "not failed") + } + + for i, sch := range s.AllOf { + if err := sch.validate(v); err != nil { + return validationError("allOf/"+strconv.Itoa(i), "allOf failed").add(err) + } + } + + if len(s.AnyOf) > 0 { + matched := false + var causes []error + for i, sch := range s.AnyOf { + if err := sch.validate(v); err == nil { + matched = true + break + } else { + causes = append(causes, addContext("", strconv.Itoa(i), err)) + } + } + if !matched { + return validationError("anyOf", "anyOf failed").add(causes...) + } + } + + if len(s.OneOf) > 0 { + matched := -1 + var causes []error + for i, sch := range s.OneOf { + if err := sch.validate(v); err == nil { + if matched == -1 { + matched = i + } else { + return validationError("oneOf", "valid against schemas at indexes %d and %d", matched, i) + } + } else { + causes = append(causes, addContext("", strconv.Itoa(i), err)) + } + } + if matched == -1 { + return validationError("oneOf", "oneOf failed").add(causes...) + } + } + + if s.If != nil { + if s.If.validate(v) == nil { + if s.Then != nil { + if err := s.Then.validate(v); err != nil { + return validationError("then", "if-then failed").add(err) + } + } + } else { + if s.Else != nil { + if err := s.Else.validate(v); err != nil { + return validationError("else", "if-else failed").add(err) + } + } + } + } + + switch v := v.(type) { + case map[string]interface{}: + if s.MinProperties != -1 && len(v) < s.MinProperties { + return validationError("minProperties", "minimum %d properties allowed, but found %d properties", s.MinProperties, len(v)) + } + if s.MaxProperties != -1 && len(v) > s.MaxProperties { + return validationError("maxProperties", "maximum %d properties allowed, but found %d properties", s.MaxProperties, len(v)) + } + if len(s.Required) > 0 { + var missing []string + for _, pname := range s.Required { + if _, ok := v[pname]; !ok { + missing = append(missing, strconv.Quote(pname)) + } + } + if len(missing) > 0 { + return validationError("required", "missing properties: %s", strings.Join(missing, ", ")) + } + } + + var additionalProps map[string]struct{} + if s.AdditionalProperties != nil { + additionalProps = make(map[string]struct{}, len(v)) + for pname := range v { + additionalProps[pname] = struct{}{} + } + } + + if len(s.Properties) > 0 { + for pname, pschema := range s.Properties { + if pvalue, ok := v[pname]; ok { + delete(additionalProps, pname) + if err := pschema.validate(pvalue); err != nil { + return addContext(escape(pname), "properties/"+escape(pname), err) + } + } + } + } + + if s.PropertyNames != nil { + for pname := range v { + if err := s.PropertyNames.validate(pname); err != nil { + return addContext(escape(pname), "propertyNames", err) + } + } + } + + if s.RegexProperties { + for pname := range v { + if !formats.IsRegex(pname) { + return validationError("", "patternProperty %q is not valid regex", pname) + } + } + } + for pattern, pschema := range s.PatternProperties { + for pname, pvalue := range v { + if pattern.MatchString(pname) { + delete(additionalProps, pname) + if err := pschema.validate(pvalue); err != nil { + return addContext(escape(pname), "patternProperties/"+escape(pattern.String()), err) + } + } + } + } + if s.AdditionalProperties != nil { + if _, ok := s.AdditionalProperties.(bool); ok { + if len(additionalProps) != 0 { + pnames := make([]string, 0, len(additionalProps)) + for pname := range additionalProps { + pnames = append(pnames, strconv.Quote(pname)) + } + return validationError("additionalProperties", "additionalProperties %s not allowed", strings.Join(pnames, ", ")) + } + } else { + schema := s.AdditionalProperties.(*Schema) + for pname := range additionalProps { + if pvalue, ok := v[pname]; ok { + if err := schema.validate(pvalue); err != nil { + return addContext(escape(pname), "additionalProperties", err) + } + } + } + } + } + for dname, dvalue := range s.Dependencies { + if _, ok := v[dname]; ok { + switch dvalue := dvalue.(type) { + case *Schema: + if err := dvalue.validate(v); err != nil { + return addContext("", "dependencies/"+escape(dname), err) + } + case []string: + for i, pname := range dvalue { + if _, ok := v[pname]; !ok { + return validationError("dependencies/"+escape(dname)+"/"+strconv.Itoa(i), "property %q is required, if %q property exists", pname, dname) + } + } + } + } + } + + case []interface{}: + if s.MinItems != -1 && len(v) < s.MinItems { + return validationError("minItems", "minimum %d items allowed, but found %d items", s.MinItems, len(v)) + } + if s.MaxItems != -1 && len(v) > s.MaxItems { + return validationError("maxItems", "maximum %d items allowed, but found %d items", s.MaxItems, len(v)) + } + if s.UniqueItems { + for i := 1; i < len(v); i++ { + for j := 0; j < i; j++ { + if equals(v[i], v[j]) { + return validationError("uniqueItems", "items at index %d and %d are equal", j, i) + } + } + } + } + switch items := s.Items.(type) { + case *Schema: + for i, item := range v { + if err := items.validate(item); err != nil { + return addContext(strconv.Itoa(i), "items", err) + } + } + case []*Schema: + if additionalItems, ok := s.AdditionalItems.(bool); ok { + if !additionalItems && len(v) > len(items) { + return validationError("additionalItems", "only %d items are allowed, but found %d items", len(items), len(v)) + } + } + for i, item := range v { + if i < len(items) { + if err := items[i].validate(item); err != nil { + return addContext(strconv.Itoa(i), "items/"+strconv.Itoa(i), err) + } + } else if sch, ok := s.AdditionalItems.(*Schema); ok { + if err := sch.validate(item); err != nil { + return addContext(strconv.Itoa(i), "additionalItems", err) + } + } else { + break + } + } + } + if s.Contains != nil { + matched := false + var causes []error + for i, item := range v { + if err := s.Contains.validate(item); err != nil { + causes = append(causes, addContext(strconv.Itoa(i), "", err)) + } else { + matched = true + break + } + } + if !matched { + return validationError("contains", "contains failed").add(causes...) + } + } + + case string: + if s.MinLength != -1 || s.MaxLength != -1 { + length := utf8.RuneCount([]byte(v)) + if s.MinLength != -1 && length < s.MinLength { + return validationError("minLength", "length must be >= %d, but got %d", s.MinLength, length) + } + if s.MaxLength != -1 && length > s.MaxLength { + return validationError("maxLength", "length must be <= %d, but got %d", s.MaxLength, length) + } + } + if s.Pattern != nil && !s.Pattern.MatchString(v) { + return validationError("pattern", "does not match pattern %q", s.Pattern) + } + if s.Format != nil && !s.Format(v) { + return validationError("format", "%q is not valid %q", v, s.FormatName) + } + + var content []byte + if s.Decoder != nil { + b, err := s.Decoder(v) + if err != nil { + return validationError("contentEncoding", "%q is not %s encoded", v, s.ContentEncoding) + } + content = b + } + if s.MediaType != nil { + if s.Decoder == nil { + content = []byte(v) + } + if err := s.MediaType(content); err != nil { + return validationError("contentMediaType", "value is not of mediatype %q", s.ContentMediaType) + } + } + + case json.Number, float64, int, int32, int64: + num, _ := new(big.Float).SetString(fmt.Sprint(v)) + if s.Minimum != nil && num.Cmp(s.Minimum) < 0 { + return validationError("minimum", "must be >= %v but found %v", s.Minimum, v) + } + if s.ExclusiveMinimum != nil && num.Cmp(s.ExclusiveMinimum) <= 0 { + return validationError("exclusiveMinimum", "must be > %v but found %v", s.ExclusiveMinimum, v) + } + if s.Maximum != nil && num.Cmp(s.Maximum) > 0 { + return validationError("maximum", "must be <= %v but found %v", s.Maximum, v) + } + if s.ExclusiveMaximum != nil && num.Cmp(s.ExclusiveMaximum) >= 0 { + return validationError("exclusiveMaximum", "must be < %v but found %v", s.ExclusiveMaximum, v) + } + if s.MultipleOf != nil { + if q := new(big.Float).Quo(num, s.MultipleOf); !q.IsInt() { + return validationError("multipleOf", "%v not multipleOf %v", v, s.MultipleOf) + } + } + } + + return nil +} + +// jsonType returns the json type of given value v. +// +// It panics if the given value is not valid json value +func jsonType(v interface{}) string { + switch v.(type) { + case nil: + return "null" + case bool: + return "boolean" + case json.Number, float64, int, int32, int64: + return "number" + case string: + return "string" + case []interface{}: + return "array" + case map[string]interface{}: + return "object" + } + panic(InvalidJSONTypeError(fmt.Sprintf("%T", v))) +} + +// equals tells if given two json values are equal or not. +func equals(v1, v2 interface{}) bool { + v1Type := jsonType(v1) + if v1Type != jsonType(v2) { + return false + } + switch v1Type { + case "array": + arr1, arr2 := v1.([]interface{}), v2.([]interface{}) + if len(arr1) != len(arr2) { + return false + } + for i := range arr1 { + if !equals(arr1[i], arr2[i]) { + return false + } + } + return true + case "object": + obj1, obj2 := v1.(map[string]interface{}), v2.(map[string]interface{}) + if len(obj1) != len(obj2) { + return false + } + for k, v1 := range obj1 { + if v2, ok := obj2[k]; ok { + if !equals(v1, v2) { + return false + } + } else { + return false + } + } + return true + case "number": + num1, _ := new(big.Float).SetString(string(v1.(json.Number))) + num2, _ := new(big.Float).SetString(string(v2.(json.Number))) + return num1.Cmp(num2) == 0 + default: + return v1 == v2 + } +} + +// escape converts given token to valid json-pointer token +func escape(token string) string { + token = strings.Replace(token, "~", "~0", -1) + token = strings.Replace(token, "/", "~1", -1) + return url.PathEscape(token) +} diff --git a/vendor/github.com/stretchr/testify/suite/doc.go b/vendor/github.com/stretchr/testify/suite/doc.go new file mode 100644 index 00000000000..f91a245d3f8 --- /dev/null +++ b/vendor/github.com/stretchr/testify/suite/doc.go @@ -0,0 +1,65 @@ +// Package suite contains logic for creating testing suite structs +// and running the methods on those structs as tests. The most useful +// piece of this package is that you can create setup/teardown methods +// on your testing suites, which will run before/after the whole suite +// or individual tests (depending on which interface(s) you +// implement). +// +// A testing suite is usually built by first extending the built-in +// suite functionality from suite.Suite in testify. Alternatively, +// you could reproduce that logic on your own if you wanted (you +// just need to implement the TestingSuite interface from +// suite/interfaces.go). +// +// After that, you can implement any of the interfaces in +// suite/interfaces.go to add setup/teardown functionality to your +// suite, and add any methods that start with "Test" to add tests. +// Methods that do not match any suite interfaces and do not begin +// with "Test" will not be run by testify, and can safely be used as +// helper methods. +// +// Once you've built your testing suite, you need to run the suite +// (using suite.Run from testify) inside any function that matches the +// identity that "go test" is already looking for (i.e. +// func(*testing.T)). +// +// Regular expression to select test suites specified command-line +// argument "-run". Regular expression to select the methods +// of test suites specified command-line argument "-m". +// Suite object has assertion methods. +// +// A crude example: +// // Basic imports +// import ( +// "testing" +// "github.com/stretchr/testify/assert" +// "github.com/stretchr/testify/suite" +// ) +// +// // Define the suite, and absorb the built-in basic suite +// // functionality from testify - including a T() method which +// // returns the current testing context +// type ExampleTestSuite struct { +// suite.Suite +// VariableThatShouldStartAtFive int +// } +// +// // Make sure that VariableThatShouldStartAtFive is set to five +// // before each test +// func (suite *ExampleTestSuite) SetupTest() { +// suite.VariableThatShouldStartAtFive = 5 +// } +// +// // All methods that begin with "Test" are run as tests within a +// // suite. +// func (suite *ExampleTestSuite) TestExample() { +// assert.Equal(suite.T(), 5, suite.VariableThatShouldStartAtFive) +// suite.Equal(5, suite.VariableThatShouldStartAtFive) +// } +// +// // In order for 'go test' to run this suite, we need to create +// // a normal test function and pass our suite to suite.Run +// func TestExampleTestSuite(t *testing.T) { +// suite.Run(t, new(ExampleTestSuite)) +// } +package suite diff --git a/vendor/github.com/stretchr/testify/suite/interfaces.go b/vendor/github.com/stretchr/testify/suite/interfaces.go new file mode 100644 index 00000000000..b37cb040987 --- /dev/null +++ b/vendor/github.com/stretchr/testify/suite/interfaces.go @@ -0,0 +1,46 @@ +package suite + +import "testing" + +// TestingSuite can store and return the current *testing.T context +// generated by 'go test'. +type TestingSuite interface { + T() *testing.T + SetT(*testing.T) +} + +// SetupAllSuite has a SetupSuite method, which will run before the +// tests in the suite are run. +type SetupAllSuite interface { + SetupSuite() +} + +// SetupTestSuite has a SetupTest method, which will run before each +// test in the suite. +type SetupTestSuite interface { + SetupTest() +} + +// TearDownAllSuite has a TearDownSuite method, which will run after +// all the tests in the suite have been run. +type TearDownAllSuite interface { + TearDownSuite() +} + +// TearDownTestSuite has a TearDownTest method, which will run after +// each test in the suite. +type TearDownTestSuite interface { + TearDownTest() +} + +// BeforeTest has a function to be executed right before the test +// starts and receives the suite and test names as input +type BeforeTest interface { + BeforeTest(suiteName, testName string) +} + +// AfterTest has a function to be executed right after the test +// finishes and receives the suite and test names as input +type AfterTest interface { + AfterTest(suiteName, testName string) +} diff --git a/vendor/github.com/stretchr/testify/suite/suite.go b/vendor/github.com/stretchr/testify/suite/suite.go new file mode 100644 index 00000000000..d708d7d7539 --- /dev/null +++ b/vendor/github.com/stretchr/testify/suite/suite.go @@ -0,0 +1,166 @@ +package suite + +import ( + "flag" + "fmt" + "os" + "reflect" + "regexp" + "runtime/debug" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var allTestsFilter = func(_, _ string) (bool, error) { return true, nil } +var matchMethod = flag.String("testify.m", "", "regular expression to select tests of the testify suite to run") + +// Suite is a basic testing suite with methods for storing and +// retrieving the current *testing.T context. +type Suite struct { + *assert.Assertions + require *require.Assertions + t *testing.T +} + +// T retrieves the current *testing.T context. +func (suite *Suite) T() *testing.T { + return suite.t +} + +// SetT sets the current *testing.T context. +func (suite *Suite) SetT(t *testing.T) { + suite.t = t + suite.Assertions = assert.New(t) + suite.require = require.New(t) +} + +// Require returns a require context for suite. +func (suite *Suite) Require() *require.Assertions { + if suite.require == nil { + suite.require = require.New(suite.T()) + } + return suite.require +} + +// Assert returns an assert context for suite. Normally, you can call +// `suite.NoError(expected, actual)`, but for situations where the embedded +// methods are overridden (for example, you might want to override +// assert.Assertions with require.Assertions), this method is provided so you +// can call `suite.Assert().NoError()`. +func (suite *Suite) Assert() *assert.Assertions { + if suite.Assertions == nil { + suite.Assertions = assert.New(suite.T()) + } + return suite.Assertions +} + +func failOnPanic(t *testing.T) { + r := recover() + if r != nil { + t.Errorf("test panicked: %v\n%s", r, debug.Stack()) + t.FailNow() + } +} + +// Run provides suite functionality around golang subtests. It should be +// called in place of t.Run(name, func(t *testing.T)) in test suite code. +// The passed-in func will be executed as a subtest with a fresh instance of t. +// Provides compatibility with go test pkg -run TestSuite/TestName/SubTestName. +func (suite *Suite) Run(name string, subtest func()) bool { + oldT := suite.T() + defer suite.SetT(oldT) + return oldT.Run(name, func(t *testing.T) { + suite.SetT(t) + subtest() + }) +} + +// Run takes a testing suite and runs all of the tests attached +// to it. +func Run(t *testing.T, suite TestingSuite) { + suite.SetT(t) + defer failOnPanic(t) + + suiteSetupDone := false + + methodFinder := reflect.TypeOf(suite) + tests := []testing.InternalTest{} + for index := 0; index < methodFinder.NumMethod(); index++ { + method := methodFinder.Method(index) + ok, err := methodFilter(method.Name) + if err != nil { + fmt.Fprintf(os.Stderr, "testify: invalid regexp for -m: %s\n", err) + os.Exit(1) + } + if !ok { + continue + } + if !suiteSetupDone { + if setupAllSuite, ok := suite.(SetupAllSuite); ok { + setupAllSuite.SetupSuite() + } + defer func() { + if tearDownAllSuite, ok := suite.(TearDownAllSuite); ok { + tearDownAllSuite.TearDownSuite() + } + }() + suiteSetupDone = true + } + test := testing.InternalTest{ + Name: method.Name, + F: func(t *testing.T) { + parentT := suite.T() + suite.SetT(t) + defer failOnPanic(t) + + if setupTestSuite, ok := suite.(SetupTestSuite); ok { + setupTestSuite.SetupTest() + } + if beforeTestSuite, ok := suite.(BeforeTest); ok { + beforeTestSuite.BeforeTest(methodFinder.Elem().Name(), method.Name) + } + defer func() { + if afterTestSuite, ok := suite.(AfterTest); ok { + afterTestSuite.AfterTest(methodFinder.Elem().Name(), method.Name) + } + if tearDownTestSuite, ok := suite.(TearDownTestSuite); ok { + tearDownTestSuite.TearDownTest() + } + suite.SetT(parentT) + }() + method.Func.Call([]reflect.Value{reflect.ValueOf(suite)}) + }, + } + tests = append(tests, test) + } + runTests(t, tests) +} + +func runTests(t testing.TB, tests []testing.InternalTest) { + r, ok := t.(runner) + if !ok { // backwards compatibility with Go 1.6 and below + if !testing.RunTests(allTestsFilter, tests) { + t.Fail() + } + return + } + + for _, test := range tests { + r.Run(test.Name, test.F) + } +} + +// Filtering method according to set regular expression +// specified command-line argument -m +func methodFilter(name string) (bool, error) { + if ok, _ := regexp.MatchString("^Test", name); !ok { + return false, nil + } + return regexp.MatchString(*matchMethod, name) +} + +type runner interface { + Run(name string, f func(t *testing.T)) bool +} diff --git a/vendor/go.elastic.co/apm/.dockerignore b/vendor/go.elastic.co/apm/.dockerignore new file mode 100644 index 00000000000..25e074c2513 --- /dev/null +++ b/vendor/go.elastic.co/apm/.dockerignore @@ -0,0 +1,2 @@ +scripts/docker* +scripts/Docker* diff --git a/vendor/go.elastic.co/apm/.gitignore b/vendor/go.elastic.co/apm/.gitignore new file mode 100644 index 00000000000..d5a32627a04 --- /dev/null +++ b/vendor/go.elastic.co/apm/.gitignore @@ -0,0 +1,5 @@ +*.swp +*.test +*.out +docs/html +build diff --git a/vendor/go.elastic.co/apm/.jenkins-edge.yml b/vendor/go.elastic.co/apm/.jenkins-edge.yml new file mode 100644 index 00000000000..3426e876db2 --- /dev/null +++ b/vendor/go.elastic.co/apm/.jenkins-edge.yml @@ -0,0 +1,2 @@ +GO_VERSION: + - "master" diff --git a/vendor/go.elastic.co/apm/.jenkins.yml b/vendor/go.elastic.co/apm/.jenkins.yml new file mode 100644 index 00000000000..e41a8a5e942 --- /dev/null +++ b/vendor/go.elastic.co/apm/.jenkins.yml @@ -0,0 +1,8 @@ +GO_VERSION: + - stable + - "1.x" + - "1.8.x" + - "1.9.x" + - "1.10.x" + - "1.11.x" + - "1.12.x" diff --git a/vendor/go.elastic.co/apm/CHANGELOG.asciidoc b/vendor/go.elastic.co/apm/CHANGELOG.asciidoc new file mode 100644 index 00000000000..f6cd5aa4be5 --- /dev/null +++ b/vendor/go.elastic.co/apm/CHANGELOG.asciidoc @@ -0,0 +1,262 @@ +ifdef::env-github[] +NOTE: Release notes are best read in our documentation at +https://www.elastic.co/guide/en/apm/agent/go/current/release-notes.html[elastic.co] +endif::[] + +//// +[[release-notes-x.x.x]] +==== x.x.x - YYYY/MM/DD + +[float] +===== Breaking changes + +[float] +===== Features +* Cool new feature: {pull}2526[#2526] + +[float] +===== Bug fixes +//// + +[[unreleased]] +=== Unreleased + +https://github.com/elastic/apm-agent-go/compare/v1.7.2...master[View commits] + +[[release-notes-1.x]] +=== Go Agent version 1.x + +[[release-notes-1.7.2]] +==== 1.7.2 - 2020/03/19 + +- Update cucumber/godog to 0.8.1 {pull}733[(#733)] + +[[release-notes-1.7.1]] +==== 1.7.1 - 2020/03/05 + +https://github.com/elastic/apm-agent-go/releases/tag/v1.7.1[View release] + +- Fix segfault on 32-bit architectures {pull}728[(#728)] + +[[release-notes-1.7.0]] +==== 1.7.0 - 2020/01/10 + +https://github.com/elastic/apm-agent-go/releases/tag/v1.7.0[View release] + + - Add span.context.destination.* {pull}664[(#664)] + - transport: fix Content-Type for pprof data {pull}679[(#679)] + - Add "tracestate" propagation {pull}690[(#690)] + - Add support for API Key auth {pull}698[(#698)] + - module/apmsql: report rows affected {pull}700[(#700)] + +[[release-notes-1.6.0]] +==== 1.6.0 - 2019/11/17 + +https://github.com/elastic/apm-agent-go/releases/tag/v1.6.0[View release] + + - module/apmhttp: add WithClientRequestName option {pull}609[(#609)] + - module/apmhttp: add WithPanicPropagation function {pull}611[(#611)] + - module/apmgoredis: add Client.RedisClient {pull}613[(#613)] + - Introduce apm.TraceFormatter, for formatting trace IDs {pull}635[(#635)] + - Report error cause(s), add support for errors.Unwrap {pull}638[(#638)] + - Setting `ELASTIC_APM_TRANSACTION_MAX_SPANS` to 0 now disables all spans {pull}640[(#640)] + - module/apmzerolog: add Writer.MinLevel {pull}641[(#641)] + - Introduce SetLabel and deprecate SetTag {pull}642[(#642)] + - Support central config for `ELASTIC_APM_CAPTURE_BODY` and `ELASTIC_APM_TRANSACTION_MAX_SPANS` {pull}648[(#648)] + - module/apmgorm: sql.ErrNoRows is no longer reported as an error {pull}645[(#645)] + - Server URL path is cleaned/canonicalizsed in order to avoid 301 redirects {pull}658[(#658)] + - `context.request.socket.remote_address` now reports the peer address {pull}662[(#662)] + - Experimental support for periodic CPU/heap profiling {pull}666[(#666)] + - module/apmnegroni: introduce tracing Negroni middleware {pull}671[(#671)] + - Unescape hyphens in k8s pod UIDs when the systemd cgroup driver is used {pull}672[(#672)] + - Read and propagate the standard W3C "traceparent" header {pull}674[(#674)] + +[[release-notes-1.5.0]] +==== 1.5.0 - 2019/07/31 + +https://github.com/elastic/apm-agent-go/releases/tag/v1.5.0[View release] + + - Add Context.SetCustom {pull}581[(#581)] + - Add support for extracting UUID-like container IDs {pull}577[(#577)] + - Introduce transaction/span breakdown metrics {pull}564[(#564)] + - Optimised HTTP request body capture {pull}592[(#592)] + - Fixed transaction encoding to drop tags (and other context) for non-sampled transactions {pull}593[(#593)] + - Introduce central config polling {pull}591[(#591)] + - Fixed apmgrpc client interceptor, propagating trace context for non-sampled transactions {pull}602[(#602)] + +[[release-notes-1.4.0]] +==== 1.4.0 - 2019/06/20 + +https://github.com/elastic/apm-agent-go/releases/tag/v1.4.0[View release] + + - Update opentracing-go dependency to v1.1.0 + - Update HTTP routers to return " unknown route" if route cannot be matched {pull}486[(#486)] + - module/apmchi: introduce instrumentation for go-chi/chi router {pull}495[(#495)] + - module/apmgoredis: introduce instrumentation for the go-redis/redis client {pull}505[(#505)] + - module/apmsql: exposed the QuerySignature function {pull}515[(#515)] + - module/apmgopg: introduce instrumentation for the go-pg/pg ORM {pull}516[(#516)] + - module/apmmongo: set minimum Go version to Go 1.10 {pull}522[(#522)] + - internal/sqlscanner: bug fix for multi-byte rune handling {pull}535[(#535)] + - module/apmgrpc: added WithServerRequestIgnorer server option {pull}531[(#531)] + - Introduce `ELASTIC_APM_GLOBAL_LABELS` config {pull}539[(#539)] + - module/apmgorm: register `row_query` callbacks {pull}532[(#532)] + - Introduce `ELASTIC_APM_STACK_TRACE_LIMIT` config {pull}559[(#559)] + - Include agent name/version and Go version in User-Agent {pull}560[(#560)] + - Truncate `error.culprit` at 1024 chars {pull}561[(#561)] + +[[release-notes-1.3.0]] +==== 1.3.0 - 2019/03/20 + +https://github.com/elastic/apm-agent-go/releases/tag/v1.3.0[View release] + + - Rename "metricset.labels" to "metricset.tags" {pull}438[(#438)] + - Introduce `ELASTIC_APM_DISABLE_METRICS` to disable metrics with matching names {pull}439[(#439)] + - module/apmelasticsearch: introduce instrumentation for Elasticsearch clients {pull}445[(#445)] + - module/apmmongo: introduce instrumentation for the MongoDB Go Driver {pull}452[(#452)] + - Introduce ErrorDetailer interface {pull}453[(#453)] + - module/apmhttp: add CloseIdleConnectons and CancelRequest to RoundTripper {pull}457[(#457)] + - Allow specifying transaction (span) ID via TransactionOptions/SpanOptions {pull}463[(#463)] + - module/apmzerolog: introduce zerolog log correlation and exception-tracking writer {pull}428[(#428)] + - module/apmelasticsearch: capture body for \_msearch, template and rollup search {pull}470[(#470)] + - Ended Transactions/Spans may now be used as parents {pull}478[(#478)] + - Introduce apm.DetachedContext for async/fire-and-forget trace propagation {pull}481[(#481)] + - module/apmechov4: add a copy of apmecho supporting echo/v4 {pull}477[(#477)] + +[[release-notes-1.2.0]] +==== 1.2.0 - 2019/01/17 + +https://github.com/elastic/apm-agent-go/releases/tag/v1.2.0[View release] + + - Add "transaction.sampled" to errors {pull}410[(#410)] + - Enforce license header in source files with go-licenser {pull}411[(#411)] + - module/apmot: ignore "follows-from" span references {pull}414[(#414)] + - module/apmot: report error log records {pull}415[(#415)] + - Introduce `ELASTIC_APM_CAPTURE_HEADERS` to control HTTP header capture {pull}418[(#418)] + - module/apmzap: introduce zap log correlation and exception-tracking hook {pull}426[(#426)] + - type Error implements error interface {pull}399[(#399)] + - Add "transaction.type" to errors {pull}433[(#433)] + - Added instrumentation-specific Go modules (i.e. one for each package under apm/module) {pull}405[(#405)] + +[[release-notes-1.1.3]] +==== 1.1.3 - 2019/01/06 + +https://github.com/elastic/apm-agent-go/releases/tag/v1.1.3[View release] + + - Remove the `agent.*` metrics {pull}407[(#407)] + - Add support for new github.com/pkg/errors.Frame type {pull}409[(#409)] + +[[release-notes-1.1.2]] +==== 1.1.2 - 2019/01/03 + +https://github.com/elastic/apm-agent-go/releases/tag/v1.1.2[View release] + + - Fix data race between Tracer.Active and Tracer.loop {pull}406[(#406)] + +[[release-notes-1.1.1]] +==== 1.1.1 - 2018/12/13 + +https://github.com/elastic/apm-agent-go/releases/tag/v1.1.1[View release] + + - CPU% metrics are now correctly in the range [0,1] + +[[release-notes-1.1.0]] +==== 1.1.0 - 2018/12/12 + +https://github.com/elastic/apm-agent-go/releases/tag/v1.1.0[View release] + + - Stop pooling Transaction/Span/Error, introduce internal pooled objects {pull}319[(#319)] + - Enable metrics collection with default interval of 30s {pull}322[(#322)] + - `ELASTIC_APM_SERVER_CERT` enables server certificate pinning {pull}325[(#325)] + - Add Docker container ID to metadata {pull}330[(#330)] + - Added distributed trace context propagation to apmgrpc {pull}335[(#335)] + - Introduce `Span.Subtype`, `Span.Action` {pull}332[(#332)] + - apm.StartSpanOptions fixed to stop ignoring options {pull}326[(#326)] + - Add Kubernetes pod info to metadata {pull}342[(#342)] + - module/apmsql: don't report driver.ErrBadConn, context.Canceled (#346, #348) + - Added ErrorLogRecord.Error field, for associating an error value with a log record {pull}380[(#380)] + - module/apmlogrus: introduce logrus exception-tracking hook, and log correlation {pull}381[(#381)] + - module/apmbeego: introduce Beego instrumentation module {pull}386[(#386)] + - module/apmhttp: report status code for client spans {pull}388[(#388)] + +[[release-notes-1.0.0]] +==== 1.0.0 - 2018/11/14 + +https://github.com/elastic/apm-agent-go/releases/tag/v1.0.0[View release] + + - Implement v2 intake protocol {pull}180[(#180)] + - Unexport Transaction.Timestamp and Span.Timestamp {pull}207[(#207)] + - Add jitter (+/-10%) to backoff on transport error {pull}212[(#212)] + - Add support for span tags {pull}213[(#213)] + - Require units for size configuration {pull}223[(#223)] + - Require units for duration configuration {pull}211[(#211)] + - Add support for multiple server URLs with failover {pull}233[(#233)] + - Add support for mixing OpenTracing spans with native transactions/spans {pull}235[(#235)] + - Drop SetHTTPResponseHeadersSent and SetHTTPResponseFinished methods from Context {pull}238[(#238)] + - Stop setting custom context (gin.handler) in apmgin {pull}238[(#238)] + - Set response context in errors reported by web modules {pull}238[(#238)] + - module/apmredigo: introduce gomodule/redigo instrumentation {pull}248[(#248)] + - Update Sampler interface to take TraceContext {pull}243[(#243)] + - Truncate SQL statements to a maximum of 10000 chars, all other strings to 1024 (#244, #276) + - Add leading slash to URLs in transaction/span context {pull}250[(#250)] + - Add `Transaction.Context` method for setting framework {pull}252[(#252)] + - Timestamps are now reported as usec since epoch, spans no longer use "start" offset {pull}257[(#257)] + - `ELASTIC_APM_SANITIZE_FIELD_NAMES` and `ELASTIC_APM_IGNORE_URLS` now use wildcard matching {pull}260[(#260)] + - Changed top-level package name to "apm", and canonical import path to "go.elastic.co/apm" {pull}202[(#202)] + - module/apmrestful: introduce emicklei/go-restful instrumentation {pull}270[(#270)] + - Fix panic handling in web instrumentations {pull}273[(#273)] + - Migrate internal/fastjson to go.elastic.co/fastjson {pull}275[(#275)] + - Report all HTTP request/response headers {pull}280[(#280)] + - Drop Context.SetCustom {pull}284[(#284)] + - Reuse memory for tags {pull}286[(#286)] + - Return a more helpful error message when /intake/v2/events 404s, to detect old servers {pull}290[(#290)] + - Implement test service for w3c/distributed-tracing test harness {pull}293[(#293)] + - End HTTP client spans on response body closure {pull}289[(#289)] + - module/apmgrpc requires Go 1.9+ {pull}300[(#300)] + - Invalid tag key characters are replaced with underscores {pull}308[(#308)] + - `ELASTIC_APM_LOG_FILE` and `ELASTIC_APM_LOG_LEVEL` introduced {pull}313[(#313)] + +[[release-notes-0.x]] +=== Go Agent version 0.x + +[[release-notes-0.5.2]] +==== 0.5.2 - 2018/09/19 + +https://github.com/elastic/apm-agent-go/releases/tag/v0.5.2[View release] + + - Fixed premature Span.End() in apmgorm callback, causing a data-race with captured errors {pull}229[(#229)] + +[[release-notes-0.5.1]] +==== 0.5.1 - 2018/09/05 + +https://github.com/elastic/apm-agent-go/releases/tag/v0.5.1[View release] + + - Fixed a bug causing error stacktraces and culprit to sometimes not be set {pull}204[(#204)] + +[[release-notes-0.5.0]] +==== 0.5.0 - 2018/08/27 + +https://github.com/elastic/apm-agent-go/releases/tag/v0.5.0[View release] + + - `ELASTIC_APM_SERVER_URL` now defaults to "http://localhost:8200" {pull}122[(#122)] + - `Transport.SetUserAgent` method added, enabling the User-Agent to be set programatically {pull}124[(#124)] + - Inlined functions are now properly reported in stacktraces {pull}127[(#127)] + - Support for the experimental metrics API added {pull}94[(#94)] + - module/apmsql: SQL is parsed to generate more useful span names {pull}129[(#129)] + - Basic vgo module added {pull}136[(#136)] + - module/apmhttprouter: added a wrapper type for `httprouter.Router` to simplify adding routes {pull}140[(#140)] + - Add `Transaction.Context` methods for setting user IDs {pull}144[(#144)] + - module/apmgocql: new instrumentation module, providing an observer for gocql {pull}148[(#148)] + - Add `ELASTIC_APM_SERVER_TIMEOUT` config {pull}157[(#157)] + - Add `ELASTIC_APM_IGNORE_URLS` config {pull}158[(#158)] + - module/apmsql: fix a bug preventing errors from being captured {pull}160[(#160)] + - Introduce `Tracer.StartTransactionOptions`, drop variadic args from `Tracer.StartTransaction` {pull}165[(#165)] + - module/apmgorm: introduce GORM instrumentation module (#169, #170) + - module/apmhttp: record outgoing request URLs in span context {pull}172[(#172)] + - module/apmot: introduce OpenTracing implementation {pull}173[(#173)] + +[[release-notes-0.4.0]] +==== 0.4.0 - 2018/06/17 + +https://github.com/elastic/apm-agent-go/releases/tag/v0.4.0[View release] + +First release of the Go agent for Elastic APM diff --git a/vendor/go.elastic.co/apm/CHANGELOG.md b/vendor/go.elastic.co/apm/CHANGELOG.md new file mode 100644 index 00000000000..e118c79191a --- /dev/null +++ b/vendor/go.elastic.co/apm/CHANGELOG.md @@ -0,0 +1 @@ +Release notes are now available in our documentation at ([elastic.co](https://www.elastic.co/guide/en/apm/agent/go/current/release-notes.html)) diff --git a/vendor/go.elastic.co/apm/CODE_OF_CONDUCT.md b/vendor/go.elastic.co/apm/CODE_OF_CONDUCT.md new file mode 100644 index 00000000000..c286a3152c4 --- /dev/null +++ b/vendor/go.elastic.co/apm/CODE_OF_CONDUCT.md @@ -0,0 +1,3 @@ +303 See Other + +Location: https://www.elastic.co/community/codeofconduct diff --git a/vendor/go.elastic.co/apm/CONTRIBUTING.md b/vendor/go.elastic.co/apm/CONTRIBUTING.md new file mode 100644 index 00000000000..cef6651e8db --- /dev/null +++ b/vendor/go.elastic.co/apm/CONTRIBUTING.md @@ -0,0 +1,91 @@ +# Contributing to the Go APM Agent + +The Go APM Agent is open source and we love to receive contributions from our community — you! + +There are many ways to contribute, from writing tutorials or blog posts, improving the +documentation, submitting bug reports and feature requests or writing code. + +You can get in touch with us through [Discuss](https://discuss.elastic.co/c/apm). +Feedback and ideas are always welcome. + +## Code contributions + +If you have a bugfix or new feature that involves significant changes that you would like to +contribute, please find or open an issue to discuss the changes first. It may be that somebody +is already working on it, or that there are particular issues that you should know about before +implementing the change. + +For minor changes (e.g. fixing a typo), you can just send your changes. + +### Submitting your changes + +Generally, we require that you test any code you are adding or modifying. Once your changes are +ready to submit for review: + +1. Sign the Contributor License Agreement + + Please make sure you have signed our [Contributor License Agreement](https://www.elastic.co/contributor-agreement/). + We are not asking you to assign copyright to us, but to give us the right to distribute + your code without restriction. We ask this of all contributors in order to assure our + users of the origin and continuing existence of the code. You only need to sign the CLA once. + +2. Test your changes + + Run the test suite to make sure that nothing is broken. + See [testing](#testing) for details. + +3. Review your changes + + Before sending your changes for review, it pays to review it yourself first! + + If you're making significant changes, please familiarize yourself with [Effective Go](https://golang.org/doc/effective_go.html) + and [go/wiki/CodeReviewComments](https://github.com/golang/go/wiki/CodeReviewComments). + These documents will walk you through writing idiomatic Go code, which we strive for. + + Here are a few things to check: + - format the code with [gofmt](https://golang.org/cmd/gofmt/) or [goimports](https://godoc.org/golang.org/x/tools/cmd/goimports) + - lint your code using [golint](https://github.com/golang/lint) + - check for common errors using [go vet](https://golang.org/cmd/vet/) + +4. Rebase your changes + + Update your local repository with the most recent code from the main repo, and rebase your + branch on top of the latest master branch. We prefer your initial changes to be squashed + into a single commit. Later, if we ask you to make changes, add them as separate commits. + This makes them easier to review. As a final step before merging we will either ask you to + squash all commits yourself or we'll do it for you. + +5. Submit a pull request + + Push your local changes to your forked copy of the repository and [submit a pull request](https://help.github.com/articles/using-pull-requests). + In the pull request, choose a title which sums up the changes that you have made, and in + the body provide more details about what your changes do, and the reason for making them. + Also mention the number of the issue where discussion has taken place, or issues that are + fixed/closed by the changes, e.g. "Closes #123". + +6. Be patient + + We might not be able to review your code as fast as we would like to, but we'll do our + best to dedicate it the attention it deserves. Your effort is much appreciated! + +### Testing + +The tests currently do not require any external resources, so just run `go test ./...`. +We test with all versions of Go from 1.8 onwards using [Travis CI](https://travis-ci.org). + +We track code coverage. 100% coverage is not a goal, but please do check that your tests +adequately cover the code using `go test -cover`. + +### Release procedure + +1. Update version.go and internal/apmversion/version.go, and then run "make update-modules" +1. Update [`CHANGELOG.asciidoc`](changelog.asciidoc), by adding a new version heading (`==== 1.x.x - yyyy/MM/dd`) and changing the base tag of the Unreleased comparison URL +1. For major and minor releases, update the EOL table in [`upgrading.asciidoc`](docs/upgrading.asciidoc). +1. Merge changes into github.com/elastic/apm-agent-go@master +1. Create tags: vN.N.N, and module/$MODULE/vN.N.N for each instrumentation module + + scripts/tagversion.sh + +1. Create release on GitHub + + hub release -d vN.N.N diff --git a/vendor/go.elastic.co/apm/Jenkinsfile b/vendor/go.elastic.co/apm/Jenkinsfile new file mode 100644 index 00000000000..07df4b18ce4 --- /dev/null +++ b/vendor/go.elastic.co/apm/Jenkinsfile @@ -0,0 +1,294 @@ +#!/usr/bin/env groovy + +@Library('apm@current') _ + +pipeline { + agent { label 'linux && immutable' } + environment { + REPO = 'apm-agent-go' + BASE_DIR = "src/go.elastic.co/apm" + NOTIFY_TO = credentials('notify-to') + JOB_GCS_BUCKET = credentials('gcs-bucket') + CODECOV_SECRET = 'secret/apm-team/ci/apm-agent-go-codecov' + GO111MODULE = 'on' + GOPATH = "${env.WORKSPACE}" + GOPROXY = 'https://proxy.golang.org' + HOME = "${env.WORKSPACE}" + GITHUB_CHECK_ITS_NAME = 'Integration Tests' + ITS_PIPELINE = 'apm-integration-tests-selector-mbp/master' + OPBEANS_REPO = 'opbeans-go' + } + options { + timeout(time: 1, unit: 'HOURS') + buildDiscarder(logRotator(numToKeepStr: '20', artifactNumToKeepStr: '20', daysToKeepStr: '30')) + timestamps() + ansiColor('xterm') + disableResume() + durabilityHint('PERFORMANCE_OPTIMIZED') + rateLimitBuilds(throttle: [count: 60, durationName: 'hour', userBoost: true]) + quietPeriod(10) + } + triggers { + issueCommentTrigger('(?i).*(?:jenkins\\W+)?run\\W+(?:the\\W+)?tests(?:\\W+please)?.*') + } + parameters { + string(name: 'GO_VERSION', defaultValue: "1.12.7", description: "Go version to use.") + booleanParam(name: 'Run_As_Master_Branch', defaultValue: false, description: 'Allow to run any steps on a PR, some steps normally only run on master branch.') + booleanParam(name: 'test_ci', defaultValue: true, description: 'Enable test') + booleanParam(name: 'docker_test_ci', defaultValue: true, description: 'Enable run docker tests') + booleanParam(name: 'bench_ci', defaultValue: true, description: 'Enable benchmarks') + } + stages { + stage('Initializing'){ + options { skipDefaultCheckout() } + environment { + GO_VERSION = "${params.GO_VERSION}" + PATH = "${env.PATH}:${env.WORKSPACE}/bin" + } + stages { + /** + Checkout the code and stash it, to use it on other stages. + */ + stage('Checkout') { + options { skipDefaultCheckout() } + steps { + pipelineManager([ cancelPreviousRunningBuilds: [ when: 'PR' ] ]) + deleteDir() + gitCheckout(basedir: "${BASE_DIR}", githubNotifyFirstTimeContributor: true, reference: '/var/lib/jenkins/.git-references/apm-agent-go.git') + stash allowEmpty: true, name: 'source', useDefaultExcludes: false + } + } + /** + Execute unit tests. + */ + stage('Tests') { + options { skipDefaultCheckout() } + when { + expression { return params.test_ci } + } + steps { + withGithubNotify(context: 'Tests', tab: 'tests') { + deleteDir() + unstash 'source' + dir("${BASE_DIR}"){ + script { + def go = readYaml(file: '.jenkins.yml') + def parallelTasks = [:] + go['GO_VERSION'].each{ version -> + parallelTasks["Go-${version}"] = generateStep(version) + } + // For the cutting edge + def edge = readYaml(file: '.jenkins-edge.yml') + edge['GO_VERSION'].each{ version -> + parallelTasks["Go-${version}"] = generateStepAndCatchError(version) + } + parallel(parallelTasks) + } + } + } + } + } + stage('Coverage') { + options { skipDefaultCheckout() } + when { + expression { return params.docker_test_ci } + } + steps { + withGithubNotify(context: 'Coverage') { + deleteDir() + unstash 'source' + dir("${BASE_DIR}"){ + sh script: './scripts/jenkins/before_install.sh', label: 'Install dependencies' + sh script: './scripts/jenkins/docker-test.sh', label: 'Docker tests' + } + } + } + post { + always { + coverageReport("${BASE_DIR}/build/coverage") + codecov(repo: env.REPO, basedir: "${BASE_DIR}", + flags: "-f build/coverage/coverage.cov -X search", + secret: "${CODECOV_SECRET}") + junit(allowEmptyResults: true, + keepLongStdio: true, + testResults: "${BASE_DIR}/build/junit-*.xml") + } + } + } + stage('Benchmark') { + agent { label 'linux && immutable' } + options { skipDefaultCheckout() } + when { + beforeAgent true + allOf { + anyOf { + branch 'master' + tag pattern: 'v\\d+\\.\\d+\\.\\d+.*', comparator: 'REGEXP' + expression { return params.Run_As_Master_Branch } + } + expression { return params.bench_ci } + } + } + steps { + withGithubNotify(context: 'Benchmark', tab: 'tests') { + deleteDir() + unstash 'source' + dir("${BASE_DIR}"){ + sh script: './scripts/jenkins/before_install.sh', label: 'Install dependencies' + sh script: './scripts/jenkins/bench.sh', label: 'Benchmarking' + sendBenchmarks(file: 'build/bench.out', index: 'benchmark-go') + } + } + } + } + } + } + stage('More OS') { + parallel { + stage('Windows') { + agent { label 'windows-2019-immutable' } + options { skipDefaultCheckout() } + environment { + GOROOT = "c:\\Go" + GOPATH = "${env.WORKSPACE}" + PATH = "${env.PATH};${env.GOROOT}\\bin;${env.GOPATH}\\bin" + GO_VERSION = "${params.GO_VERSION}" + } + steps { + withGithubNotify(context: 'Build-Test - Windows') { + cleanDir("${WORKSPACE}/${BASE_DIR}") + unstash 'source' + dir("${BASE_DIR}"){ + bat script: 'scripts/jenkins/windows/install-tools.bat', label: 'Install tools' + bat script: 'scripts/jenkins/windows/build-test.bat', label: 'Build and test' + } + } + } + post { + always { + junit(allowEmptyResults: true, keepLongStdio: true, testResults: "${BASE_DIR}/build/junit-*.xml") + } + } + } + stage('OSX') { + agent { label 'macosx' } + options { skipDefaultCheckout() } + environment { + GO_VERSION = "${params.GO_VERSION}" + PATH = "${env.PATH}:${env.WORKSPACE}/bin" + } + steps { + withGithubNotify(context: 'Build-Test - OSX') { + deleteDir() + unstash 'source' + dir("${BASE_DIR}"){ + sh script: './scripts/jenkins/before_install.sh', label: 'Install dependencies' + sh script: './scripts/jenkins/build-test.sh', label: 'Build and test' + } + } + } + post { + always { + junit(allowEmptyResults: true, keepLongStdio: true, testResults: "${BASE_DIR}/build/junit-*.xml") + deleteDir() + } + } + } + } + } + stage('Integration Tests') { + agent none + when { + beforeAgent true + allOf { + anyOf { + environment name: 'GIT_BUILD_CAUSE', value: 'pr' + expression { return !params.Run_As_Master_Branch } + } + } + } + steps { + build(job: env.ITS_PIPELINE, propagate: false, wait: false, + parameters: [string(name: 'INTEGRATION_TEST', value: 'Go'), + string(name: 'BUILD_OPTS', value: "--go-agent-version ${env.GIT_BASE_COMMIT} --opbeans-go-agent-branch ${env.GIT_BASE_COMMIT}"), + string(name: 'GITHUB_CHECK_NAME', value: env.GITHUB_CHECK_ITS_NAME), + string(name: 'GITHUB_CHECK_REPO', value: env.REPO), + string(name: 'GITHUB_CHECK_SHA1', value: env.GIT_BASE_COMMIT)]) + githubNotify(context: "${env.GITHUB_CHECK_ITS_NAME}", description: "${env.GITHUB_CHECK_ITS_NAME} ...", status: 'PENDING', targetUrl: "${env.JENKINS_URL}search/?q=${env.ITS_PIPELINE.replaceAll('/','+')}") + } + } + stage('Release') { + options { skipDefaultCheckout() } + when { + beforeAgent true + tag pattern: 'v\\d+\\.\\d+\\.\\d+', comparator: 'REGEXP' + } + stages { + stage('Opbeans') { + environment { + REPO_NAME = "${OPBEANS_REPO}" + GO_VERSION = "${params.GO_VERSION}" + } + steps { + deleteDir() + dir("${OPBEANS_REPO}"){ + git credentialsId: 'f6c7695a-671e-4f4f-a331-acdce44ff9ba', + url: "git@github.com:elastic/${OPBEANS_REPO}.git" + sh script: ".ci/bump-version.sh ${env.BRANCH_NAME}", label: 'Bump version' + // The opbeans-go pipeline will trigger a release for the master branch + gitPush() + // The opbeans-go pipeline will trigger a release for the release tag + gitCreateTag(tag: "${env.BRANCH_NAME}") + } + } + } + } + } + } + post { + cleanup { + notifyBuildResult() + } + } +} + +def generateStep(version){ + return { + node('linux && immutable'){ + try { + deleteDir() + unstash 'source' + echo "${version}" + dir("${BASE_DIR}"){ + withEnv(["GO_VERSION=${version}"]) { + // Another retry in case there are any environmental issues + // See https://issuetracker.google.com/issues/146072599 for more context + retry(2) { + sleep randomNumber(min: 2, max: 5) + sh script: './scripts/jenkins/before_install.sh', label: 'Install dependencies' + } + sh script: './scripts/jenkins/build-test.sh', label: 'Build and test' + } + } + } catch(e){ + error(e.toString()) + } finally { + junit(allowEmptyResults: true, + keepLongStdio: true, + testResults: "${BASE_DIR}/build/junit-*.xml") + } + } + } +} + +def generateStepAndCatchError(version){ + return { + catchError(buildResult: 'SUCCESS', message: 'Cutting Edge Tests', stageResult: 'UNSTABLE') { + generateStep(version) + } + } +} + +def cleanDir(path){ + powershell label: "Clean ${path}", script: "Remove-Item -Recurse -Force ${path}" +} diff --git a/vendor/go.elastic.co/apm/LICENSE b/vendor/go.elastic.co/apm/LICENSE new file mode 100644 index 00000000000..b1a731fb5a3 --- /dev/null +++ b/vendor/go.elastic.co/apm/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018 Elasticsearch BV + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/go.elastic.co/apm/Makefile b/vendor/go.elastic.co/apm/Makefile new file mode 100644 index 00000000000..572cd2647fc --- /dev/null +++ b/vendor/go.elastic.co/apm/Makefile @@ -0,0 +1,81 @@ +TEST_TIMEOUT?=5m +GO_LICENSER_EXCLUDE=stacktrace/testdata + +.PHONY: check +check: precheck check-modules test + +.PHONY: precheck +precheck: check-goimports check-lint check-vet check-dockerfile-testing check-licenses + +.PHONY: check-goimports +.PHONY: check-dockerfile-testing +.PHONY: check-lint +.PHONY: check-licenses +.PHONY: check-modules +ifeq ($(shell go run ./scripts/mingoversion.go -print 1.12),true) +check-goimports: + sh scripts/check_goimports.sh + +check-dockerfile-testing: + go run ./scripts/gendockerfile.go -d + +check-lint: + sh scripts/check_lint.sh + +check-licenses: + go-licenser -d $(patsubst %,-exclude %,$(GO_LICENSER_EXCLUDE)) . + +check-modules: + go run scripts/genmod/main.go -check . +else +check-goimports: +check-dockerfile-testing: +check-lint: +check-licenses: +check-modules: +endif + +.PHONY: check-vet +check-vet: + @for dir in $(shell scripts/moduledirs.sh); do (cd $$dir && go vet ./...) || exit $$?; done + +.PHONY: install +install: + go get -v -t ./... + +.PHONY: docker-test +docker-test: + scripts/docker-compose-testing run -T --rm go-agent-tests make test + +.PHONY: test +test: + @for dir in $(shell scripts/moduledirs.sh); do (cd $$dir && go test -v -timeout=$(TEST_TIMEOUT) ./...) || exit $$?; done + +.PHONY: coverage +coverage: + @bash scripts/test_coverage.sh + +.PHONY: fmt +fmt: + @GOIMPORTSFLAGS=-w sh scripts/goimports.sh + +.PHONY: clean +clean: + rm -fr docs/html + +.PHONY: update-modules +update-modules: + go run scripts/genmod/main.go . + +.PHONY: docs +docs: +ifdef ELASTIC_DOCS + $(ELASTIC_DOCS)/build_docs --direct_html --chunk=1 $(BUILD_DOCS_ARGS) --doc docs/index.asciidoc --out docs/html +else + @echo "\nELASTIC_DOCS is not defined.\n" + @exit 1 +endif + +.PHONY: update-licenses +update-licenses: + go-licenser $(patsubst %, -exclude %, $(GO_LICENSER_EXCLUDE)) . diff --git a/vendor/go.elastic.co/apm/NOTICE b/vendor/go.elastic.co/apm/NOTICE new file mode 100644 index 00000000000..147618ca4f9 --- /dev/null +++ b/vendor/go.elastic.co/apm/NOTICE @@ -0,0 +1,84 @@ +Elastic APM Go Agent +Copyright 2018-2019 Elasticsearch B.V. + +This product includes software developed at Elasticsearch, B.V. (https://www.elastic.co/). + +========================================= +Third party code included by the Go Agent +========================================= + +------------------------------------------------------------------------------------ +This project copies code from the Go standard library (https://github.com/golang/go) +------------------------------------------------------------------------------------ + +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------------- +This project copies code from Gorilla Mux (https://github.com/gorilla/mux) +-------------------------------------------------------------------------- + +Copyright (c) 2012-2018 The Gorilla Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +------------------------------------------------------------ +This project copies code from pq (https://github.com/lib/pq) +------------------------------------------------------------ + +Copyright (c) 2011-2013, 'pq' Contributors Portions Copyright (C) 2011 Blake Mizerany + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/go.elastic.co/apm/README.md b/vendor/go.elastic.co/apm/README.md new file mode 100644 index 00000000000..f173f83d4df --- /dev/null +++ b/vendor/go.elastic.co/apm/README.md @@ -0,0 +1,41 @@ +[![Build Status](https://apm-ci.elastic.co/buildStatus/icon?job=apm-agent-go/apm-agent-go-mbp/master)](https://apm-ci.elastic.co/job/apm-agent-go/job/apm-agent-go-mbp/job/master/) +[![GoDoc](https://godoc.org/go.elastic.co/apm?status.svg)](http://godoc.org/go.elastic.co/apm) +[![Travis-CI](https://travis-ci.org/elastic/apm-agent-go.svg)](https://travis-ci.org/elastic/apm-agent-go) +[![AppVeyor](https://ci.appveyor.com/api/projects/status/28fhswvqqc7p90f7?svg=true)](https://ci.appveyor.com/project/AndrewWilkins/apm-agent-go) +[![Go Report Card](https://goreportcard.com/badge/go.elastic.co/apm)](https://goreportcard.com/report/go.elastic.co/apm) +[![codecov.io](https://codecov.io/github/elastic/apm-agent-go/coverage.svg?branch=master)](https://codecov.io/github/elastic/apm-agent-go?branch=master) + +# apm-agent-go: APM Agent for Go + +This is the official Go package for [Elastic APM](https://www.elastic.co/solutions/apm). + +The Go agent enables you to trace the execution of operations in your application, +sending performance metrics and errors to the Elastic APM server. You can find a +list of the supported frameworks and other technologies in the [documentation](https://www.elastic.co/guide/en/apm/agent/go/current/supported-tech.html). + +We'd love to hear your feedback, please take a minute to fill out our [survey](https://docs.google.com/forms/d/e/1FAIpQLScbW7D8m-otPO7cxqeg7XstWR8vMnxG6brnXLs_TFVSTHuHvg/viewform?usp=sf_link). + +## Installation + +```bash +go get -u go.elastic.co/apm +``` + +## Requirements + +Tested with Go 1.8+ on Linux, Windows and MacOS. + +Requires [APM Server](https://github.com/elastic/apm-server) v6.5 or newer. + +## License + +Apache 2.0. + +## Documentation + +[Elastic APM Go documentation](https://www.elastic.co/guide/en/apm/agent/go/current/index.html). + +## Getting Help + +If you find a bug, please [report an issue](https://github.com/elastic/apm-agent-go/issues). +For any other assistance, please open or add to a topic on the [APM discuss forum](https://discuss.elastic.co/c/apm). diff --git a/vendor/go.elastic.co/apm/apmconfig/doc.go b/vendor/go.elastic.co/apm/apmconfig/doc.go new file mode 100644 index 00000000000..dd29e1525a6 --- /dev/null +++ b/vendor/go.elastic.co/apm/apmconfig/doc.go @@ -0,0 +1,20 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Package apmconfig provides an API for watching agent config +// changes. +package apmconfig diff --git a/vendor/go.elastic.co/apm/apmconfig/watcher.go b/vendor/go.elastic.co/apm/apmconfig/watcher.go new file mode 100644 index 00000000000..57ea3c41240 --- /dev/null +++ b/vendor/go.elastic.co/apm/apmconfig/watcher.go @@ -0,0 +1,54 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apmconfig + +import ( + "context" +) + +// Watcher provides an interface for watching config changes. +type Watcher interface { + // WatchConfig subscribes to changes to configuration for the agent, + // which must match the given ConfigSelector. + // + // If the watcher experiences an unexpected error fetching config, + // it will surface this in a Change with the Err field set. + // + // If the provided context is cancelled, or the watcher experiences + // a fatal condition, the returned channel will be closed. + WatchConfig(context.Context, WatchParams) <-chan Change +} + +// WatchParams holds parameters for watching for config changes. +type WatchParams struct { + // Service holds the name and optionally environment name used + // for filtering the config to watch. + Service struct { + Name string + Environment string + } +} + +// Change holds an agent configuration change: an error or the new config attributes. +type Change struct { + // Err holds an error that occurred while querying agent config. + Err error + + // Attrs holds the agent's configuration. May be empty. + Attrs map[string]string +} diff --git a/vendor/go.elastic.co/apm/apmtest/configwatcher.go b/vendor/go.elastic.co/apm/apmtest/configwatcher.go new file mode 100644 index 00000000000..723466a79e8 --- /dev/null +++ b/vendor/go.elastic.co/apm/apmtest/configwatcher.go @@ -0,0 +1,32 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apmtest + +import ( + "context" + + "go.elastic.co/apm/apmconfig" +) + +// WatchConfigFunc is a function type that implements apmconfig.Watcher. +type WatchConfigFunc func(context.Context, apmconfig.WatchParams) <-chan apmconfig.Change + +// WatchConfig returns f(ctx, params). +func (f WatchConfigFunc) WatchConfig(ctx context.Context, params apmconfig.WatchParams) <-chan apmconfig.Change { + return f(ctx, params) +} diff --git a/vendor/go.elastic.co/apm/apmtest/discard.go b/vendor/go.elastic.co/apm/apmtest/discard.go new file mode 100644 index 00000000000..186b21af83e --- /dev/null +++ b/vendor/go.elastic.co/apm/apmtest/discard.go @@ -0,0 +1,52 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apmtest + +import ( + "log" + + "go.elastic.co/apm" + "go.elastic.co/apm/transport/transporttest" +) + +// DiscardTracer is an apm.Tracer that discards all events. +// +// This tracer may be used by multiple tests, and so should +// not be modified or closed. +// +// Importing apmttest will close apm.DefaultTracer, and update +// it to this value. +var DiscardTracer *apm.Tracer + +// NewDiscardTracer returns a new apm.Tracer that discards all events. +func NewDiscardTracer() *apm.Tracer { + tracer, err := apm.NewTracerOptions(apm.TracerOptions{ + Transport: transporttest.Discard, + }) + if err != nil { + log.Fatal(err) + } + return tracer +} + +func init() { + apm.DefaultTracer.Close() + tracer := NewDiscardTracer() + DiscardTracer = tracer + apm.DefaultTracer = DiscardTracer +} diff --git a/vendor/go.elastic.co/apm/apmtest/httpsuite.go b/vendor/go.elastic.co/apm/apmtest/httpsuite.go new file mode 100644 index 00000000000..cf075b24abe --- /dev/null +++ b/vendor/go.elastic.co/apm/apmtest/httpsuite.go @@ -0,0 +1,137 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apmtest + +import ( + "net/http" + "net/http/httptest" + + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + "go.elastic.co/apm" + "go.elastic.co/apm/transport/transporttest" +) + +// HTTPTestSuite is a test suite for HTTP instrumentation modules. +type HTTPTestSuite struct { + suite.Suite + + // Handler holds an instrumented HTTP handler. Handler must + // support the following routes: + // + // GET /implicit_write (no explicit write on the response) + // GET /panic_before_write (panic without writing response) + // GET /panic_after_write (panic after writing response) + // + Handler http.Handler + + // Tracer is the apm.Tracer used to instrument Handler. + // + // HTTPTestSuite will close the tracer when all tests have + // been completed. + Tracer *apm.Tracer + + // Recorder is the transport used as the transport for Tracer. + Recorder *transporttest.RecorderTransport + + server *httptest.Server +} + +// SetupTest runs before each test. +func (s *HTTPTestSuite) SetupTest() { + s.Recorder.ResetPayloads() +} + +// SetupSuite runs before the tests in the suite are run. +func (s *HTTPTestSuite) SetupSuite() { + s.server = httptest.NewServer(s.Handler) +} + +// TearDownSuite runs after the tests in the suite are run. +func (s *HTTPTestSuite) TearDownSuite() { + if s.server != nil { + s.server.Close() + } + s.Tracer.Close() +} + +// TestImplicitWrite tests the behaviour of instrumented handlers +// for routes which do not explicitly write a response, but instead +// leave it to the framework to write an empty 200 response. +func (s *HTTPTestSuite) TestImplicitWrite() { + resp, err := http.Get(s.server.URL + "/implicit_write") + require.NoError(s.T(), err) + resp.Body.Close() + s.Equal(http.StatusOK, resp.StatusCode) + + s.Tracer.Flush(nil) + ps := s.Recorder.Payloads() + require.Len(s.T(), ps.Transactions, 1) + + tx := ps.Transactions[0] + s.Equal("HTTP 2xx", tx.Result) + s.Equal(resp.StatusCode, tx.Context.Response.StatusCode) +} + +// TestPanicBeforeWrite tests the behaviour of instrumented handlers +// for routes which panic before any headers are written. The handler +// is expected to recover the panic and write an empty 500 response. +func (s *HTTPTestSuite) TestPanicBeforeWrite() { + resp, err := http.Get(s.server.URL + "/panic_before_write") + require.NoError(s.T(), err) + resp.Body.Close() + s.Equal(http.StatusInternalServerError, resp.StatusCode) + + s.Tracer.Flush(nil) + ps := s.Recorder.Payloads() + require.Len(s.T(), ps.Transactions, 1) + require.Len(s.T(), ps.Errors, 1) + + tx := ps.Transactions[0] + s.Equal("HTTP 5xx", tx.Result) + s.Equal(resp.StatusCode, tx.Context.Response.StatusCode) + + e := ps.Errors[0] + s.Equal(tx.ID, e.ParentID) + s.Equal(resp.StatusCode, e.Context.Response.StatusCode) +} + +// TestPanicAfterWrite tests the behaviour of instrumented handlers +// for routes which panic after writing headers. The handler is +// expected to recover the panic without otherwise affecting the +// response. +func (s *HTTPTestSuite) TestPanicAfterWrite() { + resp, err := http.Get(s.server.URL + "/panic_after_write") + require.NoError(s.T(), err) + resp.Body.Close() + s.Equal(http.StatusOK, resp.StatusCode) + + s.Tracer.Flush(nil) + ps := s.Recorder.Payloads() + require.Len(s.T(), ps.Transactions, 1) + require.Len(s.T(), ps.Errors, 1) + + tx := ps.Transactions[0] + s.Equal("HTTP 2xx", tx.Result) + s.Equal(resp.StatusCode, tx.Context.Response.StatusCode) + + e := ps.Errors[0] + s.Equal(tx.ID, e.ParentID) + s.Equal(resp.StatusCode, e.Context.Response.StatusCode) +} diff --git a/vendor/go.elastic.co/apm/apmtest/recorder.go b/vendor/go.elastic.co/apm/apmtest/recorder.go new file mode 100644 index 00000000000..8f2e65519ee --- /dev/null +++ b/vendor/go.elastic.co/apm/apmtest/recorder.go @@ -0,0 +1,69 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apmtest + +import ( + "context" + "fmt" + + "go.elastic.co/apm" + "go.elastic.co/apm/model" + "go.elastic.co/apm/transport/transporttest" +) + +// NewRecordingTracer returns a new RecordingTracer, containing a new +// Tracer using the RecorderTransport stored inside. +func NewRecordingTracer() *RecordingTracer { + var result RecordingTracer + tracer, err := apm.NewTracerOptions(apm.TracerOptions{ + Transport: &result.RecorderTransport, + }) + if err != nil { + panic(err) + } + result.Tracer = tracer + return &result +} + +// RecordingTracer holds an apm.Tracer and transporttest.RecorderTransport. +type RecordingTracer struct { + *apm.Tracer + transporttest.RecorderTransport +} + +// WithTransaction calls rt.WithTransactionOptions with a zero apm.TransactionOptions. +func (rt *RecordingTracer) WithTransaction(f func(ctx context.Context)) (model.Transaction, []model.Span, []model.Error) { + return rt.WithTransactionOptions(apm.TransactionOptions{}, f) +} + +// WithTransactionOptions starts a transaction with the given options, +// calls f with the transaction in the provided context, ends the transaction +// and flushes the tracer, and then returns the resulting events. +func (rt *RecordingTracer) WithTransactionOptions(opts apm.TransactionOptions, f func(ctx context.Context)) (model.Transaction, []model.Span, []model.Error) { + tx := rt.StartTransactionOptions("name", "type", opts) + ctx := apm.ContextWithTransaction(context.Background(), tx) + f(ctx) + + tx.End() + rt.Flush(nil) + payloads := rt.Payloads() + if n := len(payloads.Transactions); n != 1 { + panic(fmt.Errorf("expected 1 transaction, got %d", n)) + } + return payloads.Transactions[0], payloads.Spans, payloads.Errors +} diff --git a/vendor/go.elastic.co/apm/apmtest/recordlogger.go b/vendor/go.elastic.co/apm/apmtest/recordlogger.go new file mode 100644 index 00000000000..9196c36851b --- /dev/null +++ b/vendor/go.elastic.co/apm/apmtest/recordlogger.go @@ -0,0 +1,60 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apmtest + +import "fmt" + +// RecordLogger is an implementation of apm.Logger, recording log entries. +type RecordLogger struct { + Records []LogRecord +} + +// Debugf logs debug messages. +func (l *RecordLogger) Debugf(format string, args ...interface{}) { + l.logf("debug", format, args...) +} + +// Errorf logs error messages. +func (l *RecordLogger) Errorf(format string, args ...interface{}) { + l.logf("error", format, args...) +} + +// Warningf logs error messages. +func (l *RecordLogger) Warningf(format string, args ...interface{}) { + l.logf("warning", format, args...) +} + +func (l *RecordLogger) logf(level string, format string, args ...interface{}) { + l.Records = append(l.Records, LogRecord{ + Level: level, + Format: format, + Message: fmt.Sprintf(format, args...), + }) +} + +// LogRecord holds the details of a log record. +type LogRecord struct { + // Level is the log level: "debug", "error", or "warning". + Level string + + // Format is the log message format, like "Thingy did foo %d times". + Format string + + // Message is the formatted message. + Message string +} diff --git a/vendor/go.elastic.co/apm/apmtest/testlogger.go b/vendor/go.elastic.co/apm/apmtest/testlogger.go new file mode 100644 index 00000000000..1bbbdf92a71 --- /dev/null +++ b/vendor/go.elastic.co/apm/apmtest/testlogger.go @@ -0,0 +1,45 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apmtest + +// TestLogger is an implementation of apm.Logger, +// logging to a testing.T. +type TestLogger struct { + l LogfLogger +} + +// NewTestLogger returns a new TestLogger that logs messages to l. +func NewTestLogger(l LogfLogger) TestLogger { + return TestLogger{l: l} +} + +// Debugf logs debug messages. +func (t TestLogger) Debugf(format string, args ...interface{}) { + t.l.Logf("[DEBUG] "+format, args...) +} + +// Errorf logs error messages. +func (t TestLogger) Errorf(format string, args ...interface{}) { + t.l.Logf("[ERROR] "+format, args...) +} + +// LogfLogger is an interface with the a Logf method, +// implemented by *testing.T and *testing.B. +type LogfLogger interface { + Logf(string, ...interface{}) +} diff --git a/vendor/go.elastic.co/apm/apmtest/withtransaction.go b/vendor/go.elastic.co/apm/apmtest/withtransaction.go new file mode 100644 index 00000000000..3c19998a498 --- /dev/null +++ b/vendor/go.elastic.co/apm/apmtest/withtransaction.go @@ -0,0 +1,39 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apmtest + +import ( + "context" + + "go.elastic.co/apm" + "go.elastic.co/apm/model" +) + +// WithTransaction is equivalent to calling WithTransactionOptions with a zero TransactionOptions. +func WithTransaction(f func(ctx context.Context)) (model.Transaction, []model.Span, []model.Error) { + return WithTransactionOptions(apm.TransactionOptions{}, f) +} + +// WithTransactionOptions calls f with a new context containing a transaction +// and transaction options, flushes the transaction to a test server, and returns +// the decoded transaction and any associated spans and errors. +func WithTransactionOptions(opts apm.TransactionOptions, f func(ctx context.Context)) (model.Transaction, []model.Span, []model.Error) { + tracer := NewRecordingTracer() + defer tracer.Close() + return tracer.WithTransactionOptions(opts, f) +} diff --git a/vendor/go.elastic.co/apm/breakdown.go b/vendor/go.elastic.co/apm/breakdown.go new file mode 100644 index 00000000000..df6cf519014 --- /dev/null +++ b/vendor/go.elastic.co/apm/breakdown.go @@ -0,0 +1,365 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apm + +import ( + "fmt" + "sync" + "sync/atomic" + "time" + "unsafe" + + "go.elastic.co/apm/model" +) + +const ( + // breakdownMetricsLimit is the maximum number of breakdown metric + // buckets to accumulate per reporting period. Metrics are broken + // down by {transactionType, transactionName, spanType, spanSubtype} + // tuples. + breakdownMetricsLimit = 1000 + + // appSpanType is the special span type associated with transactions, + // for reporting transaction self-time. + appSpanType = "app" + + // Breakdown metric names. + transactionDurationCountMetricName = "transaction.duration.count" + transactionDurationSumMetricName = "transaction.duration.sum.us" + transactionBreakdownCountMetricName = "transaction.breakdown.count" + spanSelfTimeCountMetricName = "span.self_time.count" + spanSelfTimeSumMetricName = "span.self_time.sum.us" +) + +type pad32 struct { + // Zero-sized on 64-bit architectures, 4 bytes on 32-bit. + _ [(unsafe.Alignof(uint64(0)) % 8) / 4]uintptr +} + +var ( + breakdownMetricsLimitWarning = fmt.Sprintf(` +The limit of %d breakdown metricsets has been reached, no new metricsets will be created. +Try to name your transactions so that there are less distinct transaction names.`[1:], + breakdownMetricsLimit, + ) +) + +// spanTimingsKey identifies a span type and subtype, for use as the key in +// spanTimingsMap. +type spanTimingsKey struct { + spanType string + spanSubtype string +} + +// spanTiming records the number of times a {spanType, spanSubtype} pair +// has occurred (within the context of a transaction group), along with +// the sum of the span durations. +type spanTiming struct { + duration int64 + count uintptr +} + +// spanTimingsMap records span timings for a transaction group. +type spanTimingsMap map[spanTimingsKey]spanTiming + +// add accumulates the timing for a {spanType, spanSubtype} pair. +func (m spanTimingsMap) add(spanType, spanSubtype string, d time.Duration) { + k := spanTimingsKey{spanType: spanType, spanSubtype: spanSubtype} + timing := m[k] + timing.count++ + timing.duration += int64(d) + m[k] = timing +} + +// reset resets m back to its initial zero state. +func (m spanTimingsMap) reset() { + for k := range m { + delete(m, k) + } +} + +// breakdownMetrics holds a pair of breakdown metrics maps. The "active" map +// accumulates new breakdown metrics, and is swapped with the "inactive" map +// just prior to when metrics gathering begins. When metrics gathering +// completes, the inactive map will be empty. +// +// breakdownMetrics may be written to concurrently by the tracer, and any +// number of other goroutines when a transaction cannot be enqueued. +type breakdownMetrics struct { + enabled bool + + mu sync.RWMutex + active, inactive *breakdownMetricsMap +} + +func newBreakdownMetrics() *breakdownMetrics { + return &breakdownMetrics{ + active: newBreakdownMetricsMap(), + inactive: newBreakdownMetricsMap(), + } +} + +type breakdownMetricsMap struct { + mu sync.RWMutex + entries int + m map[uint64][]*breakdownMetricsMapEntry + space []breakdownMetricsMapEntry +} + +func newBreakdownMetricsMap() *breakdownMetricsMap { + return &breakdownMetricsMap{ + m: make(map[uint64][]*breakdownMetricsMapEntry), + space: make([]breakdownMetricsMapEntry, breakdownMetricsLimit), + } +} + +type breakdownMetricsMapEntry struct { + breakdownTiming + breakdownMetricsKey +} + +// breakdownMetricsKey identifies a transaction group, and optionally a +// spanTimingsKey, for recording transaction and span breakdown metrics. +type breakdownMetricsKey struct { + transactionType string + transactionName string + spanTimingsKey +} + +func (k breakdownMetricsKey) hash() uint64 { + h := newFnv1a() + h.add(k.transactionType) + h.add(k.transactionName) + if k.spanType != "" { + h.add(k.spanType) + } + if k.spanSubtype != "" { + h.add(k.spanSubtype) + } + return uint64(h) +} + +// breakdownTiming holds breakdown metrics. +type breakdownTiming struct { + // transaction holds the "transaction.duration" metric values. + transaction spanTiming + + // Padding to ensure the span field below is 64-bit aligned. + _ pad32 + + // span holds the "span.self_time" metric values. + span spanTiming + + // breakdownCount records the number of transactions for which we + // have calculated breakdown metrics. If breakdown metrics are + // enabled, this will be equal transaction.count. + breakdownCount uintptr +} + +func (lhs *breakdownTiming) accumulate(rhs breakdownTiming) { + atomic.AddUintptr(&lhs.transaction.count, rhs.transaction.count) + atomic.AddInt64(&lhs.transaction.duration, rhs.transaction.duration) + atomic.AddUintptr(&lhs.span.count, rhs.span.count) + atomic.AddInt64(&lhs.span.duration, rhs.span.duration) + atomic.AddUintptr(&lhs.breakdownCount, rhs.breakdownCount) +} + +// recordTransaction records breakdown metrics for td into m. +// +// recordTransaction returns true if breakdown metrics were +// completely recorded, and false if any metrics were not +// recorded due to the limit being reached. +func (m *breakdownMetrics) recordTransaction(td *TransactionData) bool { + m.mu.RLock() + defer m.mu.RUnlock() + + k := breakdownMetricsKey{ + transactionType: td.Type, + transactionName: td.Name, + } + k.spanType = appSpanType + + var breakdownCount int + var transactionSpanTiming spanTiming + var transactionDuration = spanTiming{count: 1, duration: int64(td.Duration)} + if td.breakdownMetricsEnabled { + breakdownCount = 1 + endTime := td.timestamp.Add(td.Duration) + transactionSelfTime := td.Duration - td.childrenTimer.finalDuration(endTime) + transactionSpanTiming = spanTiming{count: 1, duration: int64(transactionSelfTime)} + } + + if !m.active.record(k, breakdownTiming{ + transaction: transactionDuration, + breakdownCount: uintptr(breakdownCount), + span: transactionSpanTiming, + }) { + // We couldn't record the transaction's metricset, so we won't + // be able to record spans for that transaction either. + return false + } + + ok := true + for sk, timing := range td.spanTimings { + k.spanTimingsKey = sk + ok = ok && m.active.record(k, breakdownTiming{span: timing}) + } + return ok +} + +// record records a single breakdown metric, identified by k. +func (m *breakdownMetricsMap) record(k breakdownMetricsKey, bt breakdownTiming) bool { + hash := k.hash() + m.mu.RLock() + entries, ok := m.m[hash] + m.mu.RUnlock() + var offset int + if ok { + for offset = range entries { + if entries[offset].breakdownMetricsKey == k { + // The append may reallocate the entries, but the + // entries are pointers into m.activeSpace. Therefore, + // entries' timings can safely be atomically incremented + // without holding the read lock. + entries[offset].breakdownTiming.accumulate(bt) + return true + } + } + offset++ // where to start searching with the write lock below + } + + m.mu.Lock() + entries, ok = m.m[hash] + if ok { + for i := range entries[offset:] { + if entries[offset+i].breakdownMetricsKey == k { + m.mu.Unlock() + entries[offset+i].breakdownTiming.accumulate(bt) + return true + } + } + } else if m.entries >= breakdownMetricsLimit { + m.mu.Unlock() + return false + } + entry := &m.space[m.entries] + *entry = breakdownMetricsMapEntry{ + breakdownTiming: bt, + breakdownMetricsKey: k, + } + m.m[hash] = append(entries, entry) + m.entries++ + m.mu.Unlock() + return true +} + +// gather is called by builtinMetricsGatherer to gather breakdown metrics. +func (m *breakdownMetrics) gather(out *Metrics) { + // Hold m.mu only long enough to swap m.active and m.inactive. + // This will be blocked by metric updates, but that's OK; only + // metrics gathering will be delayed. After swapping we do not + // need to hold m.mu, since nothing concurrently accesses + // m.inactive while the gatherer is iterating over it. + m.mu.Lock() + m.active, m.inactive = m.inactive, m.active + m.mu.Unlock() + + for hash, entries := range m.inactive.m { + for _, entry := range entries { + if entry.transaction.count > 0 { + out.transactionGroupMetrics = append(out.transactionGroupMetrics, &model.Metrics{ + Transaction: model.MetricsTransaction{ + Type: entry.transactionType, + Name: entry.transactionName, + }, + Samples: map[string]model.Metric{ + transactionDurationCountMetricName: { + Value: float64(entry.transaction.count), + }, + transactionDurationSumMetricName: { + Value: durationMicros(time.Duration(entry.transaction.duration)), + }, + transactionBreakdownCountMetricName: { + Value: float64(entry.breakdownCount), + }, + }, + }) + } + if entry.span.count > 0 { + out.transactionGroupMetrics = append(out.transactionGroupMetrics, &model.Metrics{ + Transaction: model.MetricsTransaction{ + Type: entry.transactionType, + Name: entry.transactionName, + }, + Span: model.MetricsSpan{ + Type: entry.spanType, + Subtype: entry.spanSubtype, + }, + Samples: map[string]model.Metric{ + spanSelfTimeCountMetricName: { + Value: float64(entry.span.count), + }, + spanSelfTimeSumMetricName: { + Value: durationMicros(time.Duration(entry.span.duration)), + }, + }, + }) + } + entry.breakdownMetricsKey = breakdownMetricsKey{} // release strings + } + delete(m.inactive.m, hash) + } + m.inactive.entries = 0 +} + +// childrenTimer tracks time spent by children of a transaction or span. +// +// childrenTimer is not goroutine-safe. +type childrenTimer struct { + // active holds the number active children. + active int + + // start holds the timestamp at which active went from zero to one. + start time.Time + + // totalDuration holds the total duration of time periods in which + // at least one child was active. + totalDuration time.Duration +} + +func (t *childrenTimer) childStarted(start time.Time) { + t.active++ + if t.active == 1 { + t.start = start + } +} + +func (t *childrenTimer) childEnded(end time.Time) { + t.active-- + if t.active == 0 { + t.totalDuration += end.Sub(t.start) + } +} + +func (t *childrenTimer) finalDuration(end time.Time) time.Duration { + if t.active > 0 { + t.active = 0 + t.totalDuration += end.Sub(t.start) + } + return t.totalDuration +} diff --git a/vendor/go.elastic.co/apm/builtin_metrics.go b/vendor/go.elastic.co/apm/builtin_metrics.go new file mode 100644 index 00000000000..546384efc8b --- /dev/null +++ b/vendor/go.elastic.co/apm/builtin_metrics.go @@ -0,0 +1,164 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apm + +import ( + "context" + "runtime" + + sysinfo "github.com/elastic/go-sysinfo" + "github.com/elastic/go-sysinfo/types" +) + +// builtinMetricsGatherer is an MetricsGatherer which gathers builtin metrics: +// - goroutines +// - memstats (allocations, usage, GC, etc.) +// - system and process CPU and memory usage +type builtinMetricsGatherer struct { + tracer *Tracer + lastSysMetrics sysMetrics +} + +func newBuiltinMetricsGatherer(t *Tracer) *builtinMetricsGatherer { + g := &builtinMetricsGatherer{tracer: t} + if metrics, err := gatherSysMetrics(); err == nil { + g.lastSysMetrics = metrics + } + return g +} + +// GatherMetrics gathers mem metrics into m. +func (g *builtinMetricsGatherer) GatherMetrics(ctx context.Context, m *Metrics) error { + m.Add("golang.goroutines", nil, float64(runtime.NumGoroutine())) + g.gatherSystemMetrics(m) + g.gatherMemStatsMetrics(m) + g.tracer.breakdownMetrics.gather(m) + return nil +} + +func (g *builtinMetricsGatherer) gatherSystemMetrics(m *Metrics) { + metrics, err := gatherSysMetrics() + if err != nil { + return + } + systemCPU, processCPU := calculateCPUUsage(metrics.cpu, g.lastSysMetrics.cpu) + m.Add("system.cpu.total.norm.pct", nil, systemCPU) + m.Add("system.process.cpu.total.norm.pct", nil, processCPU) + m.Add("system.memory.total", nil, float64(metrics.mem.system.Total)) + m.Add("system.memory.actual.free", nil, float64(metrics.mem.system.Available)) + m.Add("system.process.memory.size", nil, float64(metrics.mem.process.Virtual)) + m.Add("system.process.memory.rss.bytes", nil, float64(metrics.mem.process.Resident)) + g.lastSysMetrics = metrics +} + +func (g *builtinMetricsGatherer) gatherMemStatsMetrics(m *Metrics) { + var mem runtime.MemStats + runtime.ReadMemStats(&mem) + + addUint64 := func(name string, v uint64) { + m.Add(name, nil, float64(v)) + } + add := func(name string, v float64) { + m.Add(name, nil, v) + } + + addUint64("golang.heap.allocations.mallocs", mem.Mallocs) + addUint64("golang.heap.allocations.frees", mem.Frees) + addUint64("golang.heap.allocations.objects", mem.HeapObjects) + addUint64("golang.heap.allocations.total", mem.TotalAlloc) + addUint64("golang.heap.allocations.allocated", mem.HeapAlloc) + addUint64("golang.heap.allocations.idle", mem.HeapIdle) + addUint64("golang.heap.allocations.active", mem.HeapInuse) + addUint64("golang.heap.system.total", mem.Sys) + addUint64("golang.heap.system.obtained", mem.HeapSys) + addUint64("golang.heap.system.stack", mem.StackSys) + addUint64("golang.heap.system.released", mem.HeapReleased) + addUint64("golang.heap.gc.next_gc_limit", mem.NextGC) + addUint64("golang.heap.gc.total_count", uint64(mem.NumGC)) + addUint64("golang.heap.gc.total_pause.ns", mem.PauseTotalNs) + add("golang.heap.gc.cpu_fraction", mem.GCCPUFraction) +} + +func calculateCPUUsage(current, last cpuMetrics) (systemUsage, processUsage float64) { + idleDelta := current.system.Idle + current.system.IOWait - last.system.Idle - last.system.IOWait + systemTotalDelta := current.system.Total() - last.system.Total() + if systemTotalDelta <= 0 { + return 0, 0 + } + + idlePercent := float64(idleDelta) / float64(systemTotalDelta) + systemUsage = 1 - idlePercent + + processTotalDelta := current.process.Total() - last.process.Total() + processUsage = float64(processTotalDelta) / float64(systemTotalDelta) + + return systemUsage, processUsage +} + +type sysMetrics struct { + cpu cpuMetrics + mem memoryMetrics +} + +type cpuMetrics struct { + process types.CPUTimes + system types.CPUTimes +} + +type memoryMetrics struct { + process types.MemoryInfo + system *types.HostMemoryInfo +} + +func gatherSysMetrics() (sysMetrics, error) { + proc, err := sysinfo.Self() + if err != nil { + return sysMetrics{}, err + } + host, err := sysinfo.Host() + if err != nil { + return sysMetrics{}, err + } + hostTimes, err := host.CPUTime() + if err != nil { + return sysMetrics{}, err + } + hostMemory, err := host.Memory() + if err != nil { + return sysMetrics{}, err + } + procTimes, err := proc.CPUTime() + if err != nil { + return sysMetrics{}, err + } + procMemory, err := proc.Memory() + if err != nil { + return sysMetrics{}, err + } + + return sysMetrics{ + cpu: cpuMetrics{ + system: hostTimes, + process: procTimes, + }, + mem: memoryMetrics{ + system: hostMemory, + process: procMemory, + }, + }, nil +} diff --git a/vendor/go.elastic.co/apm/capturebody.go b/vendor/go.elastic.co/apm/capturebody.go new file mode 100644 index 00000000000..5e3f402b012 --- /dev/null +++ b/vendor/go.elastic.co/apm/capturebody.go @@ -0,0 +1,198 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apm + +import ( + "bytes" + "io" + "net/http" + "net/url" + "sync" + "unicode/utf8" + + "go.elastic.co/apm/internal/apmstrings" + "go.elastic.co/apm/model" +) + +// CaptureBodyMode holds a value indicating how a tracer should capture +// HTTP request bodies: for transactions, for errors, for both, or neither. +type CaptureBodyMode int + +const ( + // CaptureBodyOff disables capturing of HTTP request bodies. This is + // the default mode. + CaptureBodyOff CaptureBodyMode = 0 + + // CaptureBodyErrors captures HTTP request bodies for only errors. + CaptureBodyErrors CaptureBodyMode = 1 + + // CaptureBodyTransactions captures HTTP request bodies for only + // transactions. + CaptureBodyTransactions CaptureBodyMode = 1 << 1 + + // CaptureBodyAll captures HTTP request bodies for both transactions + // and errors. + CaptureBodyAll CaptureBodyMode = CaptureBodyErrors | CaptureBodyTransactions +) + +var bodyCapturerPool = sync.Pool{ + New: func() interface{} { + return &BodyCapturer{} + }, +} + +// CaptureHTTPRequestBody replaces req.Body and returns a possibly nil +// BodyCapturer which can later be passed to Context.SetHTTPRequestBody +// for setting the request body in a transaction or error context. If the +// tracer is not configured to capture HTTP request bodies, then req.Body +// is left alone and nil is returned. +// +// This must be called before the request body is read. The BodyCapturer's +// Discard method should be called after it is no longer needed, in order +// to recycle its memory. +func (t *Tracer) CaptureHTTPRequestBody(req *http.Request) *BodyCapturer { + if req.Body == nil { + return nil + } + captureBody := t.instrumentationConfig().captureBody + if captureBody == CaptureBodyOff { + return nil + } + + bc := bodyCapturerPool.Get().(*BodyCapturer) + bc.captureBody = captureBody + bc.request = req + bc.originalBody = req.Body + bc.buffer.Reset() + req.Body = bodyCapturerReadCloser{BodyCapturer: bc} + return bc +} + +// bodyCapturerReadCloser implements io.ReadCloser using the embedded BodyCapturer. +type bodyCapturerReadCloser struct { + *BodyCapturer +} + +// Close closes the original body. +func (bc bodyCapturerReadCloser) Close() error { + return bc.originalBody.Close() +} + +// Read reads from the original body, copying into bc.buffer. +func (bc bodyCapturerReadCloser) Read(p []byte) (int, error) { + n, err := bc.originalBody.Read(p) + if n > 0 { + bc.buffer.Write(p[:n]) + } + return n, err +} + +// BodyCapturer is returned by Tracer.CaptureHTTPRequestBody to later be +// passed to Context.SetHTTPRequestBody. +// +// Calling Context.SetHTTPRequestBody will reset req.Body to its original +// value, and invalidates the BodyCapturer. +type BodyCapturer struct { + captureBody CaptureBodyMode + + readbuf [bytes.MinRead]byte + buffer limitedBuffer + request *http.Request + originalBody io.ReadCloser +} + +// Discard discards the body capturer: the original request body is +// replaced, and the body capturer is returned to a pool for reuse. +// The BodyCapturer must not be used after calling this. +// +// Discard has no effect if bc is nil. +func (bc *BodyCapturer) Discard() { + if bc == nil { + return + } + bc.request.Body = bc.originalBody + bodyCapturerPool.Put(bc) +} + +func (bc *BodyCapturer) setContext(out *model.RequestBody) bool { + if bc.request.PostForm != nil { + // We must copy the map in case we need to + // sanitize the values. Ideally we should only + // copy if sanitization is necessary, but body + // capture shouldn't typically be enabled so + // we don't currently optimize this. + postForm := make(url.Values, len(bc.request.PostForm)) + for k, v := range bc.request.PostForm { + vcopy := make([]string, len(v)) + for i := range vcopy { + vcopy[i] = truncateString(v[i]) + } + postForm[k] = vcopy + } + out.Form = postForm + return true + } + + body, n := apmstrings.Truncate(bc.buffer.String(), stringLengthLimit) + if n == stringLengthLimit { + // There is at least enough data in the buffer + // to hit the string length limit, so we don't + // need to read from bc.originalBody as well. + out.Raw = body + return true + } + + // Read the remaining body, limiting to the maximum number of bytes + // that could make up the truncation limit. We ignore any errors here, + // and just return whatever we can. + rem := utf8.UTFMax * (stringLengthLimit - n) + for { + buf := bc.readbuf[:] + if rem < bytes.MinRead { + buf = buf[:rem] + } + n, err := bc.originalBody.Read(buf) + if n > 0 { + bc.buffer.Write(buf[:n]) + rem -= n + } + if rem == 0 || err != nil { + break + } + } + body, _ = apmstrings.Truncate(bc.buffer.String(), stringLengthLimit) + out.Raw = body + return body != "" +} + +type limitedBuffer struct { + bytes.Buffer +} + +func (b *limitedBuffer) Write(p []byte) (n int, err error) { + rem := (stringLengthLimit * utf8.UTFMax) - b.Len() + n = len(p) + if n > rem { + p = p[:rem] + } + written, err := b.Buffer.Write(p) + if err != nil { + n = written + } + return n, err +} diff --git a/vendor/go.elastic.co/apm/config.go b/vendor/go.elastic.co/apm/config.go new file mode 100644 index 00000000000..10f86e26bd3 --- /dev/null +++ b/vendor/go.elastic.co/apm/config.go @@ -0,0 +1,448 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apm + +import ( + "os" + "path/filepath" + "runtime" + "strconv" + "strings" + "sync/atomic" + "time" + "unsafe" + + "github.com/pkg/errors" + + "go.elastic.co/apm/internal/configutil" + "go.elastic.co/apm/internal/wildcard" + "go.elastic.co/apm/model" +) + +const ( + envMetricsInterval = "ELASTIC_APM_METRICS_INTERVAL" + envMaxSpans = "ELASTIC_APM_TRANSACTION_MAX_SPANS" + envTransactionSampleRate = "ELASTIC_APM_TRANSACTION_SAMPLE_RATE" + envSanitizeFieldNames = "ELASTIC_APM_SANITIZE_FIELD_NAMES" + envCaptureHeaders = "ELASTIC_APM_CAPTURE_HEADERS" + envCaptureBody = "ELASTIC_APM_CAPTURE_BODY" + envServiceName = "ELASTIC_APM_SERVICE_NAME" + envServiceVersion = "ELASTIC_APM_SERVICE_VERSION" + envEnvironment = "ELASTIC_APM_ENVIRONMENT" + envSpanFramesMinDuration = "ELASTIC_APM_SPAN_FRAMES_MIN_DURATION" + envActive = "ELASTIC_APM_ACTIVE" + envAPIRequestSize = "ELASTIC_APM_API_REQUEST_SIZE" + envAPIRequestTime = "ELASTIC_APM_API_REQUEST_TIME" + envAPIBufferSize = "ELASTIC_APM_API_BUFFER_SIZE" + envMetricsBufferSize = "ELASTIC_APM_METRICS_BUFFER_SIZE" + envDisableMetrics = "ELASTIC_APM_DISABLE_METRICS" + envGlobalLabels = "ELASTIC_APM_GLOBAL_LABELS" + envStackTraceLimit = "ELASTIC_APM_STACK_TRACE_LIMIT" + envCentralConfig = "ELASTIC_APM_CENTRAL_CONFIG" + envBreakdownMetrics = "ELASTIC_APM_BREAKDOWN_METRICS" + envUseElasticTraceparentHeader = "ELASTIC_APM_USE_ELASTIC_TRACEPARENT_HEADER" + + // NOTE(axw) profiling environment variables are experimental. + // They may be removed in a future minor version without being + // considered a breaking change. + envCPUProfileInterval = "ELASTIC_APM_CPU_PROFILE_INTERVAL" + envCPUProfileDuration = "ELASTIC_APM_CPU_PROFILE_DURATION" + envHeapProfileInterval = "ELASTIC_APM_HEAP_PROFILE_INTERVAL" + + defaultAPIRequestSize = 750 * configutil.KByte + defaultAPIRequestTime = 10 * time.Second + defaultAPIBufferSize = 1 * configutil.MByte + defaultMetricsBufferSize = 750 * configutil.KByte + defaultMetricsInterval = 30 * time.Second + defaultMaxSpans = 500 + defaultCaptureHeaders = true + defaultCaptureBody = CaptureBodyOff + defaultSpanFramesMinDuration = 5 * time.Millisecond + defaultStackTraceLimit = 50 + + minAPIBufferSize = 10 * configutil.KByte + maxAPIBufferSize = 100 * configutil.MByte + minAPIRequestSize = 1 * configutil.KByte + maxAPIRequestSize = 5 * configutil.MByte + minMetricsBufferSize = 10 * configutil.KByte + maxMetricsBufferSize = 100 * configutil.MByte +) + +var ( + defaultSanitizedFieldNames = configutil.ParseWildcardPatterns(strings.Join([]string{ + "password", + "passwd", + "pwd", + "secret", + "*key", + "*token*", + "*session*", + "*credit*", + "*card*", + "authorization", + "set-cookie", + }, ",")) + + globalLabels = func() model.StringMap { + var labels model.StringMap + for _, kv := range configutil.ParseListEnv(envGlobalLabels, ",", nil) { + i := strings.IndexRune(kv, '=') + if i > 0 { + k, v := strings.TrimSpace(kv[:i]), strings.TrimSpace(kv[i+1:]) + labels = append(labels, model.StringMapItem{ + Key: cleanLabelKey(k), + Value: truncateString(v), + }) + } + } + return labels + }() +) + +func initialRequestDuration() (time.Duration, error) { + return configutil.ParseDurationEnv(envAPIRequestTime, defaultAPIRequestTime) +} + +func initialMetricsInterval() (time.Duration, error) { + return configutil.ParseDurationEnv(envMetricsInterval, defaultMetricsInterval) +} + +func initialMetricsBufferSize() (int, error) { + size, err := configutil.ParseSizeEnv(envMetricsBufferSize, defaultMetricsBufferSize) + if err != nil { + return 0, err + } + if size < minMetricsBufferSize || size > maxMetricsBufferSize { + return 0, errors.Errorf( + "%s must be at least %s and less than %s, got %s", + envMetricsBufferSize, minMetricsBufferSize, maxMetricsBufferSize, size, + ) + } + return int(size), nil +} + +func initialAPIBufferSize() (int, error) { + size, err := configutil.ParseSizeEnv(envAPIBufferSize, defaultAPIBufferSize) + if err != nil { + return 0, err + } + if size < minAPIBufferSize || size > maxAPIBufferSize { + return 0, errors.Errorf( + "%s must be at least %s and less than %s, got %s", + envAPIBufferSize, minAPIBufferSize, maxAPIBufferSize, size, + ) + } + return int(size), nil +} + +func initialAPIRequestSize() (int, error) { + size, err := configutil.ParseSizeEnv(envAPIRequestSize, defaultAPIRequestSize) + if err != nil { + return 0, err + } + if size < minAPIRequestSize || size > maxAPIRequestSize { + return 0, errors.Errorf( + "%s must be at least %s and less than %s, got %s", + envAPIRequestSize, minAPIRequestSize, maxAPIRequestSize, size, + ) + } + return int(size), nil +} + +func initialMaxSpans() (int, error) { + value := os.Getenv(envMaxSpans) + if value == "" { + return defaultMaxSpans, nil + } + max, err := strconv.Atoi(value) + if err != nil { + return 0, errors.Wrapf(err, "failed to parse %s", envMaxSpans) + } + return max, nil +} + +// initialSampler returns a nil Sampler if all transactions should be sampled. +func initialSampler() (Sampler, error) { + value := os.Getenv(envTransactionSampleRate) + return parseSampleRate(envTransactionSampleRate, value) +} + +// parseSampleRate parses a numeric sampling rate in the range [0,1.0], returning a Sampler. +func parseSampleRate(name, value string) (Sampler, error) { + if value == "" { + value = "1" + } + ratio, err := strconv.ParseFloat(value, 64) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse %s", name) + } + if ratio < 0.0 || ratio > 1.0 { + return nil, errors.Errorf( + "invalid value for %s: %s (out of range [0,1.0])", + name, value, + ) + } + return NewRatioSampler(ratio), nil +} + +func initialSanitizedFieldNames() wildcard.Matchers { + return configutil.ParseWildcardPatternsEnv(envSanitizeFieldNames, defaultSanitizedFieldNames) +} + +func initialCaptureHeaders() (bool, error) { + return configutil.ParseBoolEnv(envCaptureHeaders, defaultCaptureHeaders) +} + +func initialCaptureBody() (CaptureBodyMode, error) { + value := os.Getenv(envCaptureBody) + if value == "" { + return defaultCaptureBody, nil + } + return parseCaptureBody(envCaptureBody, value) +} + +func parseCaptureBody(name, value string) (CaptureBodyMode, error) { + switch strings.TrimSpace(strings.ToLower(value)) { + case "all": + return CaptureBodyAll, nil + case "errors": + return CaptureBodyErrors, nil + case "transactions": + return CaptureBodyTransactions, nil + case "off": + return CaptureBodyOff, nil + } + return -1, errors.Errorf("invalid %s value %q", name, value) +} + +func initialService() (name, version, environment string) { + name = os.Getenv(envServiceName) + version = os.Getenv(envServiceVersion) + environment = os.Getenv(envEnvironment) + if name == "" { + name = filepath.Base(os.Args[0]) + if runtime.GOOS == "windows" { + name = strings.TrimSuffix(name, filepath.Ext(name)) + } + } + name = sanitizeServiceName(name) + return name, version, environment +} + +func initialSpanFramesMinDuration() (time.Duration, error) { + return configutil.ParseDurationEnv(envSpanFramesMinDuration, defaultSpanFramesMinDuration) +} + +func initialActive() (bool, error) { + return configutil.ParseBoolEnv(envActive, true) +} + +func initialDisabledMetrics() wildcard.Matchers { + return configutil.ParseWildcardPatternsEnv(envDisableMetrics, nil) +} + +func initialStackTraceLimit() (int, error) { + value := os.Getenv(envStackTraceLimit) + if value == "" { + return defaultStackTraceLimit, nil + } + limit, err := strconv.Atoi(value) + if err != nil { + return 0, errors.Wrapf(err, "failed to parse %s", envStackTraceLimit) + } + return limit, nil +} + +func initialCentralConfigEnabled() (bool, error) { + return configutil.ParseBoolEnv(envCentralConfig, true) +} + +func initialBreakdownMetricsEnabled() (bool, error) { + return configutil.ParseBoolEnv(envBreakdownMetrics, true) +} + +func initialUseElasticTraceparentHeader() (bool, error) { + return configutil.ParseBoolEnv(envUseElasticTraceparentHeader, true) +} + +func initialCPUProfileIntervalDuration() (time.Duration, time.Duration, error) { + interval, err := configutil.ParseDurationEnv(envCPUProfileInterval, 0) + if err != nil || interval <= 0 { + return 0, 0, err + } + duration, err := configutil.ParseDurationEnv(envCPUProfileDuration, 0) + if err != nil || duration <= 0 { + return 0, 0, err + } + return interval, duration, nil +} + +func initialHeapProfileInterval() (time.Duration, error) { + return configutil.ParseDurationEnv(envHeapProfileInterval, 0) +} + +// updateRemoteConfig updates t and cfg with changes held in "attrs", and reverts to local +// config for config attributes that have been removed (exist in old but not in attrs). +// +// On return from updateRemoteConfig, unapplied config will have been removed from attrs. +func (t *Tracer) updateRemoteConfig(logger WarningLogger, old, attrs map[string]string) { + warningf := func(string, ...interface{}) {} + debugf := func(string, ...interface{}) {} + errorf := func(string, ...interface{}) {} + if logger != nil { + warningf = logger.Warningf + debugf = logger.Debugf + errorf = logger.Errorf + } + envName := func(k string) string { + return "ELASTIC_APM_" + strings.ToUpper(k) + } + + var updates []func(cfg *instrumentationConfig) + for k, v := range attrs { + if oldv, ok := old[k]; ok && oldv == v { + continue + } + switch envName(k) { + case envCaptureBody: + value, err := parseCaptureBody(k, v) + if err != nil { + errorf("central config failure: %s", err) + delete(attrs, k) + continue + } else { + updates = append(updates, func(cfg *instrumentationConfig) { + cfg.captureBody = value + }) + } + case envMaxSpans: + value, err := strconv.Atoi(v) + if err != nil { + errorf("central config failure: failed to parse %s: %s", k, err) + delete(attrs, k) + continue + } else { + updates = append(updates, func(cfg *instrumentationConfig) { + cfg.maxSpans = value + }) + } + case envTransactionSampleRate: + sampler, err := parseSampleRate(k, v) + if err != nil { + errorf("central config failure: %s", err) + delete(attrs, k) + continue + } else { + updates = append(updates, func(cfg *instrumentationConfig) { + cfg.sampler = sampler + }) + } + default: + warningf("central config failure: unsupported config: %s", k) + delete(attrs, k) + continue + } + debugf("central config update: updated %s to %s", k, v) + } + for k := range old { + if _, ok := attrs[k]; ok { + continue + } + updates = append(updates, func(cfg *instrumentationConfig) { + if f, ok := cfg.local[envName(k)]; ok { + f(&cfg.instrumentationConfigValues) + } + }) + debugf("central config update: reverted %s to local config", k) + } + if updates != nil { + remote := make(map[string]struct{}) + for k := range attrs { + remote[envName(k)] = struct{}{} + } + t.updateInstrumentationConfig(func(cfg *instrumentationConfig) { + cfg.remote = remote + for _, update := range updates { + update(cfg) + } + }) + } +} + +// instrumentationConfig returns the current instrumentationConfig. +// +// The returned value is immutable. +func (t *Tracer) instrumentationConfig() *instrumentationConfig { + config := atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&t.instrumentationConfigInternal))) + return (*instrumentationConfig)(config) +} + +// setLocalInstrumentationConfig sets local transaction configuration with +// the specified environment variable key. +func (t *Tracer) setLocalInstrumentationConfig(envKey string, f func(cfg *instrumentationConfigValues)) { + t.updateInstrumentationConfig(func(cfg *instrumentationConfig) { + cfg.local[envKey] = f + if _, ok := cfg.remote[envKey]; !ok { + f(&cfg.instrumentationConfigValues) + } + }) +} + +func (t *Tracer) updateInstrumentationConfig(f func(cfg *instrumentationConfig)) { + for { + oldConfig := t.instrumentationConfig() + newConfig := *oldConfig + f(&newConfig) + if atomic.CompareAndSwapPointer( + (*unsafe.Pointer)(unsafe.Pointer(&t.instrumentationConfigInternal)), + unsafe.Pointer(oldConfig), + unsafe.Pointer(&newConfig), + ) { + return + } + } +} + +// instrumentationConfig holds current configuration values, as well as information +// required to revert from remote to local configuration. +type instrumentationConfig struct { + instrumentationConfigValues + + // local holds functions for setting instrumentationConfigValues to the most + // recently, locally specified configuration. + local map[string]func(*instrumentationConfigValues) + + // remote holds the environment variable keys for applied remote config. + remote map[string]struct{} +} + +// instrumentationConfigValues holds configuration that is accessible outside of the +// tracer loop, for instrumentation: StartTransaction, StartSpan, CaptureError, etc. +// +// NOTE(axw) when adding configuration here, you must also update `newTracer` to +// set the initial entry in instrumentationConfig.local, in order to properly reset +// to the local value, even if the default is the zero value. +type instrumentationConfigValues struct { + captureBody CaptureBodyMode + captureHeaders bool + maxSpans int + sampler Sampler + spanFramesMinDuration time.Duration + stackTraceLimit int + propagateLegacyHeader bool +} diff --git a/vendor/go.elastic.co/apm/context.go b/vendor/go.elastic.co/apm/context.go new file mode 100644 index 00000000000..9ab5e93f88a --- /dev/null +++ b/vendor/go.elastic.co/apm/context.go @@ -0,0 +1,256 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apm + +import ( + "fmt" + "net/http" + + "go.elastic.co/apm/internal/apmhttputil" + "go.elastic.co/apm/model" +) + +// Context provides methods for setting transaction and error context. +// +// NOTE this is entirely unrelated to the standard library's context.Context. +type Context struct { + model model.Context + request model.Request + requestBody model.RequestBody + requestSocket model.RequestSocket + response model.Response + user model.User + service model.Service + serviceFramework model.Framework + captureHeaders bool + captureBodyMask CaptureBodyMode +} + +func (c *Context) build() *model.Context { + switch { + case c.model.Request != nil: + case c.model.Response != nil: + case c.model.User != nil: + case c.model.Service != nil: + case len(c.model.Tags) != 0: + case len(c.model.Custom) != 0: + default: + return nil + } + return &c.model +} + +func (c *Context) reset() { + *c = Context{ + model: model.Context{ + Custom: c.model.Custom[:0], + Tags: c.model.Tags[:0], + }, + captureBodyMask: c.captureBodyMask, + request: model.Request{ + Headers: c.request.Headers[:0], + }, + response: model.Response{ + Headers: c.response.Headers[:0], + }, + } +} + +// SetTag calls SetLabel(key, value). +// +// SetTag is deprecated, and will be removed in a future major version. +func (c *Context) SetTag(key, value string) { + c.SetLabel(key, value) +} + +// SetLabel sets a label in the context. +// +// Invalid characters ('.', '*', and '"') in the key will be replaced with +// underscores. +// +// If the value is numerical or boolean, then it will be sent to the server +// as a JSON number or boolean; otherwise it will converted to a string, using +// `fmt.Sprint` if necessary. String values longer than 1024 characters will +// be truncated. +func (c *Context) SetLabel(key string, value interface{}) { + // Note that we do not attempt to de-duplicate the keys. + // This is OK, since json.Unmarshal will always take the + // final instance. + c.model.Tags = append(c.model.Tags, model.IfaceMapItem{ + Key: cleanLabelKey(key), + Value: makeLabelValue(value), + }) +} + +// SetCustom sets custom context. +// +// Invalid characters ('.', '*', and '"') in the key will be +// replaced with an underscore. The value may be any JSON-encodable +// value. +func (c *Context) SetCustom(key string, value interface{}) { + // Note that we do not attempt to de-duplicate the keys. + // This is OK, since json.Unmarshal will always take the + // final instance. + c.model.Custom = append(c.model.Custom, model.IfaceMapItem{ + Key: cleanLabelKey(key), + Value: value, + }) +} + +// SetFramework sets the framework name and version in the context. +// +// This is used for identifying the framework in which the context +// was created, such as Gin or Echo. +// +// If the name is empty, this is a no-op. If version is empty, then +// it will be set to "unspecified". +func (c *Context) SetFramework(name, version string) { + if name == "" { + return + } + if version == "" { + // Framework version is required. + version = "unspecified" + } + c.serviceFramework = model.Framework{ + Name: truncateString(name), + Version: truncateString(version), + } + c.service.Framework = &c.serviceFramework + c.model.Service = &c.service +} + +// SetHTTPRequest sets details of the HTTP request in the context. +// +// This function relates to server-side requests. Various proxy +// forwarding headers are taken into account to reconstruct the URL, +// and determining the client address. +// +// If the request URL contains user info, it will be removed and +// excluded from the URL's "full" field. +// +// If the request contains HTTP Basic Authentication, the username +// from that will be recorded in the context. Otherwise, if the +// request contains user info in the URL (i.e. a client-side URL), +// that will be used. +func (c *Context) SetHTTPRequest(req *http.Request) { + // Special cases to avoid calling into fmt.Sprintf in most cases. + var httpVersion string + switch { + case req.ProtoMajor == 1 && req.ProtoMinor == 1: + httpVersion = "1.1" + case req.ProtoMajor == 2 && req.ProtoMinor == 0: + httpVersion = "2.0" + default: + httpVersion = fmt.Sprintf("%d.%d", req.ProtoMajor, req.ProtoMinor) + } + + c.request = model.Request{ + Body: c.request.Body, + URL: apmhttputil.RequestURL(req), + Method: truncateString(req.Method), + HTTPVersion: httpVersion, + Cookies: req.Cookies(), + } + c.model.Request = &c.request + + if c.captureHeaders { + for k, values := range req.Header { + if k == "Cookie" { + // We capture cookies in the request structure. + continue + } + c.request.Headers = append(c.request.Headers, model.Header{ + Key: k, Values: values, + }) + } + } + + c.requestSocket = model.RequestSocket{ + Encrypted: req.TLS != nil, + RemoteAddress: apmhttputil.RemoteAddr(req), + } + if c.requestSocket != (model.RequestSocket{}) { + c.request.Socket = &c.requestSocket + } + + username, _, ok := req.BasicAuth() + if !ok && req.URL.User != nil { + username = req.URL.User.Username() + } + c.user.Username = truncateString(username) + if c.user.Username != "" { + c.model.User = &c.user + } +} + +// SetHTTPRequestBody sets the request body in context given a (possibly nil) +// BodyCapturer returned by Tracer.CaptureHTTPRequestBody. +func (c *Context) SetHTTPRequestBody(bc *BodyCapturer) { + if bc == nil || bc.captureBody&c.captureBodyMask == 0 { + return + } + if bc.setContext(&c.requestBody) { + c.request.Body = &c.requestBody + } +} + +// SetHTTPResponseHeaders sets the HTTP response headers in the context. +func (c *Context) SetHTTPResponseHeaders(h http.Header) { + if !c.captureHeaders { + return + } + for k, values := range h { + c.response.Headers = append(c.response.Headers, model.Header{ + Key: k, Values: values, + }) + } + if len(c.response.Headers) != 0 { + c.model.Response = &c.response + } +} + +// SetHTTPStatusCode records the HTTP response status code. +func (c *Context) SetHTTPStatusCode(statusCode int) { + c.response.StatusCode = statusCode + c.model.Response = &c.response +} + +// SetUserID sets the ID of the authenticated user. +func (c *Context) SetUserID(id string) { + c.user.ID = truncateString(id) + if c.user.ID != "" { + c.model.User = &c.user + } +} + +// SetUserEmail sets the email for the authenticated user. +func (c *Context) SetUserEmail(email string) { + c.user.Email = truncateString(email) + if c.user.Email != "" { + c.model.User = &c.user + } +} + +// SetUsername sets the username of the authenticated user. +func (c *Context) SetUsername(username string) { + c.user.Username = truncateString(username) + if c.user.Username != "" { + c.model.User = &c.user + } +} diff --git a/vendor/go.elastic.co/apm/doc.go b/vendor/go.elastic.co/apm/doc.go new file mode 100644 index 00000000000..6ca1ac8b26a --- /dev/null +++ b/vendor/go.elastic.co/apm/doc.go @@ -0,0 +1,21 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Package apm provides an API for tracing +// transactions and capturing errors, sending the +// data to Elastic APM. +package apm // import "go.elastic.co/apm" diff --git a/vendor/go.elastic.co/apm/error.go b/vendor/go.elastic.co/apm/error.go new file mode 100644 index 00000000000..fcfd1b66543 --- /dev/null +++ b/vendor/go.elastic.co/apm/error.go @@ -0,0 +1,696 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apm + +import ( + "crypto/rand" + "fmt" + "net" + "os" + "reflect" + "syscall" + "time" + + "github.com/pkg/errors" + + "go.elastic.co/apm/internal/pkgerrorsutil" + "go.elastic.co/apm/model" + "go.elastic.co/apm/stacktrace" +) + +const ( + // maxErrorGraphSize is the maximum number of errors + // to report in an error tree. Once this number of + // nodes is reached, we will stop recursing through + // error causes. + maxErrorTreeNodes = 50 +) + +// Recovered creates an Error with t.NewError(err), where +// err is either v (if v implements error), or otherwise +// fmt.Errorf("%v", v). The value v is expected to have +// come from a panic. +func (t *Tracer) Recovered(v interface{}) *Error { + var e *Error + switch v := v.(type) { + case error: + e = t.NewError(v) + default: + e = t.NewError(fmt.Errorf("%v", v)) + } + return e +} + +// NewError returns a new Error with details taken from err. +// NewError will panic if called with a nil error. +// +// The exception message will be set to err.Error(). +// The exception module and type will be set to the package +// and type name of the cause of the error, respectively, +// where the cause has the same definition as given by +// github.com/pkg/errors. +// +// If err implements +// type interface { +// StackTrace() github.com/pkg/errors.StackTrace +// } +// or +// type interface { +// StackTrace() []stacktrace.Frame +// } +// then one of those will be used to set the error +// stacktrace. Otherwise, NewError will take a stacktrace. +// +// If err implements +// type interface {Type() string} +// then that will be used to set the error type. +// +// If err implements +// type interface {Code() string} +// or +// type interface {Code() float64} +// then one of those will be used to set the error code. +func (t *Tracer) NewError(err error) *Error { + if err == nil { + panic("NewError must be called with a non-nil error") + } + e := t.newError() + e.cause = err + e.err = err.Error() + rand.Read(e.ID[:]) // ignore error, can't do anything about it + initException(&e.exception, err, e.stackTraceLimit) + if len(e.exception.stacktrace) == 0 { + e.SetStacktrace(2) + } + return e +} + +// NewErrorLog returns a new Error for the given ErrorLogRecord. +// +// The resulting Error's stacktrace will not be set. Call the +// SetStacktrace method to set it, if desired. +// +// If r.Message is empty, "[EMPTY]" will be used. +func (t *Tracer) NewErrorLog(r ErrorLogRecord) *Error { + e := t.newError() + e.log = ErrorLogRecord{ + Message: truncateString(r.Message), + MessageFormat: truncateString(r.MessageFormat), + Level: truncateString(r.Level), + LoggerName: truncateString(r.LoggerName), + } + if e.log.Message == "" { + e.log.Message = "[EMPTY]" + } + e.cause = r.Error + e.err = e.log.Message + rand.Read(e.ID[:]) // ignore error, can't do anything about it + if r.Error != nil { + initException(&e.exception, r.Error, e.stackTraceLimit) + } + return e +} + +// newError returns a new Error associated with the Tracer. +func (t *Tracer) newError() *Error { + e, _ := t.errorDataPool.Get().(*ErrorData) + if e == nil { + e = &ErrorData{ + tracer: t, + Context: Context{ + captureBodyMask: CaptureBodyErrors, + }, + } + } + e.Timestamp = time.Now() + + instrumentationConfig := t.instrumentationConfig() + e.Context.captureHeaders = instrumentationConfig.captureHeaders + e.stackTraceLimit = instrumentationConfig.stackTraceLimit + + return &Error{ErrorData: e} +} + +// Error describes an error occurring in the monitored service. +type Error struct { + // ErrorData holds the error data. This field is set to nil when + // the error's Send method is called. + *ErrorData + + // cause holds the original error. + // + // It is accessible via the Cause method: + // https://godoc.org/github.com/pkg/errors#Cause + cause error + + // string holds original error string + err string +} + +// ErrorData holds the details for an error, and is embedded inside Error. +// When the error is sent, its ErrorData field will be set to nil. +type ErrorData struct { + tracer *Tracer + stackTraceLimit int + exception exceptionData + log ErrorLogRecord + logStacktrace []stacktrace.Frame + transactionSampled bool + transactionType string + + // ID is the unique identifier of the error. This is set by + // the various error constructors, and is exposed only so + // the error ID can be logged or displayed to the user. + ID ErrorID + + // TraceID is the unique identifier of the trace in which + // this error occurred. If the error is not associated with + // a trace, this will be the zero value. + TraceID TraceID + + // TransactionID is the unique identifier of the transaction + // in which this error occurred. If the error is not associated + // with a transaction, this will be the zero value. + TransactionID SpanID + + // ParentID is the unique identifier of the transaction or span + // in which this error occurred. If the error is not associated + // with a transaction or span, this will be the zero value. + ParentID SpanID + + // Culprit is the name of the function that caused the error. + // + // This is initially unset; if it remains unset by the time + // Send is invoked, and the error has a stacktrace, the first + // non-library frame in the stacktrace will be considered the + // culprit. + Culprit string + + // Timestamp records the time at which the error occurred. + // This is set when the Error object is created, but may + // be overridden any time before the Send method is called. + Timestamp time.Time + + // Handled records whether or not the error was handled. This + // is ignored by "log" errors with no associated error value. + Handled bool + + // Context holds the context for this error. + Context Context +} + +// Cause returns original error assigned to Error, nil if Error or Error.cause is nil. +// https://godoc.org/github.com/pkg/errors#Cause +func (e *Error) Cause() error { + if e != nil { + return e.cause + } + return nil +} + +// Error returns string message for error. +// if Error or Error.cause is nil, "[EMPTY]" will be used. +func (e *Error) Error() string { + if e != nil { + return e.err + } + return "[EMPTY]" +} + +// SetTransaction sets TraceID, TransactionID, and ParentID to the transaction's +// IDs, and records the transaction's Type and whether or not it was sampled. +// +// If any custom context has been recorded in tx, it will also be carried across +// to e, but will not override any custom context already recorded on e. +func (e *Error) SetTransaction(tx *Transaction) { + tx.mu.RLock() + traceContext := tx.traceContext + var txType string + var custom model.IfaceMap + if !tx.ended() { + txType = tx.Type + custom = tx.Context.model.Custom + } + tx.mu.RUnlock() + e.setSpanData(traceContext, traceContext.Span, txType, custom) +} + +// SetSpan sets TraceID, TransactionID, and ParentID to the span's IDs. +// +// There is no need to call both SetTransaction and SetSpan. If you do call +// both, then SetSpan must be called second in order to set the error's +// ParentID correctly. +// +// If any custom context has been recorded in s's transaction, it will +// also be carried across to e, but will not override any custom context +// already recorded on e. +func (e *Error) SetSpan(s *Span) { + var txType string + var custom model.IfaceMap + if s.tx != nil { + s.tx.mu.RLock() + if !s.tx.ended() { + txType = s.tx.Type + custom = s.tx.Context.model.Custom + } + s.tx.mu.RUnlock() + } + e.setSpanData(s.traceContext, s.transactionID, txType, custom) +} + +func (e *Error) setSpanData( + traceContext TraceContext, + transactionID SpanID, + transactionType string, + customContext model.IfaceMap, +) { + e.TraceID = traceContext.Trace + e.ParentID = traceContext.Span + e.TransactionID = transactionID + e.transactionSampled = traceContext.Options.Recorded() + if e.transactionSampled { + e.transactionType = transactionType + } + if n := len(customContext); n != 0 { + m := len(e.Context.model.Custom) + e.Context.model.Custom = append(e.Context.model.Custom, customContext...) + // If there was already custom context in e, shift the custom context from + // tx to the beginning of the slice so that e's context takes precedence. + if m != 0 { + copy(e.Context.model.Custom[n:], e.Context.model.Custom[:m]) + copy(e.Context.model.Custom[:n], customContext) + } + } +} + +// Send enqueues the error for sending to the Elastic APM server. +// +// Send will set e.ErrorData to nil, so the error must not be +// modified after Send returns. +func (e *Error) Send() { + if e == nil || e.sent() { + return + } + e.ErrorData.enqueue() + e.ErrorData = nil +} + +func (e *Error) sent() bool { + return e.ErrorData == nil +} + +func (e *ErrorData) enqueue() { + select { + case e.tracer.events <- tracerEvent{eventType: errorEvent, err: e}: + default: + // Enqueuing an error should never block. + e.tracer.statsMu.Lock() + e.tracer.stats.ErrorsDropped++ + e.tracer.statsMu.Unlock() + e.reset() + } +} + +func (e *ErrorData) reset() { + *e = ErrorData{ + tracer: e.tracer, + logStacktrace: e.logStacktrace[:0], + Context: e.Context, + exception: e.exception, + } + e.Context.reset() + e.exception.reset() + e.tracer.errorDataPool.Put(e) +} + +type exceptionData struct { + message string + stacktrace []stacktrace.Frame + cause []exceptionData + ErrorDetails +} + +func (e *exceptionData) reset() { + *e = exceptionData{ + cause: e.cause[:0], + stacktrace: e.stacktrace[:0], + ErrorDetails: ErrorDetails{ + attrs: e.ErrorDetails.attrs, + Cause: e.ErrorDetails.Cause[:0], + }, + } + for k := range e.attrs { + delete(e.attrs, k) + } +} + +func initException(e *exceptionData, err error, stackTraceLimit int) { + b := exceptionDataBuilder{stackTraceLimit: stackTraceLimit} + b.init(e, err) +} + +type exceptionDataBuilder struct { + stackTraceLimit int + errorCount int + pointerErrors map[uintptr]struct{} +} + +func (b *exceptionDataBuilder) init(e *exceptionData, err error) bool { + b.errorCount++ + reflectValue := reflect.ValueOf(err) + reflectType := reflectValue.Type() + switch reflectType.Kind() { + case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer: + // Prevent infinite recursion due to cyclic error causes. + ptrVal := reflectValue.Pointer() + if b.pointerErrors == nil { + b.pointerErrors = map[uintptr]struct{}{ptrVal: struct{}{}} + } else { + if _, ok := b.pointerErrors[ptrVal]; ok { + return false + } + b.pointerErrors[ptrVal] = struct{}{} + } + } + + e.message = truncateString(err.Error()) + if e.message == "" { + e.message = "[EMPTY]" + } + + namedType := reflectType + if reflectType.Name() == "" && reflectType.Kind() == reflect.Ptr { + namedType = reflectType.Elem() + } + e.Type.Name = namedType.Name() + e.Type.PackagePath = namedType.PkgPath() + + // If the error implements Type, use that to + // override the type name determined through + // reflection. + if err, ok := err.(interface { + Type() string + }); ok { + e.Type.Name = err.Type() + } + + // If the error implements a Code method, use + // that to set the exception code. + switch err := err.(type) { + case interface { + Code() string + }: + e.Code.String = err.Code() + case interface { + Code() float64 + }: + e.Code.Number = err.Code() + } + + // If the error implements an Unwrap or Cause method, use that to set the cause error. + // Unwrap is defined by errors wrapped using fmt.Errorf, while Cause is defined by + // errors wrapped using pkg/errors.Wrap. + switch err := err.(type) { + case interface{ Unwrap() error }: + if cause := err.Unwrap(); cause != nil { + e.ErrorDetails.Cause = append(e.ErrorDetails.Cause, cause) + } + case interface{ Cause() error }: + if cause := err.Cause(); cause != nil { + e.ErrorDetails.Cause = append(e.ErrorDetails.Cause, cause) + } + } + + // Run registered ErrorDetailers over the error. + for _, ed := range typeErrorDetailers[reflectType] { + ed.ErrorDetails(err, &e.ErrorDetails) + } + for _, ed := range errorDetailers { + ed.ErrorDetails(err, &e.ErrorDetails) + } + + e.Code.String = truncateString(e.Code.String) + e.Type.Name = truncateString(e.Type.Name) + e.Type.PackagePath = truncateString(e.Type.PackagePath) + b.initErrorStacktrace(&e.stacktrace, err) + + for _, err := range e.ErrorDetails.Cause { + if b.errorCount >= maxErrorTreeNodes { + break + } + var data exceptionData + if b.init(&data, err) { + e.cause = append(e.cause, data) + } + } + return true +} + +func (b *exceptionDataBuilder) initErrorStacktrace(out *[]stacktrace.Frame, err error) { + type internalStackTracer interface { + StackTrace() []stacktrace.Frame + } + type errorsStackTracer interface { + StackTrace() errors.StackTrace + } + switch stackTracer := err.(type) { + case internalStackTracer: + stackTrace := stackTracer.StackTrace() + if b.stackTraceLimit >= 0 && len(stackTrace) > b.stackTraceLimit { + stackTrace = stackTrace[:b.stackTraceLimit] + } + *out = append(*out, stackTrace...) + case errorsStackTracer: + stackTrace := stackTracer.StackTrace() + pkgerrorsutil.AppendStacktrace(stackTrace, out, b.stackTraceLimit) + } +} + +// SetStacktrace sets the stacktrace for the error, +// skipping the first skip number of frames, excluding +// the SetStacktrace function. +func (e *Error) SetStacktrace(skip int) { + out := &e.exception.stacktrace + if e.log.Message != "" { + out = &e.logStacktrace + } + *out = stacktrace.AppendStacktrace((*out)[:0], skip+1, e.stackTraceLimit) +} + +// ErrorLogRecord holds details of an error log record. +type ErrorLogRecord struct { + // Message holds the message for the log record, + // e.g. "failed to connect to %s". + // + // If this is empty, "[EMPTY]" will be used. + Message string + + // MessageFormat holds the non-interpolated format + // of the log record, e.g. "failed to connect to %s". + // + // This is optional. + MessageFormat string + + // Level holds the severity level of the log record. + // + // This is optional. + Level string + + // LoggerName holds the name of the logger used. + // + // This is optional. + LoggerName string + + // Error is an error associated with the log record. + // + // This is optional. + Error error +} + +// ErrorID uniquely identifies an error. +type ErrorID TraceID + +// String returns id in its hex-encoded format. +func (id ErrorID) String() string { + return TraceID(id).String() +} + +func init() { + RegisterErrorDetailer(ErrorDetailerFunc(func(err error, details *ErrorDetails) { + if errTemporary(err) { + details.SetAttr("temporary", true) + } + if errTimeout(err) { + details.SetAttr("timeout", true) + } + })) + RegisterTypeErrorDetailer(reflect.TypeOf(&net.OpError{}), ErrorDetailerFunc(func(err error, details *ErrorDetails) { + opErr := err.(*net.OpError) + details.SetAttr("op", opErr.Op) + details.SetAttr("net", opErr.Net) + if opErr.Source != nil { + if addr := opErr.Source; addr != nil { + details.SetAttr("source", fmt.Sprintf("%s:%s", addr.Network(), addr.String())) + } + } + if opErr.Addr != nil { + if addr := opErr.Addr; addr != nil { + details.SetAttr("addr", fmt.Sprintf("%s:%s", addr.Network(), addr.String())) + } + } + details.Cause = append(details.Cause, opErr.Err) + })) + RegisterTypeErrorDetailer(reflect.TypeOf(&os.LinkError{}), ErrorDetailerFunc(func(err error, details *ErrorDetails) { + linkErr := err.(*os.LinkError) + details.SetAttr("op", linkErr.Op) + details.SetAttr("old", linkErr.Old) + details.SetAttr("new", linkErr.New) + details.Cause = append(details.Cause, linkErr.Err) + })) + RegisterTypeErrorDetailer(reflect.TypeOf(&os.PathError{}), ErrorDetailerFunc(func(err error, details *ErrorDetails) { + pathErr := err.(*os.PathError) + details.SetAttr("op", pathErr.Op) + details.SetAttr("path", pathErr.Path) + details.Cause = append(details.Cause, pathErr.Err) + })) + RegisterTypeErrorDetailer(reflect.TypeOf(&os.SyscallError{}), ErrorDetailerFunc(func(err error, details *ErrorDetails) { + syscallErr := err.(*os.SyscallError) + details.SetAttr("syscall", syscallErr.Syscall) + details.Cause = append(details.Cause, syscallErr.Err) + })) + RegisterTypeErrorDetailer(reflect.TypeOf(syscall.Errno(0)), ErrorDetailerFunc(func(err error, details *ErrorDetails) { + errno := err.(syscall.Errno) + details.Code.String = errnoName(errno) + if details.Code.String == "" { + details.Code.Number = float64(errno) + } + })) +} + +func errTemporary(err error) bool { + type temporaryError interface { + Temporary() bool + } + terr, ok := err.(temporaryError) + return ok && terr.Temporary() +} + +func errTimeout(err error) bool { + type timeoutError interface { + Timeout() bool + } + terr, ok := err.(timeoutError) + return ok && terr.Timeout() +} + +// RegisterTypeErrorDetailer registers e to be called for any error with +// the concrete type t. +// +// Each ErrorDetailer registered in this way will be called, in the order +// registered, for each error of type t created via Tracer.NewError or +// Tracer.NewErrorLog. +// +// RegisterTypeErrorDetailer must not be called during tracer operation; +// it is intended to be called at package init time. +func RegisterTypeErrorDetailer(t reflect.Type, e ErrorDetailer) { + typeErrorDetailers[t] = append(typeErrorDetailers[t], e) +} + +// RegisterErrorDetailer registers e in the global list of ErrorDetailers. +// +// Each ErrorDetailer registered in this way will be called, in the order +// registered, for each error created via Tracer.NewError or Tracer.NewErrorLog. +// +// RegisterErrorDetailer must not be called during tracer operation; it is +// intended to be called at package init time. +func RegisterErrorDetailer(e ErrorDetailer) { + errorDetailers = append(errorDetailers, e) +} + +var ( + typeErrorDetailers = make(map[reflect.Type][]ErrorDetailer) + errorDetailers []ErrorDetailer +) + +// ErrorDetails holds details of an error, which can be altered or +// extended by registering an ErrorDetailer with RegisterErrorDetailer +// or RegisterTypeErrorDetailer. +type ErrorDetails struct { + attrs map[string]interface{} + + // Type holds information about the error type, initialized + // with the type name and type package path using reflection. + Type struct { + // Name holds the error type name. + Name string + + // PackagePath holds the error type package path. + PackagePath string + } + + // Code holds an error code. + Code struct { + // String holds a string-based error code. If this is set, then Number is ignored. + // + // This field will be initialized to the result of calling an error's Code method, + // if the error implements the following interface: + // + // type interface StringCoder { + // Code() string + // } + String string + + // Number holds a numerical error code. This is ignored if String is set. + // + // This field will be initialized to the result of calling an error's Code + // method, if the error implements the following interface: + // + // type interface NumberCoder { + // Code() float64 + // } + Number float64 + } + + // Cause holds the errors that were the cause of this error. + Cause []error +} + +// SetAttr sets the attribute with key k to value v. +func (d *ErrorDetails) SetAttr(k string, v interface{}) { + if d.attrs == nil { + d.attrs = make(map[string]interface{}) + } + d.attrs[k] = v +} + +// ErrorDetailer defines an interface for altering or extending the ErrorDetails for an error. +// +// ErrorDetailers can be registered using the package-level functions RegisterErrorDetailer and +// RegisterTypeErrorDetailer. +type ErrorDetailer interface { + // ErrorDetails is called to update or alter details for err. + ErrorDetails(err error, details *ErrorDetails) +} + +// ErrorDetailerFunc is a function type implementing ErrorDetailer. +type ErrorDetailerFunc func(error, *ErrorDetails) + +// ErrorDetails calls f(err, details). +func (f ErrorDetailerFunc) ErrorDetails(err error, details *ErrorDetails) { + f(err, details) +} diff --git a/vendor/go.elastic.co/apm/error_unix.go b/vendor/go.elastic.co/apm/error_unix.go new file mode 100644 index 00000000000..e54f301612d --- /dev/null +++ b/vendor/go.elastic.co/apm/error_unix.go @@ -0,0 +1,30 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build !windows + +package apm + +import ( + "syscall" + + "golang.org/x/sys/unix" +) + +func errnoName(err syscall.Errno) string { + return unix.ErrnoName(err) +} diff --git a/vendor/go.elastic.co/apm/error_windows.go b/vendor/go.elastic.co/apm/error_windows.go new file mode 100644 index 00000000000..e95ac0f248d --- /dev/null +++ b/vendor/go.elastic.co/apm/error_windows.go @@ -0,0 +1,27 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apm + +import ( + "syscall" +) + +func errnoName(err syscall.Errno) string { + // There's currently no equivalent of unix.ErrnoName for Windows. + return "" +} diff --git a/vendor/go.elastic.co/apm/fmt.go b/vendor/go.elastic.co/apm/fmt.go new file mode 100644 index 00000000000..4d1ce03577b --- /dev/null +++ b/vendor/go.elastic.co/apm/fmt.go @@ -0,0 +1,85 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apm + +import ( + "context" + "fmt" + "io" +) + +// TraceFormatter returns a fmt.Formatter that can be used to +// format the identifiers of the transaction and span in ctx. +// +// The returned Formatter understands the following verbs: +// +// %v: trace ID, transaction ID, and span ID (if existing), space-separated +// the plus flag (%+v) adds field names, e.g. "trace.id=... transaction.id=..." +// %t: trace ID (hex-encoded, or empty string if non-existent) +// the plus flag (%+T) adds the field name, e.g. "trace.id=..." +// %x: transaction ID (hex-encoded, or empty string if non-existent) +// the plus flag (%+t) adds the field name, e.g. "transaction.id=..." +// %s: span ID (hex-encoded, or empty string if non-existent) +// the plus flag (%+s) adds the field name, e.g. "span.id=..." +func TraceFormatter(ctx context.Context) fmt.Formatter { + f := traceFormatter{tx: TransactionFromContext(ctx)} + if f.tx != nil { + f.span = SpanFromContext(ctx) + } + return f +} + +type traceFormatter struct { + tx *Transaction + span *Span +} + +func (t traceFormatter) Format(f fmt.State, c rune) { + switch c { + case 'v': + if t.tx != nil { + t.writeField(f, "trace.id", t.tx.TraceContext().Trace.String()) + f.Write([]byte{' '}) + t.writeField(f, "transaction.id", t.tx.TraceContext().Span.String()) + if t.span != nil { + f.Write([]byte{' '}) + t.writeField(f, "span.id", t.span.TraceContext().Span.String()) + } + } + case 't': + if t.tx != nil { + t.writeField(f, "trace.id", t.tx.TraceContext().Trace.String()) + } + case 'x': + if t.tx != nil { + t.writeField(f, "transaction.id", t.tx.TraceContext().Span.String()) + } + case 's': + if t.span != nil { + t.writeField(f, "span.id", t.span.TraceContext().Span.String()) + } + } +} + +func (t traceFormatter) writeField(f fmt.State, name, value string) { + if f.Flag('+') { + io.WriteString(f, name) + f.Write([]byte{'='}) + } + io.WriteString(f, value) +} diff --git a/vendor/go.elastic.co/apm/fnv.go b/vendor/go.elastic.co/apm/fnv.go new file mode 100644 index 00000000000..0741e224f26 --- /dev/null +++ b/vendor/go.elastic.co/apm/fnv.go @@ -0,0 +1,42 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// +// Based on Go's pkg/hash/fnv. +// +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package apm + +const ( + offset64 = 14695981039346656037 + prime64 = 1099511628211 +) + +type fnv1a uint64 + +func newFnv1a() fnv1a { + return offset64 +} + +func (f *fnv1a) add(s string) { + for i := 0; i < len(s); i++ { + *f ^= fnv1a(s[i]) + *f *= prime64 + } +} diff --git a/vendor/go.elastic.co/apm/go.mod b/vendor/go.elastic.co/apm/go.mod new file mode 100644 index 00000000000..c7f1c4bbe5f --- /dev/null +++ b/vendor/go.elastic.co/apm/go.mod @@ -0,0 +1,17 @@ +module go.elastic.co/apm + +require ( + github.com/armon/go-radix v1.0.0 + github.com/cucumber/godog v0.8.1 + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/elastic/go-sysinfo v1.1.1 + github.com/google/go-cmp v0.3.1 + github.com/pkg/errors v0.8.1 + github.com/prometheus/procfs v0.0.3 // indirect + github.com/santhosh-tekuri/jsonschema v1.2.4 + github.com/stretchr/testify v1.4.0 + go.elastic.co/fastjson v1.0.0 + golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e +) + +go 1.13 diff --git a/vendor/go.elastic.co/apm/go.sum b/vendor/go.elastic.co/apm/go.sum new file mode 100644 index 00000000000..6f89a3c9558 --- /dev/null +++ b/vendor/go.elastic.co/apm/go.sum @@ -0,0 +1,53 @@ +github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/cucumber/godog v0.8.1 h1:lVb+X41I4YDreE+ibZ50bdXmySxgRviYFgKY6Aw4XE8= +github.com/cucumber/godog v0.8.1/go.mod h1:vSh3r/lM+psC1BPXvdkSEuNjmXfpVqrMGYAElF6hxnA= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/elastic/go-sysinfo v1.1.1 h1:ZVlaLDyhVkDfjwPGU55CQRCRolNpc7P0BbyhhQZQmMI= +github.com/elastic/go-sysinfo v1.1.1/go.mod h1:i1ZYdU10oLNfRzq4vq62BEwD2fH8KaWh6eh0ikPT9F0= +github.com/elastic/go-windows v1.0.0 h1:qLURgZFkkrYyTTkvYpsZIgf83AUsdIHfvlJaqaZ7aSY= +github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU= +github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 h1:rp+c0RAYOWj8l6qbCUTSiRLG/iKnW3K3/QfPPuSsBt4= +github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/santhosh-tekuri/jsonschema v1.2.4 h1:hNhW8e7t+H1vgY+1QeEQpveR6D4+OwKPXCfD2aieJis= +github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +go.elastic.co/fastjson v1.0.0 h1:ooXV/ABvf+tBul26jcVViPT3sBir0PvXgibYB1IQQzg= +go.elastic.co/fastjson v1.0.0/go.mod h1:PmeUOMMtLHQr9ZS9J9owrAVg0FkaZDRZJEFTTGHtchs= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20191025021431-6c3a3bfe00ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e h1:9vRrk9YW2BTzLP0VCB9ZDjU4cPqkg+IDWL7XgxA1yxQ= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M= +howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= diff --git a/vendor/go.elastic.co/apm/gocontext.go b/vendor/go.elastic.co/apm/gocontext.go new file mode 100644 index 00000000000..d238c065669 --- /dev/null +++ b/vendor/go.elastic.co/apm/gocontext.go @@ -0,0 +1,138 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apm + +import ( + "context" + + "go.elastic.co/apm/internal/apmcontext" +) + +// ContextWithSpan returns a copy of parent in which the given span +// is stored, associated with the key ContextSpanKey. +func ContextWithSpan(parent context.Context, s *Span) context.Context { + return apmcontext.ContextWithSpan(parent, s) +} + +// ContextWithTransaction returns a copy of parent in which the given +// transaction is stored, associated with the key ContextTransactionKey. +func ContextWithTransaction(parent context.Context, t *Transaction) context.Context { + return apmcontext.ContextWithTransaction(parent, t) +} + +// SpanFromContext returns the current Span in context, if any. The span must +// have been added to the context previously using ContextWithSpan, or the +// top-level StartSpan function. +func SpanFromContext(ctx context.Context) *Span { + value, _ := apmcontext.SpanFromContext(ctx).(*Span) + return value +} + +// TransactionFromContext returns the current Transaction in context, if any. +// The transaction must have been added to the context previously using +// ContextWithTransaction. +func TransactionFromContext(ctx context.Context) *Transaction { + value, _ := apmcontext.TransactionFromContext(ctx).(*Transaction) + return value +} + +// DetachedContext returns a new context detached from the lifetime +// of ctx, but which still returns the values of ctx. +// +// DetachedContext can be used to maintain the trace context required +// to correlate events, but where the operation is "fire-and-forget", +// and should not be affected by the deadline or cancellation of ctx. +func DetachedContext(ctx context.Context) context.Context { + return &detachedContext{Context: context.Background(), orig: ctx} +} + +type detachedContext struct { + context.Context + orig context.Context +} + +// Value returns c.orig.Value(key). +func (c *detachedContext) Value(key interface{}) interface{} { + return c.orig.Value(key) +} + +// StartSpan is equivalent to calling StartSpanOptions with a zero SpanOptions struct. +func StartSpan(ctx context.Context, name, spanType string) (*Span, context.Context) { + return StartSpanOptions(ctx, name, spanType, SpanOptions{}) +} + +// StartSpanOptions starts and returns a new Span within the sampled transaction +// and parent span in the context, if any. If the span isn't dropped, it will be +// stored in the resulting context. +// +// If opts.Parent is non-zero, its value will be used in preference to any parent +// span in ctx. +// +// StartSpanOptions always returns a non-nil Span. Its End method must be called +// when the span completes. +func StartSpanOptions(ctx context.Context, name, spanType string, opts SpanOptions) (*Span, context.Context) { + var span *Span + if opts.parent = SpanFromContext(ctx); opts.parent != nil { + if opts.parent.tx == nil && opts.parent.tracer != nil { + span = opts.parent.tracer.StartSpan(name, spanType, opts.parent.transactionID, opts) + } else { + span = opts.parent.tx.StartSpanOptions(name, spanType, opts) + } + } else { + tx := TransactionFromContext(ctx) + span = tx.StartSpanOptions(name, spanType, opts) + } + if !span.Dropped() { + ctx = ContextWithSpan(ctx, span) + } + return span, ctx +} + +// CaptureError returns a new Error related to the sampled transaction +// and span present in the context, if any, and sets its exception info +// from err. The Error.Handled field will be set to true, and a stacktrace +// set either from err, or from the caller. +// +// If the provided error is nil, then CaptureError will also return nil; +// otherwise a non-nil Error will always be returned. If there is no +// transaction or span in the context, then the returned Error's Send +// method will have no effect. +func CaptureError(ctx context.Context, err error) *Error { + if err == nil { + return nil + } + if span := SpanFromContext(ctx); span != nil { + if span.tracer == nil { + return &Error{cause: err, err: err.Error()} + } + e := span.tracer.NewError(err) + e.Handled = true + e.SetSpan(span) + return e + } else if tx := TransactionFromContext(ctx); tx != nil { + if tx.tracer == nil { + return &Error{cause: err, err: err.Error()} + } + e := tx.tracer.NewError(err) + e.Handled = true + e.SetTransaction(tx) + return e + } else { + return &Error{cause: err, err: err.Error()} + } +} diff --git a/vendor/go.elastic.co/apm/gofuzz.go b/vendor/go.elastic.co/apm/gofuzz.go new file mode 100644 index 00000000000..1fbbcaf3384 --- /dev/null +++ b/vendor/go.elastic.co/apm/gofuzz.go @@ -0,0 +1,270 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build gofuzz + +package apm + +import ( + "bytes" + "context" + "crypto/tls" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "time" + + "github.com/pkg/errors" + "github.com/santhosh-tekuri/jsonschema" + + "go.elastic.co/apm/internal/apmschema" + "go.elastic.co/apm/model" + "go.elastic.co/apm/stacktrace" + "go.elastic.co/fastjson" +) + +func Fuzz(data []byte) int { + type Payload struct { + Service *model.Service `json:"service"` + Process *model.Process `json:"process,omitempty"` + System *model.System `json:"system,omitempty"` + Errors []*model.Error `json:"errors"` + Transactions []*model.Transaction `json:"transactions"` + } + var payload Payload + if err := json.Unmarshal(data, &payload); err != nil { + return 0 + } + + tracer := DefaultTracer + tracer.Transport = &gofuzzTransport{} + tracer.SetCaptureBody(CaptureBodyAll) + + setContext := func(in *model.Context, out *Context) error { + if in == nil { + return nil + } + for k, v := range in.Tags { + out.SetLabel(k, v) + } + if in.Request != nil { + var body io.Reader + var postForm url.Values + if in.Request.Body != nil { + body = strings.NewReader(in.Request.Body.Raw) + if in.Request.Body.Form != nil { + postForm = in.Request.Body.Form + } + } + req, err := http.NewRequest(in.Request.Method, in.Request.URL.Full, body) + if err != nil { + return err + } + capturedBody := tracer.CaptureHTTPRequestBody(req) + if in.Request.Socket != nil { + req.RemoteAddr = in.Request.Socket.RemoteAddress + if in.Request.Socket.Encrypted { + req.TLS = new(tls.ConnectionState) + } + } + req.PostForm = postForm + if in.User != nil && in.User.Username != "" { + req.SetBasicAuth(in.User.Username, "") + } + + var major, minor int + if n, err := fmt.Sscanf(in.Request.HTTPVersion, "%d.%d", &major, &minor); err != nil { + return err + } else if n != 2 { + return errors.Errorf("invalid HTTP version %s", in.Request.HTTPVersion) + } + req.ProtoMajor = major + req.ProtoMinor = minor + + if in.Request.Headers != nil { + if in.Request.Headers.UserAgent != "" { + req.Header.Set("User-Agent", in.Request.Headers.UserAgent) + } + if in.Request.Headers.ContentType != "" { + req.Header.Set("Content-Type", in.Request.Headers.ContentType) + } + if in.Request.Headers.Cookie != "" { + for _, v := range strings.Split(in.Request.Headers.Cookie, ";") { + req.Header.Add("Cookie", v) + } + } + } + + out.SetHTTPRequest(req) + out.SetHTTPRequestBody(capturedBody) + } + if in.Response != nil { + out.SetHTTPStatusCode(in.Response.StatusCode) + if in.Response.Finished != nil { + out.SetHTTPResponseFinished(*in.Response.Finished) + } + if in.Response.HeadersSent != nil { + out.SetHTTPResponseHeadersSent(*in.Response.HeadersSent) + } + if in.Response.Headers != nil { + h := make(http.Header) + h.Set("Content-Type", in.Response.Headers.ContentType) + out.SetHTTPResponseHeaders(h) + } + } + return nil + } + + for _, t := range payload.Transactions { + if t == nil { + continue + } + tx := tracer.StartTransaction(t.Name, t.Type) + tx.Result = t.Result + tx.Timestamp = time.Time(t.Timestamp) + if setContext(t.Context, &tx.Context) != nil { + return 0 + } + for _, s := range t.Spans { + span := tx.StartSpan(s.Name, s.Type, nil) + span.Timestamp = tx.Timestamp.Add(time.Duration(s.Start * float64(time.Millisecond))) + if s.Context != nil && s.Context.Database != nil { + span.Context.SetDatabase(DatabaseSpanContext{ + Instance: s.Context.Database.Instance, + Statement: s.Context.Database.Statement, + Type: s.Context.Database.Type, + User: s.Context.Database.User, + }) + } + span.Duration = time.Duration(s.Duration * float64(time.Millisecond)) + span.End() + } + tx.Duration = time.Duration(t.Duration * float64(time.Millisecond)) + tx.End() + } + + for _, e := range payload.Errors { + if e == nil { + continue + } + var err *Error + if e.Log.Message != "" { + err = tracer.NewErrorLog(ErrorLogRecord{ + Message: e.Log.Message, + MessageFormat: e.Log.ParamMessage, + Level: e.Log.Level, + LoggerName: e.Log.LoggerName, + }) + } else { + ee := exceptionError{e.Exception} + if e.Exception.Code.String != "" { + err = tracer.NewError(stringCodeException{ee}) + } else { + err = tracer.NewError(float64CodeException{ee}) + } + } + if setContext(e.Context, &err.Context) != nil { + return 0 + } + err.Culprit = e.Culprit + err.Timestamp = time.Time(e.Timestamp) + err.Send() + } + + return 0 +} + +type float64CodeException struct { + exceptionError +} + +func (e float64CodeException) Code() float64 { + return e.x.Code.Number +} + +type stringCodeException struct { + exceptionError +} + +func (e stringCodeException) Code() string { + return e.x.Code.String +} + +type exceptionError struct { + x model.Exception +} + +func (e exceptionError) Type() string { + return e.x.Type +} + +func (e exceptionError) Error() string { + return e.x.Message +} + +func (e exceptionError) StackTrace() []stacktrace.Frame { + if len(e.x.Stacktrace) == 0 { + return nil + } + frames := make([]stacktrace.Frame, len(e.x.Stacktrace)) + for i, f := range e.x.Stacktrace { + frames[i].Function = f.Function + frames[i].File = f.File + frames[i].Line = f.Line + } + return frames +} + +type gofuzzTransport struct { + writer fastjson.Writer +} + +func (t *gofuzzTransport) SendErrors(ctx context.Context, payload *model.ErrorsPayload) error { + t.writer.Reset() + if err := payload.MarshalFastJSON(&t.writer); err != nil { + return err + } + t.validate(apmschema.Errors) + return nil +} + +func (t *gofuzzTransport) SendMetrics(ctx context.Context, payload *model.MetricsPayload) error { + t.writer.Reset() + if err := payload.MarshalFastJSON(&t.writer); err != nil { + return err + } + t.validate(apmschema.Metrics) + return nil +} + +func (t *gofuzzTransport) SendTransactions(ctx context.Context, payload *model.TransactionsPayload) error { + t.writer.Reset() + if err := payload.MarshalFastJSON(&t.writer); err != nil { + return err + } + t.validate(apmschema.Transactions) + return nil +} + +func (t *gofuzzTransport) validate(schema *jsonschema.Schema) { + if err := schema.Validate(bytes.NewReader(t.writer.Bytes())); err != nil { + panic(err) + } +} diff --git a/vendor/go.elastic.co/apm/internal/apmcontext/context.go b/vendor/go.elastic.co/apm/internal/apmcontext/context.go new file mode 100644 index 00000000000..e6ad7101937 --- /dev/null +++ b/vendor/go.elastic.co/apm/internal/apmcontext/context.go @@ -0,0 +1,78 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apmcontext + +import "context" + +var ( + // ContextWithSpan takes a context and span and returns a new context + // from which the span can be extracted using SpanFromContext. + // + // ContextWithSpan is used by apm.ContextWithSpan. It is a + // variable to allow other packages, such as apmot, to replace it + // at package init time. + ContextWithSpan = DefaultContextWithSpan + + // ContextWithTransaction takes a context and transaction and returns + // a new context from which the transaction can be extracted using + // TransactionFromContext. + // + // ContextWithTransaction is used by apm.ContextWithTransaction. + // It is a variable to allow other packages, such as apmot, to replace + // it at package init time. + ContextWithTransaction = DefaultContextWithTransaction + + // SpanFromContext returns a span included in the context using + // ContextWithSpan. + // + // SpanFromContext is used by apm.SpanFromContext. It is a + // variable to allow other packages, such as apmot, to replace it + // at package init time. + SpanFromContext = DefaultSpanFromContext + + // TransactionFromContext returns a transaction included in the context + // using ContextWithTransaction. + // + // TransactionFromContext is used by apm.TransactionFromContext. + // It is a variable to allow other packages, such as apmot, to replace + // it at package init time. + TransactionFromContext = DefaultTransactionFromContext +) + +type spanKey struct{} +type transactionKey struct{} + +// DefaultContextWithSpan is the default value for ContextWithSpan. +func DefaultContextWithSpan(ctx context.Context, span interface{}) context.Context { + return context.WithValue(ctx, spanKey{}, span) +} + +// DefaultContextWithTransaction is the default value for ContextWithTransaction. +func DefaultContextWithTransaction(ctx context.Context, tx interface{}) context.Context { + return context.WithValue(ctx, transactionKey{}, tx) +} + +// DefaultSpanFromContext is the default value for SpanFromContext. +func DefaultSpanFromContext(ctx context.Context) interface{} { + return ctx.Value(spanKey{}) +} + +// DefaultTransactionFromContext is the default value for TransactionFromContext. +func DefaultTransactionFromContext(ctx context.Context) interface{} { + return ctx.Value(transactionKey{}) +} diff --git a/vendor/go.elastic.co/apm/internal/apmhostutil/container.go b/vendor/go.elastic.co/apm/internal/apmhostutil/container.go new file mode 100644 index 00000000000..ff734852b2d --- /dev/null +++ b/vendor/go.elastic.co/apm/internal/apmhostutil/container.go @@ -0,0 +1,34 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apmhostutil + +import "go.elastic.co/apm/model" + +// Container returns information about the container running the process, or an +// error the container information could not be determined. +func Container() (*model.Container, error) { + return containerInfo() +} + +// Kubernetes returns information about the Kubernetes node and pod running +// the process, or an error if they could not be determined. This information +// does not include the KUBERNETES_* environment variables that can be set via +// the Downward API. +func Kubernetes() (*model.Kubernetes, error) { + return kubernetesInfo() +} diff --git a/vendor/go.elastic.co/apm/internal/apmhostutil/container_linux.go b/vendor/go.elastic.co/apm/internal/apmhostutil/container_linux.go new file mode 100644 index 00000000000..4ce16440549 --- /dev/null +++ b/vendor/go.elastic.co/apm/internal/apmhostutil/container_linux.go @@ -0,0 +1,156 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build linux + +package apmhostutil + +import ( + "bufio" + "errors" + "io" + "os" + "path" + "regexp" + "strings" + "sync" + + "go.elastic.co/apm/model" +) + +const ( + systemdScopeSuffix = ".scope" +) + +var ( + cgroupContainerInfoOnce sync.Once + cgroupContainerInfoError error + kubernetes *model.Kubernetes + container *model.Container + + kubepodsRegexp = regexp.MustCompile( + "" + + `(?:^/kubepods/[^/]+/pod([^/]+)/$)|` + + `(?:^/kubepods\.slice/kubepods-[^/]+\.slice/kubepods-[^/]+-pod([^/]+)\.slice/$)`, + ) + + containerIDRegexp = regexp.MustCompile( + "^" + + "[[:xdigit:]]{64}|" + + "[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4,}" + + "$", + ) +) + +func containerInfo() (*model.Container, error) { + container, _, err := cgroupContainerInfo() + return container, err +} + +func kubernetesInfo() (*model.Kubernetes, error) { + _, kubernetes, err := cgroupContainerInfo() + if err == nil && kubernetes == nil { + return nil, errors.New("could not determine kubernetes info") + } + return kubernetes, err +} + +func cgroupContainerInfo() (*model.Container, *model.Kubernetes, error) { + cgroupContainerInfoOnce.Do(func() { + cgroupContainerInfoError = func() error { + f, err := os.Open("/proc/self/cgroup") + if err != nil { + return err + } + defer f.Close() + + c, k, err := readCgroupContainerInfo(f) + if err != nil { + return err + } + if c == nil { + return errors.New("could not determine container info") + } + container = c + kubernetes = k + return nil + }() + }) + return container, kubernetes, cgroupContainerInfoError +} + +func readCgroupContainerInfo(r io.Reader) (*model.Container, *model.Kubernetes, error) { + var container *model.Container + var kubernetes *model.Kubernetes + s := bufio.NewScanner(r) + for s.Scan() { + fields := strings.SplitN(s.Text(), ":", 3) + if len(fields) != 3 { + continue + } + cgroupPath := fields[2] + + // Depending on the filesystem driver used for cgroup + // management, the paths in /proc/pid/cgroup will have + // one of the following formats in a Docker container: + // + // systemd: /system.slice/docker-.scope + // cgroupfs: /docker/ + // + // In a Kubernetes pod, the cgroup path will look like: + // + // systemd: /kubepods.slice/kubepods-.slice/kubepods--pod.slice/.scope + // cgroupfs: /kubepods//pod/ + // + dir, id := path.Split(cgroupPath) + if strings.HasSuffix(id, systemdScopeSuffix) { + id = id[:len(id)-len(systemdScopeSuffix)] + if dash := strings.IndexRune(id, '-'); dash != -1 { + id = id[dash+1:] + } + } + if match := kubepodsRegexp.FindStringSubmatch(dir); match != nil { + // By default, Kubernetes will set the hostname of + // the pod containers to the pod name. Users that + // override the name should use the Downard API to + // override the pod name. + hostname, _ := os.Hostname() + uid := match[1] + if uid == "" { + // Systemd cgroup driver is being used, + // so we need to unescape '_' back to '-'. + uid = strings.Replace(match[2], "_", "-", -1) + } + kubernetes = &model.Kubernetes{ + Pod: &model.KubernetesPod{ + Name: hostname, + UID: uid, + }, + } + // We don't check the contents of the last path segment + // when we've matched "^/kubepods"; we assume that it is + // a valid container ID. + container = &model.Container{ID: id} + } else if containerIDRegexp.MatchString(id) { + container = &model.Container{ID: id} + } + } + if err := s.Err(); err != nil { + return nil, nil, err + } + return container, kubernetes, nil +} diff --git a/vendor/go.elastic.co/apm/internal/apmhostutil/container_nonlinux.go b/vendor/go.elastic.co/apm/internal/apmhostutil/container_nonlinux.go new file mode 100644 index 00000000000..a02a44292fb --- /dev/null +++ b/vendor/go.elastic.co/apm/internal/apmhostutil/container_nonlinux.go @@ -0,0 +1,36 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build !linux + +package apmhostutil + +import ( + "runtime" + + "github.com/pkg/errors" + + "go.elastic.co/apm/model" +) + +func containerInfo() (*model.Container, error) { + return nil, errors.Errorf("container ID computation not implemented for %s", runtime.GOOS) +} + +func kubernetesInfo() (*model.Kubernetes, error) { + return nil, errors.Errorf("kubernetes info gathering not implemented for %s", runtime.GOOS) +} diff --git a/vendor/go.elastic.co/apm/internal/apmhttputil/forwarded.go b/vendor/go.elastic.co/apm/internal/apmhttputil/forwarded.go new file mode 100644 index 00000000000..9001178150d --- /dev/null +++ b/vendor/go.elastic.co/apm/internal/apmhttputil/forwarded.go @@ -0,0 +1,74 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apmhttputil + +import ( + "strconv" + "strings" +) + +// ForwardedHeader holds information extracted from a "Forwarded" HTTP header. +type ForwardedHeader struct { + For string + Host string + Proto string +} + +// ParseForwarded parses a "Forwarded" HTTP header. +func ParseForwarded(f string) ForwardedHeader { + // We only consider the first value in the sequence, + // if there are multiple. Disregard everything after + // the first comma. + if comma := strings.IndexRune(f, ','); comma != -1 { + f = f[:comma] + } + var result ForwardedHeader + for f != "" { + field := f + if semi := strings.IndexRune(f, ';'); semi != -1 { + field = f[:semi] + f = f[semi+1:] + } else { + f = "" + } + eq := strings.IndexRune(field, '=') + if eq == -1 { + // Malformed field, ignore. + continue + } + key := strings.TrimSpace(field[:eq]) + value := strings.TrimSpace(field[eq+1:]) + if len(value) > 0 && value[0] == '"' { + var err error + value, err = strconv.Unquote(value) + if err != nil { + // Malformed, ignore + continue + } + } + switch strings.ToLower(key) { + case "for": + result.For = value + case "host": + result.Host = value + case "proto": + result.Proto = value + } + } + return result +} diff --git a/vendor/go.elastic.co/apm/internal/apmhttputil/remoteaddr.go b/vendor/go.elastic.co/apm/internal/apmhttputil/remoteaddr.go new file mode 100644 index 00000000000..e79400e6a8a --- /dev/null +++ b/vendor/go.elastic.co/apm/internal/apmhttputil/remoteaddr.go @@ -0,0 +1,60 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apmhttputil + +import ( + "net/http" + "strconv" +) + +// RemoteAddr returns the remote (peer) socket address for req, +// a server HTTP request. +func RemoteAddr(req *http.Request) string { + remoteAddr, _ := splitHost(req.RemoteAddr) + return remoteAddr +} + +// DestinationAddr returns the destination server address and port +// for req, a client HTTP request. +// +// If req.URL.Host contains a port it will be returned, and otherwise +// the default port according to req.URL.Scheme will be returned. If +// the included port is not a valid integer, or no port is included +// and the scheme is unknown, the returned port value will be zero. +func DestinationAddr(req *http.Request) (string, int) { + host, strport := splitHost(req.URL.Host) + var port int + if strport != "" { + port, _ = strconv.Atoi(strport) + } else { + port = SchemeDefaultPort(req.URL.Scheme) + } + return host, port +} + +// SchemeDefaultPort returns the default port for the given URI scheme, +// if known, or 0 otherwise. +func SchemeDefaultPort(scheme string) int { + switch scheme { + case "http": + return 80 + case "https": + return 443 + } + return 0 +} diff --git a/vendor/go.elastic.co/apm/internal/apmhttputil/url.go b/vendor/go.elastic.co/apm/internal/apmhttputil/url.go new file mode 100644 index 00000000000..45ea94181ad --- /dev/null +++ b/vendor/go.elastic.co/apm/internal/apmhttputil/url.go @@ -0,0 +1,113 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apmhttputil + +import ( + "net" + "net/http" + "strings" + + "go.elastic.co/apm/internal/apmstrings" + "go.elastic.co/apm/model" +) + +// RequestURL returns a model.URL for req. +// +// If req contains an absolute URI, the values will be split and +// sanitized, but no further processing performed. For all other +// requests (i.e. most server-side requests), we reconstruct the +// URL based on various proxy forwarding headers and other request +// attributes. +func RequestURL(req *http.Request) model.URL { + out := model.URL{ + Path: truncateString(req.URL.Path), + Search: truncateString(req.URL.RawQuery), + Hash: truncateString(req.URL.Fragment), + } + if req.URL.Host != "" { + // Absolute URI: client-side or proxy request, so ignore the + // headers. + hostname, port := splitHost(req.URL.Host) + out.Hostname = truncateString(hostname) + out.Port = truncateString(port) + out.Protocol = truncateString(req.URL.Scheme) + return out + } + + // This is a server-side request URI, which contains only the path. + // We synthesize the full URL by extracting the host and protocol + // from headers, or inferring from other properties. + var fullHost string + forwarded := ParseForwarded(req.Header.Get("Forwarded")) + if forwarded.Host != "" { + fullHost = forwarded.Host + out.Protocol = truncateString(forwarded.Proto) + } else if xfh := req.Header.Get("X-Forwarded-Host"); xfh != "" { + fullHost = xfh + } else { + fullHost = req.Host + } + hostname, port := splitHost(fullHost) + out.Hostname = truncateString(hostname) + out.Port = truncateString(port) + + // Protocol might be extracted from the Forwarded header. If it's not, + // look for various other headers. + if out.Protocol == "" { + if proto := req.Header.Get("X-Forwarded-Proto"); proto != "" { + out.Protocol = truncateString(proto) + } else if proto := req.Header.Get("X-Forwarded-Protocol"); proto != "" { + out.Protocol = truncateString(proto) + } else if proto := req.Header.Get("X-Url-Scheme"); proto != "" { + out.Protocol = truncateString(proto) + } else if req.Header.Get("Front-End-Https") == "on" { + out.Protocol = "https" + } else if req.Header.Get("X-Forwarded-Ssl") == "on" { + out.Protocol = "https" + } else if req.TLS != nil { + out.Protocol = "https" + } else { + // Assume http otherwise. + out.Protocol = "http" + } + } + return out +} + +func splitHost(in string) (host, port string) { + if strings.LastIndexByte(in, ':') == -1 { + // In the common (relative to other "errors") case that + // there is no colon, we can avoid allocations by not + // calling SplitHostPort. + return in, "" + } + host, port, err := net.SplitHostPort(in) + if err != nil { + if n := len(in); n > 1 && in[0] == '[' && in[n-1] == ']' { + in = in[1 : n-1] + } + return in, "" + } + return host, port +} + +func truncateString(s string) string { + // At the time of writing, all length limits are 1024. + s, _ = apmstrings.Truncate(s, 1024) + return s +} diff --git a/vendor/go.elastic.co/apm/internal/apmlog/logger.go b/vendor/go.elastic.co/apm/internal/apmlog/logger.go new file mode 100644 index 00000000000..d5cb1689338 --- /dev/null +++ b/vendor/go.elastic.co/apm/internal/apmlog/logger.go @@ -0,0 +1,173 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apmlog + +import ( + "fmt" + "io" + "log" + "os" + "strings" + "sync" + "time" + + "go.elastic.co/fastjson" +) + +var ( + // DefaultLogger is the default Logger to use, if ELASTIC_APM_LOG_* are specified. + DefaultLogger Logger + + fastjsonPool = &sync.Pool{ + New: func() interface{} { + return &fastjson.Writer{} + }, + } +) + +func init() { + initDefaultLogger() +} + +func initDefaultLogger() { + fileStr := strings.TrimSpace(os.Getenv("ELASTIC_APM_LOG_FILE")) + if fileStr == "" { + return + } + + var logWriter io.Writer + switch strings.ToLower(fileStr) { + case "stdout": + logWriter = os.Stdout + case "stderr": + logWriter = os.Stderr + default: + f, err := os.Create(fileStr) + if err != nil { + log.Printf("failed to create %q: %s (disabling logging)", fileStr, err) + return + } + logWriter = &syncFile{File: f} + } + + logLevel := errorLevel + if levelStr := strings.TrimSpace(os.Getenv("ELASTIC_APM_LOG_LEVEL")); levelStr != "" { + level, err := parseLogLevel(levelStr) + if err != nil { + log.Printf("invalid ELASTIC_APM_LOG_LEVEL %q, falling back to %q", levelStr, logLevel) + } else { + logLevel = level + } + } + DefaultLogger = levelLogger{w: logWriter, level: logLevel} +} + +const ( + debugLevel logLevel = iota + infoLevel + warnLevel + errorLevel + noLevel +) + +type logLevel uint8 + +func (l logLevel) String() string { + switch l { + case debugLevel: + return "debug" + case infoLevel: + return "info" + case warnLevel: + return "warn" + case errorLevel: + return "error" + } + return "" +} + +func parseLogLevel(s string) (logLevel, error) { + switch strings.ToLower(s) { + case "debug": + return debugLevel, nil + case "info": + return infoLevel, nil + case "warn": + return warnLevel, nil + case "error": + return errorLevel, nil + } + return noLevel, fmt.Errorf("invalid log level string %q", s) +} + +// Logger provides methods for logging. +type Logger interface { + Debugf(format string, args ...interface{}) + Errorf(format string, args ...interface{}) + Warningf(format string, args ...interface{}) +} + +type levelLogger struct { + w io.Writer + level logLevel +} + +// Debugf logs a message with log.Printf, with a DEBUG prefix. +func (l levelLogger) Debugf(format string, args ...interface{}) { + l.logf(debugLevel, format, args...) +} + +// Errorf logs a message with log.Printf, with an ERROR prefix. +func (l levelLogger) Errorf(format string, args ...interface{}) { + l.logf(errorLevel, format, args...) +} + +// Warningf logs a message with log.Printf, with a WARNING prefix. +func (l levelLogger) Warningf(format string, args ...interface{}) { + l.logf(warnLevel, format, args...) +} + +func (l levelLogger) logf(level logLevel, format string, args ...interface{}) { + if level < l.level { + return + } + jw := fastjsonPool.Get().(*fastjson.Writer) + jw.RawString(`{"level":"`) + jw.RawString(level.String()) + jw.RawString(`","time":"`) + jw.Time(time.Now(), time.RFC3339) + jw.RawString(`","message":`) + jw.String(fmt.Sprintf(format, args...)) + jw.RawString("}\n") + l.w.Write(jw.Bytes()) + jw.Reset() + fastjsonPool.Put(jw) +} + +type syncFile struct { + mu sync.Mutex + *os.File +} + +// Write calls f.File.Write with f.mu held, to protect multiple Tracers +// in the same process from one another. +func (f *syncFile) Write(data []byte) (int, error) { + f.mu.Lock() + defer f.mu.Unlock() + return f.File.Write(data) +} diff --git a/vendor/go.elastic.co/apm/internal/apmschema/schema.go b/vendor/go.elastic.co/apm/internal/apmschema/schema.go new file mode 100644 index 00000000000..412c8498847 --- /dev/null +++ b/vendor/go.elastic.co/apm/internal/apmschema/schema.go @@ -0,0 +1,69 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apmschema + +import ( + "log" + "path" + "path/filepath" + "runtime" + + "github.com/santhosh-tekuri/jsonschema" +) + +var ( + // Error is the compiled JSON Schema for an error. + Error *jsonschema.Schema + + // Metadata is the compiled JSON Schema for metadata. + Metadata *jsonschema.Schema + + // MetricSet is the compiled JSON Schema for a set of metrics. + MetricSet *jsonschema.Schema + + // Span is the compiled JSON Schema for a span. + Span *jsonschema.Schema + + // Transaction is the compiled JSON Schema for a transaction. + Transaction *jsonschema.Schema +) + +func init() { + _, filename, _, ok := runtime.Caller(0) + if !ok { + panic("source line info not available") + } + compiler := jsonschema.NewCompiler() + compiler.Draft = jsonschema.Draft4 + schemaDir := path.Join(filepath.ToSlash(filepath.Dir(filename)), "jsonschema") + if runtime.GOOS == "windows" { + schemaDir = "/" + schemaDir + } + compile := func(filepath string, out **jsonschema.Schema) { + schema, err := compiler.Compile("file://" + path.Join(schemaDir, filepath)) + if err != nil { + log.Fatal(err) + } + *out = schema + } + compile("errors/error.json", &Error) + compile("metadata.json", &Metadata) + compile("metricsets/metricset.json", &MetricSet) + compile("spans/span.json", &Span) + compile("transactions/transaction.json", &Transaction) +} diff --git a/vendor/go.elastic.co/apm/internal/apmschema/update.sh b/vendor/go.elastic.co/apm/internal/apmschema/update.sh new file mode 100644 index 00000000000..c47c86a25b5 --- /dev/null +++ b/vendor/go.elastic.co/apm/internal/apmschema/update.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +set -ex + +BRANCH=master + +FILES=( \ + "errors/error.json" \ + "sourcemaps/payload.json" \ + "spans/span.json" \ + "transactions/mark.json" \ + "transactions/transaction.json" \ + "metricsets/metricset.json" \ + "metricsets/sample.json" \ + "context.json" \ + "message.json" \ + "metadata.json" \ + "process.json" \ + "request.json" \ + "service.json" \ + "span_subtype.json" \ + "span_type.json" \ + "stacktrace_frame.json" \ + "system.json" \ + "tags.json" \ + "timestamp_epoch.json" \ + "transaction_name.json" \ + "transaction_type.json" \ + "user.json" \ +) + +mkdir -p jsonschema/errors jsonschema/transactions jsonschema/sourcemaps jsonschema/spans jsonschema/metricsets + +for i in "${FILES[@]}"; do + o=jsonschema/$i + curl -sf https://raw.githubusercontent.com/elastic/apm-server/${BRANCH}/docs/spec/${i} --compressed -o $o +done diff --git a/vendor/go.elastic.co/apm/internal/apmstrings/truncate.go b/vendor/go.elastic.co/apm/internal/apmstrings/truncate.go new file mode 100644 index 00000000000..0ed2b6d78af --- /dev/null +++ b/vendor/go.elastic.co/apm/internal/apmstrings/truncate.go @@ -0,0 +1,31 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apmstrings + +// Truncate returns s truncated at n runes, and the number +// of runes in the resulting string (<= n). +func Truncate(s string, n int) (string, int) { + var j int + for i := range s { + if j == n { + return s[:i], n + } + j++ + } + return s, j +} diff --git a/vendor/go.elastic.co/apm/internal/apmversion/version.go b/vendor/go.elastic.co/apm/internal/apmversion/version.go new file mode 100644 index 00000000000..0b3675b9420 --- /dev/null +++ b/vendor/go.elastic.co/apm/internal/apmversion/version.go @@ -0,0 +1,23 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apmversion + +const ( + // AgentVersion is the Elastic APM Go Agent version. + AgentVersion = "1.7.2" +) diff --git a/vendor/go.elastic.co/apm/internal/configutil/duration.go b/vendor/go.elastic.co/apm/internal/configutil/duration.go new file mode 100644 index 00000000000..f29a3dbf693 --- /dev/null +++ b/vendor/go.elastic.co/apm/internal/configutil/duration.go @@ -0,0 +1,73 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package configutil + +import ( + "fmt" + "strconv" + "strings" + "time" + "unicode" +) + +// ParseDuration parses s as a duration, accepting a subset +// of the syntax supported by time.ParseDuration. +// +// Valid time units are "ms", "s", "m". +func ParseDuration(s string) (time.Duration, error) { + orig := s + var mul time.Duration = 1 + if strings.HasPrefix(s, "-") { + mul = -1 + s = s[1:] + } + + sep := -1 + for i, c := range s { + if sep == -1 { + if c < '0' || c > '9' { + sep = i + break + } + } + } + if sep == -1 { + return 0, fmt.Errorf("missing unit in duration %s (allowed units: ms, s, m)", orig) + } + + n, err := strconv.ParseInt(s[:sep], 10, 32) + if err != nil { + return 0, fmt.Errorf("invalid duration %s", orig) + } + switch s[sep:] { + case "ms": + mul *= time.Millisecond + case "s": + mul *= time.Second + case "m": + mul *= time.Minute + default: + for _, c := range s[sep:] { + if unicode.IsSpace(c) { + return 0, fmt.Errorf("invalid character %q in duration %s", c, orig) + } + } + return 0, fmt.Errorf("invalid unit in duration %s (allowed units: ms, s, m)", orig) + } + return mul * time.Duration(n), nil +} diff --git a/vendor/go.elastic.co/apm/internal/configutil/env.go b/vendor/go.elastic.co/apm/internal/configutil/env.go new file mode 100644 index 00000000000..04ac3cb97d4 --- /dev/null +++ b/vendor/go.elastic.co/apm/internal/configutil/env.go @@ -0,0 +1,95 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package configutil + +import ( + "os" + "strconv" + "time" + + "github.com/pkg/errors" + + "go.elastic.co/apm/internal/wildcard" +) + +// ParseDurationEnv gets the value of the environment variable envKey +// and, if set, parses it as a duration. If the environment variable +// is unset, defaultDuration is returned. +func ParseDurationEnv(envKey string, defaultDuration time.Duration) (time.Duration, error) { + value := os.Getenv(envKey) + if value == "" { + return defaultDuration, nil + } + d, err := ParseDuration(value) + if err != nil { + return 0, errors.Wrapf(err, "failed to parse %s", envKey) + } + return d, nil +} + +// ParseSizeEnv gets the value of the environment variable envKey +// and, if set, parses it as a size. If the environment variable +// is unset, defaultSize is returned. +func ParseSizeEnv(envKey string, defaultSize Size) (Size, error) { + value := os.Getenv(envKey) + if value == "" { + return defaultSize, nil + } + s, err := ParseSize(value) + if err != nil { + return 0, errors.Wrapf(err, "failed to parse %s", envKey) + } + return s, nil +} + +// ParseBoolEnv gets the value of the environment variable envKey +// and, if set, parses it as a boolean. If the environment variable +// is unset, defaultValue is returned. +func ParseBoolEnv(envKey string, defaultValue bool) (bool, error) { + value := os.Getenv(envKey) + if value == "" { + return defaultValue, nil + } + b, err := strconv.ParseBool(value) + if err != nil { + return false, errors.Wrapf(err, "failed to parse %s", envKey) + } + return b, nil +} + +// ParseListEnv gets the value of the environment variable envKey +// and, if set, parses it as a list separated by sep. If the environment +// variable is unset, defaultValue is returned. +func ParseListEnv(envKey, sep string, defaultValue []string) []string { + value := os.Getenv(envKey) + if value == "" { + return defaultValue + } + return ParseList(value, sep) +} + +// ParseWildcardPatternsEnv gets the value of the environment variable envKey +// and, if set, parses it as a list of wildcard patterns. If the environment +// variable is unset, defaultValue is returned. +func ParseWildcardPatternsEnv(envKey string, defaultValue wildcard.Matchers) wildcard.Matchers { + value := os.Getenv(envKey) + if value == "" { + return defaultValue + } + return ParseWildcardPatterns(value) +} diff --git a/vendor/go.elastic.co/apm/internal/configutil/list.go b/vendor/go.elastic.co/apm/internal/configutil/list.go new file mode 100644 index 00000000000..ceed90199d4 --- /dev/null +++ b/vendor/go.elastic.co/apm/internal/configutil/list.go @@ -0,0 +1,34 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package configutil + +import "strings" + +// ParseList parses s as a list of strings, separated by sep, +// and with whitespace trimmed from the list items, omitting +// empty items. +func ParseList(s, sep string) []string { + var list []string + for _, item := range strings.Split(s, sep) { + item = strings.TrimSpace(item) + if item != "" { + list = append(list, item) + } + } + return list +} diff --git a/vendor/go.elastic.co/apm/internal/configutil/size.go b/vendor/go.elastic.co/apm/internal/configutil/size.go new file mode 100644 index 00000000000..84f9657f857 --- /dev/null +++ b/vendor/go.elastic.co/apm/internal/configutil/size.go @@ -0,0 +1,105 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package configutil + +import ( + "fmt" + "strconv" + "strings" + "unicode" +) + +// Size represents a size in bytes. +type Size int64 + +// Common power-of-two sizes. +const ( + Byte Size = 1 + KByte Size = 1024 + MByte Size = 1024 * 1024 + GByte Size = 1024 * 1024 * 1024 +) + +// Bytes returns s as a number of bytes. +func (s Size) Bytes() int64 { + return int64(s) +} + +// String returns s in its most compact string representation. +func (s Size) String() string { + if s == 0 { + return "0B" + } + switch { + case s%GByte == 0: + return fmt.Sprintf("%dGB", s/GByte) + case s%MByte == 0: + return fmt.Sprintf("%dMB", s/MByte) + case s%KByte == 0: + return fmt.Sprintf("%dKB", s/KByte) + default: + return fmt.Sprintf("%dB", s) + } +} + +// ParseSize parses s as a size, in bytes. +// +// Valid size units are "b", "kb", "mb", "gb". +func ParseSize(s string) (Size, error) { + orig := s + var mul Size = 1 + if strings.HasPrefix(s, "-") { + mul = -1 + s = s[1:] + } + + sep := -1 + for i, c := range s { + if sep == -1 { + if c < '0' || c > '9' { + sep = i + break + } + } + } + if sep == -1 { + return 0, fmt.Errorf("missing unit in size %s (allowed units: B, KB, MB, GB)", orig) + } + + n, err := strconv.ParseInt(s[:sep], 10, 32) + if err != nil { + return 0, fmt.Errorf("invalid size %s", orig) + } + switch strings.ToLower(s[sep:]) { + case "gb": + mul = GByte + case "mb": + mul = MByte + case "kb": + mul = KByte + case "b": + default: + for _, c := range s[sep:] { + if unicode.IsSpace(c) { + return 0, fmt.Errorf("invalid character %q in size %s", c, orig) + } + } + return 0, fmt.Errorf("invalid unit in size %s (allowed units: B, KB, MB, GB)", orig) + } + return mul * Size(n), nil +} diff --git a/vendor/go.elastic.co/apm/internal/configutil/wildcards.go b/vendor/go.elastic.co/apm/internal/configutil/wildcards.go new file mode 100644 index 00000000000..1c47383fc62 --- /dev/null +++ b/vendor/go.elastic.co/apm/internal/configutil/wildcards.go @@ -0,0 +1,62 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package configutil + +import ( + "strings" + + "go.elastic.co/apm/internal/wildcard" +) + +// ParseWildcardPatterns parses s as a comma-separated list of wildcard patterns, +// and returns wildcard.Matchers for each. +// +// Patterns support the "*" wildcard, which will match zero or more characters. +// A prefix of (?-i) treats the pattern case-sensitively, while a prefix of (?i) +// treats the pattern case-insensitively (the default). All other characters in +// the pattern are matched exactly. +func ParseWildcardPatterns(s string) wildcard.Matchers { + patterns := ParseList(s, ",") + matchers := make(wildcard.Matchers, len(patterns)) + for i, p := range patterns { + matchers[i] = ParseWildcardPattern(p) + } + return matchers +} + +// ParseWildcardPattern parses p as a wildcard pattern, returning a wildcard.Matcher. +// +// Patterns support the "*" wildcard, which will match zero or more characters. +// A prefix of (?-i) treats the pattern case-sensitively, while a prefix of (?i) +// treats the pattern case-insensitively (the default). All other characters in +// the pattern are matched exactly. +func ParseWildcardPattern(p string) *wildcard.Matcher { + const ( + caseSensitivePrefix = "(?-i)" + caseInsensitivePrefix = "(?i)" + ) + caseSensitive := wildcard.CaseInsensitive + switch { + case strings.HasPrefix(p, caseSensitivePrefix): + caseSensitive = wildcard.CaseSensitive + p = p[len(caseSensitivePrefix):] + case strings.HasPrefix(p, caseInsensitivePrefix): + p = p[len(caseInsensitivePrefix):] + } + return wildcard.NewMatcher(p, caseSensitive) +} diff --git a/vendor/go.elastic.co/apm/internal/iochan/doc.go b/vendor/go.elastic.co/apm/internal/iochan/doc.go new file mode 100644 index 00000000000..4898c05bf6e --- /dev/null +++ b/vendor/go.elastic.co/apm/internal/iochan/doc.go @@ -0,0 +1,19 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Package iochan provides a channel-based io.Reader. +package iochan diff --git a/vendor/go.elastic.co/apm/internal/iochan/reader.go b/vendor/go.elastic.co/apm/internal/iochan/reader.go new file mode 100644 index 00000000000..0025667306b --- /dev/null +++ b/vendor/go.elastic.co/apm/internal/iochan/reader.go @@ -0,0 +1,110 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package iochan + +import ( + "sync" +) + +// Reader is a channel-based io.Reader. +// +// Reader is safe for use in a single producer, single consumer pattern. +type Reader struct { + // C can be used for receiving read requests. + // + // Once a read request is received, it must be responded + // to, in order to avoid blocking the reader. + C <-chan ReadRequest + c chan ReadRequest + resp chan readResponse + + mu sync.Mutex + readClosed bool + writeClosed bool + readErr error +} + +// NewReader returns a new Reader. +func NewReader() *Reader { + c := make(chan ReadRequest) + return &Reader{ + C: c, + c: c, + resp: make(chan readResponse, 1), + } +} + +// CloseWrite closes reader.C. CloseWrite is idempotent, +// but must not be called concurrently with Read. +func (r *Reader) CloseWrite() { + r.mu.Lock() + defer r.mu.Unlock() + if !r.writeClosed { + r.writeClosed = true + close(r.c) + } +} + +// CloseRead closes the reader such that any waiting or future +// Reads return err. Additional calls to CloseRead have no +// effect. CloseRead must not be called concurrently with +// ReadRequest.Respond. +func (r *Reader) CloseRead(err error) error { + r.mu.Lock() + defer r.mu.Unlock() + if !r.readClosed { + r.readClosed = true + r.readErr = err + close(r.resp) + } + return nil +} + +// Read sends a ReadRequest to r.C containing buf, and returns the +// response sent by the channel consumer via the read request's +// Response method. +func (r *Reader) Read(buf []byte) (int, error) { + select { + case <-r.resp: + return 0, r.readErr + case r.c <- ReadRequest{Buf: buf, response: r.resp}: + } + resp, ok := <-r.resp + if !ok { + return 0, r.readErr + } + return resp.N, resp.Err +} + +// ReadRequest holds the buffer and response channel for a read request. +type ReadRequest struct { + // Buf is the read buffer into which data should be read. + Buf []byte + response chan<- readResponse +} + +// Respond responds to the Read request. Respond must not be called +// concurrently with Reader.Close. +func (rr *ReadRequest) Respond(n int, err error) { + rr.response <- readResponse{N: n, Err: err} +} + +type readResponse struct { + N int + Err error +} diff --git a/vendor/go.elastic.co/apm/internal/pkgerrorsutil/pkgerrors.go b/vendor/go.elastic.co/apm/internal/pkgerrorsutil/pkgerrors.go new file mode 100644 index 00000000000..01ace8b18d3 --- /dev/null +++ b/vendor/go.elastic.co/apm/internal/pkgerrorsutil/pkgerrors.go @@ -0,0 +1,60 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package pkgerrorsutil + +import ( + "reflect" + "runtime" + "unsafe" + + "github.com/pkg/errors" + + "go.elastic.co/apm/stacktrace" +) + +var ( + uintptrType = reflect.TypeOf(uintptr(0)) + runtimeFrameType = reflect.TypeOf(runtime.Frame{}) + errorsStackTraceUintptr = uintptrType.ConvertibleTo(reflect.TypeOf(*new(errors.Frame))) + errorsStackTraceFrame = reflect.TypeOf(*new(errors.Frame)).ConvertibleTo(runtimeFrameType) +) + +// AppendStacktrace appends stack frames to out, based on stackTrace. +func AppendStacktrace(stackTrace errors.StackTrace, out *[]stacktrace.Frame, limit int) { + // github.com/pkg/errors 0.8.x and earlier represent + // stack frames as uintptr; 0.9.0 and later represent + // them as runtime.Frames. + // + // TODO(axw) drop support for older github.com/pkg/errors + // versions when we release go.elastic.co/apm v2.0.0. + if errorsStackTraceUintptr { + pc := make([]uintptr, len(stackTrace)) + for i, frame := range stackTrace { + pc[i] = *(*uintptr)(unsafe.Pointer(&frame)) + } + *out = stacktrace.AppendCallerFrames(*out, pc, limit) + } else if errorsStackTraceFrame { + if limit >= 0 && len(stackTrace) > limit { + stackTrace = stackTrace[:limit] + } + for _, frame := range stackTrace { + rf := (*runtime.Frame)(unsafe.Pointer(&frame)) + *out = append(*out, stacktrace.RuntimeFrame(*rf)) + } + } +} diff --git a/vendor/go.elastic.co/apm/internal/ringbuffer/buffer.go b/vendor/go.elastic.co/apm/internal/ringbuffer/buffer.go new file mode 100644 index 00000000000..4994172d144 --- /dev/null +++ b/vendor/go.elastic.co/apm/internal/ringbuffer/buffer.go @@ -0,0 +1,142 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ringbuffer + +import ( + "bytes" + "encoding/binary" + "io" + "io/ioutil" +) + +// BlockHeaderSize is the size of the block header, in bytes. +const BlockHeaderSize = 5 + +// BlockTag is a block tag, which can be used for classification. +type BlockTag uint8 + +// BlockHeader holds a fixed-size block header. +type BlockHeader struct { + // Tag is the block's tag. + Tag BlockTag + + // Size is the size of the block data, in bytes. + Size uint32 +} + +// Buffer is a ring buffer of byte blocks. +type Buffer struct { + buf []byte + headerbuf [BlockHeaderSize]byte + len int + write int + read int + + // Evicted will be called when an old block is evicted to make place for a new one. + Evicted func(BlockHeader) +} + +// New returns a new Buffer with the given size in bytes. +func New(size int) *Buffer { + return &Buffer{ + buf: make([]byte, size), + Evicted: func(BlockHeader) {}, + } +} + +// Len returns the number of bytes currently in the buffer, including +// block-accounting bytes. +func (b *Buffer) Len() int { + return b.len +} + +// Cap returns the capacity of the buffer. +func (b *Buffer) Cap() int { + return len(b.buf) +} + +// WriteBlockTo writes the oldest block in b to w, returning the block header and the number of bytes written to w. +func (b *Buffer) WriteBlockTo(w io.Writer) (header BlockHeader, written int64, err error) { + if b.len == 0 { + return header, 0, io.EOF + } + if n := copy(b.headerbuf[:], b.buf[b.read:]); n < len(b.headerbuf) { + b.read = copy(b.headerbuf[n:], b.buf[:]) + } else { + b.read = (b.read + n) % b.Cap() + } + b.len -= len(b.headerbuf) + header.Tag = BlockTag(b.headerbuf[0]) + header.Size = binary.LittleEndian.Uint32(b.headerbuf[1:]) + size := int(header.Size) + + if b.read+size > b.Cap() { + tail := b.buf[b.read:] + n, err := w.Write(tail) + if err != nil { + b.read = (b.read + size) % b.Cap() + b.len -= size + len(b.headerbuf) + return header, int64(n), err + } + size -= n + written = int64(n) + b.read = 0 + b.len -= n + } + n, err := w.Write(b.buf[b.read : b.read+size]) + if err != nil { + return header, written + int64(n), err + } + written += int64(n) + b.read = (b.read + size) % b.Cap() + b.len -= size + return header, written, nil +} + +// WriteBlock writes p as a block to b, with tag t. +// +// If len(p)+BlockHeaderSize > b.Cap(), bytes.ErrTooLarge will be returned. +// If the buffer does not currently have room for the block, then the +// oldest blocks will be evicted until enough room is available. +func (b *Buffer) WriteBlock(p []byte, tag BlockTag) (int, error) { + lenp := len(p) + if lenp+BlockHeaderSize > b.Cap() { + return 0, bytes.ErrTooLarge + } + for lenp+BlockHeaderSize > b.Cap()-b.Len() { + header, _, err := b.WriteBlockTo(ioutil.Discard) + if err != nil { + return 0, err + } + b.Evicted(header) + } + b.headerbuf[0] = uint8(tag) + binary.LittleEndian.PutUint32(b.headerbuf[1:], uint32(lenp)) + if n := copy(b.buf[b.write:], b.headerbuf[:]); n < len(b.headerbuf) { + b.write = copy(b.buf, b.headerbuf[n:]) + } else { + b.write = (b.write + n) % b.Cap() + } + if n := copy(b.buf[b.write:], p); n < lenp { + b.write = copy(b.buf, p[n:]) + } else { + b.write = (b.write + n) % b.Cap() + } + b.len += lenp + BlockHeaderSize + return lenp, nil +} diff --git a/vendor/go.elastic.co/apm/internal/ringbuffer/doc.go b/vendor/go.elastic.co/apm/internal/ringbuffer/doc.go new file mode 100644 index 00000000000..897578849c3 --- /dev/null +++ b/vendor/go.elastic.co/apm/internal/ringbuffer/doc.go @@ -0,0 +1,22 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Package ringbuffer provides a ring buffer for storing blocks of bytes. +// Bytes are written and read in discrete blocks. If the buffer becomes +// full, then writing to it will evict the oldest blocks until there is +// space for a new one. +package ringbuffer diff --git a/vendor/go.elastic.co/apm/internal/wildcard/doc.go b/vendor/go.elastic.co/apm/internal/wildcard/doc.go new file mode 100644 index 00000000000..07645ad995b --- /dev/null +++ b/vendor/go.elastic.co/apm/internal/wildcard/doc.go @@ -0,0 +1,19 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Package wildcard provides a fast, zero-allocation wildcard matcher. +package wildcard diff --git a/vendor/go.elastic.co/apm/internal/wildcard/matcher.go b/vendor/go.elastic.co/apm/internal/wildcard/matcher.go new file mode 100644 index 00000000000..e406eb6eb8d --- /dev/null +++ b/vendor/go.elastic.co/apm/internal/wildcard/matcher.go @@ -0,0 +1,142 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package wildcard + +import ( + "strings" + "unicode" + "unicode/utf8" +) + +// CaseSensitivity controls the case sensitivity of matching. +type CaseSensitivity bool + +// CaseSensitivity values. +const ( + CaseSensitive CaseSensitivity = true + CaseInsensitive CaseSensitivity = false +) + +// NewMatcher constructs a new wildcard matcher for the given pattern. +// +// If p is the empty string, it will match only the empty string. +// If p is not a valid UTF-8 string, matching behaviour is undefined. +func NewMatcher(p string, caseSensitive CaseSensitivity) *Matcher { + parts := strings.Split(p, "*") + m := &Matcher{ + wildcardBegin: strings.HasPrefix(p, "*"), + wildcardEnd: strings.HasSuffix(p, "*"), + caseSensitive: caseSensitive, + } + for _, part := range parts { + if part == "" { + continue + } + if !m.caseSensitive { + part = strings.ToLower(part) + } + m.parts = append(m.parts, part) + } + return m +} + +// Matcher matches strings against a wildcard pattern with configurable case sensitivity. +type Matcher struct { + parts []string + wildcardBegin bool + wildcardEnd bool + caseSensitive CaseSensitivity +} + +// Match reports whether s matches m's wildcard pattern. +func (m *Matcher) Match(s string) bool { + if len(m.parts) == 0 && !m.wildcardBegin && !m.wildcardEnd { + return s == "" + } + if len(m.parts) == 1 && !m.wildcardBegin && !m.wildcardEnd { + if m.caseSensitive { + return s == m.parts[0] + } + return len(s) == len(m.parts[0]) && hasPrefixLower(s, m.parts[0]) == 0 + } + parts := m.parts + if !m.wildcardEnd && len(parts) > 0 { + part := parts[len(parts)-1] + if m.caseSensitive { + if !strings.HasSuffix(s, part) { + return false + } + } else { + if len(s) < len(part) { + return false + } + if hasPrefixLower(s[len(s)-len(part):], part) != 0 { + return false + } + } + parts = parts[:len(parts)-1] + } + for i, part := range parts { + j := -1 + if m.caseSensitive { + if i > 0 || m.wildcardBegin { + j = strings.Index(s, part) + } else { + if !strings.HasPrefix(s, part) { + return false + } + j = 0 + } + } else { + off := 0 + for j == -1 && len(s)-off >= len(part) { + skip := hasPrefixLower(s[off:], part) + if skip == 0 { + j = off + } else { + if i == 0 && !m.wildcardBegin { + return false + } + off += skip + } + } + } + if j == -1 { + return false + } + s = s[j+len(part):] + } + return true +} + +// hasPrefixLower reports whether or not s begins with prefixLower, +// returning 0 if it does, and the number of bytes representing the +// first rune in s otherwise. +func hasPrefixLower(s, prefixLower string) (skip int) { + var firstSize int + for i, r := range prefixLower { + r2, size := utf8.DecodeRuneInString(s[i:]) + if firstSize == 0 { + firstSize = size + } + if r2 != r && r2 != unicode.ToUpper(r) { + return firstSize + } + } + return 0 +} diff --git a/vendor/go.elastic.co/apm/internal/wildcard/matchers.go b/vendor/go.elastic.co/apm/internal/wildcard/matchers.go new file mode 100644 index 00000000000..5d443701913 --- /dev/null +++ b/vendor/go.elastic.co/apm/internal/wildcard/matchers.go @@ -0,0 +1,31 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package wildcard + +// Matchers is a slice of Matcher, matching any of the contained matchers. +type Matchers []*Matcher + +// MatchAny returns true iff any of the matchers returns true. +func (m Matchers) MatchAny(s string) bool { + for _, m := range m { + if m.Match(s) { + return true + } + } + return false +} diff --git a/vendor/go.elastic.co/apm/logger.go b/vendor/go.elastic.co/apm/logger.go new file mode 100644 index 00000000000..8e30e5918a8 --- /dev/null +++ b/vendor/go.elastic.co/apm/logger.go @@ -0,0 +1,54 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apm + +// Logger is an interface for logging, used by the tracer +// to log tracer errors and other interesting events. +type Logger interface { + // Debugf logs a message at debug level. + Debugf(format string, args ...interface{}) + + // Errorf logs a message at error level. + Errorf(format string, args ...interface{}) +} + +// WarningLogger extends Logger with a Warningf method. +// +// TODO(axw) this will be removed in v2.0.0, and the +// Warningf method will be added directly to Logger. +type WarningLogger interface { + Logger + + // Warningf logs a message at warning level. + Warningf(format string, args ...interface{}) +} + +func makeWarningLogger(l Logger) WarningLogger { + if wl, ok := l.(WarningLogger); ok { + return wl + } + return debugWarningLogger{Logger: l} +} + +type debugWarningLogger struct { + Logger +} + +func (l debugWarningLogger) Warningf(format string, args ...interface{}) { + l.Debugf(format, args...) +} diff --git a/vendor/go.elastic.co/apm/metrics.go b/vendor/go.elastic.co/apm/metrics.go new file mode 100644 index 00000000000..6f5ecb7e201 --- /dev/null +++ b/vendor/go.elastic.co/apm/metrics.go @@ -0,0 +1,161 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apm + +import ( + "context" + "sort" + "strings" + "sync" + + "go.elastic.co/apm/internal/wildcard" + "go.elastic.co/apm/model" +) + +// Metrics holds a set of metrics. +type Metrics struct { + disabled wildcard.Matchers + + mu sync.Mutex + metrics []*model.Metrics + + // transactionGroupMetrics holds metrics which are scoped to transaction + // groups, and are not sorted according to their labels. + transactionGroupMetrics []*model.Metrics +} + +func (m *Metrics) reset() { + m.metrics = m.metrics[:0] + m.transactionGroupMetrics = m.transactionGroupMetrics[:0] +} + +// MetricLabel is a name/value pair for labeling metrics. +type MetricLabel struct { + // Name is the label name. + Name string + + // Value is the label value. + Value string +} + +// MetricsGatherer provides an interface for gathering metrics. +type MetricsGatherer interface { + // GatherMetrics gathers metrics and adds them to m. + // + // If ctx.Done() is signaled, gathering should be aborted and + // ctx.Err() returned. If GatherMetrics returns an error, it + // will be logged, but otherwise there is no effect; the + // implementation must take care not to leave m in an invalid + // state due to errors. + GatherMetrics(ctx context.Context, m *Metrics) error +} + +// GatherMetricsFunc is a function type implementing MetricsGatherer. +type GatherMetricsFunc func(context.Context, *Metrics) error + +// GatherMetrics calls f(ctx, m). +func (f GatherMetricsFunc) GatherMetrics(ctx context.Context, m *Metrics) error { + return f(ctx, m) +} + +// Add adds a metric with the given name, labels, and value, +// The labels are expected to be sorted lexicographically. +func (m *Metrics) Add(name string, labels []MetricLabel, value float64) { + m.addMetric(name, labels, model.Metric{Value: value}) +} + +func (m *Metrics) addMetric(name string, labels []MetricLabel, metric model.Metric) { + if m.disabled.MatchAny(name) { + return + } + m.mu.Lock() + defer m.mu.Unlock() + + var metrics *model.Metrics + results := make([]int, len(m.metrics)) + i := sort.Search(len(m.metrics), func(j int) bool { + results[j] = compareLabels(m.metrics[j].Labels, labels) + return results[j] >= 0 + }) + if i < len(results) && results[i] == 0 { + // labels are equal + metrics = m.metrics[i] + } else { + var modelLabels model.StringMap + if len(labels) > 0 { + modelLabels = make(model.StringMap, len(labels)) + for i, l := range labels { + modelLabels[i] = model.StringMapItem{ + Key: l.Name, Value: l.Value, + } + } + } + metrics = &model.Metrics{ + Labels: modelLabels, + Samples: make(map[string]model.Metric), + } + if i == len(results) { + m.metrics = append(m.metrics, metrics) + } else { + m.metrics = append(m.metrics, nil) + copy(m.metrics[i+1:], m.metrics[i:]) + m.metrics[i] = metrics + } + } + metrics.Samples[name] = metric +} + +func compareLabels(a model.StringMap, b []MetricLabel) int { + na, nb := len(a), len(b) + n := na + if na > nb { + n = nb + } + for i := 0; i < n; i++ { + la, lb := a[i], b[i] + d := strings.Compare(la.Key, lb.Name) + if d == 0 { + d = strings.Compare(la.Value, lb.Value) + } + if d != 0 { + return d + } + } + switch { + case na < nb: + return -1 + case na > nb: + return 1 + } + return 0 +} + +func gatherMetrics(ctx context.Context, g MetricsGatherer, m *Metrics, logger Logger) { + defer func() { + if r := recover(); r != nil { + if logger != nil { + logger.Debugf("%T.GatherMetrics panicked: %s", g, r) + } + } + }() + if err := g.GatherMetrics(ctx, m); err != nil { + if logger != nil && err != context.Canceled { + logger.Debugf("%T.GatherMetrics failed: %s", g, err) + } + } +} diff --git a/vendor/go.elastic.co/apm/model/doc.go b/vendor/go.elastic.co/apm/model/doc.go new file mode 100644 index 00000000000..3fe20d31a65 --- /dev/null +++ b/vendor/go.elastic.co/apm/model/doc.go @@ -0,0 +1,21 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Package model provides the Elastic APM model types. +// +// https://www.elastic.co/guide/en/apm/server/current/intake-api.html +package model diff --git a/vendor/go.elastic.co/apm/model/generate.sh b/vendor/go.elastic.co/apm/model/generate.sh new file mode 100644 index 00000000000..f403a8dd5a9 --- /dev/null +++ b/vendor/go.elastic.co/apm/model/generate.sh @@ -0,0 +1,4 @@ +#!/bin/sh +set -e +go run go.elastic.co/fastjson/cmd/generate-fastjson -f -o marshal_fastjson.go . +exec go-licenser marshal_fastjson.go diff --git a/vendor/go.elastic.co/apm/model/gofuzz.go b/vendor/go.elastic.co/apm/model/gofuzz.go new file mode 100644 index 00000000000..04fb279e252 --- /dev/null +++ b/vendor/go.elastic.co/apm/model/gofuzz.go @@ -0,0 +1,82 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build gofuzz + +package model + +import ( + "bytes" + "encoding/json" + + "go.elastic.co/apm/internal/apmschema" + "go.elastic.co/fastjson" +) + +func Fuzz(data []byte) int { + type Payload struct { + Service *Service `json:"service"` + Process *Process `json:"process,omitempty"` + System *System `json:"system,omitempty"` + Errors []*Error `json:"errors"` + Transactions []Transaction `json:"transactions"` + } + + var payload Payload + decoder := json.NewDecoder(bytes.NewReader(data)) + decoder.DisallowUnknownFields() + if err := decoder.Decode(&payload); err != nil { + return -1 + } + raw := make(map[string]interface{}) + if err := json.Unmarshal(data, &raw); err != nil { + return -1 + } + + if len(payload.Errors) != 0 { + payload := ErrorsPayload{ + Service: payload.Service, + Process: payload.Process, + System: payload.System, + Errors: payload.Errors, + } + var w fastjson.Writer + if err := payload.MarshalFastJSON(&w); err != nil { + panic(err) + } + if err := apmschema.Errors.Validate(bytes.NewReader(w.Bytes())); err != nil { + panic(err) + } + } + + if len(payload.Transactions) != 0 { + payload := TransactionsPayload{ + Service: payload.Service, + Process: payload.Process, + System: payload.System, + Transactions: payload.Transactions, + } + var w fastjson.Writer + if err := payload.MarshalFastJSON(&w); err != nil { + panic(err) + } + if err := apmschema.Transactions.Validate(bytes.NewReader(w.Bytes())); err != nil { + panic(err) + } + } + return 0 +} diff --git a/vendor/go.elastic.co/apm/model/maps.go b/vendor/go.elastic.co/apm/model/maps.go new file mode 100644 index 00000000000..7313d9cf7b0 --- /dev/null +++ b/vendor/go.elastic.co/apm/model/maps.go @@ -0,0 +1,48 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package model + +// StringMap is a slice-representation of map[string]string, +// optimized for fast JSON encoding. +// +// Slice items are expected to be ordered by key. +type StringMap []StringMapItem + +// StringMapItem holds a string key and value. +type StringMapItem struct { + // Key is the map item's key. + Key string + + // Value is the map item's value. + Value string +} + +// IfaceMap is a slice-representation of map[string]interface{}, +// optimized for fast JSON encoding. +// +// Slice items are expected to be ordered by key. +type IfaceMap []IfaceMapItem + +// IfaceMapItem holds a string key and value. +type IfaceMapItem struct { + // Key is the map item's key. + Key string + + // Value is the map item's value. + Value interface{} +} diff --git a/vendor/go.elastic.co/apm/model/marshal.go b/vendor/go.elastic.co/apm/model/marshal.go new file mode 100644 index 00000000000..298d4bcdb68 --- /dev/null +++ b/vendor/go.elastic.co/apm/model/marshal.go @@ -0,0 +1,639 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package model + +import ( + "encoding/hex" + "encoding/json" + "net/http" + "net/url" + "sort" + "strings" + "time" + + "github.com/pkg/errors" + + "go.elastic.co/fastjson" +) + +//go:generate sh generate.sh + +// MarshalFastJSON writes the JSON representation of t to w. +func (t Time) MarshalFastJSON(w *fastjson.Writer) error { + w.Int64(time.Time(t).UnixNano() / int64(time.Microsecond)) + return nil +} + +// UnmarshalJSON unmarshals the JSON data into t. +func (t *Time) UnmarshalJSON(data []byte) error { + var usec int64 + if err := json.Unmarshal(data, &usec); err != nil { + return err + } + *t = Time(time.Unix(usec/1000000, (usec%1000000)*1000).UTC()) + return nil +} + +// UnmarshalJSON unmarshals the JSON data into v. +func (v *HTTPSpanContext) UnmarshalJSON(data []byte) error { + var httpSpanContext struct { + URL string + StatusCode int `json:"status_code"` + } + if err := json.Unmarshal(data, &httpSpanContext); err != nil { + return err + } + u, err := url.Parse(httpSpanContext.URL) + if err != nil { + return err + } + v.URL = u + v.StatusCode = httpSpanContext.StatusCode + return nil +} + +// MarshalFastJSON writes the JSON representation of v to w. +func (v *HTTPSpanContext) MarshalFastJSON(w *fastjson.Writer) error { + w.RawByte('{') + beforeURL := w.Size() + w.RawString(`"url":"`) + if v.marshalURL(w) { + w.RawByte('"') + } else { + w.Rewind(beforeURL) + } + if v.StatusCode > 0 { + w.RawString(`,"status_code":`) + w.Int64(int64(v.StatusCode)) + } + w.RawByte('}') + return nil +} + +func (v *HTTPSpanContext) marshalURL(w *fastjson.Writer) bool { + if v.URL.Scheme != "" { + if !marshalScheme(w, v.URL.Scheme) { + return false + } + w.RawString("://") + } else { + w.RawString("http://") + } + w.StringContents(v.URL.Host) + if v.URL.Path == "" { + w.RawByte('/') + } else { + if v.URL.Path[0] != '/' { + w.RawByte('/') + } + w.StringContents(v.URL.Path) + } + if v.URL.RawQuery != "" { + w.RawByte('?') + w.StringContents(v.URL.RawQuery) + } + if v.URL.Fragment != "" { + w.RawByte('#') + w.StringContents(v.URL.Fragment) + } + return true +} + +// MarshalFastJSON writes the JSON representation of v to w. +func (v *URL) MarshalFastJSON(w *fastjson.Writer) error { + w.RawByte('{') + first := true + if v.Hash != "" { + const prefix = ",\"hash\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.String(v.Hash) + } + if v.Hostname != "" { + const prefix = ",\"hostname\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.String(v.Hostname) + } + if v.Path != "" { + const prefix = `,"pathname":"` + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + if v.Path[0] != '/' { + w.RawByte('/') + } + w.StringContents(v.Path) + w.RawByte('"') + } + if v.Port != "" { + const prefix = ",\"port\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.String(v.Port) + } + schemeBegin := -1 + schemeEnd := -1 + if v.Protocol != "" { + before := w.Size() + const prefix = ",\"protocol\":\"" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + schemeBegin = w.Size() + if marshalScheme(w, v.Protocol) { + schemeEnd = w.Size() + w.RawByte('"') + } else { + w.Rewind(before) + } + } + if v.Search != "" { + const prefix = ",\"search\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.String(v.Search) + } + if schemeEnd != -1 && v.Hostname != "" && v.Path != "" { + before := w.Size() + w.RawString(",\"full\":") + if !v.marshalFullURL(w, w.Bytes()[schemeBegin:schemeEnd]) { + w.Rewind(before) + } + } + w.RawByte('}') + return nil +} + +func marshalScheme(w *fastjson.Writer, scheme string) bool { + // Canonicalize the scheme to lowercase. Don't use + // strings.ToLower, as it's too general and requires + // additional memory allocations. + // + // The scheme should start with a letter, and may + // then be followed by letters, digits, '+', '-', + // and '.'. We don't validate the scheme here, we + // just use those restrictions as a basis for + // optimization; anything not in that set will + // mean the full URL is omitted. + for i := 0; i < len(scheme); i++ { + c := scheme[i] + switch { + case c >= 'a' && c <= 'z' || c >= '0' && c <= '9' || c == '+' || c == '-' || c == '.': + w.RawByte(c) + case c >= 'A' && c <= 'Z': + w.RawByte(c + 'a' - 'A') + default: + return false + } + } + return true +} + +func (v *URL) marshalFullURL(w *fastjson.Writer, scheme []byte) bool { + w.RawByte('"') + before := w.Size() + w.RawBytes(scheme) + w.RawString("://") + if strings.IndexByte(v.Hostname, ':') == -1 { + w.StringContents(v.Hostname) + } else { + w.RawByte('[') + w.StringContents(v.Hostname) + w.RawByte(']') + } + if v.Port != "" { + w.RawByte(':') + w.StringContents(v.Port) + } + if !strings.HasPrefix(v.Path, "/") { + w.RawByte('/') + } + w.StringContents(v.Path) + if v.Search != "" { + w.RawByte('?') + w.StringContents(v.Search) + } + if v.Hash != "" { + w.RawByte('#') + w.StringContents(v.Hash) + } + if n := w.Size() - before; n > 1024 { + // Truncate the full URL to 1024 bytes. + w.Rewind(w.Size() - n + 1024) + } + w.RawByte('"') + return true +} + +func (l *Log) isZero() bool { + return l.Message == "" +} + +func (e *Exception) isZero() bool { + return e.Message == "" +} + +func (c Cookies) isZero() bool { + return len(c) == 0 +} + +// MarshalFastJSON writes the JSON representation of c to w. +func (c Cookies) MarshalFastJSON(w *fastjson.Writer) error { + w.RawByte('{') + first := true +outer: + for i := len(c) - 1; i >= 0; i-- { + for j := i + 1; j < len(c); j++ { + if c[i].Name == c[j].Name { + continue outer + } + } + if first { + first = false + } else { + w.RawByte(',') + } + w.String(c[i].Name) + w.RawByte(':') + w.String(c[i].Value) + } + w.RawByte('}') + return nil +} + +// UnmarshalJSON unmarshals the JSON data into c. +func (c *Cookies) UnmarshalJSON(data []byte) error { + m := make(map[string]string) + if err := json.Unmarshal(data, &m); err != nil { + return err + } + *c = make([]*http.Cookie, 0, len(m)) + for k, v := range m { + *c = append(*c, &http.Cookie{ + Name: k, + Value: v, + }) + } + sort.Slice(*c, func(i, j int) bool { + return (*c)[i].Name < (*c)[j].Name + }) + return nil +} + +func (hs Headers) isZero() bool { + return len(hs) == 0 +} + +// MarshalFastJSON writes the JSON representation of h to w. +func (hs Headers) MarshalFastJSON(w *fastjson.Writer) error { + w.RawByte('{') + for i, h := range hs { + if i != 0 { + w.RawByte(',') + } + w.String(h.Key) + w.RawByte(':') + if len(h.Values) == 1 { + // Just one item, add the item directly. + w.String(h.Values[0]) + } else { + // Zero or multiple items, include them all. + w.RawByte('[') + for i, v := range h.Values { + if i != 0 { + w.RawByte(',') + } + w.String(v) + } + w.RawByte(']') + } + } + w.RawByte('}') + return nil +} + +// MarshalFastJSON writes the JSON representation of h to w. +func (*Header) MarshalFastJSON(w *fastjson.Writer) error { + panic("unreachable") +} + +// UnmarshalJSON unmarshals the JSON data into c. +func (hs *Headers) UnmarshalJSON(data []byte) error { + var m map[string]interface{} + if err := json.Unmarshal(data, &m); err != nil { + return err + } + for k, v := range m { + switch v := v.(type) { + case string: + *hs = append(*hs, Header{Key: k, Values: []string{v}}) + case []interface{}: + var values []string + for _, v := range v { + switch v := v.(type) { + case string: + values = append(values, v) + default: + return errors.Errorf("expected string, got %T", v) + } + } + *hs = append(*hs, Header{Key: k, Values: values}) + default: + return errors.Errorf("expected string or []string, got %T", v) + } + } + sort.Slice(*hs, func(i, j int) bool { + return (*hs)[i].Key < (*hs)[j].Key + }) + return nil +} + +// MarshalFastJSON writes the JSON representation of c to w. +func (c *ExceptionCode) MarshalFastJSON(w *fastjson.Writer) error { + if c.String != "" { + w.String(c.String) + } else { + w.Float64(c.Number) + } + return nil +} + +// UnmarshalJSON unmarshals the JSON data into c. +func (c *ExceptionCode) UnmarshalJSON(data []byte) error { + var v interface{} + if err := json.Unmarshal(data, &v); err != nil { + return err + } + switch v := v.(type) { + case string: + c.String = v + case float64: + c.Number = v + default: + return errors.Errorf("expected string or number, got %T", v) + } + return nil +} + +// isZero is used by fastjson to implement omitempty. +func (c *ExceptionCode) isZero() bool { + return c.String == "" && c.Number == 0 +} + +// MarshalFastJSON writes the JSON representation of b to w. +func (b *RequestBody) MarshalFastJSON(w *fastjson.Writer) error { + if b.Form != nil { + w.RawByte('{') + first := true + for k, v := range b.Form { + if first { + first = false + } else { + w.RawByte(',') + } + w.String(k) + w.RawByte(':') + if len(v) == 1 { + // Just one item, add the item directly. + w.String(v[0]) + } else { + // Zero or multiple items, include them all. + w.RawByte('[') + first := true + for _, v := range v { + if first { + first = false + } else { + w.RawByte(',') + } + w.String(v) + } + w.RawByte(']') + } + } + w.RawByte('}') + } else { + w.String(b.Raw) + } + return nil +} + +// UnmarshalJSON unmarshals the JSON data into b. +func (b *RequestBody) UnmarshalJSON(data []byte) error { + var v interface{} + if err := json.Unmarshal(data, &v); err != nil { + return err + } + switch v := v.(type) { + case string: + b.Raw = v + return nil + case map[string]interface{}: + form := make(url.Values, len(v)) + for k, v := range v { + switch v := v.(type) { + case string: + form.Set(k, v) + case []interface{}: + for _, v := range v { + switch v := v.(type) { + case string: + form.Add(k, v) + default: + return errors.Errorf("expected string, got %T", v) + } + } + default: + return errors.Errorf("expected string or []string, got %T", v) + } + } + b.Form = form + default: + return errors.Errorf("expected string or map, got %T", v) + } + return nil +} + +func (m StringMap) isZero() bool { + return len(m) == 0 +} + +// MarshalFastJSON writes the JSON representation of m to w. +func (m StringMap) MarshalFastJSON(w *fastjson.Writer) (firstErr error) { + w.RawByte('{') + first := true + for _, item := range m { + if first { + first = false + } else { + w.RawByte(',') + } + w.String(item.Key) + w.RawByte(':') + if err := fastjson.Marshal(w, item.Value); err != nil && firstErr == nil { + firstErr = err + } + } + w.RawByte('}') + return nil +} + +// UnmarshalJSON unmarshals the JSON data into m. +func (m *StringMap) UnmarshalJSON(data []byte) error { + var mm map[string]string + if err := json.Unmarshal(data, &mm); err != nil { + return err + } + *m = make(StringMap, 0, len(mm)) + for k, v := range mm { + *m = append(*m, StringMapItem{Key: k, Value: v}) + } + sort.Slice(*m, func(i, j int) bool { + return (*m)[i].Key < (*m)[j].Key + }) + return nil +} + +// MarshalFastJSON exists to prevent code generation for StringMapItem. +func (*StringMapItem) MarshalFastJSON(*fastjson.Writer) error { + panic("unreachable") +} + +func (m IfaceMap) isZero() bool { + return len(m) == 0 +} + +// MarshalFastJSON writes the JSON representation of m to w. +func (m IfaceMap) MarshalFastJSON(w *fastjson.Writer) (firstErr error) { + w.RawByte('{') + first := true + for _, item := range m { + if first { + first = false + } else { + w.RawByte(',') + } + w.String(item.Key) + w.RawByte(':') + if err := fastjson.Marshal(w, item.Value); err != nil && firstErr == nil { + firstErr = err + } + } + w.RawByte('}') + return nil +} + +// UnmarshalJSON unmarshals the JSON data into m. +func (m *IfaceMap) UnmarshalJSON(data []byte) error { + var mm map[string]interface{} + if err := json.Unmarshal(data, &mm); err != nil { + return err + } + *m = make(IfaceMap, 0, len(mm)) + for k, v := range mm { + *m = append(*m, IfaceMapItem{Key: k, Value: v}) + } + sort.Slice(*m, func(i, j int) bool { + return (*m)[i].Key < (*m)[j].Key + }) + return nil +} + +// MarshalFastJSON exists to prevent code generation for IfaceMapItem. +func (*IfaceMapItem) MarshalFastJSON(*fastjson.Writer) error { + panic("unreachable") +} + +func (id *TraceID) isZero() bool { + return *id == TraceID{} +} + +// MarshalFastJSON writes the JSON representation of id to w. +func (id *TraceID) MarshalFastJSON(w *fastjson.Writer) error { + w.RawByte('"') + writeHex(w, id[:]) + w.RawByte('"') + return nil +} + +// UnmarshalJSON unmarshals the JSON data into id. +func (id *TraceID) UnmarshalJSON(data []byte) error { + _, err := hex.Decode(id[:], data[1:len(data)-1]) + return err +} + +func (id *SpanID) isZero() bool { + return *id == SpanID{} +} + +// UnmarshalJSON unmarshals the JSON data into id. +func (id *SpanID) UnmarshalJSON(data []byte) error { + _, err := hex.Decode(id[:], data[1:len(data)-1]) + return err +} + +// MarshalFastJSON writes the JSON representation of id to w. +func (id *SpanID) MarshalFastJSON(w *fastjson.Writer) error { + w.RawByte('"') + writeHex(w, id[:]) + w.RawByte('"') + return nil +} + +func (t *ErrorTransaction) isZero() bool { + return *t == ErrorTransaction{} +} + +func (t *MetricsTransaction) isZero() bool { + return *t == MetricsTransaction{} +} + +func (s *MetricsSpan) isZero() bool { + return *s == MetricsSpan{} +} + +func writeHex(w *fastjson.Writer, v []byte) { + const hextable = "0123456789abcdef" + for _, v := range v { + w.RawByte(hextable[v>>4]) + w.RawByte(hextable[v&0x0f]) + } +} diff --git a/vendor/go.elastic.co/apm/model/marshal_fastjson.go b/vendor/go.elastic.co/apm/model/marshal_fastjson.go new file mode 100644 index 00000000000..cd5749c2533 --- /dev/null +++ b/vendor/go.elastic.co/apm/model/marshal_fastjson.go @@ -0,0 +1,1297 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Code generated by "generate-fastjson". DO NOT EDIT. + +package model + +import ( + "go.elastic.co/fastjson" +) + +func (v *Service) MarshalFastJSON(w *fastjson.Writer) error { + var firstErr error + w.RawByte('{') + first := true + if v.Agent != nil { + const prefix = ",\"agent\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + if err := v.Agent.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + if v.Environment != "" { + const prefix = ",\"environment\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.String(v.Environment) + } + if v.Framework != nil { + const prefix = ",\"framework\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + if err := v.Framework.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + if v.Language != nil { + const prefix = ",\"language\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + if err := v.Language.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + if v.Name != "" { + const prefix = ",\"name\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.String(v.Name) + } + if v.Node != nil { + const prefix = ",\"node\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + if err := v.Node.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + if v.Runtime != nil { + const prefix = ",\"runtime\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + if err := v.Runtime.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + if v.Version != "" { + const prefix = ",\"version\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.String(v.Version) + } + w.RawByte('}') + return firstErr +} + +func (v *Agent) MarshalFastJSON(w *fastjson.Writer) error { + w.RawByte('{') + w.RawString("\"name\":") + w.String(v.Name) + w.RawString(",\"version\":") + w.String(v.Version) + w.RawByte('}') + return nil +} + +func (v *Framework) MarshalFastJSON(w *fastjson.Writer) error { + w.RawByte('{') + w.RawString("\"name\":") + w.String(v.Name) + w.RawString(",\"version\":") + w.String(v.Version) + w.RawByte('}') + return nil +} + +func (v *Language) MarshalFastJSON(w *fastjson.Writer) error { + w.RawByte('{') + w.RawString("\"name\":") + w.String(v.Name) + if v.Version != "" { + w.RawString(",\"version\":") + w.String(v.Version) + } + w.RawByte('}') + return nil +} + +func (v *Runtime) MarshalFastJSON(w *fastjson.Writer) error { + w.RawByte('{') + w.RawString("\"name\":") + w.String(v.Name) + w.RawString(",\"version\":") + w.String(v.Version) + w.RawByte('}') + return nil +} + +func (v *ServiceNode) MarshalFastJSON(w *fastjson.Writer) error { + w.RawByte('{') + if v.ConfiguredName != "" { + w.RawString("\"configured_name\":") + w.String(v.ConfiguredName) + } + w.RawByte('}') + return nil +} + +func (v *System) MarshalFastJSON(w *fastjson.Writer) error { + var firstErr error + w.RawByte('{') + first := true + if v.Architecture != "" { + const prefix = ",\"architecture\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.String(v.Architecture) + } + if v.Container != nil { + const prefix = ",\"container\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + if err := v.Container.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + if v.Hostname != "" { + const prefix = ",\"hostname\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.String(v.Hostname) + } + if v.Kubernetes != nil { + const prefix = ",\"kubernetes\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + if err := v.Kubernetes.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + if v.Platform != "" { + const prefix = ",\"platform\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.String(v.Platform) + } + w.RawByte('}') + return firstErr +} + +func (v *Process) MarshalFastJSON(w *fastjson.Writer) error { + w.RawByte('{') + w.RawString("\"pid\":") + w.Int64(int64(v.Pid)) + if v.Argv != nil { + w.RawString(",\"argv\":") + w.RawByte('[') + for i, v := range v.Argv { + if i != 0 { + w.RawByte(',') + } + w.String(v) + } + w.RawByte(']') + } + if v.Ppid != nil { + w.RawString(",\"ppid\":") + w.Int64(int64(*v.Ppid)) + } + if v.Title != "" { + w.RawString(",\"title\":") + w.String(v.Title) + } + w.RawByte('}') + return nil +} + +func (v *Container) MarshalFastJSON(w *fastjson.Writer) error { + w.RawByte('{') + w.RawString("\"id\":") + w.String(v.ID) + w.RawByte('}') + return nil +} + +func (v *Kubernetes) MarshalFastJSON(w *fastjson.Writer) error { + var firstErr error + w.RawByte('{') + first := true + if v.Namespace != "" { + const prefix = ",\"namespace\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.String(v.Namespace) + } + if v.Node != nil { + const prefix = ",\"node\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + if err := v.Node.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + if v.Pod != nil { + const prefix = ",\"pod\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + if err := v.Pod.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + w.RawByte('}') + return firstErr +} + +func (v *KubernetesNode) MarshalFastJSON(w *fastjson.Writer) error { + w.RawByte('{') + if v.Name != "" { + w.RawString("\"name\":") + w.String(v.Name) + } + w.RawByte('}') + return nil +} + +func (v *KubernetesPod) MarshalFastJSON(w *fastjson.Writer) error { + w.RawByte('{') + first := true + if v.Name != "" { + const prefix = ",\"name\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.String(v.Name) + } + if v.UID != "" { + const prefix = ",\"uid\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.String(v.UID) + } + w.RawByte('}') + return nil +} + +func (v *Transaction) MarshalFastJSON(w *fastjson.Writer) error { + var firstErr error + w.RawByte('{') + w.RawString("\"duration\":") + w.Float64(v.Duration) + w.RawString(",\"id\":") + if err := v.ID.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + w.RawString(",\"name\":") + w.String(v.Name) + w.RawString(",\"span_count\":") + if err := v.SpanCount.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + w.RawString(",\"timestamp\":") + if err := v.Timestamp.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + w.RawString(",\"trace_id\":") + if err := v.TraceID.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + w.RawString(",\"type\":") + w.String(v.Type) + if v.Context != nil { + w.RawString(",\"context\":") + if err := v.Context.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + if !v.ParentID.isZero() { + w.RawString(",\"parent_id\":") + if err := v.ParentID.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + if v.Result != "" { + w.RawString(",\"result\":") + w.String(v.Result) + } + if v.Sampled != nil { + w.RawString(",\"sampled\":") + w.Bool(*v.Sampled) + } + w.RawByte('}') + return firstErr +} + +func (v *SpanCount) MarshalFastJSON(w *fastjson.Writer) error { + w.RawByte('{') + w.RawString("\"dropped\":") + w.Int64(int64(v.Dropped)) + w.RawString(",\"started\":") + w.Int64(int64(v.Started)) + w.RawByte('}') + return nil +} + +func (v *Span) MarshalFastJSON(w *fastjson.Writer) error { + var firstErr error + w.RawByte('{') + w.RawString("\"duration\":") + w.Float64(v.Duration) + w.RawString(",\"id\":") + if err := v.ID.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + w.RawString(",\"name\":") + w.String(v.Name) + w.RawString(",\"timestamp\":") + if err := v.Timestamp.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + w.RawString(",\"trace_id\":") + if err := v.TraceID.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + w.RawString(",\"transaction_id\":") + if err := v.TransactionID.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + w.RawString(",\"type\":") + w.String(v.Type) + if v.Action != "" { + w.RawString(",\"action\":") + w.String(v.Action) + } + if v.Context != nil { + w.RawString(",\"context\":") + if err := v.Context.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + if !v.ParentID.isZero() { + w.RawString(",\"parent_id\":") + if err := v.ParentID.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + if v.Stacktrace != nil { + w.RawString(",\"stacktrace\":") + w.RawByte('[') + for i, v := range v.Stacktrace { + if i != 0 { + w.RawByte(',') + } + if err := v.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + w.RawByte(']') + } + if v.Subtype != "" { + w.RawString(",\"subtype\":") + w.String(v.Subtype) + } + w.RawByte('}') + return firstErr +} + +func (v *SpanContext) MarshalFastJSON(w *fastjson.Writer) error { + var firstErr error + w.RawByte('{') + first := true + if v.Database != nil { + const prefix = ",\"db\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + if err := v.Database.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + if v.Destination != nil { + const prefix = ",\"destination\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + if err := v.Destination.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + if v.HTTP != nil { + const prefix = ",\"http\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + if err := v.HTTP.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + if !v.Tags.isZero() { + const prefix = ",\"tags\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + if err := v.Tags.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + w.RawByte('}') + return firstErr +} + +func (v *DestinationSpanContext) MarshalFastJSON(w *fastjson.Writer) error { + var firstErr error + w.RawByte('{') + first := true + if v.Address != "" { + const prefix = ",\"address\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.String(v.Address) + } + if v.Port != 0 { + const prefix = ",\"port\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.Int64(int64(v.Port)) + } + if v.Service != nil { + const prefix = ",\"service\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + if err := v.Service.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + w.RawByte('}') + return firstErr +} + +func (v *DestinationServiceSpanContext) MarshalFastJSON(w *fastjson.Writer) error { + w.RawByte('{') + first := true + if v.Name != "" { + const prefix = ",\"name\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.String(v.Name) + } + if v.Resource != "" { + const prefix = ",\"resource\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.String(v.Resource) + } + if v.Type != "" { + const prefix = ",\"type\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.String(v.Type) + } + w.RawByte('}') + return nil +} + +func (v *DatabaseSpanContext) MarshalFastJSON(w *fastjson.Writer) error { + w.RawByte('{') + first := true + if v.Instance != "" { + const prefix = ",\"instance\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.String(v.Instance) + } + if v.RowsAffected != nil { + const prefix = ",\"rows_affected\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.Int64(*v.RowsAffected) + } + if v.Statement != "" { + const prefix = ",\"statement\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.String(v.Statement) + } + if v.Type != "" { + const prefix = ",\"type\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.String(v.Type) + } + if v.User != "" { + const prefix = ",\"user\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.String(v.User) + } + w.RawByte('}') + return nil +} + +func (v *Context) MarshalFastJSON(w *fastjson.Writer) error { + var firstErr error + w.RawByte('{') + first := true + if !v.Custom.isZero() { + const prefix = ",\"custom\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + if err := v.Custom.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + if v.Request != nil { + const prefix = ",\"request\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + if err := v.Request.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + if v.Response != nil { + const prefix = ",\"response\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + if err := v.Response.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + if v.Service != nil { + const prefix = ",\"service\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + if err := v.Service.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + if !v.Tags.isZero() { + const prefix = ",\"tags\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + if err := v.Tags.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + if v.User != nil { + const prefix = ",\"user\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + if err := v.User.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + w.RawByte('}') + return firstErr +} + +func (v *User) MarshalFastJSON(w *fastjson.Writer) error { + w.RawByte('{') + first := true + if v.Email != "" { + const prefix = ",\"email\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.String(v.Email) + } + if v.ID != "" { + const prefix = ",\"id\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.String(v.ID) + } + if v.Username != "" { + const prefix = ",\"username\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.String(v.Username) + } + w.RawByte('}') + return nil +} + +func (v *Error) MarshalFastJSON(w *fastjson.Writer) error { + var firstErr error + w.RawByte('{') + w.RawString("\"id\":") + if err := v.ID.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + w.RawString(",\"timestamp\":") + if err := v.Timestamp.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + if v.Context != nil { + w.RawString(",\"context\":") + if err := v.Context.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + if v.Culprit != "" { + w.RawString(",\"culprit\":") + w.String(v.Culprit) + } + if !v.Exception.isZero() { + w.RawString(",\"exception\":") + if err := v.Exception.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + if !v.Log.isZero() { + w.RawString(",\"log\":") + if err := v.Log.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + if !v.ParentID.isZero() { + w.RawString(",\"parent_id\":") + if err := v.ParentID.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + if !v.TraceID.isZero() { + w.RawString(",\"trace_id\":") + if err := v.TraceID.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + if !v.Transaction.isZero() { + w.RawString(",\"transaction\":") + if err := v.Transaction.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + if !v.TransactionID.isZero() { + w.RawString(",\"transaction_id\":") + if err := v.TransactionID.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + w.RawByte('}') + return firstErr +} + +func (v *ErrorTransaction) MarshalFastJSON(w *fastjson.Writer) error { + w.RawByte('{') + first := true + if v.Sampled != nil { + const prefix = ",\"sampled\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.Bool(*v.Sampled) + } + if v.Type != "" { + const prefix = ",\"type\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.String(v.Type) + } + w.RawByte('}') + return nil +} + +func (v *Exception) MarshalFastJSON(w *fastjson.Writer) error { + var firstErr error + w.RawByte('{') + w.RawString("\"handled\":") + w.Bool(v.Handled) + w.RawString(",\"message\":") + w.String(v.Message) + if v.Attributes != nil { + w.RawString(",\"attributes\":") + w.RawByte('{') + { + first := true + for k, v := range v.Attributes { + if first { + first = false + } else { + w.RawByte(',') + } + w.String(k) + w.RawByte(':') + if err := fastjson.Marshal(w, v); err != nil && firstErr == nil { + firstErr = err + } + } + } + w.RawByte('}') + } + if v.Cause != nil { + w.RawString(",\"cause\":") + w.RawByte('[') + for i, v := range v.Cause { + if i != 0 { + w.RawByte(',') + } + if err := v.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + w.RawByte(']') + } + if !v.Code.isZero() { + w.RawString(",\"code\":") + if err := v.Code.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + if v.Module != "" { + w.RawString(",\"module\":") + w.String(v.Module) + } + if v.Stacktrace != nil { + w.RawString(",\"stacktrace\":") + w.RawByte('[') + for i, v := range v.Stacktrace { + if i != 0 { + w.RawByte(',') + } + if err := v.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + w.RawByte(']') + } + if v.Type != "" { + w.RawString(",\"type\":") + w.String(v.Type) + } + w.RawByte('}') + return firstErr +} + +func (v *StacktraceFrame) MarshalFastJSON(w *fastjson.Writer) error { + var firstErr error + w.RawByte('{') + w.RawString("\"filename\":") + w.String(v.File) + w.RawString(",\"lineno\":") + w.Int64(int64(v.Line)) + if v.AbsolutePath != "" { + w.RawString(",\"abs_path\":") + w.String(v.AbsolutePath) + } + if v.Column != nil { + w.RawString(",\"colno\":") + w.Int64(int64(*v.Column)) + } + if v.ContextLine != "" { + w.RawString(",\"context_line\":") + w.String(v.ContextLine) + } + if v.Function != "" { + w.RawString(",\"function\":") + w.String(v.Function) + } + if v.LibraryFrame != false { + w.RawString(",\"library_frame\":") + w.Bool(v.LibraryFrame) + } + if v.Module != "" { + w.RawString(",\"module\":") + w.String(v.Module) + } + if v.PostContext != nil { + w.RawString(",\"post_context\":") + w.RawByte('[') + for i, v := range v.PostContext { + if i != 0 { + w.RawByte(',') + } + w.String(v) + } + w.RawByte(']') + } + if v.PreContext != nil { + w.RawString(",\"pre_context\":") + w.RawByte('[') + for i, v := range v.PreContext { + if i != 0 { + w.RawByte(',') + } + w.String(v) + } + w.RawByte(']') + } + if v.Vars != nil { + w.RawString(",\"vars\":") + w.RawByte('{') + { + first := true + for k, v := range v.Vars { + if first { + first = false + } else { + w.RawByte(',') + } + w.String(k) + w.RawByte(':') + if err := fastjson.Marshal(w, v); err != nil && firstErr == nil { + firstErr = err + } + } + } + w.RawByte('}') + } + w.RawByte('}') + return firstErr +} + +func (v *Log) MarshalFastJSON(w *fastjson.Writer) error { + var firstErr error + w.RawByte('{') + w.RawString("\"message\":") + w.String(v.Message) + if v.Level != "" { + w.RawString(",\"level\":") + w.String(v.Level) + } + if v.LoggerName != "" { + w.RawString(",\"logger_name\":") + w.String(v.LoggerName) + } + if v.ParamMessage != "" { + w.RawString(",\"param_message\":") + w.String(v.ParamMessage) + } + if v.Stacktrace != nil { + w.RawString(",\"stacktrace\":") + w.RawByte('[') + for i, v := range v.Stacktrace { + if i != 0 { + w.RawByte(',') + } + if err := v.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + w.RawByte(']') + } + w.RawByte('}') + return firstErr +} + +func (v *Request) MarshalFastJSON(w *fastjson.Writer) error { + var firstErr error + w.RawByte('{') + w.RawString("\"method\":") + w.String(v.Method) + w.RawString(",\"url\":") + if err := v.URL.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + if v.Body != nil { + w.RawString(",\"body\":") + if err := v.Body.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + if !v.Cookies.isZero() { + w.RawString(",\"cookies\":") + if err := v.Cookies.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + if v.Env != nil { + w.RawString(",\"env\":") + w.RawByte('{') + { + first := true + for k, v := range v.Env { + if first { + first = false + } else { + w.RawByte(',') + } + w.String(k) + w.RawByte(':') + w.String(v) + } + } + w.RawByte('}') + } + if !v.Headers.isZero() { + w.RawString(",\"headers\":") + if err := v.Headers.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + if v.HTTPVersion != "" { + w.RawString(",\"http_version\":") + w.String(v.HTTPVersion) + } + if v.Socket != nil { + w.RawString(",\"socket\":") + if err := v.Socket.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + w.RawByte('}') + return firstErr +} + +func (v *RequestSocket) MarshalFastJSON(w *fastjson.Writer) error { + w.RawByte('{') + first := true + if v.Encrypted != false { + const prefix = ",\"encrypted\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.Bool(v.Encrypted) + } + if v.RemoteAddress != "" { + const prefix = ",\"remote_address\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.String(v.RemoteAddress) + } + w.RawByte('}') + return nil +} + +func (v *Response) MarshalFastJSON(w *fastjson.Writer) error { + var firstErr error + w.RawByte('{') + first := true + if v.Finished != nil { + const prefix = ",\"finished\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.Bool(*v.Finished) + } + if !v.Headers.isZero() { + const prefix = ",\"headers\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + if err := v.Headers.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + if v.HeadersSent != nil { + const prefix = ",\"headers_sent\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.Bool(*v.HeadersSent) + } + if v.StatusCode != 0 { + const prefix = ",\"status_code\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.Int64(int64(v.StatusCode)) + } + w.RawByte('}') + return firstErr +} + +func (v *Metrics) MarshalFastJSON(w *fastjson.Writer) error { + var firstErr error + w.RawByte('{') + w.RawString("\"samples\":") + if v.Samples == nil { + w.RawString("null") + } else { + w.RawByte('{') + { + first := true + for k, v := range v.Samples { + if first { + first = false + } else { + w.RawByte(',') + } + w.String(k) + w.RawByte(':') + if err := v.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + } + w.RawByte('}') + } + w.RawString(",\"timestamp\":") + if err := v.Timestamp.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + if !v.Span.isZero() { + w.RawString(",\"span\":") + if err := v.Span.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + if !v.Labels.isZero() { + w.RawString(",\"tags\":") + if err := v.Labels.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + if !v.Transaction.isZero() { + w.RawString(",\"transaction\":") + if err := v.Transaction.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + } + w.RawByte('}') + return firstErr +} + +func (v *MetricsTransaction) MarshalFastJSON(w *fastjson.Writer) error { + w.RawByte('{') + first := true + if v.Name != "" { + const prefix = ",\"name\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.String(v.Name) + } + if v.Type != "" { + const prefix = ",\"type\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.String(v.Type) + } + w.RawByte('}') + return nil +} + +func (v *MetricsSpan) MarshalFastJSON(w *fastjson.Writer) error { + w.RawByte('{') + first := true + if v.Subtype != "" { + const prefix = ",\"subtype\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.String(v.Subtype) + } + if v.Type != "" { + const prefix = ",\"type\":" + if first { + first = false + w.RawString(prefix[1:]) + } else { + w.RawString(prefix) + } + w.String(v.Type) + } + w.RawByte('}') + return nil +} + +func (v *Metric) MarshalFastJSON(w *fastjson.Writer) error { + w.RawByte('{') + w.RawString("\"value\":") + w.Float64(v.Value) + w.RawByte('}') + return nil +} diff --git a/vendor/go.elastic.co/apm/model/model.go b/vendor/go.elastic.co/apm/model/model.go new file mode 100644 index 00000000000..71568d4c0d6 --- /dev/null +++ b/vendor/go.elastic.co/apm/model/model.go @@ -0,0 +1,671 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package model + +import ( + "net/http" + "net/url" + "time" +) + +// Service represents the service handling transactions being traced. +type Service struct { + // Name is the immutable name of the service. + Name string `json:"name,omitempty"` + + // Version is the version of the service, if it has one. + Version string `json:"version,omitempty"` + + // Environment is the name of the service's environment, if it has + // one, e.g. "production" or "staging". + Environment string `json:"environment,omitempty"` + + // Agent holds information about the Elastic APM agent tracing this + // service's transactions. + Agent *Agent `json:"agent,omitempty"` + + // Framework holds information about the service's framework, if any. + Framework *Framework `json:"framework,omitempty"` + + // Language holds information about the programming language in which + // the service is written. + Language *Language `json:"language,omitempty"` + + // Runtime holds information about the programming language runtime + // running this service. + Runtime *Runtime `json:"runtime,omitempty"` + + // Node holds unique information about each service node + Node *ServiceNode `json:"node,omitempty"` +} + +// Agent holds information about the Elastic APM agent. +type Agent struct { + // Name is the name of the Elastic APM agent, e.g. "Go". + Name string `json:"name"` + + // Version is the version of the Elastic APM agent, e.g. "1.0.0". + Version string `json:"version"` +} + +// Framework holds information about the framework (typically web) +// used by the service. +type Framework struct { + // Name is the name of the framework. + Name string `json:"name"` + + // Version is the version of the framework. + Version string `json:"version"` +} + +// Language holds information about the programming language used. +type Language struct { + // Name is the name of the programming language. + Name string `json:"name"` + + // Version is the version of the programming language. + Version string `json:"version,omitempty"` +} + +// Runtime holds information about the programming language runtime. +type Runtime struct { + // Name is the name of the programming language runtime. + Name string `json:"name"` + + // Version is the version of the programming language runtime. + Version string `json:"version"` +} + +// ServiceNode holds unique information about each service node +type ServiceNode struct { + // ConfiguredName holds the name of the service node + ConfiguredName string `json:"configured_name,omitempty"` +} + +// System represents the system (operating system and machine) running the +// service. +type System struct { + // Architecture is the system's hardware architecture. + Architecture string `json:"architecture,omitempty"` + + // Hostname is the system's hostname. + Hostname string `json:"hostname,omitempty"` + + // Platform is the system's platform, or operating system name. + Platform string `json:"platform,omitempty"` + + // Container describes the container running the service. + Container *Container `json:"container,omitempty"` + + // Kubernetes describes the kubernetes node and pod running the service. + Kubernetes *Kubernetes `json:"kubernetes,omitempty"` +} + +// Process represents an operating system process. +type Process struct { + // Pid is the process ID. + Pid int `json:"pid"` + + // Ppid is the parent process ID, if known. + Ppid *int `json:"ppid,omitempty"` + + // Title is the title of the process. + Title string `json:"title,omitempty"` + + // Argv holds the command line arguments used to start the process. + Argv []string `json:"argv,omitempty"` +} + +// Container represents the container (e.g. Docker) running the service. +type Container struct { + // ID is the unique container ID. + ID string `json:"id"` +} + +// Kubernetes describes properties of the Kubernetes node and pod in which +// the service is running. +type Kubernetes struct { + // Namespace names the Kubernetes namespace in which the pod exists. + Namespace string `json:"namespace,omitempty"` + + // Node describes the Kubernetes node running the service's pod. + Node *KubernetesNode `json:"node,omitempty"` + + // Pod describes the Kubernetes pod running the service. + Pod *KubernetesPod `json:"pod,omitempty"` +} + +// KubernetesNode describes a Kubernetes node. +type KubernetesNode struct { + // Name holds the node name. + Name string `json:"name,omitempty"` +} + +// KubernetesPod describes a Kubernetes pod. +type KubernetesPod struct { + // Name holds the pod name. + Name string `json:"name,omitempty"` + + // UID holds the pod UID. + UID string `json:"uid,omitempty"` +} + +// Transaction represents a transaction handled by the service. +type Transaction struct { + // ID holds the 64-bit hex-encoded transaction ID. + ID SpanID `json:"id"` + + // TraceID holds the ID of the trace that this transaction is a part of. + TraceID TraceID `json:"trace_id"` + + // ParentID holds the ID of the transaction's parent span or transaction. + ParentID SpanID `json:"parent_id,omitempty"` + + // Name holds the name of the transaction. + Name string `json:"name"` + + // Type identifies the service-domain specific type of the request, + // e.g. "request" or "backgroundjob". + Type string `json:"type"` + + // Timestamp holds the time at which the transaction started. + Timestamp Time `json:"timestamp"` + + // Duration records how long the transaction took to complete, + // in milliseconds. + Duration float64 `json:"duration"` + + // Result holds the result of the transaction, e.g. the status code + // for HTTP requests. + Result string `json:"result,omitempty"` + + // Context holds contextual information relating to the transaction. + Context *Context `json:"context,omitempty"` + + // Sampled indicates that the transaction was sampled, and + // includes all available information. Non-sampled transactions + // omit Context. + // + // If Sampled is unspecified (nil), it is equivalent to setting + // it to true. + Sampled *bool `json:"sampled,omitempty"` + + // SpanCount holds statistics on spans within a transaction. + SpanCount SpanCount `json:"span_count"` +} + +// SpanCount holds statistics on spans within a transaction. +type SpanCount struct { + // Dropped holds the number of spans dropped within a transaction. + // This does not include spans that were started and dropped due + // to full buffers, network errors, etc. + Dropped int `json:"dropped"` + + // Started holds the number of spans started within a transaction. + Started int `json:"started"` +} + +// Span represents a span within a transaction. +type Span struct { + // Name holds the name of the span. + Name string `json:"name"` + + // Timestamp holds the time at which the span started. + Timestamp Time `json:"timestamp"` + + // Duration holds the duration of the span, in milliseconds. + Duration float64 `json:"duration"` + + // Type identifies the overarching type of the span, + // e.g. "db" or "external". + Type string `json:"type"` + + // Subtype identifies the subtype of the span, + // e.g. "mysql" or "http". + Subtype string `json:"subtype,omitempty"` + + // Action identifies the action that is being undertaken, e.g. "query". + Action string `json:"action,omitempty"` + + // ID holds the ID of the span. + ID SpanID `json:"id"` + + // TransactionID holds the ID of the transaction of which the span is a part. + TransactionID SpanID `json:"transaction_id"` + + // TraceID holds the ID of the trace that this span is a part of. + TraceID TraceID `json:"trace_id"` + + // ParentID holds the ID of the span's parent (span or transaction). + ParentID SpanID `json:"parent_id,omitempty"` + + // Context holds contextual information relating to the span. + Context *SpanContext `json:"context,omitempty"` + + // Stacktrace holds stack frames corresponding to the span. + Stacktrace []StacktraceFrame `json:"stacktrace,omitempty"` +} + +// SpanContext holds contextual information relating to the span. +type SpanContext struct { + // Destination holds information about a destination service. + Destination *DestinationSpanContext `json:"destination,omitempty"` + + // Database holds contextual information for database + // operation spans. + Database *DatabaseSpanContext `json:"db,omitempty"` + + // HTTP holds contextual information for HTTP client request spans. + HTTP *HTTPSpanContext `json:"http,omitempty"` + + // Tags holds user-defined key/value pairs. + Tags IfaceMap `json:"tags,omitempty"` +} + +// DestinationSpanContext holds contextual information about the destination +// for a span that relates to an operation involving an external service. +type DestinationSpanContext struct { + // Address holds the network address of the destination service. + // This may be a hostname, FQDN, or (IPv4 or IPv6) network address. + Address string `json:"address,omitempty"` + + // Port holds the network port for the destination service. + Port int `json:"port,omitempty"` + + // Service holds additional destination service context. + Service *DestinationServiceSpanContext `json:"service,omitempty"` +} + +// DestinationServiceSpanContext holds contextual information about a +// destination service,. +type DestinationServiceSpanContext struct { + // Type holds the destination service type. + Type string `json:"type,omitempty"` + + // Name holds the destination service name. + Name string `json:"name,omitempty"` + + // Resource identifies the destination service + // resource, e.g. a URI or message queue name. + Resource string `json:"resource,omitempty"` +} + +// DatabaseSpanContext holds contextual information for database +// operation spans. +type DatabaseSpanContext struct { + // Instance holds the database instance name. + Instance string `json:"instance,omitempty"` + + // Statement holds the database statement (e.g. query). + Statement string `json:"statement,omitempty"` + + // RowsAffected holds the number of rows affected by the + // database operation. + RowsAffected *int64 `json:"rows_affected,omitempty"` + + // Type holds the database type. For any SQL database, + // this should be "sql"; for others, the lower-cased + // database category, e.g. "cassandra", "hbase", "redis". + Type string `json:"type,omitempty"` + + // User holds the username used for database access. + User string `json:"user,omitempty"` +} + +// HTTPSpanContext holds contextual information for HTTP client request spans. +type HTTPSpanContext struct { + // URL is the request URL. + URL *url.URL + + // StatusCode holds the HTTP response status code. + StatusCode int `json:"status_code,omitempty"` +} + +// Context holds contextual information relating to a transaction or error. +type Context struct { + // Custom holds custom context relating to the transaction or error. + Custom IfaceMap `json:"custom,omitempty"` + + // Request holds details of the HTTP request relating to the + // transaction or error, if relevant. + Request *Request `json:"request,omitempty"` + + // Response holds details of the HTTP response relating to the + // transaction or error, if relevant. + Response *Response `json:"response,omitempty"` + + // User holds details of the authenticated user relating to the + // transaction or error, if relevant. + User *User `json:"user,omitempty"` + + // Tags holds user-defined key/value pairs. + Tags IfaceMap `json:"tags,omitempty"` + + // Service holds values to overrides service-level metadata. + Service *Service `json:"service,omitempty"` +} + +// User holds information about an authenticated user. +type User struct { + // Username holds the username of the user. + Username string `json:"username,omitempty"` + + // ID identifies the user, e.g. a primary key. This may be + // a string or number. + ID string `json:"id,omitempty"` + + // Email holds the email address of the user. + Email string `json:"email,omitempty"` +} + +// Error represents an error occurring in the service. +type Error struct { + // Timestamp holds the time at which the error occurred. + Timestamp Time `json:"timestamp"` + + // ID holds the 128-bit hex-encoded error ID. + ID TraceID `json:"id"` + + // TraceID holds the ID of the trace within which the error occurred. + TraceID TraceID `json:"trace_id,omitempty"` + + // ParentID holds the ID of the transaction within which the error + // occurred. + ParentID SpanID `json:"parent_id,omitempty"` + + // TransactionID holds the ID of the transaction within which the error occurred. + TransactionID SpanID `json:"transaction_id,omitempty"` + + // Culprit holds the name of the function which + // produced the error. + Culprit string `json:"culprit,omitempty"` + + // Context holds contextual information relating to the error. + Context *Context `json:"context,omitempty"` + + // Exception holds details of the exception (error or panic) + // to which this error relates. + Exception Exception `json:"exception,omitempty"` + + // Log holds additional information added when logging the error. + Log Log `json:"log,omitempty"` + + // Transaction holds information about the transaction within which the error occurred. + Transaction ErrorTransaction `json:"transaction,omitempty"` +} + +// ErrorTransaction holds information about the transaction within which an error occurred. +type ErrorTransaction struct { + // Sampled indicates that the transaction was sampled. + Sampled *bool `json:"sampled,omitempty"` + + // Type holds the transaction type. + Type string `json:"type,omitempty"` +} + +// Exception represents an exception: an error or panic. +type Exception struct { + // Message holds the error message. + Message string `json:"message"` + + // Code holds the error code. This may be a number or a string. + Code ExceptionCode `json:"code,omitempty"` + + // Type holds the type of the exception. + Type string `json:"type,omitempty"` + + // Module holds the exception type's module namespace. + Module string `json:"module,omitempty"` + + // Attributes holds arbitrary exception-type specific attributes. + Attributes map[string]interface{} `json:"attributes,omitempty"` + + // Stacktrace holds stack frames corresponding to the exception. + Stacktrace []StacktraceFrame `json:"stacktrace,omitempty"` + + // Handled indicates whether or not the error was caught and handled. + Handled bool `json:"handled"` + + // Cause holds the causes of this error. + Cause []Exception `json:"cause,omitempty"` +} + +// ExceptionCode represents an exception code as either a number or a string. +type ExceptionCode struct { + String string + Number float64 +} + +// StacktraceFrame describes a stack frame. +type StacktraceFrame struct { + // AbsolutePath holds the absolute path of the source file for the + // stack frame. + AbsolutePath string `json:"abs_path,omitempty"` + + // File holds the base filename of the source file for the stack frame. + File string `json:"filename"` + + // Line holds the line number of the source for the stack frame. + Line int `json:"lineno"` + + // Column holds the column number of the source for the stack frame. + Column *int `json:"colno,omitempty"` + + // Module holds the module to which the frame belongs. For Go, we + // use the package path (e.g. "net/http"). + Module string `json:"module,omitempty"` + + // Function holds the name of the function to which the frame belongs. + Function string `json:"function,omitempty"` + + // LibraryFrame indicates whether or not the frame corresponds to + // library or user code. + LibraryFrame bool `json:"library_frame,omitempty"` + + // ContextLine holds the line of source code to which the frame + // corresponds. + ContextLine string `json:"context_line,omitempty"` + + // PreContext holds zero or more lines of source code preceding the + // line corresponding to the frame. + PreContext []string `json:"pre_context,omitempty"` + + // PostContext holds zero or more lines of source code proceeding the + // line corresponding to the frame. + PostContext []string `json:"post_context,omitempty"` + + // Vars holds local variables for this stack frame. + Vars map[string]interface{} `json:"vars,omitempty"` +} + +// Log holds additional information added when logging an error. +type Log struct { + // Message holds the logged error message. + Message string `json:"message"` + + // Level holds the severity of the log record. + Level string `json:"level,omitempty"` + + // LoggerName holds the name of the logger used. + LoggerName string `json:"logger_name,omitempty"` + + // ParamMessage holds a parameterized message, e.g. + // "Could not connect to %s". The string is not interpreted, + // but may be used for grouping errors. + ParamMessage string `json:"param_message,omitempty"` + + // Stacktrace holds stack frames corresponding to the error. + Stacktrace []StacktraceFrame `json:"stacktrace,omitempty"` +} + +// Request represents an HTTP request. +type Request struct { + // URL is the request URL. + URL URL `json:"url"` + + // Method holds the HTTP request method. + Method string `json:"method"` + + // Headers holds the request headers. + Headers Headers `json:"headers,omitempty"` + + // Body holds the request body, if body capture is enabled. + Body *RequestBody `json:"body,omitempty"` + + // HTTPVersion holds the HTTP version of the request. + HTTPVersion string `json:"http_version,omitempty"` + + // Cookies holds the parsed cookies. + Cookies Cookies `json:"cookies,omitempty"` + + // Env holds environment information passed from the + // web framework to the request handler. + Env map[string]string `json:"env,omitempty"` + + // Socket holds transport-level information. + Socket *RequestSocket `json:"socket,omitempty"` +} + +// Cookies holds a collection of HTTP cookies. +type Cookies []*http.Cookie + +// RequestBody holds a request body. +// +// Exactly one of Raw or Form must be set. +type RequestBody struct { + // Raw holds the raw body content. + Raw string + + // Form holds the form data from POST, PATCH, or PUT body parameters. + Form url.Values +} + +// Headers holds a collection of HTTP headers. +type Headers []Header + +// Header holds an HTTP header, with one or more values. +type Header struct { + Key string + Values []string +} + +// RequestSocket holds transport-level information relating to an HTTP request. +type RequestSocket struct { + // Encrypted indicates whether or not the request was sent + // as an SSL/HTTPS request. + Encrypted bool `json:"encrypted,omitempty"` + + // RemoteAddress holds the remote address for the request. + RemoteAddress string `json:"remote_address,omitempty"` +} + +// URL represents a server-side (transaction) request URL, +// broken down into its constituent parts. +type URL struct { + // Full is the full URL, e.g. + // "https://example.com:443/search/?q=elasticsearch#top". + Full string `json:"full,omitempty"` + + // Protocol is the scheme of the URL, e.g. "https". + Protocol string `json:"protocol,omitempty"` + + // Hostname is the hostname for the URL, e.g. "example.com". + Hostname string `json:"hostname,omitempty"` + + // Port is the port number in the URL, e.g. "443". + Port string `json:"port,omitempty"` + + // Path is the path of the URL, e.g. "/search". + Path string `json:"pathname,omitempty"` + + // Search is the query string of the URL, e.g. "q=elasticsearch". + Search string `json:"search,omitempty"` + + // Hash is the fragment for references, e.g. "top" in the + // URL example provided for Full. + Hash string `json:"hash,omitempty"` +} + +// Response represents an HTTP response. +type Response struct { + // StatusCode holds the HTTP response status code. + StatusCode int `json:"status_code,omitempty"` + + // Headers holds the response headers. + Headers Headers `json:"headers,omitempty"` + + // HeadersSent indicates whether or not headers were sent + // to the client. + HeadersSent *bool `json:"headers_sent,omitempty"` + + // Finished indicates whether or not the response was finished. + Finished *bool `json:"finished,omitempty"` +} + +// Time is a timestamp, formatted as a number of microseconds since January 1, 1970 UTC. +type Time time.Time + +// TraceID holds a 128-bit trace ID. +type TraceID [16]byte + +// SpanID holds a 64-bit span ID. Despite its name, this is used for +// both spans and transactions. +type SpanID [8]byte + +// Metrics holds a set of metric samples, with an optional set of labels. +type Metrics struct { + // Timestamp holds the time at which the metric samples were taken. + Timestamp Time `json:"timestamp"` + + // Transaction optionally holds the name and type of transactions + // with which these metrics are associated. + Transaction MetricsTransaction `json:"transaction,omitempty"` + + // Span optionally holds the type and subtype of the spans with + // which these metrics are associated. + Span MetricsSpan `json:"span,omitempty"` + + // Labels holds a set of labels associated with the metrics. + // The labels apply uniformly to all metric samples in the set. + // + // NOTE(axw) the schema calls the field "tags", but we use + // "labels" for agent-internal consistency. Labels aligns better + // with the common schema, anyway. + Labels StringMap `json:"tags,omitempty"` + + // Samples holds a map of metric samples, keyed by metric name. + Samples map[string]Metric `json:"samples"` +} + +// MetricsTransaction holds transaction identifiers for metrics. +type MetricsTransaction struct { + Type string `json:"type,omitempty"` + Name string `json:"name,omitempty"` +} + +// MetricsSpan holds span identifiers for metrics. +type MetricsSpan struct { + Type string `json:"type,omitempty"` + Subtype string `json:"subtype,omitempty"` +} + +// Metric holds metric values. +type Metric struct { + // Value holds the metric value. + Value float64 `json:"value"` +} diff --git a/vendor/go.elastic.co/apm/modelwriter.go b/vendor/go.elastic.co/apm/modelwriter.go new file mode 100644 index 00000000000..e78d9be8f50 --- /dev/null +++ b/vendor/go.elastic.co/apm/modelwriter.go @@ -0,0 +1,267 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apm + +import ( + "go.elastic.co/apm/internal/ringbuffer" + "go.elastic.co/apm/model" + "go.elastic.co/apm/stacktrace" + "go.elastic.co/fastjson" +) + +const ( + transactionBlockTag ringbuffer.BlockTag = iota + 1 + spanBlockTag + errorBlockTag + metricsBlockTag +) + +// notSampled is used as the pointee for the model.Transaction.Sampled field +// of non-sampled transactions. +var notSampled = false + +type modelWriter struct { + buffer *ringbuffer.Buffer + metricsBuffer *ringbuffer.Buffer + cfg *tracerConfig + stats *TracerStats + json fastjson.Writer + modelStacktrace []model.StacktraceFrame +} + +// writeTransaction encodes tx as JSON to the buffer, and then resets tx. +func (w *modelWriter) writeTransaction(tx *Transaction, td *TransactionData) { + var modelTx model.Transaction + w.buildModelTransaction(&modelTx, tx, td) + w.json.RawString(`{"transaction":`) + modelTx.MarshalFastJSON(&w.json) + w.json.RawByte('}') + w.buffer.WriteBlock(w.json.Bytes(), transactionBlockTag) + w.json.Reset() + td.reset(tx.tracer) +} + +// writeSpan encodes s as JSON to the buffer, and then resets s. +func (w *modelWriter) writeSpan(s *Span, sd *SpanData) { + var modelSpan model.Span + w.buildModelSpan(&modelSpan, s, sd) + w.json.RawString(`{"span":`) + modelSpan.MarshalFastJSON(&w.json) + w.json.RawByte('}') + w.buffer.WriteBlock(w.json.Bytes(), spanBlockTag) + w.json.Reset() + sd.reset(s.tracer) +} + +// writeError encodes e as JSON to the buffer, and then resets e. +func (w *modelWriter) writeError(e *ErrorData) { + var modelError model.Error + w.buildModelError(&modelError, e) + w.json.RawString(`{"error":`) + modelError.MarshalFastJSON(&w.json) + w.json.RawByte('}') + w.buffer.WriteBlock(w.json.Bytes(), errorBlockTag) + w.json.Reset() + e.reset() +} + +// writeMetrics encodes m as JSON to the w.metricsBuffer, and then resets m. +// +// Note that we do not write metrics to the main ring buffer (w.buffer), as +// periodic metrics would be evicted by transactions/spans in a busy system. +func (w *modelWriter) writeMetrics(m *Metrics) { + for _, m := range m.transactionGroupMetrics { + w.json.RawString(`{"metricset":`) + m.MarshalFastJSON(&w.json) + w.json.RawString("}") + w.metricsBuffer.WriteBlock(w.json.Bytes(), metricsBlockTag) + w.json.Reset() + } + for _, m := range m.metrics { + w.json.RawString(`{"metricset":`) + m.MarshalFastJSON(&w.json) + w.json.RawString("}") + w.metricsBuffer.WriteBlock(w.json.Bytes(), metricsBlockTag) + w.json.Reset() + } + m.reset() +} + +func (w *modelWriter) buildModelTransaction(out *model.Transaction, tx *Transaction, td *TransactionData) { + out.ID = model.SpanID(tx.traceContext.Span) + out.TraceID = model.TraceID(tx.traceContext.Trace) + sampled := tx.traceContext.Options.Recorded() + if !sampled { + out.Sampled = ¬Sampled + } + + out.ParentID = model.SpanID(td.parentSpan) + out.Name = truncateString(td.Name) + out.Type = truncateString(td.Type) + out.Result = truncateString(td.Result) + out.Timestamp = model.Time(td.timestamp.UTC()) + out.Duration = td.Duration.Seconds() * 1000 + out.SpanCount.Started = td.spansCreated + out.SpanCount.Dropped = td.spansDropped + if sampled { + out.Context = td.Context.build() + } + + if len(w.cfg.sanitizedFieldNames) != 0 && out.Context != nil { + if out.Context.Request != nil { + sanitizeRequest(out.Context.Request, w.cfg.sanitizedFieldNames) + } + if out.Context.Response != nil { + sanitizeResponse(out.Context.Response, w.cfg.sanitizedFieldNames) + } + } +} + +func (w *modelWriter) buildModelSpan(out *model.Span, span *Span, sd *SpanData) { + w.modelStacktrace = w.modelStacktrace[:0] + out.ID = model.SpanID(span.traceContext.Span) + out.TraceID = model.TraceID(span.traceContext.Trace) + out.TransactionID = model.SpanID(span.transactionID) + + out.ParentID = model.SpanID(sd.parentID) + out.Name = truncateString(sd.Name) + out.Type = truncateString(sd.Type) + out.Subtype = truncateString(sd.Subtype) + out.Action = truncateString(sd.Action) + out.Timestamp = model.Time(sd.timestamp.UTC()) + out.Duration = sd.Duration.Seconds() * 1000 + out.Context = sd.Context.build() + + // Copy the span type to context.destination.service.type. + if out.Context != nil && out.Context.Destination != nil && out.Context.Destination.Service != nil { + out.Context.Destination.Service.Type = out.Type + } + + w.modelStacktrace = appendModelStacktraceFrames(w.modelStacktrace, sd.stacktrace) + out.Stacktrace = w.modelStacktrace + w.setStacktraceContext(out.Stacktrace) +} + +func (w *modelWriter) buildModelError(out *model.Error, e *ErrorData) { + out.ID = model.TraceID(e.ID) + out.TraceID = model.TraceID(e.TraceID) + out.ParentID = model.SpanID(e.ParentID) + out.TransactionID = model.SpanID(e.TransactionID) + out.Timestamp = model.Time(e.Timestamp.UTC()) + out.Context = e.Context.build() + out.Culprit = e.Culprit + + if !e.TransactionID.isZero() { + out.Transaction.Sampled = &e.transactionSampled + if e.transactionSampled { + out.Transaction.Type = e.transactionType + } + } + + // Create model stacktrace frames, and set the context. + w.modelStacktrace = w.modelStacktrace[:0] + var appendModelErrorStacktraceFrames func(exception *exceptionData) + appendModelErrorStacktraceFrames = func(exception *exceptionData) { + if len(exception.stacktrace) != 0 { + w.modelStacktrace = appendModelStacktraceFrames(w.modelStacktrace, exception.stacktrace) + } + for _, cause := range exception.cause { + appendModelErrorStacktraceFrames(&cause) + } + } + appendModelErrorStacktraceFrames(&e.exception) + if len(e.logStacktrace) != 0 { + w.modelStacktrace = appendModelStacktraceFrames(w.modelStacktrace, e.logStacktrace) + } + w.setStacktraceContext(w.modelStacktrace) + + var modelStacktraceOffset int + if e.exception.message != "" { + var buildException func(exception *exceptionData) model.Exception + culprit := e.Culprit + buildException = func(exception *exceptionData) model.Exception { + out := model.Exception{ + Message: exception.message, + Code: model.ExceptionCode{ + String: exception.Code.String, + Number: exception.Code.Number, + }, + Type: exception.Type.Name, + Module: exception.Type.PackagePath, + Handled: e.Handled, + } + if n := len(exception.stacktrace); n != 0 { + out.Stacktrace = w.modelStacktrace[modelStacktraceOffset : modelStacktraceOffset+n] + modelStacktraceOffset += n + } + if len(exception.attrs) != 0 { + out.Attributes = exception.attrs + } + if n := len(exception.cause); n > 0 { + out.Cause = make([]model.Exception, n) + for i := range exception.cause { + out.Cause[i] = buildException(&exception.cause[i]) + } + } + if culprit == "" { + culprit = stacktraceCulprit(out.Stacktrace) + } + return out + } + out.Exception = buildException(&e.exception) + out.Culprit = culprit + } + if e.log.Message != "" { + out.Log = model.Log{ + Message: e.log.Message, + Level: e.log.Level, + LoggerName: e.log.LoggerName, + ParamMessage: e.log.MessageFormat, + } + if n := len(e.logStacktrace); n != 0 { + out.Log.Stacktrace = w.modelStacktrace[modelStacktraceOffset : modelStacktraceOffset+n] + modelStacktraceOffset += n + if out.Culprit == "" { + out.Culprit = stacktraceCulprit(out.Log.Stacktrace) + } + } + } + out.Culprit = truncateString(out.Culprit) +} + +func stacktraceCulprit(frames []model.StacktraceFrame) string { + for _, frame := range frames { + if !frame.LibraryFrame { + return frame.Function + } + } + return "" +} + +func (w *modelWriter) setStacktraceContext(stack []model.StacktraceFrame) { + if w.cfg.contextSetter == nil || len(stack) == 0 { + return + } + err := stacktrace.SetContext(w.cfg.contextSetter, stack, w.cfg.preContext, w.cfg.postContext) + if err != nil { + if w.cfg.logger != nil { + w.cfg.logger.Debugf("setting context failed: %v", err) + } + w.stats.Errors.SetContext++ + } +} diff --git a/vendor/go.elastic.co/apm/module/apmelasticsearch/LICENSE b/vendor/go.elastic.co/apm/module/apmelasticsearch/LICENSE new file mode 100644 index 00000000000..b1a731fb5a3 --- /dev/null +++ b/vendor/go.elastic.co/apm/module/apmelasticsearch/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018 Elasticsearch BV + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/go.elastic.co/apm/module/apmelasticsearch/client.go b/vendor/go.elastic.co/apm/module/apmelasticsearch/client.go new file mode 100644 index 00000000000..9222c732e8b --- /dev/null +++ b/vendor/go.elastic.co/apm/module/apmelasticsearch/client.go @@ -0,0 +1,239 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apmelasticsearch + +import ( + "bytes" + "compress/gzip" + "io" + "io/ioutil" + "net/http" + "net/url" + "path" + "sync/atomic" + "unsafe" + + "go.elastic.co/apm" + "go.elastic.co/apm/module/apmhttp" +) + +// WrapRoundTripper returns an http.RoundTripper wrapping r, reporting each +// request as a span to Elastic APM, if the request's context contains a +// sampled transaction. +// +// If r is nil, then http.DefaultTransport is wrapped. +func WrapRoundTripper(r http.RoundTripper, o ...ClientOption) http.RoundTripper { + if r == nil { + r = http.DefaultTransport + } + rt := &roundTripper{r: r} + for _, o := range o { + o(rt) + } + return rt +} + +type roundTripper struct { + r http.RoundTripper +} + +// RoundTrip delegates to r.r, emitting a span if req's context contains a transaction. +// +// If req.URL.Path corresponds to a search request, then RoundTrip will attempt to extract +// the search query to use as the span context's "database statement". If the query is +// passed in as a query parameter (i.e. "/_search?q=foo:bar"), then that will be used; +// otherwise, the request body will be read. In the latter case, req.GetBody is used +// if defined, otherwise we read req.Body, preserving its contents for the underlying +// RoundTripper. If the request body is gzip-encoded, it will be decoded. +func (r *roundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + ctx := req.Context() + tx := apm.TransactionFromContext(ctx) + if tx == nil || !tx.Sampled() { + return r.r.RoundTrip(req) + } + + name := requestName(req) + span := tx.StartSpan(name, "db.elasticsearch", apm.SpanFromContext(ctx)) + if span.Dropped() { + span.End() + return r.r.RoundTrip(req) + } + + statement, req := captureSearchStatement(req) + username, _, _ := req.BasicAuth() + ctx = apm.ContextWithSpan(ctx, span) + req = apmhttp.RequestWithContext(ctx, req) + span.Context.SetHTTPRequest(req) + span.Context.SetDestinationService(apm.DestinationServiceSpanContext{ + Name: "elasticsearch", + Resource: "elasticsearch", + }) + span.Context.SetDatabase(apm.DatabaseSpanContext{ + Type: "elasticsearch", + Statement: statement, + User: username, + }) + + resp, err := r.r.RoundTrip(req) + if err != nil { + span.End() + } else { + span.Context.SetHTTPStatusCode(resp.StatusCode) + resp.Body = &responseBody{span: span, body: resp.Body} + } + return resp, err +} + +type responseBody struct { + span *apm.Span + body io.ReadCloser +} + +// Close closes the response body, and ends the span if it hasn't already been ended. +func (b *responseBody) Close() error { + b.endSpan() + return b.body.Close() +} + +// Read reads from the response body, and ends the span when io.EOF is returend if +// the span hasn't already been ended. +func (b *responseBody) Read(p []byte) (n int, err error) { + n, err = b.body.Read(p) + if err == io.EOF { + b.endSpan() + } + return n, err +} + +func (b *responseBody) endSpan() { + addr := (*unsafe.Pointer)(unsafe.Pointer(&b.span)) + if old := atomic.SwapPointer(addr, nil); old != nil { + (*apm.Span)(old).End() + } +} + +// ClientOption sets options for tracing client requests. +type ClientOption func(*roundTripper) + +// captureSearchStatement captures the search URI query or request body. +// +// If the request must be modified (i.e. because the body must be read), +// then captureSearchStatement returns a new *http.Request to be passed +// to the underlying http.RoundTripper. Otherwise, req is returned. +func captureSearchStatement(req *http.Request) (string, *http.Request) { + if !isSearchURL(req.URL) { + return "", req + } + + // If "q" is in query params, use that for statement. + if req.URL.RawQuery != "" { + query := req.URL.Query() + if statement := query.Get("q"); statement != "" { + return statement, req + } + } + if req.Body == nil || req.Body == http.NoBody { + return "", req + } + + var bodyBuf bytes.Buffer + if req.GetBody != nil { + // req.GetBody is defined, so we can read a copy of the + // request body instead of messing with the original request + // body. + body, err := req.GetBody() + if err != nil { + return "", req + } + if _, err := bodyBuf.ReadFrom(limitedBody(body, req.ContentLength)); err != nil { + body.Close() + return "", req + } + if err := body.Close(); err != nil { + return "", req + } + } else { + type readCloser struct { + io.Reader + io.Closer + } + newBody := &readCloser{Closer: req.Body} + reqCopy := *req + reqCopy.Body = newBody + if _, err := bodyBuf.ReadFrom(limitedBody(req.Body, req.ContentLength)); err != nil { + // Continue with the request, ensuring that req.Body returns + // the same content and error, but don't use the consumed body + // for the statement. + newBody.Reader = io.MultiReader(bytes.NewReader(bodyBuf.Bytes()), errorReader{err: err}) + return "", &reqCopy + } + newBody.Reader = io.MultiReader(bytes.NewReader(bodyBuf.Bytes()), req.Body) + req = &reqCopy + } + + var statement string + if req.Header.Get("Content-Encoding") == "gzip" { + if r, err := gzip.NewReader(&bodyBuf); err == nil { + if content, err := ioutil.ReadAll(r); err == nil { + statement = string(content) + } + } + } else { + statement = bodyBuf.String() + } + return statement, req +} + +func isSearchURL(url *url.URL) bool { + switch dir, file := path.Split(url.Path); file { + case "_search", "_msearch", "_rollup_search": + return true + case "template": + if dir == "" { + return false + } + switch _, file := path.Split(dir[:len(dir)-1]); file { + case "_search", "_msearch": + // ".../_search/template" or ".../_msearch/template" + return true + } + } + return false +} + +func limitedBody(r io.Reader, n int64) io.Reader { + // maxLimit is the maximum size of the request body that we'll read, + // set to 10000 to match the maximum length of the "db.statement" + // span context field. + const maxLimit = 10000 + if n <= 0 { + return r + } + if n > maxLimit { + n = maxLimit + } + return &io.LimitedReader{R: r, N: n} +} + +type errorReader struct { + err error +} + +func (r errorReader) Read(p []byte) (int, error) { + return 0, r.err +} diff --git a/vendor/go.elastic.co/apm/module/apmelasticsearch/doc.go b/vendor/go.elastic.co/apm/module/apmelasticsearch/doc.go new file mode 100644 index 00000000000..066dd0b9030 --- /dev/null +++ b/vendor/go.elastic.co/apm/module/apmelasticsearch/doc.go @@ -0,0 +1,20 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Package apmelasticsearch provides support for tracing the +// HTTP transport layer of Elasticsearch clients. +package apmelasticsearch diff --git a/vendor/go.elastic.co/apm/module/apmelasticsearch/go.mod b/vendor/go.elastic.co/apm/module/apmelasticsearch/go.mod new file mode 100644 index 00000000000..4df7d8d7d9b --- /dev/null +++ b/vendor/go.elastic.co/apm/module/apmelasticsearch/go.mod @@ -0,0 +1,14 @@ +module go.elastic.co/apm/module/apmelasticsearch + +require ( + github.com/stretchr/testify v1.4.0 + go.elastic.co/apm v1.7.2 + go.elastic.co/apm/module/apmhttp v1.7.2 + golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 +) + +replace go.elastic.co/apm => ../.. + +replace go.elastic.co/apm/module/apmhttp => ../apmhttp + +go 1.13 diff --git a/vendor/go.elastic.co/apm/module/apmelasticsearch/go.sum b/vendor/go.elastic.co/apm/module/apmelasticsearch/go.sum new file mode 100644 index 00000000000..1976184453d --- /dev/null +++ b/vendor/go.elastic.co/apm/module/apmelasticsearch/go.sum @@ -0,0 +1,62 @@ +github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/cucumber/godog v0.8.1 h1:lVb+X41I4YDreE+ibZ50bdXmySxgRviYFgKY6Aw4XE8= +github.com/cucumber/godog v0.8.1/go.mod h1:vSh3r/lM+psC1BPXvdkSEuNjmXfpVqrMGYAElF6hxnA= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/elastic/go-sysinfo v1.1.1 h1:ZVlaLDyhVkDfjwPGU55CQRCRolNpc7P0BbyhhQZQmMI= +github.com/elastic/go-sysinfo v1.1.1/go.mod h1:i1ZYdU10oLNfRzq4vq62BEwD2fH8KaWh6eh0ikPT9F0= +github.com/elastic/go-windows v1.0.0 h1:qLURgZFkkrYyTTkvYpsZIgf83AUsdIHfvlJaqaZ7aSY= +github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU= +github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 h1:rp+c0RAYOWj8l6qbCUTSiRLG/iKnW3K3/QfPPuSsBt4= +github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/santhosh-tekuri/jsonschema v1.2.4 h1:hNhW8e7t+H1vgY+1QeEQpveR6D4+OwKPXCfD2aieJis= +github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +go.elastic.co/fastjson v1.0.0 h1:ooXV/ABvf+tBul26jcVViPT3sBir0PvXgibYB1IQQzg= +go.elastic.co/fastjson v1.0.0/go.mod h1:PmeUOMMtLHQr9ZS9J9owrAVg0FkaZDRZJEFTTGHtchs= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20191025021431-6c3a3bfe00ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e h1:9vRrk9YW2BTzLP0VCB9ZDjU4cPqkg+IDWL7XgxA1yxQ= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M= +howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= diff --git a/vendor/go.elastic.co/apm/module/apmelasticsearch/requestname.go b/vendor/go.elastic.co/apm/module/apmelasticsearch/requestname.go new file mode 100644 index 00000000000..5dbb7d7be5d --- /dev/null +++ b/vendor/go.elastic.co/apm/module/apmelasticsearch/requestname.go @@ -0,0 +1,39 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build go1.10 + +package apmelasticsearch + +import ( + "net/http" + "strings" +) + +func requestName(req *http.Request) string { + const prefix = "Elasticsearch:" + path := strings.TrimLeft(req.URL.Path, "/") + + var b strings.Builder + b.Grow(len(prefix) + 1 + len(req.Method) + 1 + len(path)) + b.WriteString(prefix) + b.WriteRune(' ') + b.WriteString(req.Method) + b.WriteRune(' ') + b.WriteString(path) + return b.String() +} diff --git a/vendor/go.elastic.co/apm/module/apmelasticsearch/requestname_go19.go b/vendor/go.elastic.co/apm/module/apmelasticsearch/requestname_go19.go new file mode 100644 index 00000000000..14c3bc697bb --- /dev/null +++ b/vendor/go.elastic.co/apm/module/apmelasticsearch/requestname_go19.go @@ -0,0 +1,30 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build !go1.10 + +package apmelasticsearch + +import ( + "fmt" + "net/http" + "strings" +) + +func requestName(req *http.Request) string { + return fmt.Sprintf("Elasticsearch: %s %s", req.Method, strings.TrimLeft(req.URL.Path, "/")) +} diff --git a/vendor/go.elastic.co/apm/module/apmhttp/LICENSE b/vendor/go.elastic.co/apm/module/apmhttp/LICENSE new file mode 100644 index 00000000000..b1a731fb5a3 --- /dev/null +++ b/vendor/go.elastic.co/apm/module/apmhttp/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018 Elasticsearch BV + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/go.elastic.co/apm/module/apmhttp/client.go b/vendor/go.elastic.co/apm/module/apmhttp/client.go new file mode 100644 index 00000000000..2d0df4034c7 --- /dev/null +++ b/vendor/go.elastic.co/apm/module/apmhttp/client.go @@ -0,0 +1,200 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apmhttp + +import ( + "io" + "net/http" + "sync/atomic" + "unsafe" + + "go.elastic.co/apm" +) + +// WrapClient returns a new *http.Client with all fields copied +// across, and the Transport field wrapped with WrapRoundTripper +// such that client requests are reported as spans to Elastic APM +// if their context contains a sampled transaction. +// +// Spans are started just before the request is sent, and ended +// immediately if the request returned an error (e.g. due to socket +// timeout, but not a valid response with a non-200 status code), +// or otherwise when the response body is fully consumed or closed. +// +// If c is nil, then http.DefaultClient is wrapped. +func WrapClient(c *http.Client, o ...ClientOption) *http.Client { + if c == nil { + c = http.DefaultClient + } + copied := *c + copied.Transport = WrapRoundTripper(copied.Transport, o...) + return &copied +} + +// WrapRoundTripper returns an http.RoundTripper wrapping r, reporting each +// request as a span to Elastic APM, if the request's context contains a +// sampled transaction. +// +// If r is nil, then http.DefaultTransport is wrapped. +func WrapRoundTripper(r http.RoundTripper, o ...ClientOption) http.RoundTripper { + if r == nil { + r = http.DefaultTransport + } + rt := &roundTripper{ + r: r, + requestName: ClientRequestName, + requestIgnorer: IgnoreNone, + } + for _, o := range o { + o(rt) + } + return rt +} + +type roundTripper struct { + r http.RoundTripper + requestName RequestNameFunc + requestIgnorer RequestIgnorerFunc +} + +// RoundTrip delegates to r.r, emitting a span if req's context +// contains a transaction. +func (r *roundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + if r.requestIgnorer(req) { + return r.r.RoundTrip(req) + } + ctx := req.Context() + tx := apm.TransactionFromContext(ctx) + if tx == nil { + return r.r.RoundTrip(req) + } + + // RoundTrip is not supposed to mutate req, so copy req + // and set the trace-context headers only in the copy. + reqCopy := *req + reqCopy.Header = make(http.Header, len(req.Header)) + for k, v := range req.Header { + reqCopy.Header[k] = v + } + req = &reqCopy + + propagateLegacyHeader := tx.ShouldPropagateLegacyHeader() + traceContext := tx.TraceContext() + if !traceContext.Options.Recorded() { + r.setHeaders(req, traceContext, propagateLegacyHeader) + return r.r.RoundTrip(req) + } + + name := r.requestName(req) + span := tx.StartSpan(name, "external.http", apm.SpanFromContext(ctx)) + if !span.Dropped() { + traceContext = span.TraceContext() + ctx = apm.ContextWithSpan(ctx, span) + req = RequestWithContext(ctx, req) + span.Context.SetHTTPRequest(req) + } else { + span.End() + span = nil + } + + r.setHeaders(req, traceContext, propagateLegacyHeader) + resp, err := r.r.RoundTrip(req) + if span != nil { + if err != nil { + span.End() + } else { + span.Context.SetHTTPStatusCode(resp.StatusCode) + resp.Body = &responseBody{span: span, body: resp.Body} + } + } + return resp, err +} + +func (r *roundTripper) setHeaders(req *http.Request, traceContext apm.TraceContext, propagateLegacyHeader bool) { + headerValue := FormatTraceparentHeader(traceContext) + if propagateLegacyHeader { + req.Header.Set(ElasticTraceparentHeader, headerValue) + } + req.Header.Set(W3CTraceparentHeader, headerValue) + if tracestate := traceContext.State.String(); tracestate != "" { + req.Header.Set(TracestateHeader, tracestate) + } +} + +// CloseIdleConnections calls r.r.CloseIdleConnections if the method exists. +func (r *roundTripper) CloseIdleConnections() { + type closeIdler interface { + CloseIdleConnections() + } + if r, ok := r.r.(closeIdler); ok { + r.CloseIdleConnections() + } +} + +// CancelRequest calls r.r.CancelRequest(req) if the method exists. +func (r *roundTripper) CancelRequest(req *http.Request) { + type cancelRequester interface { + CancelRequest(*http.Request) + } + if r, ok := r.r.(cancelRequester); ok { + r.CancelRequest(req) + } +} + +type responseBody struct { + span *apm.Span + body io.ReadCloser +} + +// Close closes the response body, and ends the span if it hasn't already been ended. +func (b *responseBody) Close() error { + b.endSpan() + return b.body.Close() +} + +// Read reads from the response body, and ends the span when io.EOF is returend if +// the span hasn't already been ended. +func (b *responseBody) Read(p []byte) (n int, err error) { + n, err = b.body.Read(p) + if err == io.EOF { + b.endSpan() + } + return n, err +} + +func (b *responseBody) endSpan() { + addr := (*unsafe.Pointer)(unsafe.Pointer(&b.span)) + if old := atomic.SwapPointer(addr, nil); old != nil { + (*apm.Span)(old).End() + } +} + +// ClientOption sets options for tracing client requests. +type ClientOption func(*roundTripper) + +// WithClientRequestName returns a ClientOption which sets r as the function +// to use to obtain the span name for the given http request. +func WithClientRequestName(r RequestNameFunc) ClientOption { + if r == nil { + panic("r == nil") + } + + return ClientOption(func(rt *roundTripper) { + rt.requestName = r + }) +} diff --git a/vendor/go.elastic.co/apm/module/apmhttp/context.go b/vendor/go.elastic.co/apm/module/apmhttp/context.go new file mode 100644 index 00000000000..00c450eba08 --- /dev/null +++ b/vendor/go.elastic.co/apm/module/apmhttp/context.go @@ -0,0 +1,40 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apmhttp + +import ( + "fmt" +) + +var standardStatusCodeResults = [...]string{ + "HTTP 1xx", + "HTTP 2xx", + "HTTP 3xx", + "HTTP 4xx", + "HTTP 5xx", +} + +// StatusCodeResult returns the transaction result value to use for the given +// status code. +func StatusCodeResult(statusCode int) string { + switch i := statusCode / 100; i { + case 1, 2, 3, 4, 5: + return standardStatusCodeResults[i-1] + } + return fmt.Sprintf("HTTP %d", statusCode) +} diff --git a/vendor/go.elastic.co/apm/module/apmhttp/doc.go b/vendor/go.elastic.co/apm/module/apmhttp/doc.go new file mode 100644 index 00000000000..659281badcd --- /dev/null +++ b/vendor/go.elastic.co/apm/module/apmhttp/doc.go @@ -0,0 +1,20 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Package apmhttp provides a tracing middleware http.Handler for +// servers, and a tracing http.RoundTripper for clients. +package apmhttp diff --git a/vendor/go.elastic.co/apm/module/apmhttp/go.mod b/vendor/go.elastic.co/apm/module/apmhttp/go.mod new file mode 100644 index 00000000000..70240cdd353 --- /dev/null +++ b/vendor/go.elastic.co/apm/module/apmhttp/go.mod @@ -0,0 +1,13 @@ +module go.elastic.co/apm/module/apmhttp + +require ( + github.com/pkg/errors v0.8.1 + github.com/stretchr/testify v1.4.0 + go.elastic.co/apm v1.7.2 + golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 + golang.org/x/text v0.3.2 // indirect +) + +replace go.elastic.co/apm => ../.. + +go 1.13 diff --git a/vendor/go.elastic.co/apm/module/apmhttp/go.sum b/vendor/go.elastic.co/apm/module/apmhttp/go.sum new file mode 100644 index 00000000000..1976184453d --- /dev/null +++ b/vendor/go.elastic.co/apm/module/apmhttp/go.sum @@ -0,0 +1,62 @@ +github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/cucumber/godog v0.8.1 h1:lVb+X41I4YDreE+ibZ50bdXmySxgRviYFgKY6Aw4XE8= +github.com/cucumber/godog v0.8.1/go.mod h1:vSh3r/lM+psC1BPXvdkSEuNjmXfpVqrMGYAElF6hxnA= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/elastic/go-sysinfo v1.1.1 h1:ZVlaLDyhVkDfjwPGU55CQRCRolNpc7P0BbyhhQZQmMI= +github.com/elastic/go-sysinfo v1.1.1/go.mod h1:i1ZYdU10oLNfRzq4vq62BEwD2fH8KaWh6eh0ikPT9F0= +github.com/elastic/go-windows v1.0.0 h1:qLURgZFkkrYyTTkvYpsZIgf83AUsdIHfvlJaqaZ7aSY= +github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU= +github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 h1:rp+c0RAYOWj8l6qbCUTSiRLG/iKnW3K3/QfPPuSsBt4= +github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/santhosh-tekuri/jsonschema v1.2.4 h1:hNhW8e7t+H1vgY+1QeEQpveR6D4+OwKPXCfD2aieJis= +github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +go.elastic.co/fastjson v1.0.0 h1:ooXV/ABvf+tBul26jcVViPT3sBir0PvXgibYB1IQQzg= +go.elastic.co/fastjson v1.0.0/go.mod h1:PmeUOMMtLHQr9ZS9J9owrAVg0FkaZDRZJEFTTGHtchs= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20191025021431-6c3a3bfe00ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e h1:9vRrk9YW2BTzLP0VCB9ZDjU4cPqkg+IDWL7XgxA1yxQ= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M= +howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= diff --git a/vendor/go.elastic.co/apm/module/apmhttp/handler.go b/vendor/go.elastic.co/apm/module/apmhttp/handler.go new file mode 100644 index 00000000000..8aecf24ba48 --- /dev/null +++ b/vendor/go.elastic.co/apm/module/apmhttp/handler.go @@ -0,0 +1,330 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apmhttp + +import ( + "context" + "net/http" + + "go.elastic.co/apm" +) + +// Wrap returns an http.Handler wrapping h, reporting each request as +// a transaction to Elastic APM. +// +// By default, the returned Handler will use apm.DefaultTracer. +// Use WithTracer to specify an alternative tracer. +// +// By default, the returned Handler will recover panics, reporting +// them to the configured tracer. To override this behaviour, use +// WithRecovery. +func Wrap(h http.Handler, o ...ServerOption) http.Handler { + if h == nil { + panic("h == nil") + } + handler := &handler{ + handler: h, + tracer: apm.DefaultTracer, + requestName: ServerRequestName, + requestIgnorer: DefaultServerRequestIgnorer(), + } + for _, o := range o { + o(handler) + } + if handler.recovery == nil { + handler.recovery = NewTraceRecovery(handler.tracer) + } + return handler +} + +// handler wraps an http.Handler, reporting a new transaction for each request. +// +// The http.Request's context will be updated with the transaction. +type handler struct { + handler http.Handler + tracer *apm.Tracer + recovery RecoveryFunc + panicPropagation bool + requestName RequestNameFunc + requestIgnorer RequestIgnorerFunc +} + +// ServeHTTP delegates to h.Handler, tracing the transaction with +// h.Tracer, or apm.DefaultTracer if h.Tracer is nil. +func (h *handler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + if !h.tracer.Active() || h.requestIgnorer(req) { + h.handler.ServeHTTP(w, req) + return + } + tx, req := StartTransaction(h.tracer, h.requestName(req), req) + defer tx.End() + + body := h.tracer.CaptureHTTPRequestBody(req) + w, resp := WrapResponseWriter(w) + defer func() { + if v := recover(); v != nil { + if h.panicPropagation { + defer panic(v) + // 500 status code will be set only for APM transaction + // to allow other middleware to choose a different response code + if resp.StatusCode == 0 { + resp.StatusCode = http.StatusInternalServerError + } + } else if resp.StatusCode == 0 { + w.WriteHeader(http.StatusInternalServerError) + } + h.recovery(w, req, resp, body, tx, v) + } + SetTransactionContext(tx, req, resp, body) + body.Discard() + }() + h.handler.ServeHTTP(w, req) + if resp.StatusCode == 0 { + resp.StatusCode = http.StatusOK + } +} + +// StartTransaction returns a new Transaction with name, +// created with tracer, and taking trace context from req. +// +// If the transaction is not ignored, the request will be +// returned with the transaction added to its context. +func StartTransaction(tracer *apm.Tracer, name string, req *http.Request) (*apm.Transaction, *http.Request) { + traceContext, ok := getRequestTraceparent(req, ElasticTraceparentHeader) + if !ok { + traceContext, ok = getRequestTraceparent(req, W3CTraceparentHeader) + } + if ok { + traceContext.State, _ = ParseTracestateHeader(req.Header[TracestateHeader]...) + } + tx := tracer.StartTransactionOptions(name, "request", apm.TransactionOptions{TraceContext: traceContext}) + ctx := apm.ContextWithTransaction(req.Context(), tx) + req = RequestWithContext(ctx, req) + return tx, req +} + +func getRequestTraceparent(req *http.Request, header string) (apm.TraceContext, bool) { + if values := req.Header[header]; len(values) == 1 && values[0] != "" { + if c, err := ParseTraceparentHeader(values[0]); err == nil { + return c, true + } + } + return apm.TraceContext{}, false +} + +// SetTransactionContext sets tx.Result and, if the transaction is being +// sampled, sets tx.Context with information from req, resp, and body. +func SetTransactionContext(tx *apm.Transaction, req *http.Request, resp *Response, body *apm.BodyCapturer) { + tx.Result = StatusCodeResult(resp.StatusCode) + if !tx.Sampled() { + return + } + SetContext(&tx.Context, req, resp, body) +} + +// SetContext sets the context for a transaction or error using information +// from req, resp, and body. +func SetContext(ctx *apm.Context, req *http.Request, resp *Response, body *apm.BodyCapturer) { + ctx.SetHTTPRequest(req) + ctx.SetHTTPRequestBody(body) + ctx.SetHTTPStatusCode(resp.StatusCode) + ctx.SetHTTPResponseHeaders(resp.Headers) +} + +// WrapResponseWriter wraps an http.ResponseWriter and returns the wrapped +// value along with a *Response which will be filled in when the handler +// is called. The *Response value must not be inspected until after the +// request has been handled, to avoid data races. If neither of the +// ResponseWriter's Write or WriteHeader methods are called, then the +// response's StatusCode field will be zero. +// +// The returned http.ResponseWriter implements http.Pusher and http.Hijacker +// if and only if the provided http.ResponseWriter does. +func WrapResponseWriter(w http.ResponseWriter) (http.ResponseWriter, *Response) { + rw := responseWriter{ + ResponseWriter: w, + resp: Response{ + Headers: w.Header(), + }, + } + h, _ := w.(http.Hijacker) + p, _ := w.(http.Pusher) + switch { + case h != nil && p != nil: + rwhp := &responseWriterHijackerPusher{ + responseWriter: rw, + Hijacker: h, + Pusher: p, + } + return rwhp, &rwhp.resp + case h != nil: + rwh := &responseWriterHijacker{ + responseWriter: rw, + Hijacker: h, + } + return rwh, &rwh.resp + case p != nil: + rwp := &responseWriterPusher{ + responseWriter: rw, + Pusher: p, + } + return rwp, &rwp.resp + } + return &rw, &rw.resp +} + +// Response records details of the HTTP response. +type Response struct { + // StatusCode records the HTTP status code set via WriteHeader. + StatusCode int + + // Headers holds the headers set in the ResponseWriter. + Headers http.Header +} + +type responseWriter struct { + http.ResponseWriter + resp Response +} + +// WriteHeader sets w.resp.StatusCode and calls through to the embedded +// ResponseWriter. +func (w *responseWriter) WriteHeader(statusCode int) { + w.ResponseWriter.WriteHeader(statusCode) + w.resp.StatusCode = statusCode +} + +// Write calls through to the embedded ResponseWriter, setting +// w.resp.StatusCode to http.StatusOK if WriteHeader has not already +// been called. +func (w *responseWriter) Write(data []byte) (int, error) { + n, err := w.ResponseWriter.Write(data) + if w.resp.StatusCode == 0 { + w.resp.StatusCode = http.StatusOK + } + return n, err +} + +// CloseNotify returns w.closeNotify() if w.closeNotify is non-nil, +// otherwise it returns nil. +func (w *responseWriter) CloseNotify() <-chan bool { + if closeNotifier, ok := w.ResponseWriter.(http.CloseNotifier); ok { + return closeNotifier.CloseNotify() + } + return nil +} + +// Flush calls w.flush() if w.flush is non-nil, otherwise +// it does nothing. +func (w *responseWriter) Flush() { + if flusher, ok := w.ResponseWriter.(http.Flusher); ok { + flusher.Flush() + } +} + +type responseWriterHijacker struct { + responseWriter + http.Hijacker +} + +type responseWriterPusher struct { + responseWriter + http.Pusher +} + +type responseWriterHijackerPusher struct { + responseWriter + http.Hijacker + http.Pusher +} + +// ServerOption sets options for tracing server requests. +type ServerOption func(*handler) + +// WithTracer returns a ServerOption which sets t as the tracer +// to use for tracing server requests. +func WithTracer(t *apm.Tracer) ServerOption { + if t == nil { + panic("t == nil") + } + return func(h *handler) { + h.tracer = t + } +} + +// WithRecovery returns a ServerOption which sets r as the recovery +// function to use for tracing server requests. +func WithRecovery(r RecoveryFunc) ServerOption { + if r == nil { + panic("r == nil") + } + return func(h *handler) { + h.recovery = r + } +} + +// WithPanicPropagation returns a ServerOption which enable panic propagation. +// Any panic will be recovered and recorded as an error in a transaction, then +// panic will be caused again. +func WithPanicPropagation() ServerOption { + return func(h *handler) { + h.panicPropagation = true + } +} + +// RequestNameFunc is the type of a function for use in +// WithServerRequestName. +type RequestNameFunc func(*http.Request) string + +// WithServerRequestName returns a ServerOption which sets r as the function +// to use to obtain the transaction name for the given server request. +func WithServerRequestName(r RequestNameFunc) ServerOption { + if r == nil { + panic("r == nil") + } + return func(h *handler) { + h.requestName = r + } +} + +// RequestIgnorerFunc is the type of a function for use in +// WithServerRequestIgnorer. +type RequestIgnorerFunc func(*http.Request) bool + +// WithServerRequestIgnorer returns a ServerOption which sets r as the +// function to use to determine whether or not a server request should +// be ignored. If r is nil, all requests will be reported. +func WithServerRequestIgnorer(r RequestIgnorerFunc) ServerOption { + if r == nil { + r = IgnoreNone + } + return func(h *handler) { + h.requestIgnorer = r + } +} + +// RequestWithContext is equivalent to req.WithContext, except that the URL +// pointer is copied, rather than the contents. +func RequestWithContext(ctx context.Context, req *http.Request) *http.Request { + url := req.URL + req.URL = nil + reqCopy := req.WithContext(ctx) + reqCopy.URL = url + req.URL = url + return reqCopy +} diff --git a/vendor/go.elastic.co/apm/module/apmhttp/ignorer.go b/vendor/go.elastic.co/apm/module/apmhttp/ignorer.go new file mode 100644 index 00000000000..6ec56dd50f3 --- /dev/null +++ b/vendor/go.elastic.co/apm/module/apmhttp/ignorer.go @@ -0,0 +1,81 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apmhttp + +import ( + "net/http" + "regexp" + "sync" + + "go.elastic.co/apm/internal/configutil" + "go.elastic.co/apm/internal/wildcard" +) + +const ( + envIgnoreURLs = "ELASTIC_APM_IGNORE_URLS" +) + +var ( + defaultServerRequestIgnorerOnce sync.Once + defaultServerRequestIgnorer RequestIgnorerFunc = IgnoreNone +) + +// DefaultServerRequestIgnorer returns the default RequestIgnorer to use in +// handlers. If ELASTIC_APM_IGNORE_URLS is set, it will be treated as a +// comma-separated list of wildcard patterns; requests that match any of the +// patterns will be ignored. +func DefaultServerRequestIgnorer() RequestIgnorerFunc { + defaultServerRequestIgnorerOnce.Do(func() { + matchers := configutil.ParseWildcardPatternsEnv(envIgnoreURLs, nil) + if len(matchers) != 0 { + defaultServerRequestIgnorer = NewWildcardPatternsRequestIgnorer(matchers) + } + }) + return defaultServerRequestIgnorer +} + +// NewRegexpRequestIgnorer returns a RequestIgnorerFunc which matches requests' +// URLs against re. Note that for server requests, typically only Path and +// possibly RawQuery will be set, so the regular expression should take this +// into account. +func NewRegexpRequestIgnorer(re *regexp.Regexp) RequestIgnorerFunc { + if re == nil { + panic("re == nil") + } + return func(r *http.Request) bool { + return re.MatchString(r.URL.String()) + } +} + +// NewWildcardPatternsRequestIgnorer returns a RequestIgnorerFunc which matches +// requests' URLs against any of the matchers. Note that for server requests, +// typically only Path and possibly RawQuery will be set, so the wildcard patterns +// should take this into account. +func NewWildcardPatternsRequestIgnorer(matchers wildcard.Matchers) RequestIgnorerFunc { + if len(matchers) == 0 { + panic("len(matchers) == 0") + } + return func(r *http.Request) bool { + return matchers.MatchAny(r.URL.String()) + } +} + +// IgnoreNone is a RequestIgnorerFunc which ignores no requests. +func IgnoreNone(*http.Request) bool { + return false +} diff --git a/vendor/go.elastic.co/apm/module/apmhttp/recovery.go b/vendor/go.elastic.co/apm/module/apmhttp/recovery.go new file mode 100644 index 00000000000..988769c1456 --- /dev/null +++ b/vendor/go.elastic.co/apm/module/apmhttp/recovery.go @@ -0,0 +1,60 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apmhttp + +import ( + "net/http" + + "go.elastic.co/apm" +) + +// RecoveryFunc is the type of a function for use in WithRecovery. +type RecoveryFunc func( + w http.ResponseWriter, + req *http.Request, + resp *Response, + body *apm.BodyCapturer, + tx *apm.Transaction, + recovered interface{}, +) + +// NewTraceRecovery returns a RecoveryFunc for use in WithRecovery. +// +// The returned RecoveryFunc will report recovered error to Elastic APM +// using the given Tracer, or apm.DefaultTracer if t is nil. The +// error will be linked to the given transaction. +// +// If headers have not already been written, a 500 response will be sent. +func NewTraceRecovery(t *apm.Tracer) RecoveryFunc { + if t == nil { + t = apm.DefaultTracer + } + return func( + w http.ResponseWriter, + req *http.Request, + resp *Response, + body *apm.BodyCapturer, + tx *apm.Transaction, + recovered interface{}, + ) { + e := t.Recovered(recovered) + e.SetTransaction(tx) + SetContext(&e.Context, req, resp, body) + e.Send() + } +} diff --git a/vendor/go.elastic.co/apm/module/apmhttp/requestname.go b/vendor/go.elastic.co/apm/module/apmhttp/requestname.go new file mode 100644 index 00000000000..877aac15306 --- /dev/null +++ b/vendor/go.elastic.co/apm/module/apmhttp/requestname.go @@ -0,0 +1,56 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build go1.10 + +package apmhttp + +import ( + "net/http" + "strings" +) + +// UnknownRouteRequestName returns the transaction name for the server request, req, +// when the route could not be determined. +func UnknownRouteRequestName(req *http.Request) string { + const suffix = " unknown route" + var b strings.Builder + b.Grow(len(req.Method) + len(suffix)) + b.WriteString(req.Method) + b.WriteString(suffix) + return b.String() +} + +// ServerRequestName returns the transaction name for the server request, req. +func ServerRequestName(req *http.Request) string { + var b strings.Builder + b.Grow(len(req.Method) + len(req.URL.Path) + 1) + b.WriteString(req.Method) + b.WriteByte(' ') + b.WriteString(req.URL.Path) + return b.String() +} + +// ClientRequestName returns the span name for the client request, req. +func ClientRequestName(req *http.Request) string { + var b strings.Builder + b.Grow(len(req.Method) + len(req.URL.Host) + 1) + b.WriteString(req.Method) + b.WriteByte(' ') + b.WriteString(req.URL.Host) + return b.String() +} diff --git a/vendor/go.elastic.co/apm/module/apmhttp/requestname_go19.go b/vendor/go.elastic.co/apm/module/apmhttp/requestname_go19.go new file mode 100644 index 00000000000..2a84ec75955 --- /dev/null +++ b/vendor/go.elastic.co/apm/module/apmhttp/requestname_go19.go @@ -0,0 +1,46 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build !go1.10 + +package apmhttp + +import "net/http" + +// UnknownRouteRequestName returns the transaction name for the server request, req, +// when the route could not be determined. +func UnknownRouteRequestName(req *http.Request) string { + return req.Method + " unknown route" +} + +// ServerRequestName returns the transaction name for the server request, req. +func ServerRequestName(req *http.Request) string { + buf := make([]byte, len(req.Method)+len(req.URL.Path)+1) + n := copy(buf, req.Method) + buf[n] = ' ' + copy(buf[n+1:], req.URL.Path) + return string(buf) +} + +// ClientRequestName returns the span name for the client request, req. +func ClientRequestName(req *http.Request) string { + buf := make([]byte, len(req.Method)+len(req.URL.Host)+1) + n := copy(buf, req.Method) + buf[n] = ' ' + copy(buf[n+1:], req.URL.Host) + return string(buf) +} diff --git a/vendor/go.elastic.co/apm/module/apmhttp/traceheaders.go b/vendor/go.elastic.co/apm/module/apmhttp/traceheaders.go new file mode 100644 index 00000000000..8a00a70db30 --- /dev/null +++ b/vendor/go.elastic.co/apm/module/apmhttp/traceheaders.go @@ -0,0 +1,168 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apmhttp + +import ( + "encoding/hex" + "fmt" + "strings" + + "github.com/pkg/errors" + + "go.elastic.co/apm" +) + +const ( + // TraceparentHeader is the HTTP header for trace propagation. + // + // For backwards compatibility, this is currently an alias for + // for ElasticTraceparentHeader, but the more specific constants + // below should be preferred. In a future version this will be + // replaced by the standard W3C header. + TraceparentHeader = ElasticTraceparentHeader + + // ElasticTraceparentHeader is the legacy HTTP header for trace propagation, + // maintained for backwards compatibility with older agents. + ElasticTraceparentHeader = "Elastic-Apm-Traceparent" + + // W3CTraceparentHeader is the standard W3C Trace-Context HTTP + // header for trace propagation. + W3CTraceparentHeader = "Traceparent" + + // TracestateHeader is the standard W3C Trace-Context HTTP header + // for vendor-specific trace propagation. + TracestateHeader = "Tracestate" +) + +// FormatTraceparentHeader formats the given trace context as a +// traceparent header. +func FormatTraceparentHeader(c apm.TraceContext) string { + const version = 0 + return fmt.Sprintf("%02x-%032x-%016x-%02x", 0, c.Trace[:], c.Span[:], c.Options) +} + +// ParseTraceparentHeader parses the given header, which is expected to be in +// the W3C Trace-Context traceparent format according to W3C Editor's Draft 23 May 2018: +// https://w3c.github.io/trace-context/#traceparent-field +// +// Note that the returned TraceContext's Trace and Span fields are not necessarily +// valid. The caller must decide whether or not it wishes to disregard invalid +// trace/span IDs, and validate them as required using their provided Validate +// methods. +// +// The returned TraceContext's TraceState field will be the empty value. Use +// ParseTracestateHeader to parse that separately. +func ParseTraceparentHeader(h string) (apm.TraceContext, error) { + var out apm.TraceContext + if len(h) < 3 || h[2] != '-' { + return out, errors.Errorf("invalid traceparent header %q", h) + } + var version byte + if !strings.HasPrefix(h, "00") { + decoded, err := hex.DecodeString(h[:2]) + if err != nil { + return out, errors.Wrap(err, "error decoding traceparent header version") + } + version = decoded[0] + } + h = h[3:] + + switch version { + case 255: + // "Version 255 is invalid." + return out, errors.Errorf("traceparent header version 255 is forbidden") + default: + // "If higher version is detected - implementation SHOULD try to parse it." + fallthrough + case 0: + // Version 00: + // + // version-format = trace-id "-" span-id "-" trace-options + // trace-id = 32HEXDIG + // span-id = 16HEXDIG + // trace-options = 2HEXDIG + const ( + traceIDEnd = 32 + spanIDStart = traceIDEnd + 1 + spanIDEnd = spanIDStart + 16 + traceOptionsStart = spanIDEnd + 1 + traceOptionsEnd = traceOptionsStart + 2 + ) + switch { + case len(h) < traceOptionsEnd, + h[traceIDEnd] != '-', + h[spanIDEnd] != '-', + version == 0 && len(h) != traceOptionsEnd, + version > 0 && len(h) > traceOptionsEnd && h[traceOptionsEnd] != '-': + return out, errors.Errorf("invalid version %d traceparent header %q", version, h) + } + if _, err := hex.Decode(out.Trace[:], []byte(h[:traceIDEnd])); err != nil { + return out, errors.Wrapf(err, "error decoding trace-id for version %d", version) + } + if err := out.Trace.Validate(); err != nil { + return out, errors.Wrap(err, "invalid trace-id") + } + if _, err := hex.Decode(out.Span[:], []byte(h[spanIDStart:spanIDEnd])); err != nil { + return out, errors.Wrapf(err, "error decoding span-id for version %d", version) + } + if err := out.Span.Validate(); err != nil { + return out, errors.Wrap(err, "invalid span-id") + } + var traceOptions [1]byte + if _, err := hex.Decode(traceOptions[:], []byte(h[traceOptionsStart:traceOptionsEnd])); err != nil { + return out, errors.Wrapf(err, "error decoding trace-options for version %d", version) + } + out.Options = apm.TraceOptions(traceOptions[0]) + return out, nil + } +} + +// ParseTracestateHeader parses the given header, which is expected to be in the +// W3C Trace-Context tracestate format according to W3C Editor's Draft 18 Nov 2019: +// https://w3c.github.io/trace-context/#tracestate-header +// +// Note that the returned TraceState is not necessarily valid. The caller must +// decide whether or not it wishes to disregard invalid tracestate entries, and +// validate them as required using their provided Validate methods. +// +// Multiple header values may be presented, in which case they will be treated as +// if they are concatenated together with commas. +func ParseTracestateHeader(h ...string) (apm.TraceState, error) { + var entries []apm.TraceStateEntry + for _, h := range h { + for { + h = strings.TrimSpace(h) + if h == "" { + break + } + kv := h + if comma := strings.IndexRune(h, ','); comma != -1 { + kv = strings.TrimSpace(h[:comma]) + h = h[comma+1:] + } else { + h = "" + } + equal := strings.IndexRune(kv, '=') + if equal == -1 { + return apm.TraceState{}, errors.New("missing '=' in tracestate entry") + } + entries = append(entries, apm.TraceStateEntry{Key: kv[:equal], Value: kv[equal+1:]}) + } + } + return apm.NewTraceState(entries...), nil +} diff --git a/vendor/go.elastic.co/apm/profiling.go b/vendor/go.elastic.co/apm/profiling.go new file mode 100644 index 00000000000..7cc3fe9affe --- /dev/null +++ b/vendor/go.elastic.co/apm/profiling.go @@ -0,0 +1,164 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apm + +import ( + "bytes" + "context" + "io" + "runtime/pprof" + "time" + + "github.com/pkg/errors" +) + +type profilingState struct { + profileType string + profileStart func(io.Writer) error + profileStop func() + sender profileSender + + interval time.Duration + duration time.Duration // not relevant to all profiles + + timer *time.Timer + timerStart time.Time + buf bytes.Buffer + finished chan struct{} +} + +// newCPUProfilingState calls newProfilingState with the +// profiler type set to "cpu", and using pprof.StartCPUProfile +// and pprof.StopCPUProfile. +func newCPUProfilingState(sender profileSender) *profilingState { + return newProfilingState("cpu", pprof.StartCPUProfile, pprof.StopCPUProfile, sender) +} + +// newHeapProfilingState calls newProfilingState with the +// profiler type set to "heap", and using pprof.Lookup("heap").WriteTo(writer, 0). +func newHeapProfilingState(sender profileSender) *profilingState { + return newLookupProfilingState("heap", sender) +} + +func newLookupProfilingState(name string, sender profileSender) *profilingState { + profileStart := func(w io.Writer) error { + profile := pprof.Lookup(name) + if profile == nil { + return errors.Errorf("no profile called %q", name) + } + return profile.WriteTo(w, 0) + } + return newProfilingState("heap", profileStart, func() {}, sender) +} + +// newProfilingState returns a new profilingState, +// with its timer stopped. The timer may be started +// by calling profilingState.updateConfig. +func newProfilingState( + profileType string, + profileStart func(io.Writer) error, + profileStop func(), + sender profileSender, +) *profilingState { + state := &profilingState{ + profileType: profileType, + profileStart: profileStart, + profileStop: profileStop, + sender: sender, + timer: time.NewTimer(0), + finished: make(chan struct{}, 1), + } + if !state.timer.Stop() { + <-state.timer.C + } + return state +} + +func (state *profilingState) updateConfig(interval, duration time.Duration) { + if state.sender == nil { + // No profile sender, no point in starting a timer. + return + } + state.duration = duration + if state.interval == interval { + return + } + if state.timerStart.IsZero() { + state.interval = interval + state.resetTimer() + } + // TODO(axw) handle extending/cutting short running timers once + // it is possible to dynamically control profiling configuration. +} + +func (state *profilingState) resetTimer() { + if state.interval != 0 { + state.timer.Reset(state.interval) + state.timerStart = time.Now() + } else { + state.timerStart = time.Time{} + } +} + +// start spawns a goroutine that will capture a profile, send it using state.sender, +// and finally signal state.finished. +// +// start will return immediately after spawning the goroutine. +func (state *profilingState) start(ctx context.Context, logger Logger, metadata io.Reader) { + // The state.duration field may be updated after the goroutine starts, + // by the caller, so it must be read outside the goroutine. + duration := state.duration + go func() { + defer func() { state.finished <- struct{}{} }() + if err := state.profile(ctx, duration); err != nil { + if logger != nil && ctx.Err() == nil { + logger.Errorf("%s", err) + } + return + } + // TODO(axw) backoff like SendStream requests + if err := state.sender.SendProfile(ctx, metadata, &state.buf); err != nil { + if logger != nil && ctx.Err() == nil { + logger.Errorf("failed to send %s profile: %s", state.profileType, err) + } + } + }() +} + +func (state *profilingState) profile(ctx context.Context, duration time.Duration) error { + state.buf.Reset() + if err := state.profileStart(&state.buf); err != nil { + return errors.Wrapf(err, "failed to start %s profile", state.profileType) + } + defer state.profileStop() + + if duration > 0 { + timer := time.NewTimer(duration) + defer timer.Stop() + select { + case <-ctx.Done(): + return ctx.Err() + case <-timer.C: + } + } + return nil +} + +type profileSender interface { + SendProfile(ctx context.Context, metadata io.Reader, profile ...io.Reader) error +} diff --git a/vendor/go.elastic.co/apm/sampler.go b/vendor/go.elastic.co/apm/sampler.go new file mode 100644 index 00000000000..3cf4591c646 --- /dev/null +++ b/vendor/go.elastic.co/apm/sampler.go @@ -0,0 +1,66 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apm + +import ( + "encoding/binary" + "math" + "math/big" + + "github.com/pkg/errors" +) + +// Sampler provides a means of sampling transactions. +type Sampler interface { + // Sample indicates whether or not a transaction + // should be sampled. This method will be invoked + // by calls to Tracer.StartTransaction for the root + // of a trace, so it must be goroutine-safe, and + // should avoid synchronization as far as possible. + Sample(TraceContext) bool +} + +// NewRatioSampler returns a new Sampler with the given ratio +// +// A ratio of 1.0 samples 100% of transactions, a ratio of 0.5 +// samples ~50%, and so on. If the ratio provided does not lie +// within the range [0,1.0], NewRatioSampler will panic. +// +// The returned Sampler bases its decision on the value of the +// transaction ID, so there is no synchronization involved. +func NewRatioSampler(r float64) Sampler { + if r < 0 || r > 1.0 { + panic(errors.Errorf("ratio %v out of range [0,1.0]", r)) + } + var x big.Float + x.SetUint64(math.MaxUint64) + x.Mul(&x, big.NewFloat(r)) + ceil, _ := x.Uint64() + return ratioSampler{ceil} +} + +type ratioSampler struct { + ceil uint64 +} + +// Sample samples the transaction according to the configured +// ratio and pseudo-random source. +func (s ratioSampler) Sample(c TraceContext) bool { + v := binary.BigEndian.Uint64(c.Span[:]) + return v > 0 && v-1 < s.ceil +} diff --git a/vendor/go.elastic.co/apm/sanitizer.go b/vendor/go.elastic.co/apm/sanitizer.go new file mode 100644 index 00000000000..7f9014840ec --- /dev/null +++ b/vendor/go.elastic.co/apm/sanitizer.go @@ -0,0 +1,66 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apm + +import ( + "go.elastic.co/apm/internal/wildcard" + "go.elastic.co/apm/model" +) + +const redacted = "[REDACTED]" + +// sanitizeRequest sanitizes HTTP request data, redacting the +// values of cookies, headers and forms whose corresponding keys +// match any of the given wildcard patterns. +func sanitizeRequest(r *model.Request, matchers wildcard.Matchers) { + for _, c := range r.Cookies { + if !matchers.MatchAny(c.Name) { + continue + } + c.Value = redacted + } + sanitizeHeaders(r.Headers, matchers) + if r.Body != nil && r.Body.Form != nil { + for key, values := range r.Body.Form { + if !matchers.MatchAny(key) { + continue + } + for i := range values { + values[i] = redacted + } + } + } +} + +// sanitizeResponse sanitizes HTTP response data, redacting +// the values of response headers whose corresponding keys +// match any of the given wildcard patterns. +func sanitizeResponse(r *model.Response, matchers wildcard.Matchers) { + sanitizeHeaders(r.Headers, matchers) +} + +func sanitizeHeaders(headers model.Headers, matchers wildcard.Matchers) { + for i := range headers { + h := &headers[i] + if !matchers.MatchAny(h.Key) || len(h.Values) == 0 { + continue + } + h.Values = h.Values[:1] + h.Values[0] = redacted + } +} diff --git a/vendor/go.elastic.co/apm/span.go b/vendor/go.elastic.co/apm/span.go new file mode 100644 index 00000000000..85b244c1db9 --- /dev/null +++ b/vendor/go.elastic.co/apm/span.go @@ -0,0 +1,415 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apm + +import ( + cryptorand "crypto/rand" + "encoding/binary" + "strings" + "sync" + "time" + + "go.elastic.co/apm/stacktrace" +) + +// droppedSpanDataPool holds *SpanData which are used when the span is created +// for a nil or non-sampled trace context, without a transaction reference. +// +// Spans started with a non-nil transaction, even if it is non-sampled, are +// always created with the transaction's tracer span pool. +var droppedSpanDataPool sync.Pool + +// StartSpan starts and returns a new Span within the transaction, +// with the specified name, type, and optional parent span, and +// with the start time set to the current time. +// +// StartSpan always returns a non-nil Span, with a non-nil SpanData +// field. Its End method must be called when the span completes. +// +// If the span type contains two dots, they are assumed to separate +// the span type, subtype, and action; a single dot separates span +// type and subtype, and the action will not be set. +// +// StartSpan is equivalent to calling StartSpanOptions with +// SpanOptions.Parent set to the trace context of parent if +// parent is non-nil. +func (tx *Transaction) StartSpan(name, spanType string, parent *Span) *Span { + return tx.StartSpanOptions(name, spanType, SpanOptions{ + parent: parent, + }) +} + +// StartSpanOptions starts and returns a new Span within the transaction, +// with the specified name, type, and options. +// +// StartSpan always returns a non-nil Span. Its End method must be called +// when the span completes. +// +// If the span type contains two dots, they are assumed to separate the +// span type, subtype, and action; a single dot separates span type and +// subtype, and the action will not be set. +func (tx *Transaction) StartSpanOptions(name, spanType string, opts SpanOptions) *Span { + if tx == nil { + return newDroppedSpan() + } + + if opts.Parent == (TraceContext{}) { + if opts.parent != nil { + opts.Parent = opts.parent.TraceContext() + } else { + opts.Parent = tx.traceContext + } + } + transactionID := tx.traceContext.Span + + // Prevent tx from being ended while we're starting a span. + tx.mu.RLock() + defer tx.mu.RUnlock() + if tx.ended() { + return tx.tracer.StartSpan(name, spanType, transactionID, opts) + } + + // Calculate the span time relative to the transaction timestamp so + // that wall-clock adjustments occurring after the transaction start + // don't affect the span timestamp. + if opts.Start.IsZero() { + opts.Start = tx.timestamp.Add(time.Since(tx.timestamp)) + } else { + opts.Start = tx.timestamp.Add(opts.Start.Sub(tx.timestamp)) + } + span := tx.tracer.startSpan(name, spanType, transactionID, opts) + span.tx = tx + span.parent = opts.parent + + // Guard access to spansCreated, spansDropped, rand, and childrenTimer. + tx.TransactionData.mu.Lock() + defer tx.TransactionData.mu.Unlock() + if !span.traceContext.Options.Recorded() { + span.tracer = nil // span is dropped + } else if tx.maxSpans >= 0 && tx.spansCreated >= tx.maxSpans { + span.tracer = nil // span is dropped + tx.spansDropped++ + } else { + if opts.SpanID.Validate() == nil { + span.traceContext.Span = opts.SpanID + } else { + binary.LittleEndian.PutUint64(span.traceContext.Span[:], tx.rand.Uint64()) + } + span.stackFramesMinDuration = tx.spanFramesMinDuration + span.stackTraceLimit = tx.stackTraceLimit + tx.spansCreated++ + } + + if tx.breakdownMetricsEnabled { + if span.parent != nil { + span.parent.mu.Lock() + defer span.parent.mu.Unlock() + if !span.parent.ended() { + span.parent.childrenTimer.childStarted(span.timestamp) + } + } else { + tx.childrenTimer.childStarted(span.timestamp) + } + } + return span +} + +// StartSpan returns a new Span with the specified name, type, transaction ID, +// and options. The parent transaction context and transaction IDs must have +// valid, non-zero values, or else the span will be dropped. +// +// In most cases, you should use Transaction.StartSpan or Transaction.StartSpanOptions. +// This method is provided for corner-cases, such as starting a span after the +// containing transaction's End method has been called. Spans created in this +// way will not have the "max spans" configuration applied, nor will they be +// considered in any transaction's span count. +func (t *Tracer) StartSpan(name, spanType string, transactionID SpanID, opts SpanOptions) *Span { + if opts.Parent.Trace.Validate() != nil || opts.Parent.Span.Validate() != nil || transactionID.Validate() != nil { + return newDroppedSpan() + } + if !opts.Parent.Options.Recorded() { + return newDroppedSpan() + } + var spanID SpanID + if opts.SpanID.Validate() == nil { + spanID = opts.SpanID + } else { + if _, err := cryptorand.Read(spanID[:]); err != nil { + return newDroppedSpan() + } + } + if opts.Start.IsZero() { + opts.Start = time.Now() + } + span := t.startSpan(name, spanType, transactionID, opts) + span.traceContext.Span = spanID + + instrumentationConfig := t.instrumentationConfig() + span.stackFramesMinDuration = instrumentationConfig.spanFramesMinDuration + span.stackTraceLimit = instrumentationConfig.stackTraceLimit + + return span +} + +// SpanOptions holds options for Transaction.StartSpanOptions and Tracer.StartSpan. +type SpanOptions struct { + // Parent, if non-zero, holds the trace context of the parent span. + Parent TraceContext + + // SpanID holds the ID to assign to the span. If this is zero, a new ID + // will be generated and used instead. + SpanID SpanID + + // parent, if non-nil, holds the parent span. + // + // This is only used if Parent is zero, and is only available to internal + // callers of Transaction.StartSpanOptions. + parent *Span + + // Start is the start time of the span. If this has the zero value, + // time.Now() will be used instead. + // + // When a span is created using Transaction.StartSpanOptions, the + // span timestamp is internally calculated relative to the transaction + // timestamp. + // + // When Tracer.StartSpan is used, this timestamp should be pre-calculated + // as relative from the transaction start time, i.e. by calculating the + // time elapsed since the transaction started, and adding that to the + // transaction timestamp. Calculating the timstamp in this way will ensure + // monotonicity of events within a transaction. + Start time.Time +} + +func (t *Tracer) startSpan(name, spanType string, transactionID SpanID, opts SpanOptions) *Span { + sd, _ := t.spanDataPool.Get().(*SpanData) + if sd == nil { + sd = &SpanData{Duration: -1} + } + span := &Span{tracer: t, SpanData: sd} + span.Name = name + span.traceContext = opts.Parent + span.parentID = opts.Parent.Span + span.transactionID = transactionID + span.timestamp = opts.Start + span.Type = spanType + if dot := strings.IndexRune(spanType, '.'); dot != -1 { + span.Type = spanType[:dot] + span.Subtype = spanType[dot+1:] + if dot := strings.IndexRune(span.Subtype, '.'); dot != -1 { + span.Subtype, span.Action = span.Subtype[:dot], span.Subtype[dot+1:] + } + } + return span +} + +// newDropped returns a new Span with a non-nil SpanData. +func newDroppedSpan() *Span { + span, _ := droppedSpanDataPool.Get().(*Span) + if span == nil { + span = &Span{SpanData: &SpanData{}} + } + return span +} + +// Span describes an operation within a transaction. +type Span struct { + tracer *Tracer // nil if span is dropped + tx *Transaction + parent *Span + traceContext TraceContext + transactionID SpanID + + mu sync.RWMutex + + // SpanData holds the span data. This field is set to nil when + // the span's End method is called. + *SpanData +} + +// TraceContext returns the span's TraceContext. +func (s *Span) TraceContext() TraceContext { + if s == nil { + return TraceContext{} + } + return s.traceContext +} + +// SetStacktrace sets the stacktrace for the span, +// skipping the first skip number of frames, +// excluding the SetStacktrace function. +func (s *Span) SetStacktrace(skip int) { + if s == nil || s.dropped() { + return + } + s.mu.RLock() + defer s.mu.RUnlock() + if s.ended() { + return + } + s.SpanData.setStacktrace(skip + 1) +} + +// Dropped indicates whether or not the span is dropped, meaning it will not +// be included in any transaction. Spans are dropped by Transaction.StartSpan +// if the transaction is nil, non-sampled, or the transaction's max spans +// limit has been reached. +// +// Dropped may be used to avoid any expensive computation required to set +// the span's context. +func (s *Span) Dropped() bool { + return s == nil || s.dropped() +} + +func (s *Span) dropped() bool { + return s.tracer == nil +} + +// End marks the s as being complete; s must not be used after this. +// +// If s.Duration has not been set, End will set it to the elapsed time +// since the span's start time. +func (s *Span) End() { + s.mu.Lock() + defer s.mu.Unlock() + if s.ended() { + return + } + if s.Duration < 0 { + s.Duration = time.Since(s.timestamp) + } + if s.dropped() { + if s.tx == nil { + droppedSpanDataPool.Put(s.SpanData) + } else { + s.reportSelfTime() + s.reset(s.tx.tracer) + } + s.SpanData = nil + return + } + if len(s.stacktrace) == 0 && s.Duration >= s.stackFramesMinDuration { + s.setStacktrace(1) + } + if s.tx != nil { + s.reportSelfTime() + } + s.enqueue() + s.SpanData = nil +} + +// reportSelfTime reports the span's self-time to its transaction, and informs +// the parent that it has ended in order for the parent to later calculate its +// own self-time. +// +// This must only be called from Span.End, with s.mu.Lock held for writing and +// s.Duration set. +func (s *Span) reportSelfTime() { + endTime := s.timestamp.Add(s.Duration) + + // TODO(axw) try to find a way to not lock the transaction when + // ending every span. We already lock them when starting spans. + s.tx.mu.RLock() + defer s.tx.mu.RUnlock() + if s.tx.ended() || !s.tx.breakdownMetricsEnabled { + return + } + + s.tx.TransactionData.mu.Lock() + defer s.tx.TransactionData.mu.Unlock() + if s.parent != nil { + s.parent.mu.Lock() + if !s.parent.ended() { + s.parent.childrenTimer.childEnded(endTime) + } + s.parent.mu.Unlock() + } else { + s.tx.childrenTimer.childEnded(endTime) + } + s.tx.spanTimings.add(s.Type, s.Subtype, s.Duration-s.childrenTimer.finalDuration(endTime)) +} + +func (s *Span) enqueue() { + event := tracerEvent{eventType: spanEvent} + event.span.Span = s + event.span.SpanData = s.SpanData + select { + case s.tracer.events <- event: + default: + // Enqueuing a span should never block. + s.tracer.statsMu.Lock() + s.tracer.stats.SpansDropped++ + s.tracer.statsMu.Unlock() + s.reset(s.tracer) + } +} + +func (s *Span) ended() bool { + return s.SpanData == nil +} + +// SpanData holds the details for a span, and is embedded inside Span. +// When a span is ended or discarded, its SpanData field will be set +// to nil. +type SpanData struct { + parentID SpanID + stackFramesMinDuration time.Duration + stackTraceLimit int + timestamp time.Time + childrenTimer childrenTimer + + // Name holds the span name, initialized with the value passed to StartSpan. + Name string + + // Type holds the overarching span type, such as "db", and will be initialized + // with the value passed to StartSpan. + Type string + + // Subtype holds the span subtype, such as "mysql". This will initially be empty, + // and can be set after starting the span. + Subtype string + + // Action holds the span action, such as "query". This will initially be empty, + // and can be set after starting the span. + Action string + + // Duration holds the span duration, initialized to -1. + // + // If you do not update Duration, calling Span.End will calculate the + // duration based on the elapsed time since the span's start time. + Duration time.Duration + + // Context describes the context in which span occurs. + Context SpanContext + + stacktrace []stacktrace.Frame +} + +func (s *SpanData) setStacktrace(skip int) { + s.stacktrace = stacktrace.AppendStacktrace(s.stacktrace[:0], skip+1, s.stackTraceLimit) +} + +func (s *SpanData) reset(tracer *Tracer) { + *s = SpanData{ + Context: s.Context, + Duration: -1, + stacktrace: s.stacktrace[:0], + } + s.Context.reset() + tracer.spanDataPool.Put(s) +} diff --git a/vendor/go.elastic.co/apm/spancontext.go b/vendor/go.elastic.co/apm/spancontext.go new file mode 100644 index 00000000000..180fe1ddc34 --- /dev/null +++ b/vendor/go.elastic.co/apm/spancontext.go @@ -0,0 +1,193 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apm + +import ( + "fmt" + "net/http" + "net/url" + "strings" + + "go.elastic.co/apm/internal/apmhttputil" + "go.elastic.co/apm/model" +) + +// SpanContext provides methods for setting span context. +type SpanContext struct { + model model.SpanContext + destination model.DestinationSpanContext + destinationService model.DestinationServiceSpanContext + databaseRowsAffected int64 + database model.DatabaseSpanContext + http model.HTTPSpanContext +} + +// DatabaseSpanContext holds database span context. +type DatabaseSpanContext struct { + // Instance holds the database instance name. + Instance string + + // Statement holds the statement executed in the span, + // e.g. "SELECT * FROM foo". + Statement string + + // Type holds the database type, e.g. "sql". + Type string + + // User holds the username used for database access. + User string +} + +// DestinationServiceSpanContext holds destination service span span. +type DestinationServiceSpanContext struct { + // Name holds a name for the destination service, which may be used + // for grouping and labeling in service maps. + Name string + + // Resource holds an identifier for a destination service resource, + // such as a message queue. + Resource string +} + +func (c *SpanContext) build() *model.SpanContext { + switch { + case len(c.model.Tags) != 0: + case c.model.Database != nil: + case c.model.HTTP != nil: + case c.model.Destination != nil: + default: + return nil + } + return &c.model +} + +func (c *SpanContext) reset() { + *c = SpanContext{ + model: model.SpanContext{ + Tags: c.model.Tags[:0], + }, + } +} + +// SetTag calls SetLabel(key, value). +// +// SetTag is deprecated, and will be removed in a future major version. +func (c *SpanContext) SetTag(key, value string) { + c.SetLabel(key, value) +} + +// SetLabel sets a label in the context. +// +// Invalid characters ('.', '*', and '"') in the key will be replaced with +// underscores. +// +// If the value is numerical or boolean, then it will be sent to the server +// as a JSON number or boolean; otherwise it will converted to a string, using +// `fmt.Sprint` if necessary. String values longer than 1024 characters will +// be truncated. +func (c *SpanContext) SetLabel(key string, value interface{}) { + // Note that we do not attempt to de-duplicate the keys. + // This is OK, since json.Unmarshal will always take the + // final instance. + c.model.Tags = append(c.model.Tags, model.IfaceMapItem{ + Key: cleanLabelKey(key), + Value: makeLabelValue(value), + }) +} + +// SetDatabase sets the span context for database-related operations. +func (c *SpanContext) SetDatabase(db DatabaseSpanContext) { + c.database = model.DatabaseSpanContext{ + Instance: truncateString(db.Instance), + Statement: truncateLongString(db.Statement), + Type: truncateString(db.Type), + User: truncateString(db.User), + } + c.model.Database = &c.database +} + +// SetDatabaseRowsAffected records the number of rows affected by +// a database operation. +func (c *SpanContext) SetDatabaseRowsAffected(n int64) { + c.databaseRowsAffected = n + c.database.RowsAffected = &c.databaseRowsAffected +} + +// SetHTTPRequest sets the details of the HTTP request in the context. +// +// This function relates to client requests. If the request URL contains +// user info, it will be removed and excluded from the stored URL. +// +// SetHTTPRequest makes implicit calls to SetDestinationAddress and +// SetDestinationService, using details from req.URL. +func (c *SpanContext) SetHTTPRequest(req *http.Request) { + if req.URL == nil { + return + } + c.http.URL = req.URL + c.model.HTTP = &c.http + + addr, port := apmhttputil.DestinationAddr(req) + c.SetDestinationAddress(addr, port) + + destinationServiceURL := url.URL{Scheme: req.URL.Scheme, Host: req.URL.Host} + destinationServiceResource := destinationServiceURL.Host + if port != 0 && port == apmhttputil.SchemeDefaultPort(req.URL.Scheme) { + var hasDefaultPort bool + if n := len(destinationServiceURL.Host); n > 0 && destinationServiceURL.Host[n-1] != ']' { + if i := strings.LastIndexByte(destinationServiceURL.Host, ':'); i != -1 { + // Remove the default port from destination.service.name. + destinationServiceURL.Host = destinationServiceURL.Host[:i] + hasDefaultPort = true + } + } + if !hasDefaultPort { + // Add the default port to destination.service.resource. + destinationServiceResource = fmt.Sprintf("%s:%d", destinationServiceResource, port) + } + } + c.SetDestinationService(DestinationServiceSpanContext{ + Name: destinationServiceURL.String(), + Resource: destinationServiceResource, + }) +} + +// SetHTTPStatusCode records the HTTP response status code. +func (c *SpanContext) SetHTTPStatusCode(statusCode int) { + c.http.StatusCode = statusCode + c.model.HTTP = &c.http +} + +// SetDestinationAddress sets the destination address and port in the context. +// +// SetDestinationAddress has no effect when called when an empty addr. +func (c *SpanContext) SetDestinationAddress(addr string, port int) { + if addr != "" { + c.destination.Address = truncateString(addr) + c.destination.Port = port + c.model.Destination = &c.destination + } +} + +// SetDestinationService sets the destination service info in the context. +func (c *SpanContext) SetDestinationService(service DestinationServiceSpanContext) { + c.destinationService.Name = truncateString(service.Name) + c.destinationService.Resource = truncateString(service.Resource) + c.destination.Service = &c.destinationService + c.model.Destination = &c.destination +} diff --git a/vendor/go.elastic.co/apm/stacktrace.go b/vendor/go.elastic.co/apm/stacktrace.go new file mode 100644 index 00000000000..1fe5cda6158 --- /dev/null +++ b/vendor/go.elastic.co/apm/stacktrace.go @@ -0,0 +1,52 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apm + +import ( + "path/filepath" + + "go.elastic.co/apm/model" + "go.elastic.co/apm/stacktrace" +) + +func appendModelStacktraceFrames(out []model.StacktraceFrame, in []stacktrace.Frame) []model.StacktraceFrame { + for _, f := range in { + out = append(out, modelStacktraceFrame(f)) + } + return out +} + +func modelStacktraceFrame(in stacktrace.Frame) model.StacktraceFrame { + var abspath string + file := in.File + if file != "" { + if filepath.IsAbs(file) { + abspath = file + } + file = filepath.Base(file) + } + packagePath, function := stacktrace.SplitFunctionName(in.Function) + return model.StacktraceFrame{ + AbsolutePath: abspath, + File: file, + Line: in.Line, + Function: function, + Module: packagePath, + LibraryFrame: stacktrace.IsLibraryPackage(packagePath), + } +} diff --git a/vendor/go.elastic.co/apm/stacktrace/context.go b/vendor/go.elastic.co/apm/stacktrace/context.go new file mode 100644 index 00000000000..b9d292432d7 --- /dev/null +++ b/vendor/go.elastic.co/apm/stacktrace/context.go @@ -0,0 +1,100 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package stacktrace + +import ( + "bufio" + "net/http" + "os" + + "go.elastic.co/apm/model" +) + +// SetContext sets the source context for the given stack frames, +// with the specified number of pre- and post- lines. +func SetContext(setter ContextSetter, frames []model.StacktraceFrame, pre, post int) error { + for i := 0; i < len(frames); i++ { + if err := setter.SetContext(&frames[i], pre, post); err != nil { + return err + } + } + return nil +} + +// ContextSetter is an interface that can be used for setting the source +// context for a stack frame. +type ContextSetter interface { + // SetContext sets the source context for the given stack frame, + // with the specified number of pre- and post- lines. + SetContext(frame *model.StacktraceFrame, pre, post int) error +} + +// FileSystemContextSetter returns a ContextSetter that sets context +// by reading file contents from the provided http.FileSystem. +func FileSystemContextSetter(fs http.FileSystem) ContextSetter { + if fs == nil { + panic("fs is nil") + } + return &fileSystemContextSetter{fs} +} + +type fileSystemContextSetter struct { + http.FileSystem +} + +func (s *fileSystemContextSetter) SetContext(frame *model.StacktraceFrame, pre, post int) error { + if frame.Line <= 0 { + return nil + } + f, err := s.Open(frame.AbsolutePath) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + defer f.Close() + + var lineno int + var line string + preLines := make([]string, 0, pre) + postLines := make([]string, 0, post) + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + lineno++ + if lineno > frame.Line+post { + break + } + switch { + case lineno == frame.Line: + line = scanner.Text() + case lineno < frame.Line && lineno >= frame.Line-pre: + preLines = append(preLines, scanner.Text()) + case lineno > frame.Line && lineno <= frame.Line+post: + postLines = append(postLines, scanner.Text()) + } + } + if err := scanner.Err(); err != nil { + return err + } + frame.ContextLine = line + frame.PreContext = preLines + frame.PostContext = postLines + return nil +} diff --git a/vendor/go.elastic.co/apm/stacktrace/doc.go b/vendor/go.elastic.co/apm/stacktrace/doc.go new file mode 100644 index 00000000000..f8cffa455d8 --- /dev/null +++ b/vendor/go.elastic.co/apm/stacktrace/doc.go @@ -0,0 +1,20 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Package stacktrace provides a simplified stack frame type, +// functions for obtaining stack frames, and related utilities. +package stacktrace diff --git a/vendor/go.elastic.co/apm/stacktrace/frame.go b/vendor/go.elastic.co/apm/stacktrace/frame.go new file mode 100644 index 00000000000..1c5053a2513 --- /dev/null +++ b/vendor/go.elastic.co/apm/stacktrace/frame.go @@ -0,0 +1,34 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package stacktrace + +// Frame describes a stack frame. +type Frame struct { + // File is the filename of the location of the stack frame. + // This may be either the absolute or base name of the file. + File string + + // Line is the 1-based line number of the location of the + // stack frame, or zero if unknown. + Line int + + // Function is the name of the function name for this stack + // frame. This should be package-qualified, and may be split + // using stacktrace.SplitFunctionName. + Function string +} diff --git a/vendor/go.elastic.co/apm/stacktrace/generate_library.bash b/vendor/go.elastic.co/apm/stacktrace/generate_library.bash new file mode 100644 index 00000000000..06bff3cb6ce --- /dev/null +++ b/vendor/go.elastic.co/apm/stacktrace/generate_library.bash @@ -0,0 +1,77 @@ +#!/bin/bash + +set -e + +_PKGS=$(go list -f '{{printf "\t%q,\n" .ImportPath}}' "$@" | grep -v vendor/golang_org) + +cat > library.go < 0 && n <= 10 { + pc = make([]uintptr, n) + pc = pc[:runtime.Callers(skip+1, pc)] + } else { + // n is negative or > 10, allocate space for 10 + // and make repeated calls to runtime.Callers + // until we've got all the frames or reached n. + pc = make([]uintptr, 10) + m := 0 + for { + m += runtime.Callers(skip+m+1, pc[m:]) + if m < len(pc) || m == n { + pc = pc[:m] + break + } + // Extend pc's length, ensuring its length + // extends to its new capacity to minimise + // the number of calls to runtime.Callers. + pc = append(pc, 0) + for len(pc) < cap(pc) { + pc = append(pc, 0) + } + } + } + return AppendCallerFrames(frames, pc, n) +} + +// AppendCallerFrames appends to n frames for the PCs in callers, +// and returns the extended slice. If n is negative, all available +// frames will be added. Multiple frames may exist for the same +// caller/PC in the case of function call inlining. +// +// See RuntimeFrame for information on what details are included. +func AppendCallerFrames(frames []Frame, callers []uintptr, n int) []Frame { + if len(callers) == 0 { + return frames + } + runtimeFrames := runtime.CallersFrames(callers) + for i := 0; n < 0 || i < n; i++ { + runtimeFrame, more := runtimeFrames.Next() + frames = append(frames, RuntimeFrame(runtimeFrame)) + if !more { + break + } + } + return frames +} + +// RuntimeFrame returns a Frame based on the given runtime.Frame. +// +// The resulting Frame will have the file path, package-qualified +// function name, and line number set. The function name can be +// split using SplitFunctionName, and the absolute path of the +// file and its base name can be determined using standard filepath +// functions. +func RuntimeFrame(in runtime.Frame) Frame { + return Frame{ + File: in.File, + Function: in.Function, + Line: in.Line, + } +} + +// SplitFunctionName splits the function name as formatted in +// runtime.Frame.Function, and returns the package path and +// function name components. +func SplitFunctionName(in string) (packagePath, function string) { + function = in + if function == "" { + return "", "" + } + // The last part of a package path will always have "." + // encoded as "%2e", so we can pick off the package path + // by finding the last part of the package path, and then + // the proceeding ".". + // + // Unexported method names may contain the package path. + // In these cases, the method receiver will be enclosed + // in parentheses, so we can treat that as the start of + // the function name. + sep := strings.Index(function, ".(") + if sep >= 0 { + packagePath = unescape(function[:sep]) + function = function[sep+1:] + } else { + offset := 0 + if sep := strings.LastIndex(function, "/"); sep >= 0 { + offset = sep + } + if sep := strings.IndexRune(function[offset+1:], '.'); sep >= 0 { + packagePath = unescape(function[:offset+1+sep]) + function = function[offset+1+sep+1:] + } + } + return packagePath, function +} + +func unescape(s string) string { + var n int + for i := 0; i < len(s); i++ { + if s[i] == '%' { + n++ + } + } + if n == 0 { + return s + } + bytes := make([]byte, 0, len(s)-2*n) + for i := 0; i < len(s); i++ { + b := s[i] + if b == '%' && i+2 < len(s) { + b = fromhex(s[i+1])<<4 | fromhex(s[i+2]) + i += 2 + } + bytes = append(bytes, b) + } + return string(bytes) +} + +func fromhex(b byte) byte { + if b >= 'a' { + return 10 + b - 'a' + } + return b - '0' +} diff --git a/vendor/go.elastic.co/apm/tracecontext.go b/vendor/go.elastic.co/apm/tracecontext.go new file mode 100644 index 00000000000..2983e85d6da --- /dev/null +++ b/vendor/go.elastic.co/apm/tracecontext.go @@ -0,0 +1,263 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apm + +import ( + "bytes" + "encoding/hex" + "fmt" + "regexp" + "unicode" + + "github.com/pkg/errors" +) + +var ( + errZeroTraceID = errors.New("zero trace-id is invalid") + errZeroSpanID = errors.New("zero span-id is invalid") +) + +// tracestateKeyRegexp holds a regular expression used for validating +// tracestate keys according to the standard rules: +// +// key = lcalpha 0*255( lcalpha / DIGIT / "_" / "-"/ "*" / "/" ) +// key = ( lcalpha / DIGIT ) 0*240( lcalpha / DIGIT / "_" / "-"/ "*" / "/" ) "@" lcalpha 0*13( lcalpha / DIGIT / "_" / "-"/ "*" / "/" ) +// lcalpha = %x61-7A ; a-z +// +// nblkchr is used for defining valid runes for tracestate values. +var ( + tracestateKeyRegexp = regexp.MustCompile(`^[a-z](([a-z0-9_*/-]{0,255})|([a-z0-9_*/-]{0,240}@[a-z][a-z0-9_*/-]{0,13}))$`) + + nblkchr = &unicode.RangeTable{ + R16: []unicode.Range16{ + {0x21, 0x2B, 1}, + {0x2D, 0x3C, 1}, + {0x3E, 0x7E, 1}, + }, + LatinOffset: 3, + } +) + +const ( + traceOptionsRecordedFlag = 0x01 +) + +// TraceContext holds trace context for an incoming or outgoing request. +type TraceContext struct { + // Trace identifies the trace forest. + Trace TraceID + + // Span identifies a span: the parent span if this context + // corresponds to an incoming request, or the current span + // if this is an outgoing request. + Span SpanID + + // Options holds the trace options propagated by the parent. + Options TraceOptions + + // State holds the trace state. + State TraceState +} + +// TraceID identifies a trace forest. +type TraceID [16]byte + +// Validate validates the trace ID. +// This will return non-nil for a zero trace ID. +func (id TraceID) Validate() error { + if id.isZero() { + return errZeroTraceID + } + return nil +} + +func (id TraceID) isZero() bool { + return id == (TraceID{}) +} + +// String returns id encoded as hex. +func (id TraceID) String() string { + text, _ := id.MarshalText() + return string(text) +} + +// MarshalText returns id encoded as hex, satisfying encoding.TextMarshaler. +func (id TraceID) MarshalText() ([]byte, error) { + text := make([]byte, hex.EncodedLen(len(id))) + hex.Encode(text, id[:]) + return text, nil +} + +// SpanID identifies a span within a trace. +type SpanID [8]byte + +// Validate validates the span ID. +// This will return non-nil for a zero span ID. +func (id SpanID) Validate() error { + if id.isZero() { + return errZeroSpanID + } + return nil +} + +func (id SpanID) isZero() bool { + return id == SpanID{} +} + +// String returns id encoded as hex. +func (id SpanID) String() string { + text, _ := id.MarshalText() + return string(text) +} + +// MarshalText returns id encoded as hex, satisfying encoding.TextMarshaler. +func (id SpanID) MarshalText() ([]byte, error) { + text := make([]byte, hex.EncodedLen(len(id))) + hex.Encode(text, id[:]) + return text, nil +} + +// TraceOptions describes the options for a trace. +type TraceOptions uint8 + +// Recorded reports whether or not the transaction/span may have been (or may be) recorded. +func (o TraceOptions) Recorded() bool { + return (o & traceOptionsRecordedFlag) == traceOptionsRecordedFlag +} + +// WithRecorded changes the "recorded" flag, and returns the new options +// without modifying the original value. +func (o TraceOptions) WithRecorded(recorded bool) TraceOptions { + if recorded { + return o | traceOptionsRecordedFlag + } + return o & (0xFF ^ traceOptionsRecordedFlag) +} + +// TraceState holds vendor-specific state for a trace. +type TraceState struct { + head *TraceStateEntry +} + +// NewTraceState returns a TraceState based on entries. +func NewTraceState(entries ...TraceStateEntry) TraceState { + out := TraceState{} + var last *TraceStateEntry + for _, e := range entries { + e := e // copy + if last == nil { + out.head = &e + } else { + last.next = &e + } + last = &e + } + return out +} + +// String returns s as a comma-separated list of key-value pairs. +func (s TraceState) String() string { + if s.head == nil { + return "" + } + var buf bytes.Buffer + s.head.writeBuf(&buf) + for e := s.head.next; e != nil; e = e.next { + buf.WriteByte(',') + e.writeBuf(&buf) + } + return buf.String() +} + +// Validate validates the trace state. +// +// This will return non-nil if any entries are invalid, +// if there are too many entries, or if an entry key is +// repeated. +func (s TraceState) Validate() error { + if s.head == nil { + return nil + } + recorded := make(map[string]int) + var i int + for e := s.head; e != nil; e = e.next { + if i == 32 { + return errors.New("tracestate contains more than the maximum allowed number of entries, 32") + } + if err := e.Validate(); err != nil { + return errors.Wrapf(err, "invalid tracestate entry at position %d", i) + } + if prev, ok := recorded[e.Key]; ok { + return fmt.Errorf("duplicate tracestate key %q at positions %d and %d", e.Key, prev, i) + } + recorded[e.Key] = i + i++ + } + return nil +} + +// TraceStateEntry holds a trace state entry: a key/value pair +// representing state for a vendor. +type TraceStateEntry struct { + next *TraceStateEntry + + // Key holds a vendor (and optionally, tenant) ID. + Key string + + // Value holds a string representing trace state. + Value string +} + +func (e *TraceStateEntry) writeBuf(buf *bytes.Buffer) { + buf.WriteString(e.Key) + buf.WriteByte('=') + buf.WriteString(e.Value) +} + +// Validate validates the trace state entry. +// +// This will return non-nil if either the key or value is invalid. +func (e *TraceStateEntry) Validate() error { + if !tracestateKeyRegexp.MatchString(e.Key) { + return fmt.Errorf("invalid key %q", e.Key) + } + if err := e.validateValue(); err != nil { + return errors.Wrapf(err, "invalid value for key %q", e.Key) + } + return nil +} + +func (e *TraceStateEntry) validateValue() error { + if e.Value == "" { + return errors.New("value is empty") + } + runes := []rune(e.Value) + n := len(runes) + if n > 256 { + return errors.Errorf("value contains %d characters, maximum allowed is 256", n) + } + if !unicode.In(runes[n-1], nblkchr) { + return errors.Errorf("value contains invalid character %q", runes[n-1]) + } + for _, r := range runes[:n-1] { + if r != 0x20 && !unicode.In(r, nblkchr) { + return errors.Errorf("value contains invalid character %q", r) + } + } + return nil +} diff --git a/vendor/go.elastic.co/apm/tracer.go b/vendor/go.elastic.co/apm/tracer.go new file mode 100644 index 00000000000..3170e2f226b --- /dev/null +++ b/vendor/go.elastic.co/apm/tracer.go @@ -0,0 +1,1170 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apm + +import ( + "bytes" + "compress/zlib" + "context" + "io" + "log" + "math/rand" + "sync" + "sync/atomic" + "time" + + "go.elastic.co/apm/apmconfig" + "go.elastic.co/apm/internal/apmlog" + "go.elastic.co/apm/internal/configutil" + "go.elastic.co/apm/internal/iochan" + "go.elastic.co/apm/internal/ringbuffer" + "go.elastic.co/apm/internal/wildcard" + "go.elastic.co/apm/model" + "go.elastic.co/apm/stacktrace" + "go.elastic.co/apm/transport" + "go.elastic.co/fastjson" +) + +const ( + defaultPreContext = 3 + defaultPostContext = 3 + gracePeriodJitter = 0.1 // +/- 10% + tracerEventChannelCap = 1000 +) + +var ( + // DefaultTracer is the default global Tracer, set at package + // initialization time, configured via environment variables. + // + // This will always be initialized to a non-nil value. If any + // of the environment variables are invalid, the corresponding + // errors will be logged to stderr and the default values will + // be used instead. + DefaultTracer *Tracer +) + +func init() { + var opts TracerOptions + opts.initDefaults(true) + DefaultTracer = newTracer(opts) +} + +// TracerOptions holds initial tracer options, for passing to NewTracerOptions. +type TracerOptions struct { + // ServiceName holds the service name. + // + // If ServiceName is empty, the service name will be defined using the + // ELASTIC_APM_SERVICE_NAME environment variable, or if that is not set, + // the executable name. + ServiceName string + + // ServiceVersion holds the service version. + // + // If ServiceVersion is empty, the service version will be defined using + // the ELASTIC_APM_SERVICE_VERSION environment variable. + ServiceVersion string + + // ServiceEnvironment holds the service environment. + // + // If ServiceEnvironment is empty, the service environment will be defined + // using the ELASTIC_APM_ENVIRONMENT environment variable. + ServiceEnvironment string + + // Transport holds the transport to use for sending events. + // + // If Transport is nil, transport.Default will be used. + // + // If Transport implements apmconfig.Watcher, the tracer will begin watching + // for remote changes immediately. This behaviour can be disabled by setting + // the environment variable ELASTIC_APM_CENTRAL_CONFIG=false. + Transport transport.Transport + + requestDuration time.Duration + metricsInterval time.Duration + maxSpans int + requestSize int + bufferSize int + metricsBufferSize int + sampler Sampler + sanitizedFieldNames wildcard.Matchers + disabledMetrics wildcard.Matchers + captureHeaders bool + captureBody CaptureBodyMode + spanFramesMinDuration time.Duration + stackTraceLimit int + active bool + configWatcher apmconfig.Watcher + breakdownMetrics bool + propagateLegacyHeader bool + profileSender profileSender + cpuProfileInterval time.Duration + cpuProfileDuration time.Duration + heapProfileInterval time.Duration +} + +// initDefaults updates opts with default values. +func (opts *TracerOptions) initDefaults(continueOnError bool) error { + var errs []error + failed := func(err error) bool { + if err == nil { + return false + } + errs = append(errs, err) + return true + } + + requestDuration, err := initialRequestDuration() + if failed(err) { + requestDuration = defaultAPIRequestTime + } + + metricsInterval, err := initialMetricsInterval() + if err != nil { + metricsInterval = defaultMetricsInterval + errs = append(errs, err) + } + + requestSize, err := initialAPIRequestSize() + if err != nil { + requestSize = int(defaultAPIRequestSize) + errs = append(errs, err) + } + + bufferSize, err := initialAPIBufferSize() + if err != nil { + bufferSize = int(defaultAPIBufferSize) + errs = append(errs, err) + } + + metricsBufferSize, err := initialMetricsBufferSize() + if err != nil { + metricsBufferSize = int(defaultMetricsBufferSize) + errs = append(errs, err) + } + + maxSpans, err := initialMaxSpans() + if failed(err) { + maxSpans = defaultMaxSpans + } + + sampler, err := initialSampler() + if failed(err) { + sampler = nil + } + + captureHeaders, err := initialCaptureHeaders() + if failed(err) { + captureHeaders = defaultCaptureHeaders + } + + captureBody, err := initialCaptureBody() + if failed(err) { + captureBody = CaptureBodyOff + } + + spanFramesMinDuration, err := initialSpanFramesMinDuration() + if failed(err) { + spanFramesMinDuration = defaultSpanFramesMinDuration + } + + stackTraceLimit, err := initialStackTraceLimit() + if failed(err) { + stackTraceLimit = defaultStackTraceLimit + } + + active, err := initialActive() + if failed(err) { + active = true + } + + centralConfigEnabled, err := initialCentralConfigEnabled() + if failed(err) { + centralConfigEnabled = true + } + + breakdownMetricsEnabled, err := initialBreakdownMetricsEnabled() + if failed(err) { + breakdownMetricsEnabled = true + } + + propagateLegacyHeader, err := initialUseElasticTraceparentHeader() + if failed(err) { + propagateLegacyHeader = true + } + + cpuProfileInterval, cpuProfileDuration, err := initialCPUProfileIntervalDuration() + if failed(err) { + cpuProfileInterval = 0 + cpuProfileDuration = 0 + } + heapProfileInterval, err := initialHeapProfileInterval() + if failed(err) { + heapProfileInterval = 0 + } + + if opts.ServiceName != "" { + err := validateServiceName(opts.ServiceName) + if failed(err) { + opts.ServiceName = "" + } + } + + if len(errs) != 0 && !continueOnError { + return errs[0] + } + for _, err := range errs { + log.Printf("[apm]: %s", err) + } + + opts.requestDuration = requestDuration + opts.metricsInterval = metricsInterval + opts.requestSize = requestSize + opts.bufferSize = bufferSize + opts.metricsBufferSize = metricsBufferSize + opts.maxSpans = maxSpans + opts.sampler = sampler + opts.sanitizedFieldNames = initialSanitizedFieldNames() + opts.disabledMetrics = initialDisabledMetrics() + opts.breakdownMetrics = breakdownMetricsEnabled + opts.captureHeaders = captureHeaders + opts.captureBody = captureBody + opts.spanFramesMinDuration = spanFramesMinDuration + opts.stackTraceLimit = stackTraceLimit + opts.active = active + opts.propagateLegacyHeader = propagateLegacyHeader + if opts.Transport == nil { + opts.Transport = transport.Default + } + if centralConfigEnabled { + if cw, ok := opts.Transport.(apmconfig.Watcher); ok { + opts.configWatcher = cw + } + } + if ps, ok := opts.Transport.(profileSender); ok { + opts.profileSender = ps + opts.cpuProfileInterval = cpuProfileInterval + opts.cpuProfileDuration = cpuProfileDuration + opts.heapProfileInterval = heapProfileInterval + } + + serviceName, serviceVersion, serviceEnvironment := initialService() + if opts.ServiceName == "" { + opts.ServiceName = serviceName + } + if opts.ServiceVersion == "" { + opts.ServiceVersion = serviceVersion + } + if opts.ServiceEnvironment == "" { + opts.ServiceEnvironment = serviceEnvironment + } + return nil +} + +// Tracer manages the sampling and sending of transactions to +// Elastic APM. +// +// Transactions are buffered until they are flushed (forcibly +// with a Flush call, or when the flush timer expires), or when +// the maximum transaction queue size is reached. Failure to +// send will be periodically retried. Once the queue limit has +// been reached, new transactions will replace older ones in +// the queue. +// +// Errors are sent as soon as possible, but will buffered and +// later sent in bulk if the tracer is busy, or otherwise cannot +// send to the server, e.g. due to network failure. There is +// a limit to the number of errors that will be buffered, and +// once that limit has been reached, new errors will be dropped +// until the queue is drained. +// +// The exported fields be altered or replaced any time up until +// any Tracer methods have been invoked. +type Tracer struct { + Transport transport.Transport + Service struct { + Name string + Version string + Environment string + } + + process *model.Process + system *model.System + + active int32 + bufferSize int + metricsBufferSize int + closing chan struct{} + closed chan struct{} + forceFlush chan chan<- struct{} + forceSendMetrics chan chan<- struct{} + configCommands chan tracerConfigCommand + configWatcher chan apmconfig.Watcher + events chan tracerEvent + breakdownMetrics *breakdownMetrics + profileSender profileSender + + statsMu sync.Mutex + stats TracerStats + + // instrumentationConfig_ must only be accessed and mutated + // using Tracer.instrumentationConfig() and Tracer.setInstrumentationConfig(). + instrumentationConfigInternal *instrumentationConfig + + errorDataPool sync.Pool + spanDataPool sync.Pool + transactionDataPool sync.Pool +} + +// NewTracer returns a new Tracer, using the default transport, +// and with the specified service name and version if specified. +// This is equivalent to calling NewTracerOptions with a +// TracerOptions having ServiceName and ServiceVersion set to +// the provided arguments. +func NewTracer(serviceName, serviceVersion string) (*Tracer, error) { + return NewTracerOptions(TracerOptions{ + ServiceName: serviceName, + ServiceVersion: serviceVersion, + }) +} + +// NewTracerOptions returns a new Tracer using the provided options. +// See TracerOptions for details on the options, and their default +// values. +func NewTracerOptions(opts TracerOptions) (*Tracer, error) { + if err := opts.initDefaults(false); err != nil { + return nil, err + } + return newTracer(opts), nil +} + +func newTracer(opts TracerOptions) *Tracer { + t := &Tracer{ + Transport: opts.Transport, + process: ¤tProcess, + system: &localSystem, + closing: make(chan struct{}), + closed: make(chan struct{}), + forceFlush: make(chan chan<- struct{}), + forceSendMetrics: make(chan chan<- struct{}), + configCommands: make(chan tracerConfigCommand), + configWatcher: make(chan apmconfig.Watcher), + events: make(chan tracerEvent, tracerEventChannelCap), + active: 1, + breakdownMetrics: newBreakdownMetrics(), + bufferSize: opts.bufferSize, + metricsBufferSize: opts.metricsBufferSize, + profileSender: opts.profileSender, + instrumentationConfigInternal: &instrumentationConfig{ + local: make(map[string]func(*instrumentationConfigValues)), + }, + } + t.Service.Name = opts.ServiceName + t.Service.Version = opts.ServiceVersion + t.Service.Environment = opts.ServiceEnvironment + t.breakdownMetrics.enabled = opts.breakdownMetrics + + // Initialise local transaction config. + t.setLocalInstrumentationConfig(envCaptureBody, func(cfg *instrumentationConfigValues) { + cfg.captureBody = opts.captureBody + }) + t.setLocalInstrumentationConfig(envCaptureHeaders, func(cfg *instrumentationConfigValues) { + cfg.captureHeaders = opts.captureHeaders + }) + t.setLocalInstrumentationConfig(envMaxSpans, func(cfg *instrumentationConfigValues) { + cfg.maxSpans = opts.maxSpans + }) + t.setLocalInstrumentationConfig(envTransactionSampleRate, func(cfg *instrumentationConfigValues) { + cfg.sampler = opts.sampler + }) + t.setLocalInstrumentationConfig(envSpanFramesMinDuration, func(cfg *instrumentationConfigValues) { + cfg.spanFramesMinDuration = opts.spanFramesMinDuration + }) + t.setLocalInstrumentationConfig(envStackTraceLimit, func(cfg *instrumentationConfigValues) { + cfg.stackTraceLimit = opts.stackTraceLimit + }) + t.setLocalInstrumentationConfig(envUseElasticTraceparentHeader, func(cfg *instrumentationConfigValues) { + cfg.propagateLegacyHeader = opts.propagateLegacyHeader + }) + + if !opts.active { + t.active = 0 + close(t.closed) + return t + } + + go t.loop() + t.configCommands <- func(cfg *tracerConfig) { + cfg.cpuProfileInterval = opts.cpuProfileInterval + cfg.cpuProfileDuration = opts.cpuProfileDuration + cfg.heapProfileInterval = opts.heapProfileInterval + cfg.metricsInterval = opts.metricsInterval + cfg.requestDuration = opts.requestDuration + cfg.requestSize = opts.requestSize + cfg.sanitizedFieldNames = opts.sanitizedFieldNames + cfg.disabledMetrics = opts.disabledMetrics + cfg.preContext = defaultPreContext + cfg.postContext = defaultPostContext + cfg.metricsGatherers = []MetricsGatherer{newBuiltinMetricsGatherer(t)} + if apmlog.DefaultLogger != nil { + cfg.logger = apmlog.DefaultLogger + } + } + if opts.configWatcher != nil { + t.configWatcher <- opts.configWatcher + } + return t +} + +// tracerConfig holds the tracer's runtime configuration, which may be modified +// by sending a tracerConfigCommand to the tracer's configCommands channel. +type tracerConfig struct { + requestSize int + requestDuration time.Duration + metricsInterval time.Duration + logger WarningLogger + metricsGatherers []MetricsGatherer + contextSetter stacktrace.ContextSetter + preContext, postContext int + sanitizedFieldNames wildcard.Matchers + disabledMetrics wildcard.Matchers + cpuProfileDuration time.Duration + cpuProfileInterval time.Duration + heapProfileInterval time.Duration +} + +type tracerConfigCommand func(*tracerConfig) + +// Close closes the Tracer, preventing transactions from being +// sent to the APM server. +func (t *Tracer) Close() { + select { + case <-t.closing: + default: + close(t.closing) + } + <-t.closed +} + +// Flush waits for the Tracer to flush any transactions and errors it currently +// has queued to the APM server, the tracer is stopped, or the abort channel +// is signaled. +func (t *Tracer) Flush(abort <-chan struct{}) { + flushed := make(chan struct{}, 1) + select { + case t.forceFlush <- flushed: + select { + case <-abort: + case <-flushed: + case <-t.closed: + } + case <-t.closed: + } +} + +// Active reports whether the tracer is active. If the tracer is inactive, +// no transactions or errors will be sent to the Elastic APM server. +func (t *Tracer) Active() bool { + return atomic.LoadInt32(&t.active) == 1 +} + +// SetRequestDuration sets the maximum amount of time to keep a request open +// to the APM server for streaming data before closing the stream and starting +// a new request. +func (t *Tracer) SetRequestDuration(d time.Duration) { + t.sendConfigCommand(func(cfg *tracerConfig) { + cfg.requestDuration = d + }) +} + +// SetMetricsInterval sets the metrics interval -- the amount of time in +// between metrics samples being gathered. +func (t *Tracer) SetMetricsInterval(d time.Duration) { + t.sendConfigCommand(func(cfg *tracerConfig) { + cfg.metricsInterval = d + }) +} + +// SetContextSetter sets the stacktrace.ContextSetter to be used for +// setting stacktrace source context. If nil (which is the initial +// value), no context will be set. +func (t *Tracer) SetContextSetter(setter stacktrace.ContextSetter) { + t.sendConfigCommand(func(cfg *tracerConfig) { + cfg.contextSetter = setter + }) +} + +// SetLogger sets the Logger to be used for logging the operation of +// the tracer. +// +// If logger implements WarningLogger, its Warningf method will be used +// for logging warnings. Otherwise, warnings will logged using Debugf. +// +// The tracer is initialized with a default logger configured with the +// environment variables ELASTIC_APM_LOG_FILE and ELASTIC_APM_LOG_LEVEL. +// Calling SetLogger will replace the default logger. +func (t *Tracer) SetLogger(logger Logger) { + t.sendConfigCommand(func(cfg *tracerConfig) { + cfg.logger = makeWarningLogger(logger) + }) +} + +// SetSanitizedFieldNames sets the wildcard patterns that will be used to +// match cookie and form field names for sanitization. Fields matching any +// of the the supplied patterns will have their values redacted. If +// SetSanitizedFieldNames is called with no arguments, then no fields +// will be redacted. +func (t *Tracer) SetSanitizedFieldNames(patterns ...string) error { + var matchers wildcard.Matchers + if len(patterns) != 0 { + matchers = make(wildcard.Matchers, len(patterns)) + for i, p := range patterns { + matchers[i] = configutil.ParseWildcardPattern(p) + } + } + t.sendConfigCommand(func(cfg *tracerConfig) { + cfg.sanitizedFieldNames = matchers + }) + return nil +} + +// RegisterMetricsGatherer registers g for periodic (or forced) metrics +// gathering by t. +// +// RegisterMetricsGatherer returns a function which will deregister g. +// It may safely be called multiple times. +func (t *Tracer) RegisterMetricsGatherer(g MetricsGatherer) func() { + // Wrap g in a pointer-to-struct, so we can safely compare. + wrapped := &struct{ MetricsGatherer }{MetricsGatherer: g} + t.sendConfigCommand(func(cfg *tracerConfig) { + cfg.metricsGatherers = append(cfg.metricsGatherers, wrapped) + }) + deregister := func(cfg *tracerConfig) { + for i, g := range cfg.metricsGatherers { + if g != wrapped { + continue + } + cfg.metricsGatherers = append(cfg.metricsGatherers[:i], cfg.metricsGatherers[i+1:]...) + } + } + var once sync.Once + return func() { + once.Do(func() { + t.sendConfigCommand(deregister) + }) + } +} + +// SetConfigWatcher sets w as the config watcher. +// +// By default, the tracer will be configured to use the transport for +// watching config, if the transport implements apmconfig.Watcher. This +// can be overridden by calling SetConfigWatcher. +// +// If w is nil, config watching will be stopped. +// +// Calling SetConfigWatcher will discard any previously observed remote +// config, reverting to local config until a config change from w is +// observed. +func (t *Tracer) SetConfigWatcher(w apmconfig.Watcher) { + select { + case t.configWatcher <- w: + case <-t.closing: + case <-t.closed: + } +} + +func (t *Tracer) sendConfigCommand(cmd tracerConfigCommand) { + select { + case t.configCommands <- cmd: + case <-t.closing: + case <-t.closed: + } +} + +// SetSampler sets the sampler the tracer. +// +// It is valid to pass nil, in which case all transactions will be sampled. +// +// Configuration via Kibana takes precedence over local configuration, so +// if sampling has been configured via Kibana, this call will not have any +// effect until/unless that configuration has been removed. +func (t *Tracer) SetSampler(s Sampler) { + t.setLocalInstrumentationConfig(envTransactionSampleRate, func(cfg *instrumentationConfigValues) { + cfg.sampler = s + }) +} + +// SetMaxSpans sets the maximum number of spans that will be added +// to a transaction before dropping spans. +// +// Passing in zero will disable all spans, while negative values will +// permit an unlimited number of spans. +func (t *Tracer) SetMaxSpans(n int) { + t.setLocalInstrumentationConfig(envMaxSpans, func(cfg *instrumentationConfigValues) { + cfg.maxSpans = n + }) +} + +// SetSpanFramesMinDuration sets the minimum duration for a span after which +// we will capture its stack frames. +func (t *Tracer) SetSpanFramesMinDuration(d time.Duration) { + t.setLocalInstrumentationConfig(envMaxSpans, func(cfg *instrumentationConfigValues) { + cfg.spanFramesMinDuration = d + }) +} + +// SetStackTraceLimit sets the the maximum number of stack frames to collect +// for each stack trace. If limit is negative, then all frames will be collected. +func (t *Tracer) SetStackTraceLimit(limit int) { + t.setLocalInstrumentationConfig(envMaxSpans, func(cfg *instrumentationConfigValues) { + cfg.stackTraceLimit = limit + }) +} + +// SetCaptureHeaders enables or disables capturing of HTTP headers. +func (t *Tracer) SetCaptureHeaders(capture bool) { + t.setLocalInstrumentationConfig(envMaxSpans, func(cfg *instrumentationConfigValues) { + cfg.captureHeaders = capture + }) +} + +// SetCaptureBody sets the HTTP request body capture mode. +func (t *Tracer) SetCaptureBody(mode CaptureBodyMode) { + t.setLocalInstrumentationConfig(envMaxSpans, func(cfg *instrumentationConfigValues) { + cfg.captureBody = mode + }) +} + +// SendMetrics forces the tracer to gather and send metrics immediately, +// blocking until the metrics have been sent or the abort channel is +// signalled. +func (t *Tracer) SendMetrics(abort <-chan struct{}) { + sent := make(chan struct{}, 1) + select { + case t.forceSendMetrics <- sent: + select { + case <-abort: + case <-sent: + case <-t.closed: + } + case <-t.closed: + } +} + +// Stats returns the current TracerStats. This will return the most +// recent values even after the tracer has been closed. +func (t *Tracer) Stats() TracerStats { + t.statsMu.Lock() + stats := t.stats + t.statsMu.Unlock() + return stats +} + +func (t *Tracer) loop() { + ctx, cancelContext := context.WithCancel(context.Background()) + defer cancelContext() + defer close(t.closed) + defer atomic.StoreInt32(&t.active, 0) + + var req iochan.ReadRequest + var requestBuf bytes.Buffer + var metadata []byte + var gracePeriod time.Duration = -1 + var flushed chan<- struct{} + var requestBufTransactions, requestBufSpans, requestBufErrors, requestBufMetricsets uint64 + zlibWriter, _ := zlib.NewWriterLevel(&requestBuf, zlib.BestSpeed) + zlibFlushed := true + zlibClosed := false + iochanReader := iochan.NewReader() + requestBytesRead := 0 + requestActive := false + closeRequest := false + flushRequest := false + requestResult := make(chan error, 1) + requestTimer := time.NewTimer(0) + requestTimerActive := false + if !requestTimer.Stop() { + <-requestTimer.C + } + + // Run another goroutine to perform the blocking requests, + // communicating with the tracer loop to obtain stream data. + sendStreamRequest := make(chan time.Duration) + defer close(sendStreamRequest) + go func() { + jitterRand := rand.New(rand.NewSource(time.Now().UnixNano())) + for gracePeriod := range sendStreamRequest { + if gracePeriod > 0 { + select { + case <-time.After(jitterDuration(gracePeriod, jitterRand, gracePeriodJitter)): + case <-ctx.Done(): + } + } + requestResult <- t.Transport.SendStream(ctx, iochanReader) + } + }() + + var breakdownMetricsLimitWarningLogged bool + var stats TracerStats + var metrics Metrics + var sentMetrics chan<- struct{} + var gatheringMetrics bool + var metricsTimerStart time.Time + metricsBuffer := ringbuffer.New(t.metricsBufferSize) + gatheredMetrics := make(chan struct{}, 1) + metricsTimer := time.NewTimer(0) + if !metricsTimer.Stop() { + <-metricsTimer.C + } + + var lastConfigChange map[string]string + var configChanges <-chan apmconfig.Change + var stopConfigWatcher func() + defer func() { + if stopConfigWatcher != nil { + stopConfigWatcher() + } + }() + + cpuProfilingState := newCPUProfilingState(t.profileSender) + heapProfilingState := newHeapProfilingState(t.profileSender) + + var cfg tracerConfig + buffer := ringbuffer.New(t.bufferSize) + buffer.Evicted = func(h ringbuffer.BlockHeader) { + switch h.Tag { + case errorBlockTag: + stats.ErrorsDropped++ + case spanBlockTag: + stats.SpansDropped++ + case transactionBlockTag: + stats.TransactionsDropped++ + } + } + modelWriter := modelWriter{ + buffer: buffer, + metricsBuffer: metricsBuffer, + cfg: &cfg, + stats: &stats, + } + + for { + var gatherMetrics bool + select { + case <-t.closing: + cancelContext() // informs transport that EOF is expected + iochanReader.CloseRead(io.EOF) + return + case cmd := <-t.configCommands: + oldMetricsInterval := cfg.metricsInterval + cmd(&cfg) + cpuProfilingState.updateConfig(cfg.cpuProfileInterval, cfg.cpuProfileDuration) + heapProfilingState.updateConfig(cfg.heapProfileInterval, 0) + if !gatheringMetrics && cfg.metricsInterval != oldMetricsInterval { + if metricsTimerStart.IsZero() { + if cfg.metricsInterval > 0 { + metricsTimer.Reset(cfg.metricsInterval) + metricsTimerStart = time.Now() + } + } else { + if cfg.metricsInterval <= 0 { + metricsTimerStart = time.Time{} + if !metricsTimer.Stop() { + <-metricsTimer.C + } + } else { + alreadyPassed := time.Since(metricsTimerStart) + if alreadyPassed >= cfg.metricsInterval { + metricsTimer.Reset(0) + } else { + metricsTimer.Reset(cfg.metricsInterval - alreadyPassed) + } + } + } + } + continue + case cw := <-t.configWatcher: + if configChanges != nil { + stopConfigWatcher() + t.updateRemoteConfig(cfg.logger, lastConfigChange, nil) + lastConfigChange = nil + configChanges = nil + } + if cw == nil { + continue + } + var configWatcherContext context.Context + var watchParams apmconfig.WatchParams + watchParams.Service.Name = t.Service.Name + watchParams.Service.Environment = t.Service.Environment + configWatcherContext, stopConfigWatcher = context.WithCancel(ctx) + configChanges = cw.WatchConfig(configWatcherContext, watchParams) + // Silence go vet's "possible context leak" false positive. + // We call a previous stopConfigWatcher before reassigning + // the variable, and we have a defer at the top level of the + // loop method that will call the final stopConfigWatcher + // value on method exit. + _ = stopConfigWatcher + continue + case change, ok := <-configChanges: + if !ok { + configChanges = nil + continue + } + if change.Err != nil { + if cfg.logger != nil { + cfg.logger.Errorf("config request failed: %s", change.Err) + } + } else { + t.updateRemoteConfig(cfg.logger, lastConfigChange, change.Attrs) + lastConfigChange = change.Attrs + } + continue + case event := <-t.events: + switch event.eventType { + case transactionEvent: + if !t.breakdownMetrics.recordTransaction(event.tx.TransactionData) { + if !breakdownMetricsLimitWarningLogged && cfg.logger != nil { + cfg.logger.Warningf("%s", breakdownMetricsLimitWarning) + breakdownMetricsLimitWarningLogged = true + } + } + modelWriter.writeTransaction(event.tx.Transaction, event.tx.TransactionData) + case spanEvent: + modelWriter.writeSpan(event.span.Span, event.span.SpanData) + case errorEvent: + modelWriter.writeError(event.err) + // Flush the buffer to transmit the error immediately. + flushRequest = true + } + case <-requestTimer.C: + requestTimerActive = false + closeRequest = true + case <-metricsTimer.C: + metricsTimerStart = time.Time{} + gatherMetrics = !gatheringMetrics + case sentMetrics = <-t.forceSendMetrics: + if !metricsTimerStart.IsZero() { + if !metricsTimer.Stop() { + <-metricsTimer.C + } + metricsTimerStart = time.Time{} + } + gatherMetrics = !gatheringMetrics + case <-gatheredMetrics: + modelWriter.writeMetrics(&metrics) + gatheringMetrics = false + flushRequest = true + if cfg.metricsInterval > 0 { + metricsTimerStart = time.Now() + metricsTimer.Reset(cfg.metricsInterval) + } + case <-cpuProfilingState.timer.C: + cpuProfilingState.start(ctx, cfg.logger, t.metadataReader()) + case <-cpuProfilingState.finished: + cpuProfilingState.resetTimer() + case <-heapProfilingState.timer.C: + heapProfilingState.start(ctx, cfg.logger, t.metadataReader()) + case <-heapProfilingState.finished: + heapProfilingState.resetTimer() + case flushed = <-t.forceFlush: + // Drain any objects buffered in the channels. + for n := len(t.events); n > 0; n-- { + event := <-t.events + switch event.eventType { + case transactionEvent: + if !t.breakdownMetrics.recordTransaction(event.tx.TransactionData) { + if !breakdownMetricsLimitWarningLogged && cfg.logger != nil { + cfg.logger.Warningf("%s", breakdownMetricsLimitWarning) + breakdownMetricsLimitWarningLogged = true + } + } + modelWriter.writeTransaction(event.tx.Transaction, event.tx.TransactionData) + case spanEvent: + modelWriter.writeSpan(event.span.Span, event.span.SpanData) + case errorEvent: + modelWriter.writeError(event.err) + } + } + if !requestActive && buffer.Len() == 0 && metricsBuffer.Len() == 0 { + flushed <- struct{}{} + continue + } + closeRequest = true + case req = <-iochanReader.C: + case err := <-requestResult: + if err != nil { + stats.Errors.SendStream++ + gracePeriod = nextGracePeriod(gracePeriod) + if cfg.logger != nil { + logf := cfg.logger.Debugf + if err, ok := err.(*transport.HTTPError); ok && err.Response.StatusCode == 404 { + // 404 typically means the server is too old, meaning + // the error is due to a misconfigured environment. + logf = cfg.logger.Errorf + } + logf("request failed: %s (next request in ~%s)", err, gracePeriod) + } + } else { + gracePeriod = -1 // Reset grace period after success. + stats.TransactionsSent += requestBufTransactions + stats.SpansSent += requestBufSpans + stats.ErrorsSent += requestBufErrors + if cfg.logger != nil { + s := func(n uint64) string { + if n != 1 { + return "s" + } + return "" + } + cfg.logger.Debugf( + "sent request with %d transaction%s, %d span%s, %d error%s, %d metricset%s", + requestBufTransactions, s(requestBufTransactions), + requestBufSpans, s(requestBufSpans), + requestBufErrors, s(requestBufErrors), + requestBufMetricsets, s(requestBufMetricsets), + ) + } + } + if !stats.isZero() { + t.statsMu.Lock() + t.stats.accumulate(stats) + t.statsMu.Unlock() + stats = TracerStats{} + } + if sentMetrics != nil && requestBufMetricsets > 0 { + sentMetrics <- struct{}{} + sentMetrics = nil + } + if flushed != nil { + flushed <- struct{}{} + flushed = nil + } + if req.Buf != nil { + // req will be canceled by CloseRead below. + req.Buf = nil + } + iochanReader.CloseRead(io.EOF) + iochanReader = iochan.NewReader() + flushRequest = false + closeRequest = false + requestActive = false + requestBytesRead = 0 + requestBuf.Reset() + requestBufTransactions = 0 + requestBufSpans = 0 + requestBufErrors = 0 + requestBufMetricsets = 0 + if requestTimerActive { + if !requestTimer.Stop() { + <-requestTimer.C + } + requestTimerActive = false + } + } + + if !stats.isZero() { + t.statsMu.Lock() + t.stats.accumulate(stats) + t.statsMu.Unlock() + stats = TracerStats{} + } + + if gatherMetrics { + gatheringMetrics = true + metrics.disabled = cfg.disabledMetrics + t.gatherMetrics(ctx, cfg.metricsGatherers, &metrics, cfg.logger, gatheredMetrics) + if cfg.logger != nil { + cfg.logger.Debugf("gathering metrics") + } + } + + if !requestActive { + if buffer.Len() == 0 && metricsBuffer.Len() == 0 { + continue + } + sendStreamRequest <- gracePeriod + if metadata == nil { + metadata = t.jsonRequestMetadata() + } + zlibWriter.Reset(&requestBuf) + zlibWriter.Write(metadata) + zlibFlushed = false + zlibClosed = false + requestActive = true + requestTimer.Reset(cfg.requestDuration) + requestTimerActive = true + } + + if !closeRequest || !zlibClosed { + for requestBytesRead+requestBuf.Len() < cfg.requestSize { + if metricsBuffer.Len() > 0 { + if _, _, err := metricsBuffer.WriteBlockTo(zlibWriter); err == nil { + requestBufMetricsets++ + zlibWriter.Write([]byte("\n")) + zlibFlushed = false + if sentMetrics != nil { + // SendMetrics was called: close the request + // off so we can inform the user when the + // metrics have been processed. + closeRequest = true + } + } + continue + } + if buffer.Len() == 0 { + break + } + if h, _, err := buffer.WriteBlockTo(zlibWriter); err == nil { + switch h.Tag { + case transactionBlockTag: + requestBufTransactions++ + case spanBlockTag: + requestBufSpans++ + case errorBlockTag: + requestBufErrors++ + } + zlibWriter.Write([]byte("\n")) + zlibFlushed = false + } + } + if !closeRequest { + closeRequest = requestBytesRead+requestBuf.Len() >= cfg.requestSize + } + } + if closeRequest { + if !zlibClosed { + zlibWriter.Close() + zlibClosed = true + } + } else if flushRequest && !zlibFlushed { + zlibWriter.Flush() + flushRequest = false + zlibFlushed = true + } + + if req.Buf == nil || requestBuf.Len() == 0 { + continue + } + const zlibHeaderLen = 2 + if requestBytesRead+requestBuf.Len() > zlibHeaderLen { + n, err := requestBuf.Read(req.Buf) + if closeRequest && err == nil && requestBuf.Len() == 0 { + err = io.EOF + } + req.Respond(n, err) + req.Buf = nil + if n > 0 { + requestBytesRead += n + } + } + } +} + +// jsonRequestMetadata returns a JSON-encoded metadata object that features +// at the head of every request body. This is called exactly once, when the +// first request is made. +func (t *Tracer) jsonRequestMetadata() []byte { + var json fastjson.Writer + json.RawString(`{"metadata":`) + t.encodeRequestMetadata(&json) + json.RawString("}\n") + return json.Bytes() +} + +// metadataReader returns an io.Reader that holds the JSON-encoded metadata, +// suitable for including in a profile request. +func (t *Tracer) metadataReader() io.Reader { + var metadata fastjson.Writer + t.encodeRequestMetadata(&metadata) + return bytes.NewReader(metadata.Bytes()) +} + +func (t *Tracer) encodeRequestMetadata(json *fastjson.Writer) { + service := makeService(t.Service.Name, t.Service.Version, t.Service.Environment) + json.RawString(`{"system":`) + t.system.MarshalFastJSON(json) + json.RawString(`,"process":`) + t.process.MarshalFastJSON(json) + json.RawString(`,"service":`) + service.MarshalFastJSON(json) + if len(globalLabels) > 0 { + json.RawString(`,"labels":`) + globalLabels.MarshalFastJSON(json) + } + json.RawByte('}') +} + +// gatherMetrics gathers metrics from each of the registered +// metrics gatherers. Once all gatherers have returned, a value +// will be sent on the "gathered" channel. +func (t *Tracer) gatherMetrics(ctx context.Context, gatherers []MetricsGatherer, m *Metrics, l Logger, gathered chan<- struct{}) { + timestamp := model.Time(time.Now().UTC()) + var group sync.WaitGroup + for _, g := range gatherers { + group.Add(1) + go func(g MetricsGatherer) { + defer group.Done() + gatherMetrics(ctx, g, m, l) + }(g) + } + go func() { + group.Wait() + for _, m := range m.transactionGroupMetrics { + m.Timestamp = timestamp + } + for _, m := range m.metrics { + m.Timestamp = timestamp + } + gathered <- struct{}{} + }() +} + +type tracerEventType int + +const ( + transactionEvent tracerEventType = iota + spanEvent + errorEvent +) + +type tracerEvent struct { + eventType tracerEventType + + // err is set only if eventType == errorEvent. + err *ErrorData + + // tx is set only if eventType == transactionEvent. + tx struct { + *Transaction + // Transaction.TransactionData is nil at the + // point tracerEvent is created (to signify + // that the transaction is ended), so we pass + // it along side. + *TransactionData + } + + // span is set only if eventType == spanEvent. + span struct { + *Span + // Span.SpanData is nil at the point tracerEvent + // is created (to signify that the span is ended), + // so we pass it along side. + *SpanData + } +} diff --git a/vendor/go.elastic.co/apm/tracer_stats.go b/vendor/go.elastic.co/apm/tracer_stats.go new file mode 100644 index 00000000000..6e1436e8598 --- /dev/null +++ b/vendor/go.elastic.co/apm/tracer_stats.go @@ -0,0 +1,52 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apm + +// TracerStats holds statistics for a Tracer. +type TracerStats struct { + Errors TracerStatsErrors + ErrorsSent uint64 + ErrorsDropped uint64 + TransactionsSent uint64 + TransactionsDropped uint64 + SpansSent uint64 + SpansDropped uint64 +} + +// TracerStatsErrors holds error statistics for a Tracer. +type TracerStatsErrors struct { + SetContext uint64 + SendStream uint64 +} + +func (s TracerStats) isZero() bool { + return s == TracerStats{} +} + +// accumulate updates the stats by accumulating them with +// the values in rhs. +func (s *TracerStats) accumulate(rhs TracerStats) { + s.Errors.SetContext += rhs.Errors.SetContext + s.Errors.SendStream += rhs.Errors.SendStream + s.ErrorsSent += rhs.ErrorsSent + s.ErrorsDropped += rhs.ErrorsDropped + s.SpansSent += rhs.SpansSent + s.SpansDropped += rhs.SpansDropped + s.TransactionsSent += rhs.TransactionsSent + s.TransactionsDropped += rhs.TransactionsDropped +} diff --git a/vendor/go.elastic.co/apm/transaction.go b/vendor/go.elastic.co/apm/transaction.go new file mode 100644 index 00000000000..1e2ce56e9d5 --- /dev/null +++ b/vendor/go.elastic.co/apm/transaction.go @@ -0,0 +1,324 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apm + +import ( + cryptorand "crypto/rand" + "encoding/binary" + "math/rand" + "sync" + "time" +) + +// StartTransaction returns a new Transaction with the specified +// name and type, and with the start time set to the current time. +// This is equivalent to calling StartTransactionOptions with a +// zero TransactionOptions. +func (t *Tracer) StartTransaction(name, transactionType string) *Transaction { + return t.StartTransactionOptions(name, transactionType, TransactionOptions{}) +} + +// StartTransactionOptions returns a new Transaction with the +// specified name, type, and options. +func (t *Tracer) StartTransactionOptions(name, transactionType string, opts TransactionOptions) *Transaction { + td, _ := t.transactionDataPool.Get().(*TransactionData) + if td == nil { + td = &TransactionData{ + Duration: -1, + Context: Context{ + captureBodyMask: CaptureBodyTransactions, + }, + spanTimings: make(spanTimingsMap), + } + var seed int64 + if err := binary.Read(cryptorand.Reader, binary.LittleEndian, &seed); err != nil { + seed = time.Now().UnixNano() + } + td.rand = rand.New(rand.NewSource(seed)) + } + tx := &Transaction{tracer: t, TransactionData: td} + + tx.Name = name + tx.Type = transactionType + + var root bool + if opts.TraceContext.Trace.Validate() == nil { + tx.traceContext.Trace = opts.TraceContext.Trace + tx.traceContext.Options = opts.TraceContext.Options + if opts.TraceContext.Span.Validate() == nil { + tx.parentSpan = opts.TraceContext.Span + } + if opts.TransactionID.Validate() == nil { + tx.traceContext.Span = opts.TransactionID + } else { + binary.LittleEndian.PutUint64(tx.traceContext.Span[:], tx.rand.Uint64()) + } + if opts.TraceContext.State.Validate() == nil { + tx.traceContext.State = opts.TraceContext.State + } + } else { + // Start a new trace. We reuse the trace ID for the root transaction's ID + // if one is not specified in the options. + root = true + binary.LittleEndian.PutUint64(tx.traceContext.Trace[:8], tx.rand.Uint64()) + binary.LittleEndian.PutUint64(tx.traceContext.Trace[8:], tx.rand.Uint64()) + if opts.TransactionID.Validate() == nil { + tx.traceContext.Span = opts.TransactionID + } else { + copy(tx.traceContext.Span[:], tx.traceContext.Trace[:]) + } + } + + // Take a snapshot of config that should apply to all spans within the + // transaction. + instrumentationConfig := t.instrumentationConfig() + tx.maxSpans = instrumentationConfig.maxSpans + tx.spanFramesMinDuration = instrumentationConfig.spanFramesMinDuration + tx.stackTraceLimit = instrumentationConfig.stackTraceLimit + tx.Context.captureHeaders = instrumentationConfig.captureHeaders + tx.breakdownMetricsEnabled = t.breakdownMetrics.enabled + tx.propagateLegacyHeader = instrumentationConfig.propagateLegacyHeader + + if root { + sampler := instrumentationConfig.sampler + if sampler == nil || sampler.Sample(tx.traceContext) { + o := tx.traceContext.Options.WithRecorded(true) + tx.traceContext.Options = o + } + } else { + // TODO(axw) make this behaviour configurable. In some cases + // it may not be a good idea to honour the recorded flag, as + // it may open up the application to DoS by forced sampling. + // Even ignoring bad actors, a service that has many feeder + // applications may end up being sampled at a very high rate. + tx.traceContext.Options = opts.TraceContext.Options + } + tx.timestamp = opts.Start + if tx.timestamp.IsZero() { + tx.timestamp = time.Now() + } + return tx +} + +// TransactionOptions holds options for Tracer.StartTransactionOptions. +type TransactionOptions struct { + // TraceContext holds the TraceContext for a new transaction. If this is + // zero, a new trace will be started. + TraceContext TraceContext + + // TransactionID holds the ID to assign to the transaction. If this is + // zero, a new ID will be generated and used instead. + TransactionID SpanID + + // Start is the start time of the transaction. If this has the + // zero value, time.Now() will be used instead. + Start time.Time +} + +// Transaction describes an event occurring in the monitored service. +type Transaction struct { + tracer *Tracer + traceContext TraceContext + + mu sync.RWMutex + + // TransactionData holds the transaction data. This field is set to + // nil when either of the transaction's End or Discard methods are called. + *TransactionData +} + +// Sampled reports whether or not the transaction is sampled. +func (tx *Transaction) Sampled() bool { + if tx == nil { + return false + } + return tx.traceContext.Options.Recorded() +} + +// TraceContext returns the transaction's TraceContext. +// +// The resulting TraceContext's Span field holds the transaction's ID. +// If tx is nil, a zero (invalid) TraceContext is returned. +func (tx *Transaction) TraceContext() TraceContext { + if tx == nil { + return TraceContext{} + } + return tx.traceContext +} + +// ShouldPropagateLegacyHeader reports whether instrumentation should +// propagate the legacy "Elastic-Apm-Traceparent" header in addition to +// the standard W3C "traceparent" header. +// +// This method will be removed in a future major version when we remove +// support for propagating the legacy header. +func (tx *Transaction) ShouldPropagateLegacyHeader() bool { + tx.mu.Lock() + defer tx.mu.Unlock() + if tx.ended() { + return false + } + return tx.propagateLegacyHeader +} + +// EnsureParent returns the span ID for for tx's parent, generating a +// parent span ID if one has not already been set and tx has not been +// ended. If tx is nil or has been ended, a zero (invalid) SpanID is +// returned. +// +// This method can be used for generating a span ID for the RUM +// (Real User Monitoring) agent, where the RUM agent is initialized +// after the backend service returns. +func (tx *Transaction) EnsureParent() SpanID { + if tx == nil { + return SpanID{} + } + + tx.mu.Lock() + defer tx.mu.Unlock() + if tx.ended() { + return SpanID{} + } + + tx.TransactionData.mu.Lock() + defer tx.TransactionData.mu.Unlock() + if tx.parentSpan.isZero() { + // parentSpan can only be zero if tx is a root transaction + // for which GenerateParentTraceContext() has not previously + // been called. Reuse the latter half of the trace ID for + // the parent span ID; the first half is used for the + // transaction ID. + copy(tx.parentSpan[:], tx.traceContext.Trace[8:]) + } + return tx.parentSpan +} + +// Discard discards a previously started transaction. +// +// Calling Discard will set tx's TransactionData field to nil, so callers must +// ensure tx is not updated after Discard returns. +func (tx *Transaction) Discard() { + tx.mu.Lock() + defer tx.mu.Unlock() + if tx.ended() { + return + } + tx.reset(tx.tracer) +} + +// End enqueues tx for sending to the Elastic APM server. +// +// Calling End will set tx's TransactionData field to nil, so callers +// must ensure tx is not updated after End returns. +// +// If tx.Duration has not been set, End will set it to the elapsed time +// since the transaction's start time. +func (tx *Transaction) End() { + tx.mu.Lock() + defer tx.mu.Unlock() + if tx.ended() { + return + } + if tx.Duration < 0 { + tx.Duration = time.Since(tx.timestamp) + } + tx.enqueue() + tx.TransactionData = nil +} + +func (tx *Transaction) enqueue() { + event := tracerEvent{eventType: transactionEvent} + event.tx.Transaction = tx + event.tx.TransactionData = tx.TransactionData + select { + case tx.tracer.events <- event: + default: + // Enqueuing a transaction should never block. + tx.tracer.breakdownMetrics.recordTransaction(tx.TransactionData) + + // TODO(axw) use an atomic operation to increment. + tx.tracer.statsMu.Lock() + tx.tracer.stats.TransactionsDropped++ + tx.tracer.statsMu.Unlock() + tx.reset(tx.tracer) + } +} + +// ended reports whether or not End or Discard has been called. +// +// This must be called with tx.mu held. +func (tx *Transaction) ended() bool { + return tx.TransactionData == nil +} + +// TransactionData holds the details for a transaction, and is embedded +// inside Transaction. When a transaction is ended, its TransactionData +// field will be set to nil. +type TransactionData struct { + // Name holds the transaction name, initialized with the value + // passed to StartTransaction. + Name string + + // Type holds the transaction type, initialized with the value + // passed to StartTransaction. + Type string + + // Duration holds the transaction duration, initialized to -1. + // + // If you do not update Duration, calling Transaction.End will + // calculate the duration based on the elapsed time since the + // transaction's start time. + Duration time.Duration + + // Context describes the context in which the transaction occurs. + Context Context + + // Result holds the transaction result. + Result string + + maxSpans int + spanFramesMinDuration time.Duration + stackTraceLimit int + breakdownMetricsEnabled bool + propagateLegacyHeader bool + timestamp time.Time + + mu sync.Mutex + spansCreated int + spansDropped int + childrenTimer childrenTimer + spanTimings spanTimingsMap + rand *rand.Rand // for ID generation + // parentSpan holds the transaction's parent ID. It is protected by + // mu, since it can be updated by calling EnsureParent. + parentSpan SpanID +} + +// reset resets the TransactionData back to its zero state and places it back +// into the transaction pool. +func (td *TransactionData) reset(tracer *Tracer) { + *td = TransactionData{ + Context: td.Context, + Duration: -1, + rand: td.rand, + spanTimings: td.spanTimings, + } + td.Context.reset() + td.spanTimings.reset() + tracer.transactionDataPool.Put(td) +} diff --git a/vendor/go.elastic.co/apm/transport/api.go b/vendor/go.elastic.co/apm/transport/api.go new file mode 100644 index 00000000000..a9c2fe263d3 --- /dev/null +++ b/vendor/go.elastic.co/apm/transport/api.go @@ -0,0 +1,33 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package transport + +import ( + "context" + "io" +) + +// Transport provides an interface for sending streams of encoded model +// entities to the Elastic APM server, and for querying config. Methods +// are not required to be safe for concurrent use. +type Transport interface { + // SendStream sends a data stream to the server, returning when the + // stream has been closed (Read returns io.EOF) or the HTTP request + // terminates. + SendStream(context.Context, io.Reader) error +} diff --git a/vendor/go.elastic.co/apm/transport/default.go b/vendor/go.elastic.co/apm/transport/default.go new file mode 100644 index 00000000000..2a730eda67f --- /dev/null +++ b/vendor/go.elastic.co/apm/transport/default.go @@ -0,0 +1,54 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package transport + +var ( + // Default is the default Transport, using the + // ELASTIC_APM_* environment variables. + // + // If ELASTIC_APM_SERVER_URL is set to an invalid + // location, Default will be set to a Transport + // returning an error for every operation. + Default Transport + + // Discard is a Transport on which all operations + // succeed without doing anything. + Discard = discardTransport{} +) + +func init() { + _, _ = InitDefault() +} + +// InitDefault (re-)initializes Default, the default Transport, returning +// its new value along with the error that will be returned by the Transport +// if the environment variable configuration is invalid. The result is always +// non-nil. +func InitDefault() (Transport, error) { + t, err := getDefault() + Default = t + return t, err +} + +func getDefault() (Transport, error) { + s, err := NewHTTPTransport() + if err != nil { + return discardTransport{err}, err + } + return s, nil +} diff --git a/vendor/go.elastic.co/apm/transport/discard.go b/vendor/go.elastic.co/apm/transport/discard.go new file mode 100644 index 00000000000..3d61a34da7f --- /dev/null +++ b/vendor/go.elastic.co/apm/transport/discard.go @@ -0,0 +1,31 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package transport + +import ( + "context" + "io" +) + +type discardTransport struct { + err error +} + +func (s discardTransport) SendStream(context.Context, io.Reader) error { + return s.err +} diff --git a/vendor/go.elastic.co/apm/transport/doc.go b/vendor/go.elastic.co/apm/transport/doc.go new file mode 100644 index 00000000000..c5bf29e1909 --- /dev/null +++ b/vendor/go.elastic.co/apm/transport/doc.go @@ -0,0 +1,20 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Package transport provides an interface and implementation +// for transporting data to the Elastic APM server. +package transport diff --git a/vendor/go.elastic.co/apm/transport/http.go b/vendor/go.elastic.co/apm/transport/http.go new file mode 100644 index 00000000000..d084a51d417 --- /dev/null +++ b/vendor/go.elastic.co/apm/transport/http.go @@ -0,0 +1,638 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package transport + +import ( + "bytes" + "context" + "crypto/tls" + "crypto/x509" + "encoding/json" + "encoding/pem" + "fmt" + "io" + "io/ioutil" + "math/rand" + "mime/multipart" + "net/http" + "net/textproto" + "net/url" + "os" + "path" + "runtime" + "strconv" + "strings" + "sync/atomic" + "time" + + "github.com/pkg/errors" + + "go.elastic.co/apm/apmconfig" + "go.elastic.co/apm/internal/apmversion" + "go.elastic.co/apm/internal/configutil" +) + +const ( + intakePath = "/intake/v2/events" + profilePath = "/intake/v2/profile" + configPath = "/config/v1/agents" + + envAPIKey = "ELASTIC_APM_API_KEY" + envSecretToken = "ELASTIC_APM_SECRET_TOKEN" + envServerURLs = "ELASTIC_APM_SERVER_URLS" + envServerURL = "ELASTIC_APM_SERVER_URL" + envServerTimeout = "ELASTIC_APM_SERVER_TIMEOUT" + envServerCert = "ELASTIC_APM_SERVER_CERT" + envVerifyServerCert = "ELASTIC_APM_VERIFY_SERVER_CERT" +) + +var ( + // Take a copy of the http.DefaultTransport pointer, + // in case another package replaces the value later. + defaultHTTPTransport = http.DefaultTransport.(*http.Transport) + + defaultServerURL, _ = url.Parse("http://localhost:8200") + defaultServerTimeout = 30 * time.Second +) + +// HTTPTransport is an implementation of Transport, sending payloads via +// a net/http client. +type HTTPTransport struct { + // Client exposes the http.Client used by the HTTPTransport for + // sending requests to the APM Server. + Client *http.Client + intakeHeaders http.Header + configHeaders http.Header + profileHeaders http.Header + shuffleRand *rand.Rand + + urlIndex int32 + intakeURLs []*url.URL + configURLs []*url.URL + profileURLs []*url.URL +} + +// NewHTTPTransport returns a new HTTPTransport which can be used for +// streaming data to the APM Server. The returned HTTPTransport will be +// initialized using the following environment variables: +// +// - ELASTIC_APM_SERVER_URLS: a comma-separated list of APM Server URLs. +// The transport will use this list of URLs for sending requests, +// switching to the next URL in the list upon error. The list will be +// shuffled first. If no URLs are specified, then the transport will +// use the default URL "http://localhost:8200". +// +// - ELASTIC_APM_SERVER_TIMEOUT: timeout for requests to the APM Server. +// If not specified, defaults to 30 seconds. +// +// - ELASTIC_APM_SECRET_TOKEN: used to authenticate the agent. +// +// - ELASTIC_APM_SERVER_CERT: path to a PEM-encoded certificate that +// must match the APM Server-supplied certificate. This can be used +// to pin a self signed certificate. If this is set, then +// ELASTIC_APM_VERIFY_SERVER_CERT is ignored. +// +// - ELASTIC_APM_VERIFY_SERVER_CERT: if set to "false", the transport +// will not verify the APM Server's TLS certificate. Only relevant +// when using HTTPS. By default, the transport will verify server +// certificates. +// +func NewHTTPTransport() (*HTTPTransport, error) { + verifyServerCert, err := configutil.ParseBoolEnv(envVerifyServerCert, true) + if err != nil { + return nil, err + } + + serverTimeout, err := configutil.ParseDurationEnv(envServerTimeout, defaultServerTimeout) + if err != nil { + return nil, err + } + if serverTimeout < 0 { + serverTimeout = 0 + } + + serverURLs, err := initServerURLs() + if err != nil { + return nil, err + } + + tlsConfig := &tls.Config{InsecureSkipVerify: !verifyServerCert} + serverCertPath := os.Getenv(envServerCert) + if serverCertPath != "" { + serverCert, err := loadCertificate(serverCertPath) + if err != nil { + return nil, errors.Wrapf(err, "failed to load certificate from %s", serverCertPath) + } + // Disable standard verification, we'll check that the + // server supplies the exact certificate provided. + tlsConfig.InsecureSkipVerify = true + tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { + return verifyPeerCertificate(rawCerts, serverCert) + } + } + + client := &http.Client{ + Timeout: serverTimeout, + Transport: &http.Transport{ + Proxy: defaultHTTPTransport.Proxy, + DialContext: defaultHTTPTransport.DialContext, + MaxIdleConns: defaultHTTPTransport.MaxIdleConns, + IdleConnTimeout: defaultHTTPTransport.IdleConnTimeout, + TLSHandshakeTimeout: defaultHTTPTransport.TLSHandshakeTimeout, + ExpectContinueTimeout: defaultHTTPTransport.ExpectContinueTimeout, + TLSClientConfig: tlsConfig, + }, + } + + commonHeaders := make(http.Header) + commonHeaders.Set("User-Agent", defaultUserAgent()) + + intakeHeaders := copyHeaders(commonHeaders) + intakeHeaders.Set("Content-Type", "application/x-ndjson") + intakeHeaders.Set("Content-Encoding", "deflate") + intakeHeaders.Set("Transfer-Encoding", "chunked") + + profileHeaders := copyHeaders(commonHeaders) + + t := &HTTPTransport{ + Client: client, + configHeaders: commonHeaders, + intakeHeaders: intakeHeaders, + profileHeaders: profileHeaders, + } + if apiKey := os.Getenv(envAPIKey); apiKey != "" { + t.SetAPIKey(apiKey) + } else if secretToken := os.Getenv(envSecretToken); secretToken != "" { + t.SetSecretToken(secretToken) + } + t.SetServerURL(serverURLs...) + return t, nil +} + +// SetServerURL sets the APM Server URL (or URLs) for sending requests. +// At least one URL must be specified, or the method will panic. The +// list will be randomly shuffled. +func (t *HTTPTransport) SetServerURL(u ...*url.URL) { + if len(u) == 0 { + panic("SetServerURL expects at least one URL") + } + intakeURLs := make([]*url.URL, len(u)) + configURLs := make([]*url.URL, len(u)) + profileURLs := make([]*url.URL, len(u)) + for i, u := range u { + intakeURLs[i] = urlWithPath(u, intakePath) + configURLs[i] = urlWithPath(u, configPath) + profileURLs[i] = urlWithPath(u, profilePath) + } + if n := len(intakeURLs); n > 0 { + if t.shuffleRand == nil { + t.shuffleRand = rand.New(rand.NewSource(time.Now().UnixNano())) + } + for i := n - 1; i > 0; i-- { + j := t.shuffleRand.Intn(i + 1) + intakeURLs[i], intakeURLs[j] = intakeURLs[j], intakeURLs[i] + configURLs[i], configURLs[j] = configURLs[j], configURLs[i] + profileURLs[i], profileURLs[j] = profileURLs[j], profileURLs[i] + } + } + t.intakeURLs = intakeURLs + t.configURLs = configURLs + t.profileURLs = profileURLs + t.urlIndex = 0 +} + +// SetUserAgent sets the User-Agent header that will be sent with each request. +func (t *HTTPTransport) SetUserAgent(ua string) { + t.setCommonHeader("User-Agent", ua) +} + +// SetSecretToken sets the Authorization header with the given secret token. +// +// This overrides the value specified via the ELASTIC_APM_SECRET_TOKEN or +// ELASTIC_APM_API_KEY environment variables, if either are set. +func (t *HTTPTransport) SetSecretToken(secretToken string) { + if secretToken != "" { + t.setCommonHeader("Authorization", "Bearer "+secretToken) + } else { + t.deleteCommonHeader("Authorization") + } +} + +// SetAPIKey sets the Authorization header with the given API Key. +// +// This overrides the value specified via the ELASTIC_APM_SECRET_TOKEN or +// ELASTIC_APM_API_KEY environment variables, if either are set. +func (t *HTTPTransport) SetAPIKey(apiKey string) { + if apiKey != "" { + t.setCommonHeader("Authorization", "ApiKey "+apiKey) + } else { + t.deleteCommonHeader("Authorization") + } +} + +func (t *HTTPTransport) setCommonHeader(key, value string) { + t.configHeaders.Set(key, value) + t.intakeHeaders.Set(key, value) + t.profileHeaders.Set(key, value) +} + +func (t *HTTPTransport) deleteCommonHeader(key string) { + t.configHeaders.Del(key) + t.intakeHeaders.Del(key) + t.profileHeaders.Del(key) +} + +// SendStream sends the stream over HTTP. If SendStream returns an error and +// the transport is configured with more than one APM Server URL, then the +// following request will be sent to the next URL in the list. +func (t *HTTPTransport) SendStream(ctx context.Context, r io.Reader) error { + urlIndex := atomic.LoadInt32(&t.urlIndex) + intakeURL := t.intakeURLs[urlIndex] + req := t.newRequest("POST", intakeURL) + req = requestWithContext(ctx, req) + req.Header = t.intakeHeaders + req.Body = ioutil.NopCloser(r) + if err := t.sendStreamRequest(req); err != nil { + atomic.StoreInt32(&t.urlIndex, (urlIndex+1)%int32(len(t.intakeURLs))) + return err + } + return nil +} + +func (t *HTTPTransport) sendStreamRequest(req *http.Request) error { + resp, err := t.Client.Do(req) + if err != nil { + return errors.Wrap(err, "sending event request failed") + } + switch resp.StatusCode { + case http.StatusOK, http.StatusAccepted: + resp.Body.Close() + return nil + } + defer resp.Body.Close() + + result := newHTTPError(resp) + if resp.StatusCode == http.StatusNotFound && result.Message == "404 page not found" { + // This may be an old (pre-6.5) APM server + // that does not support the v2 intake API. + result.Message = fmt.Sprintf("%s not found (requires APM Server 6.5.0 or newer)", req.URL) + } + return result +} + +// SendProfile sends a symbolised pprof profile, encoded as protobuf, and gzip-compressed. +// +// NOTE this is an experimental API, and may be removed in a future minor version, without +// being considered a breaking change. +func (t *HTTPTransport) SendProfile( + ctx context.Context, + metadataReader io.Reader, + profileReaders ...io.Reader, +) error { + urlIndex := atomic.LoadInt32(&t.urlIndex) + profileURL := t.profileURLs[urlIndex] + req := t.newRequest("POST", profileURL) + req = requestWithContext(ctx, req) + req.Header = t.profileHeaders + + writeBody := func(w *multipart.Writer) error { + h := make(textproto.MIMEHeader) + h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="metadata"`)) + h.Set("Content-Type", "application/json") + part, err := w.CreatePart(h) + if err != nil { + return err + } + if _, err := io.Copy(part, metadataReader); err != nil { + return err + } + + for _, profileReader := range profileReaders { + h = make(textproto.MIMEHeader) + h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="profile"`)) + h.Set("Content-Type", `application/x-protobuf; messageType="perftools.profiles.Profile"`) + part, err = w.CreatePart(h) + if err != nil { + return err + } + if _, err := io.Copy(part, profileReader); err != nil { + return err + } + } + return w.Close() + } + pipeR, pipeW := io.Pipe() + mpw := multipart.NewWriter(pipeW) + req.Header.Set("Content-Type", mpw.FormDataContentType()) + req.Body = pipeR + go func() { + err := writeBody(mpw) + pipeW.CloseWithError(err) + }() + return t.sendProfileRequest(req) +} + +func (t *HTTPTransport) sendProfileRequest(req *http.Request) error { + resp, err := t.Client.Do(req) + if err != nil { + return errors.Wrap(err, "sending profile request failed") + } + switch resp.StatusCode { + case http.StatusOK, http.StatusAccepted: + resp.Body.Close() + return nil + } + defer resp.Body.Close() + + result := newHTTPError(resp) + if resp.StatusCode == http.StatusNotFound && result.Message == "404 page not found" { + // TODO(axw) correct minimum server version. + result.Message = fmt.Sprintf("%s not found (requires APM Server 7.5.0 or newer)", req.URL) + } + return result +} + +// WatchConfig polls the APM Server for agent config changes, sending +// them over the returned channel. +func (t *HTTPTransport) WatchConfig(ctx context.Context, args apmconfig.WatchParams) <-chan apmconfig.Change { + // We have an initial delay to allow application initialisation code + // to close apm.DefaultTracer, which would cancel watching config. + const initialDelay = 1 * time.Second + + changes := make(chan apmconfig.Change) + go func() { + defer close(changes) + + var etag string + var out chan apmconfig.Change + var change apmconfig.Change + timer := time.NewTimer(initialDelay) + for { + select { + case <-ctx.Done(): + return + case out <- change: + out = nil + change = apmconfig.Change{} + continue + case <-timer.C: + } + + urlIndex := atomic.LoadInt32(&t.urlIndex) + query := make(url.Values) + query.Set("service.name", args.Service.Name) + if args.Service.Environment != "" { + query.Set("service.environment", args.Service.Environment) + } + url := *t.configURLs[urlIndex] + url.RawQuery = query.Encode() + + req := t.newRequest("GET", &url) + req.Header = t.configHeaders + if etag != "" { + req.Header = copyHeaders(req.Header) + req.Header.Set("If-None-Match", strconv.QuoteToASCII(etag)) + } + + req = requestWithContext(ctx, req) + resp := t.configRequest(req) + var send bool + if resp.err != nil { + // The request will have failed if the context has been + // cancelled. No need to send a a change in this case. + send = ctx.Err() == nil + } + if !send && resp.attrs != nil { + etag = resp.etag + send = true + } + if send { + change = apmconfig.Change{Err: resp.err, Attrs: resp.attrs} + out = changes + } + timer.Reset(resp.maxAge) + } + }() + return changes +} + +func (t *HTTPTransport) configRequest(req *http.Request) configResponse { + // defaultMaxAge is the default amount of time to wait between + // requests. This should only be used when the server does not + // respond with a Cache-Control header, or where the header is + // malformed. + const defaultMaxAge = 5 * time.Minute + + resp, err := t.Client.Do(req) + if err != nil { + // TODO(axw) this might indicate that the APM Server is unavailable. + // In this case, we should allow a change in URL due to SendStream + // to cut the defaultMaxAge delay short. + return configResponse{ + err: errors.Wrap(err, "sending config request failed"), + maxAge: defaultMaxAge, + } + } + defer resp.Body.Close() + + var response configResponse + if etag, err := strconv.Unquote(resp.Header.Get("Etag")); err == nil { + response.etag = etag + } + cacheControl := parseCacheControl(resp.Header.Get("Cache-Control")) + response.maxAge = cacheControl.maxAge + if response.maxAge < 0 { + response.maxAge = defaultMaxAge + } + + switch resp.StatusCode { + case http.StatusNotModified, http.StatusForbidden, http.StatusNotFound: + // 304 (Not Modified) is returned when the config has not changed since the previous query. + // 403 (Forbidden) is returned if the server does not have the connection to Kibana enabled. + // 404 (Not Found) is returned by old servers that do not implement the config endpoint. + return response + case http.StatusOK: + attrs := make(map[string]string) + // TODO(axw) handling EOF shouldn't be necessary, server currently responds with an empty + // body when there is no config. + if err := json.NewDecoder(resp.Body).Decode(&attrs); err != nil && err != io.EOF { + response.err = err + } else { + response.attrs = attrs + } + return response + } + response.err = newHTTPError(resp) + return response +} + +func (t *HTTPTransport) newRequest(method string, url *url.URL) *http.Request { + req := &http.Request{ + Method: method, + URL: url, + Proto: "HTTP/1.1", + ProtoMajor: 1, + ProtoMinor: 1, + Host: url.Host, + } + return req +} + +func urlWithPath(url *url.URL, p string) *url.URL { + urlCopy := *url + urlCopy.Path = path.Clean(urlCopy.Path + p) + if urlCopy.RawPath != "" { + urlCopy.RawPath = path.Clean(urlCopy.RawPath + p) + } + return &urlCopy +} + +// HTTPError is an error returned by HTTPTransport methods when requests fail. +type HTTPError struct { + Response *http.Response + Message string +} + +func newHTTPError(resp *http.Response) *HTTPError { + bodyContents, err := ioutil.ReadAll(resp.Body) + if err == nil { + resp.Body = ioutil.NopCloser(bytes.NewReader(bodyContents)) + } + return &HTTPError{ + Response: resp, + Message: strings.TrimSpace(string(bodyContents)), + } +} + +func (e *HTTPError) Error() string { + msg := fmt.Sprintf("request failed with %s", e.Response.Status) + if e.Message != "" { + msg += ": " + e.Message + } + return msg +} + +// initServerURLs parses ELASTIC_APM_SERVER_URLS if specified, +// otherwise parses ELASTIC_APM_SERVER_URL if specified. If +// neither are specified, then the default localhost URL is +// returned. +func initServerURLs() ([]*url.URL, error) { + key := envServerURLs + value := os.Getenv(key) + if value == "" { + key = envServerURL + value = os.Getenv(key) + } + var urls []*url.URL + for _, field := range strings.Split(value, ",") { + field = strings.TrimSpace(field) + if field == "" { + continue + } + u, err := url.Parse(field) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse %s", key) + } + urls = append(urls, u) + } + if len(urls) == 0 { + urls = []*url.URL{defaultServerURL} + } + return urls, nil +} + +func requestWithContext(ctx context.Context, req *http.Request) *http.Request { + url := req.URL + req.URL = nil + reqCopy := req.WithContext(ctx) + reqCopy.URL = url + req.URL = url + return reqCopy +} + +func loadCertificate(path string) (*x509.Certificate, error) { + pemBytes, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + for { + var certBlock *pem.Block + certBlock, pemBytes = pem.Decode(pemBytes) + if certBlock == nil { + return nil, errors.New("missing or invalid certificate") + } + if certBlock.Type == "CERTIFICATE" { + return x509.ParseCertificate(certBlock.Bytes) + } + } +} + +func verifyPeerCertificate(rawCerts [][]byte, trusted *x509.Certificate) error { + if len(rawCerts) == 0 { + return errors.New("missing leaf certificate") + } + cert, err := x509.ParseCertificate(rawCerts[0]) + if err != nil { + return errors.Wrap(err, "failed to parse certificate from server") + } + if !cert.Equal(trusted) { + return errors.New("failed to verify server certificate") + } + return nil +} + +func defaultUserAgent() string { + return fmt.Sprintf("elasticapm-go/%s go/%s", apmversion.AgentVersion, runtime.Version()) +} + +func copyHeaders(in http.Header) http.Header { + out := make(http.Header, len(in)) + for k, vs := range in { + vsCopy := make([]string, len(vs)) + copy(vsCopy, vs) + out[k] = vsCopy + } + return out +} + +type configResponse struct { + err error + attrs map[string]string + etag string + maxAge time.Duration +} + +type cacheControl struct { + maxAge time.Duration +} + +func parseCacheControl(s string) cacheControl { + fields := strings.SplitN(s, "max-age=", 2) + if len(fields) < 2 { + return cacheControl{maxAge: -1} + } + s = fields[1] + if i := strings.IndexRune(s, ','); i != -1 { + s = s[:i] + } + maxAge, err := strconv.ParseUint(s, 10, 32) + if err != nil { + return cacheControl{maxAge: -1} + } + return cacheControl{maxAge: time.Duration(maxAge) * time.Second} +} diff --git a/vendor/go.elastic.co/apm/transport/transporttest/doc.go b/vendor/go.elastic.co/apm/transport/transporttest/doc.go new file mode 100644 index 00000000000..13f9e3adf1f --- /dev/null +++ b/vendor/go.elastic.co/apm/transport/transporttest/doc.go @@ -0,0 +1,20 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Package transporttest provides implementations of +// transport.Transport for testing purposes. +package transporttest diff --git a/vendor/go.elastic.co/apm/transport/transporttest/err.go b/vendor/go.elastic.co/apm/transport/transporttest/err.go new file mode 100644 index 00000000000..668fff8a310 --- /dev/null +++ b/vendor/go.elastic.co/apm/transport/transporttest/err.go @@ -0,0 +1,54 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package transporttest + +import ( + "context" + "io" + "io/ioutil" + + "go.elastic.co/apm/transport" +) + +// Discard is a transport.Transport which discards +// all streams, and returns no errors. +var Discard transport.Transport = ErrorTransport{} + +// ErrorTransport is a transport that returns the stored error +// for each method call. +type ErrorTransport struct { + Error error +} + +// SendStream discards the stream and returns t.Error. +func (t ErrorTransport) SendStream(ctx context.Context, r io.Reader) error { + errc := make(chan error, 1) + go func() { + _, err := io.Copy(ioutil.Discard, r) + errc <- err + }() + select { + case err := <-errc: + if err != nil { + return err + } + return t.Error + case <-ctx.Done(): + return ctx.Err() + } +} diff --git a/vendor/go.elastic.co/apm/transport/transporttest/recorder.go b/vendor/go.elastic.co/apm/transport/transporttest/recorder.go new file mode 100644 index 00000000000..4db125ab8b4 --- /dev/null +++ b/vendor/go.elastic.co/apm/transport/transporttest/recorder.go @@ -0,0 +1,203 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package transporttest + +import ( + "compress/zlib" + "context" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "sync" + + "github.com/google/go-cmp/cmp" + + "go.elastic.co/apm" + "go.elastic.co/apm/model" +) + +// NewRecorderTracer returns a new apm.Tracer and +// RecorderTransport, which is set as the tracer's transport. +// +// DEPRECATED. Use apmtest.NewRecordingTracer instead. +func NewRecorderTracer() (*apm.Tracer, *RecorderTransport) { + var transport RecorderTransport + tracer, err := apm.NewTracerOptions(apm.TracerOptions{ + ServiceName: "transporttest", + Transport: &transport, + }) + if err != nil { + panic(err) + } + return tracer, &transport +} + +// RecorderTransport implements transport.Transport, recording the +// streams sent. The streams can be retrieved using the Payloads +// method. +type RecorderTransport struct { + mu sync.Mutex + metadata *metadata + payloads Payloads +} + +// ResetPayloads clears out any recorded payloads. +func (r *RecorderTransport) ResetPayloads() { + r.mu.Lock() + defer r.mu.Unlock() + r.payloads = Payloads{} +} + +// SendStream records the stream such that it can later be obtained via Payloads. +func (r *RecorderTransport) SendStream(ctx context.Context, stream io.Reader) error { + return r.record(ctx, stream) +} + +// SendProfile records the stream such that it can later be obtained via Payloads. +func (r *RecorderTransport) SendProfile(ctx context.Context, metadata io.Reader, profiles ...io.Reader) error { + return r.recordProto(ctx, metadata, profiles) +} + +// Metadata returns the metadata recorded by the transport. If metadata is yet to +// be received, this method will panic. +func (r *RecorderTransport) Metadata() (_ model.System, _ model.Process, _ model.Service, labels model.StringMap) { + r.mu.Lock() + defer r.mu.Unlock() + return r.metadata.System, r.metadata.Process, r.metadata.Service, r.metadata.Labels +} + +// Payloads returns the payloads recorded by SendStream. +func (r *RecorderTransport) Payloads() Payloads { + r.mu.Lock() + defer r.mu.Unlock() + return r.payloads +} + +func (r *RecorderTransport) record(ctx context.Context, stream io.Reader) error { + reader, err := zlib.NewReader(stream) + if err != nil { + if err == io.ErrUnexpectedEOF { + if contextDone(ctx) { + return ctx.Err() + } + // truly unexpected + } + panic(err) + } + decoder := json.NewDecoder(reader) + + // The first object of any request must be a metadata struct. + var metadataPayload struct { + Metadata metadata `json:"metadata"` + } + if err := decoder.Decode(&metadataPayload); err != nil { + panic(err) + } + r.recordMetadata(&metadataPayload.Metadata) + + for { + var payload struct { + Error *model.Error `json:"error"` + Metrics *model.Metrics `json:"metricset"` + Span *model.Span `json:"span"` + Transaction *model.Transaction `json:"transaction"` + } + err := decoder.Decode(&payload) + if err == io.EOF || (err == io.ErrUnexpectedEOF && contextDone(ctx)) { + break + } else if err != nil { + panic(err) + } + r.mu.Lock() + switch { + case payload.Error != nil: + r.payloads.Errors = append(r.payloads.Errors, *payload.Error) + case payload.Metrics != nil: + r.payloads.Metrics = append(r.payloads.Metrics, *payload.Metrics) + case payload.Span != nil: + r.payloads.Spans = append(r.payloads.Spans, *payload.Span) + case payload.Transaction != nil: + r.payloads.Transactions = append(r.payloads.Transactions, *payload.Transaction) + } + r.mu.Unlock() + } + return nil +} + +func (r *RecorderTransport) recordProto(ctx context.Context, metadataReader io.Reader, profileReaders []io.Reader) error { + var metadata metadata + if err := json.NewDecoder(metadataReader).Decode(&metadata); err != nil { + panic(err) + } + r.recordMetadata(&metadata) + + r.mu.Lock() + defer r.mu.Unlock() + for _, profileReader := range profileReaders { + data, err := ioutil.ReadAll(profileReader) + if err != nil { + panic(err) + } + r.payloads.Profiles = append(r.payloads.Profiles, data) + } + return nil +} + +func (r *RecorderTransport) recordMetadata(m *metadata) { + r.mu.Lock() + defer r.mu.Unlock() + if r.metadata == nil { + r.metadata = m + } else { + // Make sure the metadata doesn't change between requests. + if diff := cmp.Diff(r.metadata, m); diff != "" { + panic(fmt.Errorf("metadata changed\n%s", diff)) + } + } +} + +func contextDone(ctx context.Context) bool { + select { + case <-ctx.Done(): + return true + default: + return false + } +} + +// Payloads holds the recorded payloads. +type Payloads struct { + Errors []model.Error + Metrics []model.Metrics + Spans []model.Span + Transactions []model.Transaction + Profiles [][]byte +} + +// Len returns the number of recorded payloads. +func (p *Payloads) Len() int { + return len(p.Transactions) + len(p.Errors) + len(p.Metrics) +} + +type metadata struct { + System model.System `json:"system"` + Process model.Process `json:"process"` + Service model.Service `json:"service"` + Labels model.StringMap `json:"labels,omitempty"` +} diff --git a/vendor/go.elastic.co/apm/utils.go b/vendor/go.elastic.co/apm/utils.go new file mode 100644 index 00000000000..ae24404e315 --- /dev/null +++ b/vendor/go.elastic.co/apm/utils.go @@ -0,0 +1,242 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apm + +import ( + "fmt" + "math/rand" + "os" + "path/filepath" + "reflect" + "regexp" + "runtime" + "strings" + "time" + + "github.com/pkg/errors" + + "go.elastic.co/apm/internal/apmhostutil" + "go.elastic.co/apm/internal/apmstrings" + "go.elastic.co/apm/model" +) + +var ( + currentProcess model.Process + goAgent = model.Agent{Name: "go", Version: AgentVersion} + goLanguage = model.Language{Name: "go", Version: runtime.Version()} + goRuntime = model.Runtime{Name: runtime.Compiler, Version: runtime.Version()} + localSystem model.System + + serviceNameInvalidRegexp = regexp.MustCompile("[^" + serviceNameValidClass + "]") + labelKeyReplacer = strings.NewReplacer(`.`, `_`, `*`, `_`, `"`, `_`) + + rtypeBool = reflect.TypeOf(false) + rtypeFloat64 = reflect.TypeOf(float64(0)) +) + +const ( + envHostname = "ELASTIC_APM_HOSTNAME" + envServiceNodeName = "ELASTIC_APM_SERVICE_NODE_NAME" + + serviceNameValidClass = "a-zA-Z0-9 _-" + + // At the time of writing, all keyword length limits + // are 1024 runes, enforced by JSON Schema. + stringLengthLimit = 1024 + + // Non-keyword string fields are not limited in length + // by JSON Schema, but we still truncate all strings. + // Some strings, such as database statement, we explicitly + // allow to be longer than others. + longStringLengthLimit = 10000 +) + +func init() { + currentProcess = getCurrentProcess() + localSystem = getLocalSystem() +} + +func getCurrentProcess() model.Process { + ppid := os.Getppid() + title, err := currentProcessTitle() + if err != nil || title == "" { + title = filepath.Base(os.Args[0]) + } + return model.Process{ + Pid: os.Getpid(), + Ppid: &ppid, + Title: truncateString(title), + Argv: os.Args, + } +} + +func makeService(name, version, environment string) model.Service { + service := model.Service{ + Name: truncateString(name), + Version: truncateString(version), + Environment: truncateString(environment), + Agent: &goAgent, + Language: &goLanguage, + Runtime: &goRuntime, + } + + serviceNodeName := os.Getenv(envServiceNodeName) + if serviceNodeName != "" { + service.Node = &model.ServiceNode{ConfiguredName: truncateString(serviceNodeName)} + } + + return service +} + +func getLocalSystem() model.System { + system := model.System{ + Architecture: runtime.GOARCH, + Platform: runtime.GOOS, + } + system.Hostname = os.Getenv(envHostname) + if system.Hostname == "" { + if hostname, err := os.Hostname(); err == nil { + system.Hostname = hostname + } + } + system.Hostname = truncateString(system.Hostname) + if container, err := apmhostutil.Container(); err == nil { + system.Container = container + } + system.Kubernetes = getKubernetesMetadata() + return system +} + +func getKubernetesMetadata() *model.Kubernetes { + kubernetes, err := apmhostutil.Kubernetes() + if err != nil { + kubernetes = nil + } + namespace := os.Getenv("KUBERNETES_NAMESPACE") + podName := os.Getenv("KUBERNETES_POD_NAME") + podUID := os.Getenv("KUBERNETES_POD_UID") + nodeName := os.Getenv("KUBERNETES_NODE_NAME") + if namespace == "" && podName == "" && podUID == "" && nodeName == "" { + return kubernetes + } + if kubernetes == nil { + kubernetes = &model.Kubernetes{} + } + if namespace != "" { + kubernetes.Namespace = namespace + } + if nodeName != "" { + if kubernetes.Node == nil { + kubernetes.Node = &model.KubernetesNode{} + } + kubernetes.Node.Name = nodeName + } + if podName != "" || podUID != "" { + if kubernetes.Pod == nil { + kubernetes.Pod = &model.KubernetesPod{} + } + if podName != "" { + kubernetes.Pod.Name = podName + } + if podUID != "" { + kubernetes.Pod.UID = podUID + } + } + return kubernetes +} + +func cleanLabelKey(k string) string { + return labelKeyReplacer.Replace(k) +} + +// makeLabelValue returns v as a value suitable for including +// in a label value. If v is numerical or boolean, then it will +// be returned as-is; otherwise the value will be returned as a +// string, using fmt.Sprint if necessary, and possibly truncated +// using truncateString. +func makeLabelValue(v interface{}) interface{} { + switch v.(type) { + case nil, bool, float32, float64, + uint, uint8, uint16, uint32, uint64, + int, int8, int16, int32, int64: + return v + case string: + return truncateString(v.(string)) + } + // Slow path. If v has a non-basic type whose underlying + // type is convertible to bool or float64, return v as-is. + // Otherwise, stringify. + rtype := reflect.TypeOf(v) + if rtype.ConvertibleTo(rtypeBool) || rtype.ConvertibleTo(rtypeFloat64) { + // Custom type + return v + } + return truncateString(fmt.Sprint(v)) +} + +func validateServiceName(name string) error { + idx := serviceNameInvalidRegexp.FindStringIndex(name) + if idx == nil { + return nil + } + return errors.Errorf( + "invalid service name %q: character %q is not in the allowed set (%s)", + name, name[idx[0]], serviceNameValidClass, + ) +} + +func sanitizeServiceName(name string) string { + return serviceNameInvalidRegexp.ReplaceAllString(name, "_") +} + +func truncateString(s string) string { + s, _ = apmstrings.Truncate(s, stringLengthLimit) + return s +} + +func truncateLongString(s string) string { + s, _ = apmstrings.Truncate(s, longStringLengthLimit) + return s +} + +func nextGracePeriod(p time.Duration) time.Duration { + if p == -1 { + return 0 + } + for i := time.Duration(0); i < 6; i++ { + if p == (i * i * time.Second) { + return (i + 1) * (i + 1) * time.Second + } + } + return p +} + +// jitterDuration returns d +/- some multiple of d in the range [0,j]. +func jitterDuration(d time.Duration, rng *rand.Rand, j float64) time.Duration { + if d == 0 || j == 0 { + return d + } + r := (rng.Float64() * j * 2) - j + return d + time.Duration(float64(d)*r) +} + +func durationMicros(d time.Duration) float64 { + us := d / time.Microsecond + ns := d % time.Microsecond + return float64(us) + float64(ns)/1e9 +} diff --git a/vendor/go.elastic.co/apm/utils_linux.go b/vendor/go.elastic.co/apm/utils_linux.go new file mode 100644 index 00000000000..abf97366ab2 --- /dev/null +++ b/vendor/go.elastic.co/apm/utils_linux.go @@ -0,0 +1,40 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apm + +import ( + "bytes" + "syscall" + "unsafe" +) + +func currentProcessTitle() (string, error) { + // PR_GET_NAME (since Linux 2.6.11) + // Return the name of the calling thread, in the buffer pointed to by + // (char *) arg2. The buffer should allow space for up to 16 bytes; + // the returned string will be null-terminated. + var buf [16]byte + if _, _, errno := syscall.RawSyscall6( + syscall.SYS_PRCTL, syscall.PR_GET_NAME, + uintptr(unsafe.Pointer(&buf[0])), + 0, 0, 0, 0, + ); errno != 0 { + return "", errno + } + return string(buf[:bytes.IndexByte(buf[:], 0)]), nil +} diff --git a/vendor/go.elastic.co/apm/utils_other.go b/vendor/go.elastic.co/apm/utils_other.go new file mode 100644 index 00000000000..06a38c5d9e4 --- /dev/null +++ b/vendor/go.elastic.co/apm/utils_other.go @@ -0,0 +1,38 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//+build !linux + +package apm + +import ( + "github.com/pkg/errors" + + sysinfo "github.com/elastic/go-sysinfo" +) + +func currentProcessTitle() (string, error) { + proc, err := sysinfo.Self() + if err != nil { + return "", errors.Wrap(err, "failed to get process info") + } + info, err := proc.Info() + if err != nil { + return "", errors.Wrap(err, "failed to get process info") + } + return info.Name, nil +} diff --git a/vendor/go.elastic.co/apm/version.go b/vendor/go.elastic.co/apm/version.go new file mode 100644 index 00000000000..4992c913715 --- /dev/null +++ b/vendor/go.elastic.co/apm/version.go @@ -0,0 +1,23 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package apm + +const ( + // AgentVersion is the Elastic APM Go Agent version. + AgentVersion = "1.7.2" +) diff --git a/vendor/go.elastic.co/fastjson/.travis.yml b/vendor/go.elastic.co/fastjson/.travis.yml new file mode 100644 index 00000000000..9949d26e09c --- /dev/null +++ b/vendor/go.elastic.co/fastjson/.travis.yml @@ -0,0 +1,9 @@ +language: go +go_import_path: go.elastic.co/fastjson + +go: + - stable + - "1.8.x" + +script: + - go test -v ./... diff --git a/vendor/go.elastic.co/fastjson/LICENSE b/vendor/go.elastic.co/fastjson/LICENSE new file mode 100644 index 00000000000..f44e6a0fa12 --- /dev/null +++ b/vendor/go.elastic.co/fastjson/LICENSE @@ -0,0 +1,23 @@ +Copyright 2018 Elasticsearch BV + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +--- + +Copyright (c) 2016 Mail.Ru Group + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/go.elastic.co/fastjson/README.md b/vendor/go.elastic.co/fastjson/README.md new file mode 100644 index 00000000000..59ccccfaada --- /dev/null +++ b/vendor/go.elastic.co/fastjson/README.md @@ -0,0 +1,134 @@ +[![Travis-CI](https://travis-ci.org/elastic/go-fastjson.svg)](https://travis-ci.org/elastic/go-fastjson) + +# fastjson: fast JSON encoder for Go + +Package fastjson provides a library and code generator for fast JSON encoding. + +The supplied code generator (cmd/generate-fastjson) generates JSON marshalling +methods for all exported types within a specified package. + +## Requirements + +Go 1.8+ + +## License + +Apache 2.0. + +## Installation + +```bash +go get -u go.elastic.co/fastjson/... +``` + +## Code generation + +Package fastjson is intended to be used with the accompanying code generator, +cmd/generate-fastjson. This code generator will parse the Go code of a +specified package, and write out fastjson marshalling method (MarshalFastJSON) +definitions for the exported types in the package. + +You can provide your own custom marshalling logic for a type by defining a +MarshalFastJSON method for it. The generator will not generate methods for +those types with existing marshalling methods. + +### Usage + +``` +generate-fastjson + -f remove the output file if it exists + -o string + file to which output will be written (default "-") +``` + +### Custom omitempty extension + +The standard `json` struct tags defined by `encoding/json` are honoured, +enabling you to generate fastjson-marshalling code for your existing code. + +We extend the `omitempty` option by enabling you to define an unexported +method on your type `T`, `func (T) isZero() bool`, which will be called +to determine whether or not the value is considered empty. This enables +`omitempty` on non-pointer struct types. + +### Example + +Given the following package: + +```go +package example + +type Foo struct { + Bar Bar `json:",omitempty"` +} + +type Bar struct { + Baz Baz + Qux *Qux `json:"quux,omitempty"` +} + +func (b Bar) isZero() bool { + return b == (Bar{}) +} + +type Baz struct { +} + +func (Baz) MarshalFastJSON(w *fastjson.Writer) error { +} + +type Qux struct{} +``` + +Assuming we're in the package directory, we would generate the methods like so, +which will write a Go file to stdout: + +```bash +generate-fastjson . +``` + +Output: +```go +// Code generated by "generate-fastjson". DO NOT EDIT. + +package example + +import ( + "go.elastic.co/fastjson" +) + +func (v *Foo) MarshalFastJSON(w *fastjson.Writer) error { + w.RawByte('{') + if !v.Bar.isZero() { + w.RawString("\"Bar\":") + if err := v.Bar.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr == err + } + } + w.RawByte('}') + return nil +} + +func (v *Bar) MarshalFastJSON(w *fastjson.Writer) error { + var firstErr error + w.RawByte('{') + w.RawString("\"Baz\":") + if err := v.Baz.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr = err + } + if v.Qux != nil { + w.RawString(",\"quux\":") + if err := v.Qux.MarshalFastJSON(w); err != nil && firstErr == nil { + firstErr == err + } + } + w.RawByte('}') + return firstErr +} + +func (v *Qux) MarshalFastJSON(w *fastjson.Writer) error { + w.RawByte('{') + w.RawByte('}') + return nil +} +``` diff --git a/vendor/go.elastic.co/fastjson/doc.go b/vendor/go.elastic.co/fastjson/doc.go new file mode 100644 index 00000000000..4a3576be31e --- /dev/null +++ b/vendor/go.elastic.co/fastjson/doc.go @@ -0,0 +1,23 @@ +// Copyright 2018 Elasticsearch BV +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package fastjson provides a library for fast JSON encoding, +// optimised for static code generation. +// +// Fastjson functions and interfaces are structured such that +// all encoding appends to a buffer, enabling buffer reuse +// without forcing specific mechanisms such as sync.Pool. This +// enables zero-allocation encoding without incurring any +// concurrency overhead in certain applications. +package fastjson // import "go.elastic.co/fastjson" diff --git a/vendor/go.elastic.co/fastjson/go.mod b/vendor/go.elastic.co/fastjson/go.mod new file mode 100644 index 00000000000..0b130f35c1c --- /dev/null +++ b/vendor/go.elastic.co/fastjson/go.mod @@ -0,0 +1,3 @@ +module go.elastic.co/fastjson + +require github.com/pkg/errors v0.8.0 diff --git a/vendor/go.elastic.co/fastjson/go.sum b/vendor/go.elastic.co/fastjson/go.sum new file mode 100644 index 00000000000..3dfe462f062 --- /dev/null +++ b/vendor/go.elastic.co/fastjson/go.sum @@ -0,0 +1,2 @@ +github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/vendor/go.elastic.co/fastjson/marshaler.go b/vendor/go.elastic.co/fastjson/marshaler.go new file mode 100644 index 00000000000..7359e6dfde1 --- /dev/null +++ b/vendor/go.elastic.co/fastjson/marshaler.go @@ -0,0 +1,151 @@ +// Copyright 2018 Elasticsearch BV +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fastjson + +import ( + "encoding/json" + "fmt" + + "github.com/pkg/errors" +) + +// Marshaler defines an interface that types can implement to provide +// fast JSON marshaling. +type Marshaler interface { + // MarshalFastJSON writes a JSON representation of the type to w. + // + // MarshalFastJSON is expected to suppress any panics. Depending + // on the application, it may be expected that MarshalFastJSON + // writes valid JSON to w, even in error cases. + // + // The returned error will be propagated up through to callers of + // fastjson.Marshal. + MarshalFastJSON(w *Writer) error +} + +// Appender defines an interface that types can implement to append +// their JSON representation to a buffer. +type Appender interface { + // AppendJSON appends the JSON representation of the value to the + // buffer, and returns the extended buffer. + // + // AppendJSON is required not to panic or fail. + AppendJSON([]byte) []byte +} + +// Marshal marshals v as JSON to w. +// +// For all basic types, Marshal uses w's methods to marshal the values +// directly. If v implements Marshaler, its MarshalFastJSON method will +// be used; if v implements Appender, its AppendJSON method will be used, +// and it is assumed to append valid JSON. As a final resort, we use +// json.Marshal. +// +// Where json.Marshal is used internally (see above), errors or panics +// produced by json.Marshal will be encoded as JSON objects, with special keys +// "__ERROR__" for errors, and "__PANIC__" for panics. e.g. if json.Marshal +// panics due to a broken json.Marshaler implementation or assumption, then +// Marshal will encode the panic as +// +// {"__PANIC__": "panic calling MarshalJSON for type Foo: reason"} +// +// Marshal returns the first error encountered. +func Marshal(w *Writer, v interface{}) error { + switch v := v.(type) { + case nil: + w.RawString("null") + case string: + w.String(v) + case uint: + w.Uint64(uint64(v)) + case uint8: + w.Uint64(uint64(v)) + case uint16: + w.Uint64(uint64(v)) + case uint32: + w.Uint64(uint64(v)) + case uint64: + w.Uint64(v) + case int: + w.Int64(int64(v)) + case int8: + w.Int64(int64(v)) + case int16: + w.Int64(int64(v)) + case int32: + w.Int64(int64(v)) + case int64: + w.Int64(v) + case float32: + w.Float32(v) + case float64: + w.Float64(v) + case bool: + w.Bool(v) + case map[string]interface{}: + if v == nil { + w.RawString("null") + return nil + } + w.RawByte('{') + var firstErr error + first := true + for k, v := range v { + if first { + first = false + } else { + w.RawByte(',') + } + w.String(k) + w.RawByte(':') + if err := Marshal(w, v); err != nil && firstErr == nil { + firstErr = err + } + } + w.RawByte('}') + return firstErr + case Marshaler: + return v.MarshalFastJSON(w) + case Appender: + w.buf = v.AppendJSON(w.buf) + default: + return marshalReflect(w, v) + } + return nil +} + +func marshalReflect(w *Writer, v interface{}) (result error) { + defer func() { + if r := recover(); r != nil { + err, ok := r.(error) + if !ok { + err = fmt.Errorf("%s", r) + } + result = errors.Wrapf(err, "panic calling MarshalJSON for type %T", v) + w.RawString(`{"__PANIC__":`) + w.String(fmt.Sprint(result)) + w.RawByte('}') + } + }() + raw, err := json.Marshal(v) + if err != nil { + w.RawString(`{"__ERROR__":`) + w.String(fmt.Sprint(err)) + w.RawByte('}') + return err + } + w.RawBytes(raw) + return nil +} diff --git a/vendor/go.elastic.co/fastjson/writer.go b/vendor/go.elastic.co/fastjson/writer.go new file mode 100644 index 00000000000..e66731d9f80 --- /dev/null +++ b/vendor/go.elastic.co/fastjson/writer.go @@ -0,0 +1,181 @@ +// Copyright 2018 Elasticsearch BV +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fastjson + +import ( + "strconv" + "time" + "unicode/utf8" +) + +// Writer is a JSON writer, appending to an internal buffer. +// +// Writer is not safe for concurrent use. A Writer can be +// reset and reused, which will reuse the underlying buffer. +type Writer struct { + buf []byte +} + +// Bytes returns the internal buffer. The result is invalidated when Reset is called. +func (w *Writer) Bytes() []byte { + return w.buf +} + +// Size returns the current size of the buffer. Size is typically used in conjunction +// with Rewind, to mark a position to which the writer may later be rewound. +func (w *Writer) Size() int { + return len(w.buf) +} + +// Rewind rewinds the buffer such that it has size bytes, dropping everything proceeding. +func (w *Writer) Rewind(size int) { + w.buf = w.buf[:size] +} + +// Reset resets the internal []byte buffer to empty. +func (w *Writer) Reset() { + w.buf = w.buf[:0] +} + +// RawByte appends c to the buffer. +func (w *Writer) RawByte(c byte) { + w.buf = append(w.buf, c) +} + +// RawBytes appends data, unmodified, to the buffer. +func (w *Writer) RawBytes(data []byte) { + w.buf = append(w.buf, data...) +} + +// RawString appends s to the buffer. +func (w *Writer) RawString(s string) { + w.buf = append(w.buf, s...) +} + +// Uint64 appends n to the buffer. +func (w *Writer) Uint64(n uint64) { + w.buf = strconv.AppendUint(w.buf, uint64(n), 10) +} + +// Int64 appends n to the buffer. +func (w *Writer) Int64(n int64) { + w.buf = strconv.AppendInt(w.buf, int64(n), 10) +} + +// Float32 appends n to the buffer. +func (w *Writer) Float32(n float32) { + w.buf = strconv.AppendFloat(w.buf, float64(n), 'g', -1, 32) +} + +// Float64 appends n to the buffer. +func (w *Writer) Float64(n float64) { + w.buf = strconv.AppendFloat(w.buf, float64(n), 'g', -1, 64) +} + +// Bool appends v to the buffer. +func (w *Writer) Bool(v bool) { + w.buf = strconv.AppendBool(w.buf, v) +} + +// Time appends t to the buffer, formatted according to layout. +// +// The encoded time is not surrounded by quotes; it is the +// responsibility of the caller to ensure the formatted time is +// quoted as necessary. +func (w *Writer) Time(t time.Time, layout string) { + w.buf = t.AppendFormat(w.buf, layout) +} + +// String appends s, quoted and escaped, to the buffer. +func (w *Writer) String(s string) { + w.RawByte('"') + w.StringContents(s) + w.RawByte('"') +} + +// Note: code below taken from mailru/easyjson, adapted to use Writer. + +const chars = "0123456789abcdef" + +func isNotEscapedSingleChar(c byte, escapeHTML bool) bool { + // Note: might make sense to use a table if there are more chars to escape. With 4 chars + // it benchmarks the same. + if escapeHTML { + return c != '<' && c != '>' && c != '&' && c != '\\' && c != '"' && c >= 0x20 && c < utf8.RuneSelf + } + return c != '\\' && c != '"' && c >= 0x20 && c < utf8.RuneSelf +} + +// StringContents is the same as String, but without the surrounding quotes. +func (w *Writer) StringContents(s string) { + // Portions of the string that contain no escapes are appended as byte slices. + + p := 0 // last non-escape symbol + + for i := 0; i < len(s); { + c := s[i] + + if isNotEscapedSingleChar(c, true) { + // single-width character, no escaping is required + i++ + continue + } else if c < utf8.RuneSelf { + // single-with character, need to escape + w.RawString(s[p:i]) + switch c { + case '\t': + w.RawString(`\t`) + case '\r': + w.RawString(`\r`) + case '\n': + w.RawString(`\n`) + case '\\': + w.RawString(`\\`) + case '"': + w.RawString(`\"`) + default: + w.RawString(`\u00`) + w.RawByte(chars[c>>4]) + w.RawByte(chars[c&0xf]) + } + + i++ + p = i + continue + } + + // broken utf + runeValue, runeWidth := utf8.DecodeRuneInString(s[i:]) + if runeValue == utf8.RuneError && runeWidth == 1 { + w.RawString(s[p:i]) + w.RawString(`\ufffd`) + i++ + p = i + continue + } + + // jsonp stuff - tab separator and line separator + if runeValue == '\u2028' || runeValue == '\u2029' { + w.RawString(s[p:i]) + w.RawString(`\u202`) + w.RawByte(chars[runeValue&0xf]) + i += runeWidth + p = i + continue + } + i += runeWidth + } + w.RawString(s[p:]) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 9be44254b45..c41813e4481 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -118,6 +118,8 @@ github.com/akavel/rsrc/ico github.com/andrewkroh/sys/windows/svc/eventlog # github.com/antlr/antlr4 v0.0.0-20200225173536-225249fdaef5 github.com/antlr/antlr4/runtime/Go/antlr +# github.com/armon/go-radix v1.0.0 +github.com/armon/go-radix # github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 github.com/armon/go-socks5 # github.com/aws/aws-lambda-go v1.6.0 @@ -736,6 +738,12 @@ github.com/samuel/go-thrift/parser github.com/sanathkr/go-yaml # github.com/sanathkr/yaml v1.0.1-0.20170819201035-0056894fa522 github.com/sanathkr/yaml +# github.com/santhosh-tekuri/jsonschema v1.2.4 +github.com/santhosh-tekuri/jsonschema +github.com/santhosh-tekuri/jsonschema/decoders +github.com/santhosh-tekuri/jsonschema/formats +github.com/santhosh-tekuri/jsonschema/loader +github.com/santhosh-tekuri/jsonschema/mediatypes # github.com/shirou/gopsutil v2.19.11+incompatible github.com/shirou/gopsutil/disk github.com/shirou/gopsutil/internal/common @@ -752,6 +760,7 @@ github.com/stretchr/objx github.com/stretchr/testify/assert github.com/stretchr/testify/mock github.com/stretchr/testify/require +github.com/stretchr/testify/suite # github.com/tsg/go-daemon v0.0.0-20200207173439-e704b93fd89b github.com/tsg/go-daemon # github.com/tsg/gopacket v0.0.0-20190320122513-dd3d0e41124a @@ -811,6 +820,32 @@ github.com/yuin/gopher-lua github.com/yuin/gopher-lua/ast github.com/yuin/gopher-lua/parse github.com/yuin/gopher-lua/pm +# go.elastic.co/apm v1.7.2 +go.elastic.co/apm +go.elastic.co/apm/apmconfig +go.elastic.co/apm/apmtest +go.elastic.co/apm/internal/apmcontext +go.elastic.co/apm/internal/apmhostutil +go.elastic.co/apm/internal/apmhttputil +go.elastic.co/apm/internal/apmlog +go.elastic.co/apm/internal/apmschema +go.elastic.co/apm/internal/apmstrings +go.elastic.co/apm/internal/apmversion +go.elastic.co/apm/internal/configutil +go.elastic.co/apm/internal/iochan +go.elastic.co/apm/internal/pkgerrorsutil +go.elastic.co/apm/internal/ringbuffer +go.elastic.co/apm/internal/wildcard +go.elastic.co/apm/model +go.elastic.co/apm/stacktrace +go.elastic.co/apm/transport +go.elastic.co/apm/transport/transporttest +# go.elastic.co/apm/module/apmelasticsearch v1.7.2 +go.elastic.co/apm/module/apmelasticsearch +# go.elastic.co/apm/module/apmhttp v1.7.2 +go.elastic.co/apm/module/apmhttp +# go.elastic.co/fastjson v1.0.0 +go.elastic.co/fastjson # go.opencensus.io v0.22.2 go.opencensus.io go.opencensus.io/internal From e6d47873fc9004763095555a8b8a766a91c20b3c Mon Sep 17 00:00:00 2001 From: Lee Hinman <57081003+leehinman@users.noreply.github.com> Date: Mon, 4 May 2020 08:33:27 -0500 Subject: [PATCH 087/116] [Filebeat] Improve ECS categorization field mappings for netflow module (#18108) * Improve ECS categorization field mappings for netflow module - event.category : make array and add network - event.type - related.ip Closes #16135 --- CHANGELOG.next.asciidoc | 1 + x-pack/filebeat/input/netflow/convert.go | 13 +- ...-extended-uniflow-template-256.golden.json | 32 +- .../IPFIX-Barracuda-firewall.golden.json | 128 +++- ...IPFIX-Mikrotik-RouterOS-6.39.2.golden.json | 628 +++++++++++++++--- ...er-with-variable-length-fields.golden.json | 48 +- .../golden/IPFIX-Nokia-BRAS.golden.json | 16 +- .../golden/IPFIX-OpenBSD-pflow.golden.json | 416 ++++++++++-- .../testdata/golden/IPFIX-Procera.golden.json | 128 +++- ...are-virtual-distributed-switch.golden.json | 74 ++- .../IPFIX-YAF-basic-with-applabel.golden.json | 37 +- ...igured-with-include_flowset_id.golden.json | 48 +- ...Juniper-MX240-JunOS-15.1-R6-S3.golden.json | 5 +- .../IPFIX-vIPtela-with-VPN-id.golden.json | 16 +- .../netflow/testdata/golden/IPFIX.golden.json | 197 +++++- ...w-9-Cisco-1941-K9-release-15.1.golden.json | 464 +++++++++++-- .../golden/Netflow-9-Cisco-ASA-2.golden.json | 304 +++++++-- .../golden/Netflow-9-Cisco-ASA.golden.json | 224 ++++++- ...00-series-options-template-256.golden.json | 95 ++- ...o-ASR-9000-series-template-260.golden.json | 336 ++++++++-- .../Netflow-9-Cisco-ASR1001--X.golden.json | 400 +++++++++-- ...tflow-9-Cisco-NBAR-flowset-262.golden.json | 80 ++- ...isco-NBAR-options-template-260.golden.json | 75 ++- .../golden/Netflow-9-Cisco-WLC.golden.json | 190 ++++-- ...flow-9-Fortigate-FortiOS-5.2.1.golden.json | 21 +- ...-9-Fortigate-FortiOS-54x-appid.golden.json | 272 +++++++- ...9-H3C-Netstream-with-varstring.golden.json | 16 +- .../testdata/golden/Netflow-9-H3C.golden.json | 256 ++++++- .../Netflow-9-Huawei-Netstream.golden.json | 16 +- .../golden/Netflow-9-IE150-IE151.golden.json | 32 +- ...et-in-large-zero-filled-packet.golden.json | 16 +- ...Palo-Alto-PAN--OS-with-app--id.golden.json | 128 +++- .../golden/Netflow-9-Streamcore.golden.json | 64 +- ...ti-Edgerouter-with-MPLS-labels.golden.json | 256 ++++++- ...etflow-9-field-layer2segmentid.golden.json | 16 +- ..._netflow-reduced-size-encoding.golden.json | 192 +++++- .../golden/Netflow-9-macaddress.golden.json | 469 +++++++++++-- ...w-9-multiple-netflow-exporters.golden.json | 127 +++- .../Netflow-9-nprobe-DPI-L7.golden.json | 16 +- ...ons-template-with-scope-fields.golden.json | 5 +- ...-template-with-0-length-fields.golden.json | 160 ++++- .../golden/Netflow-9-valid-01.golden.json | 106 ++- ...late-with-0-scope-field-length.golden.json | 5 +- .../golden/ipfix_cisco.pcap.golden.json | 145 +++- ...flow9_ubiquiti_edgerouter.pcap.golden.json | 160 ++++- 45 files changed, 5572 insertions(+), 861 deletions(-) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 29e90199083..78a1385e589 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -285,6 +285,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Improve ECS categorization field mappings in rabbitmq module. {issue}16178[16178] {pull}17916[17916] - Improve ECS categorization field mappings in redis module. {issue}16179[16179] {pull}17918[17918] - Improve ECS categorization field mappings for zeek module. {issue}16029[16029] {pull}17738[17738] +- Improve ECS categorization field mappings for netflow module. {issue}16135[16135] {pull}18108[18108] *Heartbeat* diff --git a/x-pack/filebeat/input/netflow/convert.go b/x-pack/filebeat/input/netflow/convert.go index cfc3fd1736a..69d7a2ea6cd 100644 --- a/x-pack/filebeat/input/netflow/convert.go +++ b/x-pack/filebeat/input/netflow/convert.go @@ -66,9 +66,12 @@ func toBeatEventCommon(flow record.Record) (event beat.Event) { ecsEvent := common.MapStr{ "created": flow.Timestamp, "kind": "event", - "category": "network_traffic", + "category": []string{"network_traffic", "network"}, "action": flow.Fields["type"], } + if ecsEvent["action"] == "netflow_flow" { + ecsEvent["type"] = []string{"connection"} + } // ECS Fields -- device ecsDevice := common.MapStr{} if exporter, ok := getKeyString(flow.Exporter, "address"); ok { @@ -155,9 +158,10 @@ func flowToBeatEvent(flow record.Record) (event beat.Event) { } flowDirection, hasFlowDirection := getKeyUint64(flow.Fields, "flowDirection") - // ECS Fields -- source and destination + // ECS Fields -- source, destination & related.ip ecsSource := common.MapStr{} ecsDest := common.MapStr{} + var relatedIP []net.IP // Populate first with WLAN fields if hasFlowDirection { @@ -189,6 +193,7 @@ func flowToBeatEvent(flow record.Record) (event beat.Event) { // Regular IPv4 fields if ip, found := getKeyIP(flow.Fields, "sourceIPv4Address"); found { ecsSource["ip"] = ip + relatedIP = append(relatedIP, ip) ecsSource["locality"] = getIPLocality(ip).String() } if sourcePort, found := getKeyUint64(flow.Fields, "sourceTransportPort"); found { @@ -201,6 +206,7 @@ func flowToBeatEvent(flow record.Record) (event beat.Event) { // ECS Fields -- destination if ip, found := getKeyIP(flow.Fields, "destinationIPv4Address"); found { ecsDest["ip"] = ip + relatedIP = append(relatedIP, ip) ecsDest["locality"] = getIPLocality(ip).String() } if destPort, found := getKeyUint64(flow.Fields, "destinationTransportPort"); found { @@ -313,6 +319,9 @@ func flowToBeatEvent(flow record.Record) (event beat.Event) { if len(ecsNetwork) > 0 { event.Fields["network"] = ecsNetwork } + if len(relatedIP) > 0 { + event.Fields["related"] = common.MapStr{"ip": relatedIP} + } return } diff --git a/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-Barracuda-extended-uniflow-template-256.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-Barracuda-extended-uniflow-template-256.golden.json index 37d62175d9b..3bdc7c5d159 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-Barracuda-extended-uniflow-template-256.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-Barracuda-extended-uniflow-template-256.golden.json @@ -12,10 +12,16 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-04-18T08:16:47Z", "duration": 0, - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "kSpZ1WuBhjc", @@ -70,6 +76,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.236.5.4", + "64.235.151.76" + ] + }, "source": { "bytes": 0, "ip": "10.236.5.4", @@ -93,10 +105,16 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-04-18T08:16:47Z", "duration": 0, - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "kSpZ1WuBhjc", @@ -151,6 +169,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "64.235.151.76", + "10.236.5.4" + ] + }, "source": { "bytes": 0, "ip": "64.235.151.76", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-Barracuda-firewall.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-Barracuda-firewall.golden.json index ad5333cfc04..3814fa8c843 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-Barracuda-firewall.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-Barracuda-firewall.golden.json @@ -12,10 +12,16 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-06-29T13:58:28Z", "duration": 20269000000, - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "2vFIarATx_4", @@ -58,6 +64,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.99.130.239", + "10.99.252.50" + ] + }, "source": { "bytes": 0, "ip": "10.99.130.239", @@ -81,10 +93,16 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-06-29T13:58:28Z", "duration": 20269000000, - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "2vFIarATx_4", @@ -127,6 +145,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.99.252.50", + "10.99.130.239" + ] + }, "source": { "bytes": 81, "ip": "10.99.252.50", @@ -150,10 +174,16 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-06-29T13:58:28Z", "duration": 20306000000, - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "wU3G8idsscw", @@ -196,6 +226,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.99.130.239", + "10.98.243.20" + ] + }, "source": { "bytes": 0, "ip": "10.99.130.239", @@ -219,10 +255,16 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-06-29T13:58:28Z", "duration": 20306000000, - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "wU3G8idsscw", @@ -265,6 +307,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.98.243.20", + "10.99.130.239" + ] + }, "source": { "bytes": 81, "ip": "10.98.243.20", @@ -288,10 +336,16 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-06-29T13:58:28Z", "duration": 20317000000, - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "rOmj8EdZ2dc", @@ -334,6 +388,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.99.168.140", + "10.98.243.20" + ] + }, "source": { "bytes": 0, "ip": "10.99.168.140", @@ -357,10 +417,16 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-06-29T13:58:28Z", "duration": 20317000000, - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "rOmj8EdZ2dc", @@ -403,6 +469,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.98.243.20", + "10.99.168.140" + ] + }, "source": { "bytes": 113, "ip": "10.98.243.20", @@ -426,10 +498,16 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-06-29T13:58:28Z", "duration": 20368000000, - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "JE7pThaMwJY", @@ -472,6 +550,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.99.168.140", + "10.98.243.20" + ] + }, "source": { "bytes": 0, "ip": "10.99.168.140", @@ -495,10 +579,16 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-06-29T13:58:28Z", "duration": 20368000000, - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "JE7pThaMwJY", @@ -541,6 +631,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.98.243.20", + "10.99.168.140" + ] + }, "source": { "bytes": 113, "ip": "10.98.243.20", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-Mikrotik-RouterOS-6.39.2.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-Mikrotik-RouterOS-6.39.2.golden.json index 1f990e524ab..5b7004c43f3 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-Mikrotik-RouterOS-6.39.2.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-Mikrotik-RouterOS-6.39.2.golden.json @@ -12,9 +12,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "1SREAwMSn_Y", @@ -57,6 +63,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.10.8.197", + "192.168.128.17" + ] + }, "source": { "bytes": 152, "ip": "10.10.8.197", @@ -79,9 +91,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "-1ecQ0Y-YzY", @@ -124,6 +142,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.35.143", + "192.168.230.216" + ] + }, "source": { "bytes": 502, "ip": "192.168.35.143", @@ -146,9 +170,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "_ztnBsqvzw4", @@ -191,6 +221,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.10.6.11", + "192.168.35.143" + ] + }, "source": { "bytes": 2233, "ip": "10.10.6.11", @@ -213,9 +249,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "83jerlRbQig", @@ -258,6 +300,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.128.17", + "192.168.230.216" + ] + }, "source": { "bytes": 152, "ip": "192.168.128.17", @@ -280,9 +328,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "r6DcuKSlKG8", @@ -325,6 +379,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.10.8.220", + "172.20.5.191" + ] + }, "source": { "bytes": 79724, "ip": "10.10.8.220", @@ -347,9 +407,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "MJV4se1d1EY", @@ -392,6 +458,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.20.4.199", + "172.20.4.1" + ] + }, "source": { "bytes": 161, "ip": "172.20.4.199", @@ -414,9 +486,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "MJV4se1d1EY", @@ -459,6 +537,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.20.4.1", + "172.20.4.199" + ] + }, "source": { "bytes": 245, "ip": "172.20.4.1", @@ -481,9 +565,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Md4y9RxWsu0", @@ -526,6 +616,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.20.4.30", + "10.10.8.34" + ] + }, "source": { "bytes": 504, "ip": "172.20.4.30", @@ -548,9 +644,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "_XZysP4InTc", @@ -593,6 +695,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.10.8.105", + "172.20.4.30" + ] + }, "source": { "bytes": 784, "ip": "10.10.8.105", @@ -615,9 +723,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "_XZysP4InTc", @@ -660,6 +774,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.20.4.30", + "10.10.8.105" + ] + }, "source": { "bytes": 433, "ip": "172.20.4.30", @@ -682,9 +802,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "5stvUzTWY8c", @@ -727,6 +853,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.10.7.11", + "192.168.183.199" + ] + }, "source": { "bytes": 196, "ip": "10.10.7.11", @@ -749,9 +881,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "VdPCBSYnnS0", @@ -794,6 +932,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.183.199", + "192.168.230.216" + ] + }, "source": { "bytes": 206, "ip": "192.168.183.199", @@ -816,9 +960,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "asoP1PL3Pao", @@ -861,6 +1011,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.10.8.34", + "172.20.4.30" + ] + }, "source": { "bytes": 504, "ip": "10.10.8.34", @@ -883,9 +1039,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "r6DcuKSlKG8", @@ -928,6 +1090,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.20.5.191", + "10.10.8.220" + ] + }, "source": { "bytes": 3539, "ip": "172.20.5.191", @@ -950,9 +1118,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "4AA5ETLDkm0", @@ -995,6 +1169,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.20.4.1", + "255.255.255.255" + ] + }, "source": { "bytes": 495, "ip": "172.20.4.1", @@ -1017,9 +1197,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "4AA5ETLDkm0", @@ -1062,6 +1248,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.20.4.1", + "255.255.255.255" + ] + }, "source": { "bytes": 330, "ip": "172.20.4.1", @@ -1084,9 +1276,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "BaTGW6h8V9s", @@ -1129,6 +1327,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.30.0.1", + "255.255.255.255" + ] + }, "source": { "bytes": 435, "ip": "172.30.0.1", @@ -1151,9 +1355,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "BaTGW6h8V9s", @@ -1196,6 +1406,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.30.0.1", + "255.255.255.255" + ] + }, "source": { "bytes": 290, "ip": "172.30.0.1", @@ -1218,9 +1434,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "a0peNOTOYXA", @@ -1263,6 +1485,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.10.6.1", + "255.255.255.255" + ] + }, "source": { "bytes": 495, "ip": "10.10.6.1", @@ -1285,9 +1513,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "a0peNOTOYXA", @@ -1330,6 +1564,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.10.6.1", + "255.255.255.255" + ] + }, "source": { "bytes": 330, "ip": "10.10.6.1", @@ -1352,9 +1592,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "rX81_0wnl4c", @@ -1397,6 +1643,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.10.7.1", + "255.255.255.255" + ] + }, "source": { "bytes": 495, "ip": "10.10.7.1", @@ -1419,9 +1671,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "rX81_0wnl4c", @@ -1464,6 +1722,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.10.7.1", + "255.255.255.255" + ] + }, "source": { "bytes": 330, "ip": "10.10.7.1", @@ -1486,9 +1750,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "7EW3D8kjT4Q", @@ -1531,6 +1801,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.10.8.1", + "255.255.255.255" + ] + }, "source": { "bytes": 495, "ip": "10.10.8.1", @@ -1553,9 +1829,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "7EW3D8kjT4Q", @@ -1598,6 +1880,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.10.8.1", + "255.255.255.255" + ] + }, "source": { "bytes": 330, "ip": "10.10.8.1", @@ -1620,9 +1908,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "JacJ1_FgpYg", @@ -1665,6 +1959,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.20.0.1", + "255.255.255.255" + ] + }, "source": { "bytes": 495, "ip": "10.20.0.1", @@ -1687,9 +1987,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "JacJ1_FgpYg", @@ -1732,6 +2038,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.20.0.1", + "255.255.255.255" + ] + }, "source": { "bytes": 330, "ip": "10.20.0.1", @@ -1754,9 +2066,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "38frmBtEgfI", @@ -1799,6 +2117,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.10.10.1", + "255.255.255.255" + ] + }, "source": { "bytes": 495, "ip": "10.10.10.1", @@ -1821,9 +2145,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "38frmBtEgfI", @@ -1866,6 +2196,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.10.10.1", + "255.255.255.255" + ] + }, "source": { "bytes": 330, "ip": "10.10.10.1", @@ -1886,9 +2222,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "RlrAo_U1Y14", @@ -1947,9 +2289,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "RlrAo_U1Y14", @@ -2008,9 +2356,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "RlrAo_U1Y14", @@ -2069,9 +2423,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "RlrAo_U1Y14", @@ -2130,9 +2490,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "RlrAo_U1Y14", @@ -2191,9 +2557,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "RlrAo_U1Y14", @@ -2252,9 +2624,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "RlrAo_U1Y14", @@ -2313,9 +2691,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "RlrAo_U1Y14", @@ -2374,9 +2758,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "RlrAo_U1Y14", @@ -2435,9 +2825,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "RlrAo_U1Y14", @@ -2496,9 +2892,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "RlrAo_U1Y14", @@ -2557,9 +2959,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "RlrAo_U1Y14", @@ -2618,9 +3026,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "RlrAo_U1Y14", @@ -2679,9 +3093,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "RlrAo_U1Y14", @@ -2740,9 +3160,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "RlrAo_U1Y14", @@ -2801,9 +3227,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "RlrAo_U1Y14", @@ -2862,9 +3294,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "RlrAo_U1Y14", @@ -2923,9 +3361,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-19T16:18:08Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "RlrAo_U1Y14", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-Netscaler-with-variable-length-fields.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-Netscaler-with-variable-length-fields.golden.json index 5037ae27ecb..d1ccaac0791 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-Netscaler-with-variable-length-fields.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-Netscaler-with-variable-length-fields.golden.json @@ -12,9 +12,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-11-11T12:09:19Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "8wXIKNz6u_8", @@ -80,6 +86,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.1", + "10.0.0.1" + ] + }, "source": { "bytes": 40, "ip": "192.168.0.1", @@ -102,9 +114,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-11-11T12:09:19Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "8wXIKNz6u_8", @@ -158,6 +176,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.0.0.1", + "192.168.0.1" + ] + }, "source": { "bytes": 1525, "ip": "10.0.0.1", @@ -180,9 +204,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-11-11T12:09:19Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "8wXIKNz6u_8", @@ -248,6 +278,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.1", + "10.0.0.1" + ] + }, "source": { "bytes": 1541, "ip": "192.168.0.1", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-Nokia-BRAS.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-Nokia-BRAS.golden.json index 3c4a77c41b6..b77d79ff35c 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-Nokia-BRAS.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-Nokia-BRAS.golden.json @@ -12,9 +12,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-12-14T07:23:45Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "aVnWxMM8qxI", @@ -50,6 +56,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.0.1.228", + "10.0.0.34" + ] + }, "source": { "ip": "10.0.1.228", "locality": "private", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-OpenBSD-pflow.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-OpenBSD-pflow.golden.json index e8331bf0f97..41fb9b9c48d 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-OpenBSD-pflow.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-OpenBSD-pflow.golden.json @@ -12,9 +12,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:30:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "_dzJqQAoWYk", @@ -53,6 +59,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.17", + "192.168.0.1" + ] + }, "source": { "bytes": 373, "ip": "192.168.0.17", @@ -75,9 +87,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:30:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "_dzJqQAoWYk", @@ -116,6 +134,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.1", + "192.168.0.17" + ] + }, "source": { "bytes": 6634, "ip": "192.168.0.1", @@ -138,9 +162,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:30:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "iSYE82PBcbQ", @@ -179,6 +209,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.17", + "192.168.0.1" + ] + }, "source": { "bytes": 453, "ip": "192.168.0.17", @@ -201,9 +237,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:30:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "iSYE82PBcbQ", @@ -242,6 +284,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.1", + "192.168.0.17" + ] + }, "source": { "bytes": 10893, "ip": "192.168.0.1", @@ -264,9 +312,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:30:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "iSYE82PBcbQ", @@ -305,6 +359,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.17", + "192.168.0.1" + ] + }, "source": { "bytes": 453, "ip": "192.168.0.17", @@ -327,9 +387,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:30:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "iSYE82PBcbQ", @@ -368,6 +434,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.1", + "192.168.0.17" + ] + }, "source": { "bytes": 10893, "ip": "192.168.0.1", @@ -390,9 +462,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:30:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "L_N7tNeOZwc", @@ -431,6 +509,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.17", + "192.168.0.1" + ] + }, "source": { "bytes": 373, "ip": "192.168.0.17", @@ -453,9 +537,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:30:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "L_N7tNeOZwc", @@ -494,6 +584,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.1", + "192.168.0.17" + ] + }, "source": { "bytes": 6780, "ip": "192.168.0.1", @@ -516,9 +612,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:30:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "L_N7tNeOZwc", @@ -557,6 +659,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.17", + "192.168.0.1" + ] + }, "source": { "bytes": 373, "ip": "192.168.0.17", @@ -579,9 +687,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:30:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "L_N7tNeOZwc", @@ -620,6 +734,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.1", + "192.168.0.17" + ] + }, "source": { "bytes": 6780, "ip": "192.168.0.1", @@ -642,9 +762,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:30:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Dsp4RZAzcPQ", @@ -683,6 +809,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.17", + "192.168.0.1" + ] + }, "source": { "bytes": 373, "ip": "192.168.0.17", @@ -705,9 +837,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:30:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Dsp4RZAzcPQ", @@ -746,6 +884,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.1", + "192.168.0.17" + ] + }, "source": { "bytes": 7319, "ip": "192.168.0.1", @@ -768,9 +912,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:30:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Dsp4RZAzcPQ", @@ -809,6 +959,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.17", + "192.168.0.1" + ] + }, "source": { "bytes": 373, "ip": "192.168.0.17", @@ -831,9 +987,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:30:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Dsp4RZAzcPQ", @@ -872,6 +1034,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.1", + "192.168.0.17" + ] + }, "source": { "bytes": 7319, "ip": "192.168.0.1", @@ -894,9 +1062,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:30:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "B9Jsqhany8Q", @@ -935,6 +1109,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.17", + "192.168.0.1" + ] + }, "source": { "bytes": 333, "ip": "192.168.0.17", @@ -957,9 +1137,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:30:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "B9Jsqhany8Q", @@ -998,6 +1184,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.1", + "192.168.0.17" + ] + }, "source": { "bytes": 1833, "ip": "192.168.0.1", @@ -1020,9 +1212,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:30:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "B9Jsqhany8Q", @@ -1061,6 +1259,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.17", + "192.168.0.1" + ] + }, "source": { "bytes": 333, "ip": "192.168.0.17", @@ -1083,9 +1287,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:30:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "B9Jsqhany8Q", @@ -1124,6 +1334,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.1", + "192.168.0.17" + ] + }, "source": { "bytes": 1833, "ip": "192.168.0.1", @@ -1146,9 +1362,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:30:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "O7k79Py4ef0", @@ -1187,6 +1409,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.17", + "192.168.0.1" + ] + }, "source": { "bytes": 453, "ip": "192.168.0.17", @@ -1209,9 +1437,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:30:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "O7k79Py4ef0", @@ -1250,6 +1484,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.1", + "192.168.0.17" + ] + }, "source": { "bytes": 10550, "ip": "192.168.0.1", @@ -1272,9 +1512,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:30:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "O7k79Py4ef0", @@ -1313,6 +1559,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.17", + "192.168.0.1" + ] + }, "source": { "bytes": 453, "ip": "192.168.0.17", @@ -1335,9 +1587,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:30:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "O7k79Py4ef0", @@ -1376,6 +1634,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.1", + "192.168.0.17" + ] + }, "source": { "bytes": 10550, "ip": "192.168.0.1", @@ -1398,9 +1662,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:30:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "T1etbJ4WSI0", @@ -1439,6 +1709,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.17", + "192.168.0.1" + ] + }, "source": { "bytes": 373, "ip": "192.168.0.17", @@ -1461,9 +1737,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:30:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "T1etbJ4WSI0", @@ -1502,6 +1784,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.1", + "192.168.0.17" + ] + }, "source": { "bytes": 6425, "ip": "192.168.0.1", @@ -1524,9 +1812,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:30:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "T1etbJ4WSI0", @@ -1565,6 +1859,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.17", + "192.168.0.1" + ] + }, "source": { "bytes": 373, "ip": "192.168.0.17", @@ -1587,9 +1887,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:30:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "T1etbJ4WSI0", @@ -1628,6 +1934,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.1", + "192.168.0.17" + ] + }, "source": { "bytes": 6425, "ip": "192.168.0.1", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-Procera.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-Procera.golden.json index 8295166e4c0..1ec8673c346 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-Procera.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-Procera.golden.json @@ -12,9 +12,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-04-15T03:30:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "gEodlN50y4w", @@ -62,6 +68,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "181.214.87.71", + "138.44.161.14" + ] + }, "source": { "ip": "181.214.87.71", "locality": "public", @@ -82,9 +94,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-04-15T03:30:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "GYmhjYyvaAI", @@ -132,6 +150,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "0.0.0.0", + "0.0.0.0" + ] + }, "source": { "ip": "0.0.0.0", "locality": "private", @@ -152,9 +176,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-04-15T03:30:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "qSSNfC38l0c", @@ -202,6 +232,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "5.188.11.35", + "138.44.161.14" + ] + }, "source": { "ip": "5.188.11.35", "locality": "public", @@ -222,9 +258,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-04-15T03:30:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Tv1jmZy2vn4", @@ -272,6 +314,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "206.117.25.89", + "138.44.161.14" + ] + }, "source": { "ip": "206.117.25.89", "locality": "public", @@ -292,9 +340,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-04-15T03:30:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "GYmhjYyvaAI", @@ -342,6 +396,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "0.0.0.0", + "0.0.0.0" + ] + }, "source": { "ip": "0.0.0.0", "locality": "private", @@ -362,9 +422,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-04-15T03:30:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "JhEHWMX5XwI", @@ -412,6 +478,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "185.232.29.199", + "138.44.161.14" + ] + }, "source": { "ip": "185.232.29.199", "locality": "public", @@ -432,9 +504,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-04-15T03:30:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Q_zyIhDZuIo", @@ -482,6 +560,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "177.188.228.137", + "138.44.161.14" + ] + }, "source": { "ip": "177.188.228.137", "locality": "public", @@ -502,9 +586,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-04-15T03:30:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "pNMKY7O9aVc", @@ -552,6 +642,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "138.44.161.14", + "138.44.161.13" + ] + }, "source": { "ip": "138.44.161.14", "locality": "public", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-VMware-virtual-distributed-switch.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-VMware-virtual-distributed-switch.golden.json index c77bc562aad..7f97270bb03 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-VMware-virtual-distributed-switch.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-VMware-virtual-distributed-switch.golden.json @@ -12,9 +12,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-22T12:17:52Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "-Sv1di8xiKE", @@ -62,6 +68,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.18.65.21", + "172.18.65.211" + ] + }, "source": { "bytes": 100, "ip": "172.18.65.21", @@ -84,9 +96,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-22T12:17:56Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "OQCLJ5IN83c", @@ -134,6 +152,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.18.65.91", + "172.18.65.255" + ] + }, "source": { "bytes": 229, "ip": "172.18.65.91", @@ -156,9 +180,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-22T12:17:56Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "OQCLJ5IN83c", @@ -206,6 +236,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.18.65.91", + "172.18.65.255" + ] + }, "source": { "bytes": 229, "ip": "172.18.65.91", @@ -228,9 +264,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-22T12:26:04Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "xcyYrM-QBl0", @@ -278,6 +320,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.18.65.21", + "224.0.0.252" + ] + }, "source": { "bytes": 104, "ip": "172.18.65.21", @@ -298,9 +346,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-22T12:26:04Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "y_Vml2vPNtw", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-YAF-basic-with-applabel.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-YAF-basic-with-applabel.golden.json index 95c1c37fb42..fa7eed00986 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-YAF-basic-with-applabel.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-YAF-basic-with-applabel.golden.json @@ -14,9 +14,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-25T13:03:38Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "QMH_S2K9KdI", @@ -63,6 +69,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.201", + "172.16.32.100" + ] + }, "source": { "bytes": 132, "ip": "172.16.32.201", @@ -87,9 +99,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-25T12:58:38Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "YlvEOsG0NHc", @@ -142,6 +160,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.100", + "172.16.32.215" + ] + }, "source": { "bytes": 172, "ip": "172.16.32.100", @@ -159,7 +183,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-25T13:03:33Z", "kind": "event" }, diff --git a/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-configured-with-include_flowset_id.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-configured-with-include_flowset_id.golden.json index 50892931663..1eda2ee228b 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-configured-with-include_flowset_id.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-configured-with-include_flowset_id.golden.json @@ -12,9 +12,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-11-11T12:09:19Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "8wXIKNz6u_8", @@ -80,6 +86,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.1", + "10.0.0.1" + ] + }, "source": { "bytes": 40, "ip": "192.168.0.1", @@ -102,9 +114,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-11-11T12:09:19Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "8wXIKNz6u_8", @@ -158,6 +176,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.0.0.1", + "192.168.0.1" + ] + }, "source": { "bytes": 1525, "ip": "10.0.0.1", @@ -180,9 +204,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-11-11T12:09:19Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "8wXIKNz6u_8", @@ -248,6 +278,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.1", + "10.0.0.1" + ] + }, "source": { "bytes": 1541, "ip": "192.168.0.1", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-options-template-from-Juniper-MX240-JunOS-15.1-R6-S3.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-options-template-from-Juniper-MX240-JunOS-15.1-R6-S3.golden.json index 763e20e774e..d4aa929699b 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-options-template-from-Juniper-MX240-JunOS-15.1-R6-S3.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-options-template-from-Juniper-MX240-JunOS-15.1-R6-S3.golden.json @@ -7,7 +7,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-06-01T15:11:53Z", "kind": "event" }, diff --git a/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-vIPtela-with-VPN-id.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-vIPtela-with-VPN-id.golden.json index b43b3a4f6b5..f477e8c3d37 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-vIPtela-with-VPN-id.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/IPFIX-vIPtela-with-VPN-id.golden.json @@ -12,9 +12,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-11-21T14:32:15Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "dO-Anbp9xpw", @@ -65,6 +71,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.113.7.54", + "172.16.21.27" + ] + }, "source": { "bytes": 775, "ip": "10.113.7.54", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/IPFIX.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/IPFIX.golden.json index c458b21dd9e..1fdb6707c2e 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/IPFIX.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/IPFIX.golden.json @@ -7,7 +7,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-05-13T11:20:26Z", "kind": "event" }, @@ -48,9 +51,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-05-13T11:20:26Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "ofdVXz7_x6E", @@ -93,6 +102,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.253.1", + "192.168.253.128" + ] + }, "source": { "bytes": 260, "ip": "192.168.253.1", @@ -115,9 +130,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-05-13T11:20:26Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "ofdVXz7_x6E", @@ -160,6 +181,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.253.128", + "192.168.253.1" + ] + }, "source": { "bytes": 1000, "ip": "192.168.253.128", @@ -182,9 +209,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-05-13T11:20:26Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "ztL93_3GZNs", @@ -227,6 +260,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.253.2", + "192.168.253.132" + ] + }, "source": { "bytes": 601, "ip": "192.168.253.2", @@ -249,9 +288,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-05-13T11:20:26Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "ztL93_3GZNs", @@ -294,6 +339,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.253.132", + "192.168.253.2" + ] + }, "source": { "bytes": 148, "ip": "192.168.253.132", @@ -316,9 +367,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-05-13T11:20:26Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "VANFUe1rklc", @@ -361,6 +418,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "54.214.9.161", + "192.168.253.132" + ] + }, "source": { "bytes": 5946, "ip": "54.214.9.161", @@ -383,9 +446,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-05-13T11:20:26Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "VANFUe1rklc", @@ -428,6 +497,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.253.132", + "54.214.9.161" + ] + }, "source": { "bytes": 2608, "ip": "192.168.253.132", @@ -450,9 +525,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-05-13T11:20:26Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "iDHwMSG6faQ", @@ -495,6 +576,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.253.130", + "10.4.36.64" + ] + }, "source": { "bytes": 60, "ip": "192.168.253.130", @@ -517,9 +604,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-05-13T11:20:28Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "ofdVXz7_x6E", @@ -562,6 +655,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.253.1", + "192.168.253.128" + ] + }, "source": { "bytes": 256, "ip": "192.168.253.1", @@ -584,9 +683,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-05-13T11:20:28Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "ofdVXz7_x6E", @@ -629,6 +734,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.253.128", + "192.168.253.1" + ] + }, "source": { "bytes": 1916, "ip": "192.168.253.128", @@ -651,9 +762,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-05-13T11:20:28Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "WgPN9s2D0jg", @@ -696,6 +813,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.253.1", + "192.168.253.128" + ] + }, "source": { "bytes": 168, "ip": "192.168.253.1", @@ -718,9 +841,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-05-13T11:20:28Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "WgPN9s2D0jg", @@ -763,6 +892,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.253.128", + "192.168.253.1" + ] + }, "source": { "bytes": 84, "ip": "192.168.253.128", @@ -785,9 +920,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-05-13T11:20:28Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "PSMPOofjjVU", @@ -830,6 +971,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.253.1", + "224.0.0.251" + ] + }, "source": { "bytes": 232, "ip": "192.168.253.1", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-1941-K9-release-15.1.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-1941-K9-release-15.1.golden.json index b3b9bec5c1c..ad5bbec160f 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-1941-K9-release-15.1.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-1941-K9-release-15.1.golden.json @@ -12,9 +12,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-03T17:03:39Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "BPlkuHwo9sU", @@ -56,6 +62,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.111", + "62.217.193.1" + ] + }, "source": { "bytes": 75, "ip": "192.168.0.111", @@ -79,9 +91,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-03T17:03:39Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "-PhJhHv5gvE", @@ -123,6 +141,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.111", + "62.217.193.65" + ] + }, "source": { "bytes": 75, "ip": "192.168.0.111", @@ -146,9 +170,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-03T17:03:39Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "zTrEnrxMnjo", @@ -190,6 +220,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.111", + "62.217.193.1" + ] + }, "source": { "bytes": 75, "ip": "192.168.0.111", @@ -213,9 +249,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-03T17:03:39Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "G4AVpSxBAVo", @@ -257,6 +299,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.111", + "62.217.193.65" + ] + }, "source": { "bytes": 75, "ip": "192.168.0.111", @@ -280,9 +328,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-03T17:03:39Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "2nQmjOOzSH0", @@ -324,6 +378,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "158.85.58.115", + "192.168.3.142" + ] + }, "source": { "bytes": 964, "ip": "158.85.58.115", @@ -347,9 +407,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-03T17:03:39Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "z7uHiA5SrD0", @@ -391,6 +457,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.88", + "216.58.212.195" + ] + }, "source": { "bytes": 2748, "ip": "192.168.0.88", @@ -414,9 +486,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-03T17:03:39Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "z7uHiA5SrD0", @@ -458,6 +536,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "216.58.212.195", + "192.168.0.88" + ] + }, "source": { "bytes": 2023, "ip": "216.58.212.195", @@ -481,9 +565,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-03T17:03:39Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "eyNcUtWu34I", @@ -525,6 +615,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.1.201", + "216.58.201.106" + ] + }, "source": { "bytes": 2180, "ip": "192.168.1.201", @@ -548,9 +644,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-03T17:03:39Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "eyNcUtWu34I", @@ -592,6 +694,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "216.58.201.106", + "192.168.1.201" + ] + }, "source": { "bytes": 700, "ip": "216.58.201.106", @@ -615,9 +723,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-03T17:03:39Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "i7e4W23LBGg", @@ -659,6 +773,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "52.236.33.163", + "192.168.2.118" + ] + }, "source": { "bytes": 161, "ip": "52.236.33.163", @@ -682,9 +802,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-03T17:03:39Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "ALOJ32qLh_s", @@ -726,6 +852,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.3.34", + "52.216.130.237" + ] + }, "source": { "bytes": 1764, "ip": "192.168.3.34", @@ -749,9 +881,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-03T17:03:39Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "h9s7TXaoMZw", @@ -793,6 +931,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "209.197.3.19", + "192.168.3.34" + ] + }, "source": { "bytes": 13811, "ip": "209.197.3.19", @@ -816,9 +960,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-03T17:03:39Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "ALOJ32qLh_s", @@ -860,6 +1010,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "52.216.130.237", + "192.168.3.34" + ] + }, "source": { "bytes": 4717, "ip": "52.216.130.237", @@ -883,9 +1039,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-03T17:03:39Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "2GPS5gJiF8g", @@ -927,6 +1089,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.157", + "172.217.23.232" + ] + }, "source": { "bytes": 2419, "ip": "192.168.0.157", @@ -950,9 +1118,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-03T17:03:39Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "2GPS5gJiF8g", @@ -994,6 +1168,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.217.23.232", + "192.168.0.157" + ] + }, "source": { "bytes": 5551, "ip": "172.217.23.232", @@ -1017,9 +1197,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-03T17:03:39Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "ughO0a0lrBw", @@ -1061,6 +1247,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "107.21.232.174", + "192.168.3.178" + ] + }, "source": { "bytes": 187, "ip": "107.21.232.174", @@ -1084,9 +1276,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-03T17:03:39Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "ughO0a0lrBw", @@ -1128,6 +1326,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.3.178", + "107.21.232.174" + ] + }, "source": { "bytes": 104, "ip": "192.168.3.178", @@ -1151,9 +1355,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-03T17:03:39Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Ie4W_7Snl8w", @@ -1195,6 +1405,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.2.118", + "95.0.145.242" + ] + }, "source": { "bytes": 4050, "ip": "192.168.2.118", @@ -1218,9 +1434,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-03T17:03:39Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Ie4W_7Snl8w", @@ -1262,6 +1484,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "95.0.145.242", + "192.168.2.118" + ] + }, "source": { "bytes": 3719, "ip": "95.0.145.242", @@ -1285,9 +1513,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-03T17:03:39Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "yokq763qB0U", @@ -1329,6 +1563,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.79", + "23.5.100.66" + ] + }, "source": { "bytes": 1402, "ip": "192.168.0.79", @@ -1352,9 +1592,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-03T17:03:39Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "DCY-5ocv9ik", @@ -1396,6 +1642,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.79", + "23.5.100.66" + ] + }, "source": { "bytes": 1538, "ip": "192.168.0.79", @@ -1419,9 +1671,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-03T17:03:39Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "DCY-5ocv9ik", @@ -1463,6 +1721,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "23.5.100.66", + "192.168.0.79" + ] + }, "source": { "bytes": 13002, "ip": "23.5.100.66", @@ -1486,9 +1750,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-03T17:03:39Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "B7rjR_940zU", @@ -1530,6 +1800,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "170.251.180.15", + "192.168.0.61" + ] + }, "source": { "bytes": 1194, "ip": "170.251.180.15", @@ -1553,9 +1829,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-03T17:03:39Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "B7rjR_940zU", @@ -1597,6 +1879,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.61", + "170.251.180.15" + ] + }, "source": { "bytes": 682, "ip": "192.168.0.61", @@ -1620,9 +1908,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-03T17:03:39Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "0RrmR_QtH34", @@ -1664,6 +1958,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.3.34", + "74.119.119.84" + ] + }, "source": { "bytes": 1804, "ip": "192.168.3.34", @@ -1687,9 +1987,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-03T17:03:39Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "O1-Y9rjVH2A", @@ -1731,6 +2037,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "185.60.218.19", + "192.168.3.142" + ] + }, "source": { "bytes": 4774, "ip": "185.60.218.19", @@ -1754,9 +2066,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-03T17:03:39Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "CtFBGbTcLpg", @@ -1798,6 +2116,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.3.200", + "185.60.218.15" + ] + }, "source": { "bytes": 135, "ip": "192.168.3.200", @@ -1821,9 +2145,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-03T17:03:39Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "CtFBGbTcLpg", @@ -1865,6 +2195,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "185.60.218.15", + "192.168.3.200" + ] + }, "source": { "bytes": 135, "ip": "185.60.218.15", @@ -1888,9 +2224,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-03T17:03:39Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "lT_guTKc7y4", @@ -1932,6 +2274,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.95", + "169.45.214.246" + ] + }, "source": { "bytes": 194, "ip": "192.168.0.95", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-ASA-2.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-ASA-2.golden.json index dc73be6acf3..68ca3bdb60e 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-ASA-2.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-ASA-2.golden.json @@ -13,9 +13,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:50:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "UTkRrDbrhnI", @@ -61,6 +67,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.2", + "192.168.0.17" + ] + }, "source": { "bytes": 81, "ip": "192.168.0.2", @@ -83,9 +95,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:50:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "WQVc0v7217I", @@ -131,6 +149,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.2", + "192.168.0.17" + ] + }, "source": { "bytes": 81, "ip": "192.168.0.2", @@ -153,9 +177,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:50:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "WQVc0v7217I", @@ -201,6 +231,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.2", + "192.168.0.17" + ] + }, "source": { "bytes": 81, "ip": "192.168.0.2", @@ -223,9 +259,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:50:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Nle5z0FLBjA", @@ -271,6 +313,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.1", + "192.168.0.18" + ] + }, "source": { "bytes": 81, "ip": "192.168.0.1", @@ -293,9 +341,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:50:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Nle5z0FLBjA", @@ -341,6 +395,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.1", + "192.168.0.18" + ] + }, "source": { "bytes": 81, "ip": "192.168.0.1", @@ -363,9 +423,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:50:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "lfYzCmoZgqo", @@ -411,6 +477,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.2", + "192.168.0.17" + ] + }, "source": { "bytes": 81, "ip": "192.168.0.2", @@ -433,9 +505,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:50:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "lfYzCmoZgqo", @@ -481,6 +559,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.2", + "192.168.0.17" + ] + }, "source": { "bytes": 81, "ip": "192.168.0.2", @@ -502,9 +586,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:50:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "_9ahEyFsD94", @@ -550,6 +640,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.1", + "192.168.0.18" + ] + }, "source": { "ip": "192.168.0.1", "locality": "private", @@ -571,9 +667,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:50:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "_9ahEyFsD94", @@ -619,6 +721,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.1", + "192.168.0.18" + ] + }, "source": { "bytes": 69, "ip": "192.168.0.1", @@ -641,9 +749,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:50:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "_9ahEyFsD94", @@ -689,6 +803,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.1", + "192.168.0.18" + ] + }, "source": { "bytes": 69, "ip": "192.168.0.1", @@ -710,9 +830,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:50:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "bnG6S7DUlEE", @@ -758,6 +884,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.2", + "192.168.0.17" + ] + }, "source": { "ip": "192.168.0.2", "locality": "private", @@ -779,9 +911,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:50:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "bnG6S7DUlEE", @@ -827,6 +965,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.2", + "192.168.0.17" + ] + }, "source": { "bytes": 69, "ip": "192.168.0.2", @@ -849,9 +993,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:50:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "bnG6S7DUlEE", @@ -897,6 +1047,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.2", + "192.168.0.17" + ] + }, "source": { "bytes": 69, "ip": "192.168.0.2", @@ -918,9 +1074,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:50:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "wuMbsS0oTj4", @@ -966,6 +1128,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.1", + "192.168.0.17" + ] + }, "source": { "ip": "192.168.0.1", "locality": "private", @@ -987,9 +1155,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:50:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "wuMbsS0oTj4", @@ -1035,6 +1209,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.1", + "192.168.0.17" + ] + }, "source": { "bytes": 75, "ip": "192.168.0.1", @@ -1057,9 +1237,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:50:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "wuMbsS0oTj4", @@ -1105,6 +1291,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.1", + "192.168.0.17" + ] + }, "source": { "bytes": 75, "ip": "192.168.0.1", @@ -1126,9 +1318,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:50:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "geQD5O-NWw8", @@ -1174,6 +1372,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.1", + "192.168.0.18" + ] + }, "source": { "ip": "192.168.0.1", "locality": "private", @@ -1195,9 +1399,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:50:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "geQD5O-NWw8", @@ -1243,6 +1453,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.1", + "192.168.0.18" + ] + }, "source": { "bytes": 69, "ip": "192.168.0.1", @@ -1265,9 +1481,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-07-21T13:50:37Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "geQD5O-NWw8", @@ -1313,6 +1535,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.1", + "192.168.0.18" + ] + }, "source": { "bytes": 69, "ip": "192.168.0.1", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-ASA.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-ASA.golden.json index df4f450fbc3..e250cce2afa 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-ASA.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-ASA.golden.json @@ -12,9 +12,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-09T09:47:51Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "5JpExP8VeSU", @@ -62,6 +68,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.14.1", + "2.2.2.11" + ] + }, "source": { "bytes": 56, "ip": "192.168.14.1", @@ -83,9 +95,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-09T09:47:51Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "MSQgezzAYh0", @@ -133,6 +151,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.23.22", + "164.164.37.11" + ] + }, "source": { "bytes": 56, "ip": "192.168.23.22", @@ -154,9 +178,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-09T09:47:51Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "MSQgezzAYh0", @@ -204,6 +234,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "164.164.37.11", + "192.168.23.22" + ] + }, "source": { "bytes": 56, "ip": "164.164.37.11", @@ -225,9 +261,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-09T09:47:51Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "ioGVEAJtaEQ", @@ -275,6 +317,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.23.20", + "164.164.37.11" + ] + }, "source": { "bytes": 56, "ip": "192.168.23.20", @@ -296,9 +344,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-09T09:47:51Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "ioGVEAJtaEQ", @@ -346,6 +400,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "164.164.37.11", + "192.168.23.20" + ] + }, "source": { "bytes": 56, "ip": "164.164.37.11", @@ -367,9 +427,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-09T09:47:51Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "0xqELVtMeog", @@ -417,6 +483,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.14.11", + "2.2.2.11" + ] + }, "source": { "bytes": 56, "ip": "192.168.14.11", @@ -438,9 +510,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-09T09:47:51Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "0xqELVtMeog", @@ -488,6 +566,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "2.2.2.11", + "192.168.14.11" + ] + }, "source": { "bytes": 56, "ip": "2.2.2.11", @@ -509,9 +593,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-09T09:47:51Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "LA3WpK17LAw", @@ -559,6 +649,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "2.2.2.11", + "192.168.14.1" + ] + }, "source": { "bytes": 56, "ip": "2.2.2.11", @@ -580,9 +676,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-09T09:47:51Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "LA3WpK17LAw", @@ -630,6 +732,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.14.1", + "2.2.2.11" + ] + }, "source": { "bytes": 56, "ip": "192.168.14.1", @@ -651,9 +759,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-09T09:47:51Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "tBFZO1WrQyk", @@ -701,6 +815,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "164.164.37.11", + "192.168.23.1" + ] + }, "source": { "bytes": 160, "ip": "164.164.37.11", @@ -722,9 +842,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-09T09:47:51Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "oil2JqFPSyE", @@ -772,6 +898,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.23.22", + "164.164.37.11" + ] + }, "source": { "bytes": 56, "ip": "192.168.23.22", @@ -793,9 +925,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-09T09:47:51Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "oil2JqFPSyE", @@ -843,6 +981,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "164.164.37.11", + "192.168.23.22" + ] + }, "source": { "bytes": 56, "ip": "164.164.37.11", @@ -864,9 +1008,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-09T09:47:51Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Pbk_o-xetL4", @@ -914,6 +1064,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.23.20", + "164.164.37.11" + ] + }, "source": { "bytes": 56, "ip": "192.168.23.20", @@ -935,9 +1091,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-09T09:47:51Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Pbk_o-xetL4", @@ -985,6 +1147,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "164.164.37.11", + "192.168.23.20" + ] + }, "source": { "bytes": 56, "ip": "164.164.37.11", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-ASR-9000-series-options-template-256.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-ASR-9000-series-options-template-256.golden.json index bc346d8c98e..625225c213b 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-ASR-9000-series-options-template-256.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-ASR-9000-series-options-template-256.golden.json @@ -7,7 +7,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:48Z", "kind": "event" }, @@ -41,7 +44,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:48Z", "kind": "event" }, @@ -75,7 +81,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:48Z", "kind": "event" }, @@ -109,7 +118,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:48Z", "kind": "event" }, @@ -143,7 +155,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:48Z", "kind": "event" }, @@ -177,7 +192,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:48Z", "kind": "event" }, @@ -211,7 +229,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:48Z", "kind": "event" }, @@ -245,7 +266,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:48Z", "kind": "event" }, @@ -279,7 +303,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:48Z", "kind": "event" }, @@ -313,7 +340,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:48Z", "kind": "event" }, @@ -347,7 +377,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:48Z", "kind": "event" }, @@ -381,7 +414,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:48Z", "kind": "event" }, @@ -415,7 +451,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:48Z", "kind": "event" }, @@ -449,7 +488,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:48Z", "kind": "event" }, @@ -483,7 +525,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:48Z", "kind": "event" }, @@ -517,7 +562,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:48Z", "kind": "event" }, @@ -551,7 +599,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:48Z", "kind": "event" }, @@ -585,7 +636,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:48Z", "kind": "event" }, @@ -619,7 +673,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:48Z", "kind": "event" }, diff --git a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-ASR-9000-series-template-260.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-ASR-9000-series-template-260.golden.json index cf1ad940af8..fa2e4920cce 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-ASR-9000-series-template-260.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-ASR-9000-series-template-260.golden.json @@ -12,12 +12,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:24Z", "duration": 0, "end": "2016-12-06T10:08:53.94Z", "kind": "event", - "start": "2016-12-06T10:08:53.94Z" + "start": "2016-12-06T10:08:53.94Z", + "type": [ + "connection" + ] }, "flow": { "id": "kkhtKjgAywQ", @@ -66,6 +72,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.0.9.146", + "10.0.31.81" + ] + }, "source": { "bytes": 40, "ip": "10.0.9.146", @@ -88,12 +100,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:24Z", "duration": 641000000, "end": "2016-12-06T10:08:54.583Z", "kind": "event", - "start": "2016-12-06T10:08:53.942Z" + "start": "2016-12-06T10:08:53.942Z", + "type": [ + "connection" + ] }, "flow": { "id": "4su7p2nlyno", @@ -142,6 +160,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.0.17.42", + "10.0.35.4" + ] + }, "source": { "bytes": 104, "ip": "10.0.17.42", @@ -164,12 +188,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:24Z", "duration": 0, "end": "2016-12-06T10:08:53.945Z", "kind": "event", - "start": "2016-12-06T10:08:53.945Z" + "start": "2016-12-06T10:08:53.945Z", + "type": [ + "connection" + ] }, "flow": { "id": "mfb1_zWayo4", @@ -218,6 +248,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.0.22.111", + "10.0.34.141" + ] + }, "source": { "bytes": 52, "ip": "10.0.22.111", @@ -240,12 +276,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:24Z", "duration": 0, "end": "2016-12-06T10:08:53.947Z", "kind": "event", - "start": "2016-12-06T10:08:53.947Z" + "start": "2016-12-06T10:08:53.947Z", + "type": [ + "connection" + ] }, "flow": { "id": "jKhffDbQq0o", @@ -294,6 +336,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.0.23.59", + "10.0.36.170" + ] + }, "source": { "bytes": 435, "ip": "10.0.23.59", @@ -316,12 +364,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:24Z", "duration": 0, "end": "2016-12-06T10:08:53.948Z", "kind": "event", - "start": "2016-12-06T10:08:53.948Z" + "start": "2016-12-06T10:08:53.948Z", + "type": [ + "connection" + ] }, "flow": { "id": "5siGD7iCzo4", @@ -370,6 +424,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.0.34.71", + "10.0.20.242" + ] + }, "source": { "bytes": 969, "ip": "10.0.34.71", @@ -392,12 +452,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:24Z", "duration": 83000000, "end": "2016-12-06T10:08:53.948Z", "kind": "event", - "start": "2016-12-06T10:08:53.865Z" + "start": "2016-12-06T10:08:53.865Z", + "type": [ + "connection" + ] }, "flow": { "id": "IyuegsSri_U", @@ -446,6 +512,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.0.10.133", + "10.0.30.102" + ] + }, "source": { "bytes": 104, "ip": "10.0.10.133", @@ -468,12 +540,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:24Z", "duration": 0, "end": "2016-12-06T10:08:53.951Z", "kind": "event", - "start": "2016-12-06T10:08:53.951Z" + "start": "2016-12-06T10:08:53.951Z", + "type": [ + "connection" + ] }, "flow": { "id": "9JGzjsOdNi4", @@ -522,6 +600,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.0.37.29", + "10.0.6.24" + ] + }, "source": { "bytes": 52, "ip": "10.0.37.29", @@ -544,12 +628,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:24Z", "duration": 0, "end": "2016-12-06T10:08:53.951Z", "kind": "event", - "start": "2016-12-06T10:08:53.951Z" + "start": "2016-12-06T10:08:53.951Z", + "type": [ + "connection" + ] }, "flow": { "id": "Y3aiAEAjjys", @@ -598,6 +688,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.0.32.176", + "10.0.11.113" + ] + }, "source": { "bytes": 614, "ip": "10.0.32.176", @@ -620,12 +716,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:24Z", "duration": 5418000000, "end": "2016-12-06T10:08:53.952Z", "kind": "event", - "start": "2016-12-06T10:08:48.534Z" + "start": "2016-12-06T10:08:48.534Z", + "type": [ + "connection" + ] }, "flow": { "id": "sC3kzwxISec", @@ -674,6 +776,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.0.12.21", + "10.0.15.38" + ] + }, "source": { "bytes": 4350, "ip": "10.0.12.21", @@ -696,12 +804,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:24Z", "duration": 3317000000, "end": "2016-12-06T10:08:57.27Z", "kind": "event", - "start": "2016-12-06T10:08:53.953Z" + "start": "2016-12-06T10:08:53.953Z", + "type": [ + "connection" + ] }, "flow": { "id": "dTmlxL48EoA", @@ -750,6 +864,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.0.4.212", + "10.0.3.110" + ] + }, "source": { "bytes": 533, "ip": "10.0.4.212", @@ -772,12 +892,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:24Z", "duration": 19894000000, "end": "2016-12-06T10:09:04.383Z", "kind": "event", - "start": "2016-12-06T10:08:44.489Z" + "start": "2016-12-06T10:08:44.489Z", + "type": [ + "connection" + ] }, "flow": { "id": "oMLDxCSgNuA", @@ -826,6 +952,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.0.33.122", + "10.0.1.136" + ] + }, "source": { "bytes": 13660, "ip": "10.0.33.122", @@ -848,12 +980,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:24Z", "duration": 0, "end": "2016-12-06T10:08:53.955Z", "kind": "event", - "start": "2016-12-06T10:08:53.955Z" + "start": "2016-12-06T10:08:53.955Z", + "type": [ + "connection" + ] }, "flow": { "id": "5siGD7iCzo4", @@ -902,6 +1040,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.0.20.242", + "10.0.34.71" + ] + }, "source": { "bytes": 89, "ip": "10.0.20.242", @@ -924,12 +1068,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:24Z", "duration": 0, "end": "2016-12-06T10:08:53.957Z", "kind": "event", - "start": "2016-12-06T10:08:53.957Z" + "start": "2016-12-06T10:08:53.957Z", + "type": [ + "connection" + ] }, "flow": { "id": "-IcTJfcRi8w", @@ -978,6 +1128,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.0.13.25", + "10.0.15.38" + ] + }, "source": { "bytes": 833, "ip": "10.0.13.25", @@ -1000,12 +1156,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:24Z", "duration": 89000000, "end": "2016-12-06T10:08:53.959Z", "kind": "event", - "start": "2016-12-06T10:08:53.87Z" + "start": "2016-12-06T10:08:53.87Z", + "type": [ + "connection" + ] }, "flow": { "id": "tyf0jfEIDwM", @@ -1054,6 +1216,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.0.25.59", + "10.0.2.18" + ] + }, "source": { "bytes": 1625, "ip": "10.0.25.59", @@ -1076,12 +1244,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:24Z", "duration": 17325000000, "end": "2016-12-06T10:09:05.882Z", "kind": "event", - "start": "2016-12-06T10:08:48.557Z" + "start": "2016-12-06T10:08:48.557Z", + "type": [ + "connection" + ] }, "flow": { "id": "OYKOBQNKdF4", @@ -1130,6 +1304,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.0.7.73", + "10.0.27.168" + ] + }, "source": { "bytes": 142184, "ip": "10.0.7.73", @@ -1152,12 +1332,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:24Z", "duration": 2705000000, "end": "2016-12-06T10:08:56.186Z", "kind": "event", - "start": "2016-12-06T10:08:53.481Z" + "start": "2016-12-06T10:08:53.481Z", + "type": [ + "connection" + ] }, "flow": { "id": "fC6tFjsdK54", @@ -1206,6 +1392,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.0.19.50", + "10.0.27.169" + ] + }, "source": { "bytes": 3016, "ip": "10.0.19.50", @@ -1228,12 +1420,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:24Z", "duration": 361000000, "end": "2016-12-06T10:08:54.28Z", "kind": "event", - "start": "2016-12-06T10:08:53.919Z" + "start": "2016-12-06T10:08:53.919Z", + "type": [ + "connection" + ] }, "flow": { "id": "Kk4bVU4hDRk", @@ -1282,6 +1480,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.0.28.150", + "10.0.24.13" + ] + }, "source": { "bytes": 31500, "ip": "10.0.28.150", @@ -1304,12 +1508,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:24Z", "duration": 378000000, "end": "2016-12-06T10:08:54.037Z", "kind": "event", - "start": "2016-12-06T10:08:53.659Z" + "start": "2016-12-06T10:08:53.659Z", + "type": [ + "connection" + ] }, "flow": { "id": "_Fk2ywvptGE", @@ -1358,6 +1568,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.0.26.188", + "10.0.21.200" + ] + }, "source": { "bytes": 2919, "ip": "10.0.26.188", @@ -1380,12 +1596,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:24Z", "duration": 11106000000, "end": "2016-12-06T10:09:03.759Z", "kind": "event", - "start": "2016-12-06T10:08:52.653Z" + "start": "2016-12-06T10:08:52.653Z", + "type": [ + "connection" + ] }, "flow": { "id": "MrTF7IZhOrg", @@ -1434,6 +1656,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.0.29.34", + "10.0.15.38" + ] + }, "source": { "bytes": 4514, "ip": "10.0.29.34", @@ -1456,12 +1684,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:24Z", "duration": 0, "end": "2016-12-06T10:08:53.964Z", "kind": "event", - "start": "2016-12-06T10:08:53.964Z" + "start": "2016-12-06T10:08:53.964Z", + "type": [ + "connection" + ] }, "flow": { "id": "hUKUTbBVmIY", @@ -1510,6 +1744,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.0.8.200", + "10.0.5.224" + ] + }, "source": { "bytes": 326, "ip": "10.0.8.200", @@ -1532,12 +1772,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-06T10:09:24Z", "duration": 1587000000, "end": "2016-12-06T10:08:53.964Z", "kind": "event", - "start": "2016-12-06T10:08:52.377Z" + "start": "2016-12-06T10:08:52.377Z", + "type": [ + "connection" + ] }, "flow": { "id": "IoEUbnBqGXE", @@ -1586,6 +1832,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.0.29.46", + "10.0.15.38" + ] + }, "source": { "bytes": 112, "ip": "10.0.29.46", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-ASR1001--X.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-ASR1001--X.golden.json index 2484a8a7fa9..05ac80b86c9 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-ASR1001--X.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-ASR1001--X.golden.json @@ -12,9 +12,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-09T20:22:35Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "_qSyv-Xe8IM", @@ -56,6 +62,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.111.111.242", + "10.12.100.13" + ] + }, "source": { "bytes": 965, "ip": "10.111.111.242", @@ -78,9 +90,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-09T20:22:35Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "7s_4xBb69Y0", @@ -122,6 +140,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.10.4.29", + "10.100.105.85" + ] + }, "source": { "bytes": 284, "ip": "10.10.4.29", @@ -144,9 +168,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-09T20:22:35Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "_qSyv-Xe8IM", @@ -188,6 +218,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.12.100.13", + "10.111.111.242" + ] + }, "source": { "bytes": 670, "ip": "10.12.100.13", @@ -210,9 +246,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-09T20:22:35Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "jk1T8-P2OHM", @@ -254,6 +296,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.12.104.239", + "10.10.11.21" + ] + }, "source": { "bytes": 80, "ip": "10.12.104.239", @@ -276,9 +324,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-09T20:22:35Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "jk1T8-P2OHM", @@ -320,6 +374,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.10.11.21", + "10.12.104.239" + ] + }, "source": { "bytes": 80, "ip": "10.10.11.21", @@ -342,9 +402,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-09T20:22:35Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "6AEj_wlzQm4", @@ -386,6 +452,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.100.101.45", + "10.15.131.98" + ] + }, "source": { "bytes": 101, "ip": "10.100.101.45", @@ -408,9 +480,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-09T20:22:35Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "MtCuD-nvBTY", @@ -452,6 +530,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.100.101.43", + "10.12.105.23" + ] + }, "source": { "bytes": 1134, "ip": "10.100.101.43", @@ -474,9 +558,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-09T20:22:35Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "8zAXung0YbA", @@ -518,6 +608,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "31.13.71.7", + "10.11.31.108" + ] + }, "source": { "bytes": 237, "ip": "31.13.71.7", @@ -540,9 +636,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-09T20:22:35Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "5LxKkXX5FfM", @@ -584,6 +686,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.11.21.60", + "10.100.105.86" + ] + }, "source": { "bytes": 91, "ip": "10.11.21.60", @@ -606,9 +714,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-09T20:22:35Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "MnDMft-qZjs", @@ -650,6 +764,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.12.92.102", + "172.217.11.5" + ] + }, "source": { "bytes": 41, "ip": "10.12.92.102", @@ -672,9 +792,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-09T20:22:35Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Ddy-Ii-ZDDI", @@ -716,6 +842,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.100.105.86", + "10.11.21.60" + ] + }, "source": { "bytes": 111, "ip": "10.100.105.86", @@ -738,9 +870,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-09T20:22:35Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Hiy-Ti0eVlY", @@ -782,6 +920,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.10.4.234", + "10.100.105.85" + ] + }, "source": { "bytes": 1164, "ip": "10.10.4.234", @@ -804,9 +948,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-09T20:22:35Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "7iMintjCsaw", @@ -848,6 +998,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.12.106.83", + "10.10.11.21" + ] + }, "source": { "bytes": 80, "ip": "10.12.106.83", @@ -870,9 +1026,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-09T20:22:35Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "MnDMft-qZjs", @@ -914,6 +1076,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.217.11.5", + "10.12.92.102" + ] + }, "source": { "bytes": 52, "ip": "172.217.11.5", @@ -936,9 +1104,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-09T20:22:35Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "7iMintjCsaw", @@ -980,6 +1154,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.10.11.21", + "10.12.106.83" + ] + }, "source": { "bytes": 80, "ip": "10.10.11.21", @@ -1002,9 +1182,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-09T20:22:35Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "hphBugBrKPY", @@ -1046,6 +1232,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.12.81.86", + "74.201.129.29" + ] + }, "source": { "bytes": 3088, "ip": "10.12.81.86", @@ -1068,9 +1260,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-09T20:22:35Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "gJ7Z20zGGk8", @@ -1112,6 +1310,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.14.121.98", + "10.12.100.13" + ] + }, "source": { "bytes": 5306, "ip": "10.14.121.98", @@ -1134,9 +1338,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-09T20:22:35Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Ddy-Ii-ZDDI", @@ -1178,6 +1388,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.11.21.60", + "10.100.105.86" + ] + }, "source": { "bytes": 116, "ip": "10.11.21.60", @@ -1200,9 +1416,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-09T20:22:35Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "gJ7Z20zGGk8", @@ -1244,6 +1466,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.12.100.13", + "10.14.121.98" + ] + }, "source": { "bytes": 22764, "ip": "10.12.100.13", @@ -1266,9 +1494,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-09T20:22:35Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "LZaFrMI9jg0", @@ -1310,6 +1544,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.12.102.125", + "10.10.11.21" + ] + }, "source": { "bytes": 80, "ip": "10.12.102.125", @@ -1332,9 +1572,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-09T20:22:35Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "f6pXcQQIzpU", @@ -1376,6 +1622,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.100.105.86", + "10.11.21.60" + ] + }, "source": { "bytes": 75, "ip": "10.100.105.86", @@ -1398,9 +1650,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-09T20:22:35Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "LZaFrMI9jg0", @@ -1442,6 +1700,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.10.11.21", + "10.12.102.125" + ] + }, "source": { "bytes": 80, "ip": "10.10.11.21", @@ -1464,9 +1728,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-09T20:22:35Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "gQGJtHjUcB8", @@ -1508,6 +1778,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.100.105.85", + "10.10.4.151" + ] + }, "source": { "bytes": 160, "ip": "10.100.105.85", @@ -1530,9 +1806,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-09T20:22:35Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "UHiF_w4I6zM", @@ -1574,6 +1856,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.14.25.80", + "17.253.24.253" + ] + }, "source": { "bytes": 76, "ip": "10.14.25.80", @@ -1596,9 +1884,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-10-09T20:22:35Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "czsFrOKrayM", @@ -1640,6 +1934,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.12.150.13", + "10.100.101.43" + ] + }, "source": { "bytes": 1340, "ip": "10.12.150.13", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-NBAR-flowset-262.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-NBAR-flowset-262.golden.json index c667fa408ad..8f1a0a6b951 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-NBAR-flowset-262.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-NBAR-flowset-262.golden.json @@ -13,12 +13,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-02-14T11:10:36Z", "duration": 0, "end": "2017-02-14T11:10:19.368Z", "kind": "event", - "start": "2017-02-14T11:10:19.368Z" + "start": "2017-02-14T11:10:19.368Z", + "type": [ + "connection" + ] }, "flow": { "id": "Bk-2FcuOyCU", @@ -70,6 +76,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.30.18.62", + "10.30.19.180" + ] + }, "source": { "bytes": 44, "ip": "10.30.18.62", @@ -94,12 +106,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-02-14T11:10:36Z", "duration": 0, "end": "2017-02-14T11:10:19.368Z", "kind": "event", - "start": "2017-02-14T11:10:19.368Z" + "start": "2017-02-14T11:10:19.368Z", + "type": [ + "connection" + ] }, "flow": { "id": "4Xk8GtQfUAo", @@ -151,6 +169,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.30.18.62", + "10.30.19.180" + ] + }, "source": { "bytes": 106, "ip": "10.30.18.62", @@ -175,12 +199,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-02-14T11:10:36Z", "duration": 0, "end": "2017-02-14T11:10:19.924Z", "kind": "event", - "start": "2017-02-14T11:10:19.924Z" + "start": "2017-02-14T11:10:19.924Z", + "type": [ + "connection" + ] }, "flow": { "id": "tfLRXnB6AOA", @@ -232,6 +262,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.10.172.60", + "10.30.19.180" + ] + }, "source": { "bytes": 44, "ip": "10.10.172.60", @@ -256,12 +292,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-02-14T11:10:36Z", "duration": 0, "end": "2017-02-14T11:10:19.996Z", "kind": "event", - "start": "2017-02-14T11:10:19.996Z" + "start": "2017-02-14T11:10:19.996Z", + "type": [ + "connection" + ] }, "flow": { "id": "1mfP23NPuB8", @@ -313,6 +355,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.10.172.60", + "10.30.19.180" + ] + }, "source": { "bytes": 76, "ip": "10.10.172.60", @@ -337,12 +385,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-02-14T11:10:36Z", "duration": 72000000, "end": "2017-02-14T11:10:20.008Z", "kind": "event", - "start": "2017-02-14T11:10:19.936Z" + "start": "2017-02-14T11:10:19.936Z", + "type": [ + "connection" + ] }, "flow": { "id": "g6a7KlISbtM", @@ -394,6 +448,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.10.172.60", + "10.30.19.180" + ] + }, "source": { "bytes": 2794, "ip": "10.10.172.60", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-NBAR-options-template-260.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-NBAR-options-template-260.golden.json index 3be5e2844f6..8dd560a8d64 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-NBAR-options-template-260.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-NBAR-options-template-260.golden.json @@ -7,7 +7,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-02-14T11:09:59Z", "kind": "event" }, @@ -42,7 +45,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-02-14T11:09:59Z", "kind": "event" }, @@ -77,7 +83,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-02-14T11:09:59Z", "kind": "event" }, @@ -112,7 +121,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-02-14T11:09:59Z", "kind": "event" }, @@ -147,7 +159,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-02-14T11:09:59Z", "kind": "event" }, @@ -182,7 +197,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-02-14T11:09:59Z", "kind": "event" }, @@ -217,7 +235,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-02-14T11:09:59Z", "kind": "event" }, @@ -252,7 +273,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-02-14T11:09:59Z", "kind": "event" }, @@ -287,7 +311,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-02-14T11:09:59Z", "kind": "event" }, @@ -322,7 +349,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-02-14T11:09:59Z", "kind": "event" }, @@ -357,7 +387,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-02-14T11:09:59Z", "kind": "event" }, @@ -392,7 +425,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-02-14T11:09:59Z", "kind": "event" }, @@ -427,7 +463,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-02-14T11:09:59Z", "kind": "event" }, @@ -462,7 +501,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-02-14T11:09:59Z", "kind": "event" }, @@ -497,7 +539,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-02-14T11:09:59Z", "kind": "event" }, diff --git a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-WLC.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-WLC.golden.json index 9782ab2e9f2..c55814ca02b 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-WLC.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Cisco-WLC.golden.json @@ -10,9 +10,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-06-22T06:31:14Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "lTcFptYSabQ", @@ -70,9 +76,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-06-22T06:31:14Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Q1JIGzkHw0I", @@ -126,9 +138,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-06-22T06:31:14Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "lTcFptYSabQ", @@ -186,9 +204,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-06-22T06:31:14Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Q1JIGzkHw0I", @@ -242,9 +266,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-06-22T06:31:14Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "lTcFptYSabQ", @@ -300,9 +330,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-06-22T06:31:14Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "lTcFptYSabQ", @@ -360,9 +396,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-06-22T06:31:14Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Q1JIGzkHw0I", @@ -416,9 +458,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-06-22T06:31:14Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "lTcFptYSabQ", @@ -476,9 +524,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-06-22T06:31:14Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Q1JIGzkHw0I", @@ -532,9 +586,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-06-22T06:31:14Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "lTcFptYSabQ", @@ -592,9 +652,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-06-22T06:31:14Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Q1JIGzkHw0I", @@ -648,9 +714,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-06-22T06:31:14Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "lTcFptYSabQ", @@ -708,9 +780,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-06-22T06:31:14Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Q1JIGzkHw0I", @@ -764,9 +842,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-06-22T06:31:14Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "lTcFptYSabQ", @@ -824,9 +908,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-06-22T06:31:14Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Q1JIGzkHw0I", @@ -880,9 +970,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-06-22T06:31:14Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "lTcFptYSabQ", @@ -940,9 +1036,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-06-22T06:31:14Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Q1JIGzkHw0I", @@ -996,9 +1098,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-06-22T06:31:14Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "lTcFptYSabQ", @@ -1056,9 +1164,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-06-22T06:31:14Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Q1JIGzkHw0I", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Fortigate-FortiOS-5.2.1.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Fortigate-FortiOS-5.2.1.golden.json index 197212e152c..4fca04b8016 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Fortigate-FortiOS-5.2.1.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Fortigate-FortiOS-5.2.1.golden.json @@ -7,7 +7,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-18T05:42:14Z", "kind": "event" }, @@ -51,9 +54,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-07-18T05:41:59Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "SKsZNpZob60", @@ -93,6 +102,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.99.7", + "31.13.87.36" + ] + }, "source": { "bytes": 152, "ip": "192.168.99.7", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Fortigate-FortiOS-54x-appid.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Fortigate-FortiOS-54x-appid.golden.json index 7e2cf662d47..bb4b82133d3 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Fortigate-FortiOS-54x-appid.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Fortigate-FortiOS-54x-appid.golden.json @@ -12,12 +12,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-05-11T00:54:11Z", "duration": 410000000, "end": "2018-05-11T00:54:09.99Z", "kind": "event", - "start": "2018-05-11T00:54:09.58Z" + "start": "2018-05-11T00:54:09.58Z", + "type": [ + "connection" + ] }, "flow": { "id": "FfT-8jRRvok", @@ -64,6 +70,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.100.151", + "182.50.136.239" + ] + }, "source": { "bytes": 748, "ip": "192.168.100.151", @@ -86,12 +98,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-05-11T00:54:11Z", "duration": 1130000000, "end": "2018-05-11T00:54:09.74Z", "kind": "event", - "start": "2018-05-11T00:54:08.61Z" + "start": "2018-05-11T00:54:08.61Z", + "type": [ + "connection" + ] }, "flow": { "id": "bZjTG4EkhLs", @@ -138,6 +156,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "208.100.17.187", + "192.168.100.151" + ] + }, "source": { "bytes": 6948, "ip": "208.100.17.187", @@ -160,12 +184,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-05-11T00:54:11Z", "duration": 1130000000, "end": "2018-05-11T00:54:09.74Z", "kind": "event", - "start": "2018-05-11T00:54:08.61Z" + "start": "2018-05-11T00:54:08.61Z", + "type": [ + "connection" + ] }, "flow": { "id": "bZjTG4EkhLs", @@ -212,6 +242,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.100.151", + "208.100.17.187" + ] + }, "source": { "bytes": 1584, "ip": "192.168.100.151", @@ -234,12 +270,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-05-11T00:54:11Z", "duration": 1040000000, "end": "2018-05-11T00:54:09.74Z", "kind": "event", - "start": "2018-05-11T00:54:08.7Z" + "start": "2018-05-11T00:54:08.7Z", + "type": [ + "connection" + ] }, "flow": { "id": "kZjCeMUhjqE", @@ -286,6 +328,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "208.100.17.189", + "192.168.100.151" + ] + }, "source": { "bytes": 8201, "ip": "208.100.17.189", @@ -308,12 +356,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-05-11T00:54:11Z", "duration": 1040000000, "end": "2018-05-11T00:54:09.74Z", "kind": "event", - "start": "2018-05-11T00:54:08.7Z" + "start": "2018-05-11T00:54:08.7Z", + "type": [ + "connection" + ] }, "flow": { "id": "kZjCeMUhjqE", @@ -360,6 +414,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.100.151", + "208.100.17.189" + ] + }, "source": { "bytes": 1729, "ip": "192.168.100.151", @@ -382,12 +442,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-05-11T00:54:11Z", "duration": 410000000, "end": "2018-05-11T00:54:09.11Z", "kind": "event", - "start": "2018-05-11T00:54:08.7Z" + "start": "2018-05-11T00:54:08.7Z", + "type": [ + "connection" + ] }, "flow": { "id": "8PR91KFjFKw", @@ -434,6 +500,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "178.255.83.1", + "192.168.100.151" + ] + }, "source": { "bytes": 1122, "ip": "178.255.83.1", @@ -456,12 +528,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-05-11T00:54:11Z", "duration": 410000000, "end": "2018-05-11T00:54:09.11Z", "kind": "event", - "start": "2018-05-11T00:54:08.7Z" + "start": "2018-05-11T00:54:08.7Z", + "type": [ + "connection" + ] }, "flow": { "id": "8PR91KFjFKw", @@ -508,6 +586,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.100.151", + "178.255.83.1" + ] + }, "source": { "bytes": 705, "ip": "192.168.100.151", @@ -530,12 +614,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-05-11T00:54:11Z", "duration": 370000000, "end": "2018-05-11T00:54:08.53Z", "kind": "event", - "start": "2018-05-11T00:54:08.16Z" + "start": "2018-05-11T00:54:08.16Z", + "type": [ + "connection" + ] }, "flow": { "id": "O5vacJG8mLQ", @@ -582,6 +672,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "178.255.83.1", + "192.168.100.151" + ] + }, "source": { "bytes": 1123, "ip": "178.255.83.1", @@ -604,12 +700,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-05-11T00:54:11Z", "duration": 370000000, "end": "2018-05-11T00:54:08.53Z", "kind": "event", - "start": "2018-05-11T00:54:08.16Z" + "start": "2018-05-11T00:54:08.16Z", + "type": [ + "connection" + ] }, "flow": { "id": "O5vacJG8mLQ", @@ -656,6 +758,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.100.151", + "178.255.83.1" + ] + }, "source": { "bytes": 706, "ip": "192.168.100.151", @@ -678,12 +786,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-05-11T00:54:11Z", "duration": 80000000, "end": "2018-05-11T00:51:08.63Z", "kind": "event", - "start": "2018-05-11T00:51:08.55Z" + "start": "2018-05-11T00:51:08.55Z", + "type": [ + "connection" + ] }, "flow": { "id": "wdz94oax40U", @@ -726,6 +840,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.100.111", + "192.168.100.150" + ] + }, "source": { "bytes": 74, "ip": "192.168.100.111", @@ -748,12 +868,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-05-11T00:54:11Z", "duration": 80000000, "end": "2018-05-11T00:51:08.63Z", "kind": "event", - "start": "2018-05-11T00:51:08.55Z" + "start": "2018-05-11T00:51:08.55Z", + "type": [ + "connection" + ] }, "flow": { "id": "wdz94oax40U", @@ -796,6 +922,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.100.150", + "192.168.100.111" + ] + }, "source": { "bytes": 58, "ip": "192.168.100.150", @@ -818,12 +950,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-05-11T00:54:11Z", "duration": 80000000, "end": "2018-05-11T00:51:08.63Z", "kind": "event", - "start": "2018-05-11T00:51:08.55Z" + "start": "2018-05-11T00:51:08.55Z", + "type": [ + "connection" + ] }, "flow": { "id": "KvZZ7LW-Qdc", @@ -866,6 +1004,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.100.111", + "192.168.100.150" + ] + }, "source": { "bytes": 74, "ip": "192.168.100.111", @@ -888,12 +1032,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-05-11T00:54:11Z", "duration": 80000000, "end": "2018-05-11T00:51:08.63Z", "kind": "event", - "start": "2018-05-11T00:51:08.55Z" + "start": "2018-05-11T00:51:08.55Z", + "type": [ + "connection" + ] }, "flow": { "id": "KvZZ7LW-Qdc", @@ -936,6 +1086,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.100.150", + "192.168.100.111" + ] + }, "source": { "bytes": 58, "ip": "192.168.100.150", @@ -958,12 +1114,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-05-11T00:54:11Z", "duration": 2020000000, "end": "2018-05-11T00:54:06.21Z", "kind": "event", - "start": "2018-05-11T00:54:04.19Z" + "start": "2018-05-11T00:54:04.19Z", + "type": [ + "connection" + ] }, "flow": { "id": "PC3a5T13Dpw", @@ -1006,6 +1168,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.100.111", + "192.168.100.150" + ] + }, "source": { "bytes": 1071, "ip": "192.168.100.111", @@ -1028,12 +1196,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-05-11T00:54:11Z", "duration": 2020000000, "end": "2018-05-11T00:54:06.21Z", "kind": "event", - "start": "2018-05-11T00:54:04.19Z" + "start": "2018-05-11T00:54:04.19Z", + "type": [ + "connection" + ] }, "flow": { "id": "PC3a5T13Dpw", @@ -1076,6 +1250,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.100.150", + "192.168.100.111" + ] + }, "source": { "bytes": 1147, "ip": "192.168.100.150", @@ -1098,12 +1278,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-05-11T00:54:11Z", "duration": 4000000000, "end": "2018-05-11T00:54:00.19Z", "kind": "event", - "start": "2018-05-11T00:53:56.19Z" + "start": "2018-05-11T00:53:56.19Z", + "type": [ + "connection" + ] }, "flow": { "id": "zdGWMwGlfsg", @@ -1146,6 +1332,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.100.111", + "192.168.100.150" + ] + }, "source": { "bytes": 1980, "ip": "192.168.100.111", @@ -1168,12 +1360,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-05-11T00:54:11Z", "duration": 4000000000, "end": "2018-05-11T00:54:00.19Z", "kind": "event", - "start": "2018-05-11T00:53:56.19Z" + "start": "2018-05-11T00:53:56.19Z", + "type": [ + "connection" + ] }, "flow": { "id": "zdGWMwGlfsg", @@ -1216,6 +1414,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.100.150", + "192.168.100.111" + ] + }, "source": { "bytes": 2164, "ip": "192.168.100.150", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-H3C-Netstream-with-varstring.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-H3C-Netstream-with-varstring.golden.json index d40a60441f9..93ea138cbac 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-H3C-Netstream-with-varstring.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-H3C-Netstream-with-varstring.golden.json @@ -12,12 +12,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-07-18T01:35:35Z", "duration": 29695000000, "end": "2018-07-18T01:35:02.969Z", "kind": "event", - "start": "2018-07-18T01:34:33.274Z" + "start": "2018-07-18T01:34:33.274Z", + "type": [ + "connection" + ] }, "flow": { "id": "dK1E5m-O-ns", @@ -70,6 +76,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "20.20.20.20", + "20.20.255.255" + ] + }, "source": { "bytes": 702, "ip": "20.20.20.20", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-H3C.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-H3C.golden.json index fee2e5d4e03..5884e5c3a85 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-H3C.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-H3C.golden.json @@ -12,12 +12,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-05-21T09:25:04Z", "duration": 89519000000, "end": "2018-05-21T09:25:03.677Z", "kind": "event", - "start": "2018-05-21T09:23:34.158Z" + "start": "2018-05-21T09:23:34.158Z", + "type": [ + "connection" + ] }, "flow": { "id": "6gDDasxO-4o", @@ -69,6 +75,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.22.166.30", + "10.22.163.21" + ] + }, "source": { "bytes": 1027087, "ip": "10.22.166.30", @@ -91,12 +103,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-05-21T09:25:04Z", "duration": 60005000000, "end": "2018-05-21T09:25:03.662Z", "kind": "event", - "start": "2018-05-21T09:24:03.657Z" + "start": "2018-05-21T09:24:03.657Z", + "type": [ + "connection" + ] }, "flow": { "id": "RJbWY0zxttI", @@ -148,6 +166,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.22.166.12", + "10.21.3.172" + ] + }, "source": { "bytes": 6200, "ip": "10.22.166.12", @@ -170,12 +194,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-05-21T09:25:04Z", "duration": 60016000000, "end": "2018-05-21T09:25:03.656Z", "kind": "event", - "start": "2018-05-21T09:24:03.64Z" + "start": "2018-05-21T09:24:03.64Z", + "type": [ + "connection" + ] }, "flow": { "id": "MfdYhUDA3Y4", @@ -227,6 +257,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.22.166.33", + "10.22.178.37" + ] + }, "source": { "bytes": 11896, "ip": "10.22.166.33", @@ -249,12 +285,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-05-21T09:25:04Z", "duration": 90011000000, "end": "2018-05-21T09:25:03.643Z", "kind": "event", - "start": "2018-05-21T09:23:33.632Z" + "start": "2018-05-21T09:23:33.632Z", + "type": [ + "connection" + ] }, "flow": { "id": "_QFogYw9xiY", @@ -306,6 +348,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.22.166.35", + "10.20.100.253" + ] + }, "source": { "bytes": 1041, "ip": "10.22.166.35", @@ -328,12 +376,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-05-21T09:25:04Z", "duration": 30000000000, "end": "2018-05-21T09:24:03.629Z", "kind": "event", - "start": "2018-05-21T09:23:33.629Z" + "start": "2018-05-21T09:23:33.629Z", + "type": [ + "connection" + ] }, "flow": { "id": "-O7eEnuq5LI", @@ -385,6 +439,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.22.166.36", + "10.20.136.36" + ] + }, "source": { "bytes": 1740, "ip": "10.22.166.36", @@ -407,12 +467,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-05-21T09:25:04Z", "duration": 29467000000, "end": "2018-05-21T09:24:03.669Z", "kind": "event", - "start": "2018-05-21T09:23:34.202Z" + "start": "2018-05-21T09:23:34.202Z", + "type": [ + "connection" + ] }, "flow": { "id": "pcgnaJ3iCvI", @@ -464,6 +530,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.22.166.36", + "10.20.147.28" + ] + }, "source": { "bytes": 2998, "ip": "10.22.166.36", @@ -486,12 +558,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-05-21T09:25:04Z", "duration": 29452000000, "end": "2018-05-21T09:24:03.67Z", "kind": "event", - "start": "2018-05-21T09:23:34.218Z" + "start": "2018-05-21T09:23:34.218Z", + "type": [ + "connection" + ] }, "flow": { "id": "_gbuwRW4AVE", @@ -543,6 +621,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.22.166.28", + "10.20.141.16" + ] + }, "source": { "bytes": 55773, "ip": "10.22.166.28", @@ -565,12 +649,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-05-21T09:25:04Z", "duration": 29449000000, "end": "2018-05-21T09:24:03.684Z", "kind": "event", - "start": "2018-05-21T09:23:34.235Z" + "start": "2018-05-21T09:23:34.235Z", + "type": [ + "connection" + ] }, "flow": { "id": "VOe0rUor-cg", @@ -622,6 +712,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.22.166.35", + "10.20.162.17" + ] + }, "source": { "bytes": 3239438, "ip": "10.22.166.35", @@ -644,12 +740,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-05-21T09:25:04Z", "duration": 30000000000, "end": "2018-05-21T09:24:03.685Z", "kind": "event", - "start": "2018-05-21T09:23:33.685Z" + "start": "2018-05-21T09:23:33.685Z", + "type": [ + "connection" + ] }, "flow": { "id": "nkp7tr2MVcs", @@ -701,6 +803,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.22.166.15", + "10.20.171.36" + ] + }, "source": { "bytes": 5701, "ip": "10.22.166.15", @@ -723,12 +831,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-05-21T09:25:04Z", "duration": 29391000000, "end": "2018-05-21T09:24:03.691Z", "kind": "event", - "start": "2018-05-21T09:23:34.3Z" + "start": "2018-05-21T09:23:34.3Z", + "type": [ + "connection" + ] }, "flow": { "id": "WxCFEmsTIh0", @@ -780,6 +894,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.22.166.2", + "10.22.208.12" + ] + }, "source": { "bytes": 4255012, "ip": "10.22.166.2", @@ -802,12 +922,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-05-21T09:25:04Z", "duration": 29196000000, "end": "2018-05-21T09:24:03.699Z", "kind": "event", - "start": "2018-05-21T09:23:34.503Z" + "start": "2018-05-21T09:23:34.503Z", + "type": [ + "connection" + ] }, "flow": { "id": "rAIv2psXy74", @@ -859,6 +985,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.22.166.28", + "10.22.196.21" + ] + }, "source": { "bytes": 37557, "ip": "10.22.166.28", @@ -881,12 +1013,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-05-21T09:25:04Z", "duration": 30000000000, "end": "2018-05-21T09:24:03.753Z", "kind": "event", - "start": "2018-05-21T09:23:33.753Z" + "start": "2018-05-21T09:23:33.753Z", + "type": [ + "connection" + ] }, "flow": { "id": "lR18K-eSVNM", @@ -938,6 +1076,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.22.166.25", + "10.22.202.15" + ] + }, "source": { "bytes": 23676, "ip": "10.22.166.25", @@ -960,12 +1104,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-05-21T09:25:04Z", "duration": 89282000000, "end": "2018-05-21T09:25:03.971Z", "kind": "event", - "start": "2018-05-21T09:23:34.689Z" + "start": "2018-05-21T09:23:34.689Z", + "type": [ + "connection" + ] }, "flow": { "id": "1XCFo-Jv19g", @@ -1017,6 +1167,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.22.166.25", + "10.20.166.26" + ] + }, "source": { "bytes": 22821, "ip": "10.22.166.25", @@ -1039,12 +1195,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-05-21T09:25:04Z", "duration": 90012000000, "end": "2018-05-21T09:25:03.95Z", "kind": "event", - "start": "2018-05-21T09:23:33.938Z" + "start": "2018-05-21T09:23:33.938Z", + "type": [ + "connection" + ] }, "flow": { "id": "DkV-9Meb8W8", @@ -1096,6 +1258,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.22.166.12", + "10.21.3.117" + ] + }, "source": { "bytes": 526, "ip": "10.22.166.12", @@ -1118,12 +1286,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-05-21T09:25:04Z", "duration": 60005000000, "end": "2018-05-21T09:25:03.938Z", "kind": "event", - "start": "2018-05-21T09:24:03.933Z" + "start": "2018-05-21T09:24:03.933Z", + "type": [ + "connection" + ] }, "flow": { "id": "v1m_MeAqdL4", @@ -1175,6 +1349,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.22.166.17", + "10.22.145.26" + ] + }, "source": { "bytes": 33129, "ip": "10.22.166.17", @@ -1197,12 +1377,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-05-21T09:25:04Z", "duration": 60006000000, "end": "2018-05-21T09:25:03.928Z", "kind": "event", - "start": "2018-05-21T09:24:03.922Z" + "start": "2018-05-21T09:24:03.922Z", + "type": [ + "connection" + ] }, "flow": { "id": "ru0mPvG-tKw", @@ -1254,6 +1440,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.22.166.36", + "10.21.75.38" + ] + }, "source": { "bytes": 5092, "ip": "10.22.166.36", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Huawei-Netstream.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Huawei-Netstream.golden.json index 99db67a6ed8..231fd5e8797 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Huawei-Netstream.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Huawei-Netstream.golden.json @@ -13,12 +13,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-01-29T03:02:20Z", "duration": 327060000000, "end": "2018-01-29T03:02:19Z", "kind": "event", - "start": "2018-01-29T02:56:51.94Z" + "start": "2018-01-29T02:56:51.94Z", + "type": [ + "connection" + ] }, "flow": { "id": "d-FUjj8eKi8", @@ -70,6 +76,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.108.219.53", + "10.111.112.204" + ] + }, "source": { "bytes": 200, "ip": "10.108.219.53", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-IE150-IE151.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-IE150-IE151.golden.json index 55e02042c28..326828e8304 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-IE150-IE151.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-IE150-IE151.golden.json @@ -12,9 +12,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-12-01T17:04:39Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "X6k2SQeAX5c", @@ -56,6 +62,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.3", + "192.168.0.2" + ] + }, "source": { "bytes": 78, "ip": "192.168.0.3", @@ -78,9 +90,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-12-01T17:04:39Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "XEzNKvE_H1k", @@ -122,6 +140,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.0.4", + "192.168.0.5" + ] + }, "source": { "bytes": 232, "ip": "192.168.0.4", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Palo-Alto-1-flowset-in-large-zero-filled-packet.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Palo-Alto-1-flowset-in-large-zero-filled-packet.golden.json index 35439694199..c5b53c195be 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Palo-Alto-1-flowset-in-large-zero-filled-packet.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Palo-Alto-1-flowset-in-large-zero-filled-packet.golden.json @@ -12,12 +12,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-06-06T13:20:17Z", "duration": 0, "end": "2018-06-06T13:20:02Z", "kind": "event", - "start": "2018-06-06T13:20:02Z" + "start": "2018-06-06T13:20:02Z", + "type": [ + "connection" + ] }, "flow": { "id": "A-NpGXd6eh4", @@ -62,6 +68,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "134.220.2.6", + "134.220.1.156" + ] + }, "source": { "bytes": 363, "ip": "134.220.2.6", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Palo-Alto-PAN--OS-with-app--id.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Palo-Alto-PAN--OS-with-app--id.golden.json index 78e7d67a489..754241fb7e0 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Palo-Alto-PAN--OS-with-app--id.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Palo-Alto-PAN--OS-with-app--id.golden.json @@ -12,12 +12,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-11-13T14:39:31Z", "duration": 0, "end": "2017-11-13T14:39:31Z", "kind": "event", - "start": "2017-11-13T14:39:31Z" + "start": "2017-11-13T14:39:31Z", + "type": [ + "connection" + ] }, "flow": { "id": "0HZ2F4aNlps", @@ -62,6 +68,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "23.35.171.27", + "10.32.91.205" + ] + }, "source": { "bytes": 70, "ip": "23.35.171.27", @@ -84,12 +96,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-11-13T14:39:31Z", "duration": 339000000000, "end": "2017-11-13T14:39:31Z", "kind": "event", - "start": "2017-11-13T14:33:52Z" + "start": "2017-11-13T14:33:52Z", + "type": [ + "connection" + ] }, "flow": { "id": "GTu1zsDt3yw", @@ -134,6 +152,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.32.105.103", + "162.115.24.30" + ] + }, "source": { "bytes": 111, "ip": "10.32.105.103", @@ -156,12 +180,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-11-13T14:39:31Z", "duration": 0, "end": "2017-11-13T14:39:31Z", "kind": "event", - "start": "2017-11-13T14:39:31Z" + "start": "2017-11-13T14:39:31Z", + "type": [ + "connection" + ] }, "flow": { "id": "nUCuFEB8z_c", @@ -206,6 +236,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.32.144.145", + "34.202.173.126" + ] + }, "source": { "bytes": 70, "ip": "10.32.144.145", @@ -228,12 +264,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-11-13T14:39:31Z", "duration": 0, "end": "2017-11-13T14:39:31Z", "kind": "event", - "start": "2017-11-13T14:39:31Z" + "start": "2017-11-13T14:39:31Z", + "type": [ + "connection" + ] }, "flow": { "id": "inYZm0Y9EVM", @@ -278,6 +320,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "23.209.52.99", + "10.130.145.44" + ] + }, "source": { "bytes": 70, "ip": "23.209.52.99", @@ -300,12 +348,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-11-13T14:39:31Z", "duration": 0, "end": "2017-11-13T14:39:31Z", "kind": "event", - "start": "2017-11-13T14:39:31Z" + "start": "2017-11-13T14:39:31Z", + "type": [ + "connection" + ] }, "flow": { "id": "6vds_sLxXqE", @@ -350,6 +404,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.50.97.57", + "10.50.96.20" + ] + }, "source": { "bytes": 78, "ip": "10.50.97.57", @@ -372,12 +432,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-11-13T14:39:31Z", "duration": 0, "end": "2017-11-13T14:39:31Z", "kind": "event", - "start": "2017-11-13T14:39:31Z" + "start": "2017-11-13T14:39:31Z", + "type": [ + "connection" + ] }, "flow": { "id": "6vds_sLxXqE", @@ -422,6 +488,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.50.96.20", + "10.50.97.57" + ] + }, "source": { "bytes": 78, "ip": "10.50.96.20", @@ -444,12 +516,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-11-13T14:39:31Z", "duration": 0, "end": "2017-11-13T14:39:31Z", "kind": "event", - "start": "2017-11-13T14:39:31Z" + "start": "2017-11-13T14:39:31Z", + "type": [ + "connection" + ] }, "flow": { "id": "v3XVGdLaIe4", @@ -494,6 +572,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "34.234.173.147", + "10.48.208.209" + ] + }, "source": { "bytes": 70, "ip": "34.234.173.147", @@ -516,12 +600,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-11-13T14:39:31Z", "duration": 0, "end": "2017-11-13T14:39:31Z", "kind": "event", - "start": "2017-11-13T14:39:31Z" + "start": "2017-11-13T14:39:31Z", + "type": [ + "connection" + ] }, "flow": { "id": "aenMB9Z5Tzc", @@ -566,6 +656,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.130.167.43", + "65.52.108.254" + ] + }, "source": { "bytes": 70, "ip": "10.130.167.43", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Streamcore.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Streamcore.golden.json index 1a8a189fb03..0fd9b02e864 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Streamcore.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Streamcore.golden.json @@ -12,12 +12,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-01-11T11:48:15Z", "duration": 6012000000, "end": "2017-01-11T11:47:28.879Z", "kind": "event", - "start": "2017-01-11T11:47:22.867Z" + "start": "2017-01-11T11:47:22.867Z", + "type": [ + "connection" + ] }, "flow": { "id": "wdxUeEaOBho", @@ -57,6 +63,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "100.78.40.201", + "10.231.128.150" + ] + }, "source": { "bytes": 128, "ip": "100.78.40.201", @@ -79,12 +91,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-01-11T11:48:15Z", "duration": 6020000000, "end": "2017-01-11T11:47:28.886Z", "kind": "event", - "start": "2017-01-11T11:47:22.866Z" + "start": "2017-01-11T11:47:22.866Z", + "type": [ + "connection" + ] }, "flow": { "id": "wdxUeEaOBho", @@ -124,6 +142,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.231.128.150", + "100.78.40.201" + ] + }, "source": { "bytes": 172, "ip": "10.231.128.150", @@ -146,12 +170,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-01-11T11:23:51Z", "duration": 50997000000, "end": "2017-01-11T11:23:34.936Z", "kind": "event", - "start": "2017-01-11T11:22:43.939Z" + "start": "2017-01-11T11:22:43.939Z", + "type": [ + "connection" + ] }, "flow": { "id": "6_Ia6lqx2cg", @@ -191,6 +221,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "100.78.40.201", + "10.27.8.20" + ] + }, "source": { "bytes": 3943, "ip": "100.78.40.201", @@ -213,12 +249,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2017-01-11T11:23:51Z", "duration": 51015000000, "end": "2017-01-11T11:23:34.954Z", "kind": "event", - "start": "2017-01-11T11:22:43.939Z" + "start": "2017-01-11T11:22:43.939Z", + "type": [ + "connection" + ] }, "flow": { "id": "6_Ia6lqx2cg", @@ -258,6 +300,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.27.8.20", + "100.78.40.201" + ] + }, "source": { "bytes": 3052, "ip": "10.27.8.20", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Ubiquiti-Edgerouter-with-MPLS-labels.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Ubiquiti-Edgerouter-with-MPLS-labels.golden.json index 06dc8e8c35c..cbfc95d8e22 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Ubiquiti-Edgerouter-with-MPLS-labels.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-Ubiquiti-Edgerouter-with-MPLS-labels.golden.json @@ -13,12 +13,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-09-10T16:23:30Z", "duration": 0, "end": "2016-09-10T16:17:25.825Z", "kind": "event", - "start": "2016-09-10T16:17:25.825Z" + "start": "2016-09-10T16:17:25.825Z", + "type": [ + "connection" + ] }, "flow": { "id": "KYJ6RiyA5YM", @@ -65,6 +71,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.1.0.135", + "10.4.0.251" + ] + }, "source": { "bytes": 174, "ip": "10.1.0.135", @@ -89,12 +101,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-09-10T16:23:30Z", "duration": 0, "end": "2016-09-10T16:17:25.825Z", "kind": "event", - "start": "2016-09-10T16:17:25.825Z" + "start": "2016-09-10T16:17:25.825Z", + "type": [ + "connection" + ] }, "flow": { "id": "4GHcyowN7sg", @@ -141,6 +159,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.1.0.136", + "10.4.0.251" + ] + }, "source": { "bytes": 87, "ip": "10.1.0.136", @@ -165,12 +189,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-09-10T16:23:30Z", "duration": 140227000000, "end": "2016-09-10T15:22:30.891Z", "kind": "event", - "start": "2016-09-10T15:20:10.664Z" + "start": "2016-09-10T15:20:10.664Z", + "type": [ + "connection" + ] }, "flow": { "id": "GRn2z1Rao3c", @@ -217,6 +247,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.1.0.232", + "10.4.0.251" + ] + }, "source": { "bytes": 1920, "ip": "10.1.0.232", @@ -241,12 +277,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-09-10T16:23:30Z", "duration": 140227000000, "end": "2016-09-10T15:22:30.891Z", "kind": "event", - "start": "2016-09-10T15:20:10.664Z" + "start": "2016-09-10T15:20:10.664Z", + "type": [ + "connection" + ] }, "flow": { "id": "iHA6jdIkqjA", @@ -293,6 +335,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.1.0.232", + "10.4.0.251" + ] + }, "source": { "bytes": 610, "ip": "10.1.0.232", @@ -317,12 +365,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-09-10T16:23:30Z", "duration": 177102000000, "end": "2016-09-10T16:20:32.763Z", "kind": "event", - "start": "2016-09-10T16:17:35.661Z" + "start": "2016-09-10T16:17:35.661Z", + "type": [ + "connection" + ] }, "flow": { "id": "cBjtKefzGos", @@ -369,6 +423,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.5.0.91", + "10.4.0.251" + ] + }, "source": { "bytes": 2420, "ip": "10.5.0.91", @@ -393,12 +453,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-09-10T16:23:30Z", "duration": 176903000000, "end": "2016-09-10T16:20:32.666Z", "kind": "event", - "start": "2016-09-10T16:17:35.763Z" + "start": "2016-09-10T16:17:35.763Z", + "type": [ + "connection" + ] }, "flow": { "id": "EzT0lQWYBRw", @@ -445,6 +511,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.1.0.30", + "10.4.0.251" + ] + }, "source": { "bytes": 10204, "ip": "10.1.0.30", @@ -469,12 +541,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-09-10T16:23:30Z", "duration": 0, "end": "2016-09-10T15:22:36.207Z", "kind": "event", - "start": "2016-09-10T15:22:36.207Z" + "start": "2016-09-10T15:22:36.207Z", + "type": [ + "connection" + ] }, "flow": { "id": "TROGwofkmJA", @@ -521,6 +599,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.3.0.100", + "10.4.0.251" + ] + }, "source": { "bytes": 216, "ip": "10.3.0.100", @@ -545,12 +629,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-09-10T16:23:30Z", "duration": 0, "end": "2016-09-10T16:17:35.661Z", "kind": "event", - "start": "2016-09-10T16:17:35.661Z" + "start": "2016-09-10T16:17:35.661Z", + "type": [ + "connection" + ] }, "flow": { "id": "wLclDbADA9s", @@ -597,6 +687,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.1.0.135", + "10.4.0.251" + ] + }, "source": { "bytes": 152, "ip": "10.1.0.135", @@ -620,12 +716,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-09-10T16:24:08Z", "duration": 116000000, "end": "2016-09-10T15:23:38.951Z", "kind": "event", - "start": "2016-09-10T15:23:38.835Z" + "start": "2016-09-10T15:23:38.835Z", + "type": [ + "connection" + ] }, "flow": { "id": "LpdyE0SSB-o", @@ -672,6 +774,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.1.98", + "10.0.0.73" + ] + }, "source": { "bytes": 260, "ip": "192.168.1.98", @@ -694,12 +802,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-09-10T16:24:08Z", "duration": 0, "end": "2016-09-10T16:18:39.443Z", "kind": "event", - "start": "2016-09-10T16:18:39.443Z" + "start": "2016-09-10T16:18:39.443Z", + "type": [ + "connection" + ] }, "flow": { "id": "32P6av-L8P0", @@ -746,6 +860,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.4.0.251", + "255.255.255.255" + ] + }, "source": { "bytes": 32, "ip": "10.4.0.251", @@ -768,12 +888,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-09-10T16:24:08Z", "duration": 0, "end": "2016-09-10T16:18:39.443Z", "kind": "event", - "start": "2016-09-10T16:18:39.443Z" + "start": "2016-09-10T16:18:39.443Z", + "type": [ + "connection" + ] }, "flow": { "id": "ft_m5C7Hgpo", @@ -820,6 +946,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.4.0.251", + "255.255.255.255" + ] + }, "source": { "bytes": 135, "ip": "10.4.0.251", @@ -842,12 +974,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-09-10T16:24:08Z", "duration": 0, "end": "2016-09-10T16:18:39.443Z", "kind": "event", - "start": "2016-09-10T16:18:39.443Z" + "start": "2016-09-10T16:18:39.443Z", + "type": [ + "connection" + ] }, "flow": { "id": "bVX88Ii80AQ", @@ -894,6 +1032,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.4.0.251", + "255.255.255.255" + ] + }, "source": { "bytes": 135, "ip": "10.4.0.251", @@ -916,12 +1060,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-09-10T16:24:08Z", "duration": 0, "end": "2016-09-10T16:18:39.443Z", "kind": "event", - "start": "2016-09-10T16:18:39.443Z" + "start": "2016-09-10T16:18:39.443Z", + "type": [ + "connection" + ] }, "flow": { "id": "bA4nBN4veuI", @@ -968,6 +1118,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.4.0.251", + "255.255.255.255" + ] + }, "source": { "bytes": 135, "ip": "10.4.0.251", @@ -990,12 +1146,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-09-10T16:24:08Z", "duration": 0, "end": "2016-09-10T16:18:39.443Z", "kind": "event", - "start": "2016-09-10T16:18:39.443Z" + "start": "2016-09-10T16:18:39.443Z", + "type": [ + "connection" + ] }, "flow": { "id": "lY5yfRKXE3s", @@ -1042,6 +1204,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.4.0.251", + "255.255.255.255" + ] + }, "source": { "bytes": 135, "ip": "10.4.0.251", @@ -1064,12 +1232,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-09-10T16:24:08Z", "duration": 0, "end": "2016-09-10T16:18:39.443Z", "kind": "event", - "start": "2016-09-10T16:18:39.443Z" + "start": "2016-09-10T16:18:39.443Z", + "type": [ + "connection" + ] }, "flow": { "id": "x3GfEtY3zCQ", @@ -1116,6 +1290,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.4.0.251", + "255.255.255.255" + ] + }, "source": { "bytes": 135, "ip": "10.4.0.251", @@ -1138,12 +1318,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-09-10T16:24:08Z", "duration": 1250988000000, "end": "2016-09-10T15:23:44.363Z", "kind": "event", - "start": "2016-09-10T15:02:53.375Z" + "start": "2016-09-10T15:02:53.375Z", + "type": [ + "connection" + ] }, "flow": { "id": "bfT831bq5AI", @@ -1190,6 +1376,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.1.102", + "10.2.0.95" + ] + }, "source": { "bytes": 3668, "ip": "192.168.1.102", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-field-layer2segmentid.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-field-layer2segmentid.golden.json index c8e2f1e00f5..a3f5a4e3cc1 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-field-layer2segmentid.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-field-layer2segmentid.golden.json @@ -12,12 +12,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-01-16T09:45:02Z", "duration": 0, "end": "2018-01-16T09:44:47Z", "kind": "event", - "start": "2018-01-16T09:44:47Z" + "start": "2018-01-16T09:44:47Z", + "type": [ + "connection" + ] }, "flow": { "id": "tS3zN7t_rFg", @@ -61,6 +67,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.200.136", + "80.82.237.40" + ] + }, "source": { "bytes": 52, "ip": "192.168.200.136", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-ipt_netflow-reduced-size-encoding.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-ipt_netflow-reduced-size-encoding.golden.json index ec7c0c9f8a7..106d624530a 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-ipt_netflow-reduced-size-encoding.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-ipt_netflow-reduced-size-encoding.golden.json @@ -13,12 +13,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-02-18T05:47:09Z", "duration": 8996000000, "end": "2018-02-18T05:46:53.996Z", "kind": "event", - "start": "2018-02-18T05:46:45Z" + "start": "2018-02-18T05:46:45Z", + "type": [ + "connection" + ] }, "flow": { "id": "XLC-7u3wi0U", @@ -65,6 +71,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "37.122.1.226", + "193.151.198.166" + ] + }, "source": { "bytes": 156, "ip": "37.122.1.226", @@ -89,12 +101,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-02-18T05:47:09Z", "duration": 0, "end": "2018-02-18T05:46:53.992Z", "kind": "event", - "start": "2018-02-18T05:46:53.992Z" + "start": "2018-02-18T05:46:53.992Z", + "type": [ + "connection" + ] }, "flow": { "id": "2mdiEm9z6pA", @@ -141,6 +159,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "5.141.231.166", + "193.151.199.69" + ] + }, "source": { "bytes": 48, "ip": "5.141.231.166", @@ -165,12 +189,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-02-18T05:47:09Z", "duration": 7652000000, "end": "2018-02-18T05:46:53.988Z", "kind": "event", - "start": "2018-02-18T05:46:46.336Z" + "start": "2018-02-18T05:46:46.336Z", + "type": [ + "connection" + ] }, "flow": { "id": "IKsDJxZK5UA", @@ -217,6 +247,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.233.128.4", + "212.224.113.74" + ] + }, "source": { "bytes": 584, "ip": "10.233.128.4", @@ -241,12 +277,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-02-18T05:47:09Z", "duration": 16000000, "end": "2018-02-18T05:46:53.992Z", "kind": "event", - "start": "2018-02-18T05:46:53.976Z" + "start": "2018-02-18T05:46:53.976Z", + "type": [ + "connection" + ] }, "flow": { "id": "lfpS1KL7LwI", @@ -293,6 +335,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "193.151.192.46", + "10.236.8.4" + ] + }, "source": { "bytes": 577, "ip": "193.151.192.46", @@ -317,12 +365,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-02-18T05:47:09Z", "duration": 1168000000, "end": "2018-02-18T05:46:53.988Z", "kind": "event", - "start": "2018-02-18T05:46:52.82Z" + "start": "2018-02-18T05:46:52.82Z", + "type": [ + "connection" + ] }, "flow": { "id": "HRyho8QOr5M", @@ -369,6 +423,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.235.197.6", + "62.221.115.205" + ] + }, "source": { "bytes": 152, "ip": "10.235.197.6", @@ -393,12 +453,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-02-18T05:47:09Z", "duration": 8992000000, "end": "2018-02-18T05:46:53.992Z", "kind": "event", - "start": "2018-02-18T05:46:45Z" + "start": "2018-02-18T05:46:45Z", + "type": [ + "connection" + ] }, "flow": { "id": "jbL3H_oK7ok", @@ -445,6 +511,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.236.31.7", + "37.146.125.64" + ] + }, "source": { "bytes": 152, "ip": "10.236.31.7", @@ -469,12 +541,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-02-18T05:47:09Z", "duration": 4432000000, "end": "2018-02-18T05:46:53.992Z", "kind": "event", - "start": "2018-02-18T05:46:49.56Z" + "start": "2018-02-18T05:46:49.56Z", + "type": [ + "connection" + ] }, "flow": { "id": "ayKjfr1z0QU", @@ -521,6 +599,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.233.151.8", + "52.198.214.72" + ] + }, "source": { "bytes": 1809, "ip": "10.233.151.8", @@ -545,12 +629,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-02-18T05:47:09Z", "duration": 80000000, "end": "2018-02-18T05:46:53.996Z", "kind": "event", - "start": "2018-02-18T05:46:53.916Z" + "start": "2018-02-18T05:46:53.916Z", + "type": [ + "connection" + ] }, "flow": { "id": "B15R8wv_tVI", @@ -597,6 +687,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.234.22.4", + "64.233.161.188" + ] + }, "source": { "bytes": 234, "ip": "10.234.22.4", @@ -621,12 +717,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-02-18T05:47:09Z", "duration": 400000000, "end": "2018-02-18T05:46:53.992Z", "kind": "event", - "start": "2018-02-18T05:46:53.592Z" + "start": "2018-02-18T05:46:53.592Z", + "type": [ + "connection" + ] }, "flow": { "id": "oYN-uwp504w", @@ -673,6 +775,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.233.36.7", + "185.209.20.240" + ] + }, "source": { "bytes": 1681, "ip": "10.233.36.7", @@ -697,12 +805,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-02-18T05:47:09Z", "duration": 9024000000, "end": "2018-02-18T05:46:53.988Z", "kind": "event", - "start": "2018-02-18T05:46:44.964Z" + "start": "2018-02-18T05:46:44.964Z", + "type": [ + "connection" + ] }, "flow": { "id": "MUPum_LUoxk", @@ -749,6 +863,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "10.233.200.7", + "84.39.245.175" + ] + }, "source": { "bytes": 152, "ip": "10.233.200.7", @@ -773,12 +893,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-02-18T05:47:09Z", "duration": 60000000, "end": "2018-02-18T05:46:53.992Z", "kind": "event", - "start": "2018-02-18T05:46:53.932Z" + "start": "2018-02-18T05:46:53.932Z", + "type": [ + "connection" + ] }, "flow": { "id": "YStkNP0pV1E", @@ -825,6 +951,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "23.43.139.27", + "10.232.8.45" + ] + }, "source": { "bytes": 1866, "ip": "23.43.139.27", @@ -849,12 +981,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-02-18T05:47:09Z", "duration": 192000000, "end": "2018-02-18T05:46:53.992Z", "kind": "event", - "start": "2018-02-18T05:46:53.8Z" + "start": "2018-02-18T05:46:53.8Z", + "type": [ + "connection" + ] }, "flow": { "id": "nkastJ_vPI4", @@ -901,6 +1039,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "2.17.140.47", + "10.233.150.21" + ] + }, "source": { "bytes": 187, "ip": "2.17.140.47", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-macaddress.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-macaddress.golden.json index 0115c023fb6..c1857f85210 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-macaddress.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-macaddress.golden.json @@ -7,7 +7,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-10T08:46:56Z", "kind": "event" }, @@ -47,9 +50,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-10T08:47:01Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "zQfsdfKgh-o", @@ -81,6 +90,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.1", + "172.16.32.201" + ] + }, "source": { "ip": "172.16.32.1", "locality": "private", @@ -103,9 +118,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-10T08:47:01Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Tw1iOKJ-dfE", @@ -137,6 +158,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.201", + "172.16.32.100" + ] + }, "source": { "ip": "172.16.32.201", "locality": "private", @@ -159,9 +186,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-10T08:47:01Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "NF1W3jyrHAA", @@ -193,6 +226,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.100", + "172.16.32.201" + ] + }, "source": { "ip": "172.16.32.100", "locality": "private", @@ -215,9 +254,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-10T08:47:01Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "B-_-kE8PEgA", @@ -249,6 +294,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.1", + "172.16.32.201" + ] + }, "source": { "ip": "172.16.32.1", "locality": "private", @@ -271,9 +322,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-10T08:47:01Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "B-_-kE8PEgA", @@ -305,6 +362,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.201", + "172.16.32.1" + ] + }, "source": { "ip": "172.16.32.201", "locality": "private", @@ -327,9 +390,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-10T08:47:01Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "q6jss8DvXWE", @@ -361,6 +430,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.1", + "172.16.32.201" + ] + }, "source": { "ip": "172.16.32.1", "locality": "private", @@ -383,9 +458,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-10T08:47:01Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "q6jss8DvXWE", @@ -417,6 +498,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.201", + "172.16.32.1" + ] + }, "source": { "ip": "172.16.32.201", "locality": "private", @@ -439,9 +526,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-10T08:47:01Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "3TmuMjQR8Mk", @@ -473,6 +566,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.1", + "172.16.32.201" + ] + }, "source": { "ip": "172.16.32.1", "locality": "private", @@ -495,9 +594,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-10T08:47:01Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "3TmuMjQR8Mk", @@ -529,6 +634,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.201", + "172.16.32.1" + ] + }, "source": { "ip": "172.16.32.201", "locality": "private", @@ -551,9 +662,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-10T08:47:01Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "2KDgFVtVKGg", @@ -585,6 +702,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.1", + "172.16.32.201" + ] + }, "source": { "ip": "172.16.32.1", "locality": "private", @@ -607,9 +730,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-10T08:47:01Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "2KDgFVtVKGg", @@ -641,6 +770,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.201", + "172.16.32.1" + ] + }, "source": { "ip": "172.16.32.201", "locality": "private", @@ -663,9 +798,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-10T08:47:01Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "vwr6dNcr6FE", @@ -697,6 +838,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.1", + "172.16.32.201" + ] + }, "source": { "ip": "172.16.32.1", "locality": "private", @@ -719,9 +866,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-10T08:47:01Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "vwr6dNcr6FE", @@ -753,6 +906,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.201", + "172.16.32.1" + ] + }, "source": { "ip": "172.16.32.201", "locality": "private", @@ -775,9 +934,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-10T08:47:01Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "tmgCubSF_CU", @@ -809,6 +974,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.1", + "172.16.32.201" + ] + }, "source": { "ip": "172.16.32.1", "locality": "private", @@ -831,9 +1002,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-10T08:47:01Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "tmgCubSF_CU", @@ -865,6 +1042,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.201", + "172.16.32.1" + ] + }, "source": { "ip": "172.16.32.201", "locality": "private", @@ -887,9 +1070,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-10T08:47:01Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Agzgga7RAr0", @@ -921,6 +1110,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.1", + "172.16.32.201" + ] + }, "source": { "ip": "172.16.32.1", "locality": "private", @@ -943,9 +1138,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-10T08:47:01Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Agzgga7RAr0", @@ -977,6 +1178,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.201", + "172.16.32.1" + ] + }, "source": { "ip": "172.16.32.201", "locality": "private", @@ -999,9 +1206,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-10T08:47:01Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "-cqFlm16mLc", @@ -1033,6 +1246,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.1", + "172.16.32.201" + ] + }, "source": { "ip": "172.16.32.1", "locality": "private", @@ -1055,9 +1274,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-10T08:47:01Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "-cqFlm16mLc", @@ -1089,6 +1314,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.201", + "172.16.32.1" + ] + }, "source": { "ip": "172.16.32.201", "locality": "private", @@ -1111,9 +1342,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-10T08:47:01Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Txfldw7-948", @@ -1145,6 +1382,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.1", + "172.16.32.201" + ] + }, "source": { "ip": "172.16.32.1", "locality": "private", @@ -1167,9 +1410,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-10T08:47:01Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Txfldw7-948", @@ -1201,6 +1450,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.201", + "172.16.32.1" + ] + }, "source": { "ip": "172.16.32.201", "locality": "private", @@ -1223,9 +1478,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-10T08:47:01Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "iaXg6w051Ho", @@ -1257,6 +1518,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.1", + "172.16.32.201" + ] + }, "source": { "ip": "172.16.32.1", "locality": "private", @@ -1279,9 +1546,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-10T08:47:01Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "iaXg6w051Ho", @@ -1313,6 +1586,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.201", + "172.16.32.1" + ] + }, "source": { "ip": "172.16.32.201", "locality": "private", @@ -1335,9 +1614,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-10T08:47:01Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "cEvEMCFhKJk", @@ -1369,6 +1654,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.1", + "172.16.32.201" + ] + }, "source": { "ip": "172.16.32.1", "locality": "private", @@ -1391,9 +1682,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-10T08:47:01Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "cEvEMCFhKJk", @@ -1425,6 +1722,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.201", + "172.16.32.1" + ] + }, "source": { "ip": "172.16.32.201", "locality": "private", @@ -1447,9 +1750,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-10T08:47:01Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "DnN0kX-gR3Q", @@ -1481,6 +1790,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.1", + "172.16.32.201" + ] + }, "source": { "ip": "172.16.32.1", "locality": "private", @@ -1503,9 +1818,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-10T08:47:01Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "DnN0kX-gR3Q", @@ -1537,6 +1858,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.201", + "172.16.32.1" + ] + }, "source": { "ip": "172.16.32.201", "locality": "private", @@ -1559,9 +1886,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-10T08:47:01Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "-kLcuxmRzgk", @@ -1593,6 +1926,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.1", + "172.16.32.201" + ] + }, "source": { "ip": "172.16.32.1", "locality": "private", @@ -1615,9 +1954,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-10T08:47:01Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "-kLcuxmRzgk", @@ -1649,6 +1994,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.201", + "172.16.32.1" + ] + }, "source": { "ip": "172.16.32.201", "locality": "private", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-multiple-netflow-exporters.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-multiple-netflow-exporters.golden.json index e408d9f7488..cfec457a4f0 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-multiple-netflow-exporters.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-multiple-netflow-exporters.golden.json @@ -7,7 +7,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-08T19:06:29Z", "kind": "event" }, @@ -46,12 +49,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-08T19:04:30Z", "duration": 1000000, "end": "2015-10-08T19:03:46.141Z", "kind": "event", - "start": "2015-10-08T19:03:46.14Z" + "start": "2015-10-08T19:03:46.14Z", + "type": [ + "connection" + ] }, "flow": { "id": "1E-M5OJg_go", @@ -92,6 +101,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.100", + "172.16.32.248" + ] + }, "source": { "bytes": 76, "ip": "172.16.32.100", @@ -114,12 +129,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-08T19:04:30Z", "duration": 1000000, "end": "2015-10-08T19:03:46.141Z", "kind": "event", - "start": "2015-10-08T19:03:46.14Z" + "start": "2015-10-08T19:03:46.14Z", + "type": [ + "connection" + ] }, "flow": { "id": "yMxFd8CW_Ok", @@ -160,6 +181,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.248", + "172.16.32.100" + ] + }, "source": { "bytes": 76, "ip": "172.16.32.248", @@ -182,12 +209,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-08T19:04:30Z", "duration": 1000000, "end": "2015-10-08T19:03:51.814Z", "kind": "event", - "start": "2015-10-08T19:03:51.813Z" + "start": "2015-10-08T19:03:51.813Z", + "type": [ + "connection" + ] }, "flow": { "id": "NF1W3jyrHAA", @@ -228,6 +261,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.100", + "172.16.32.201" + ] + }, "source": { "bytes": 76, "ip": "172.16.32.100", @@ -250,12 +289,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-08T19:04:30Z", "duration": 1000000, "end": "2015-10-08T19:03:51.814Z", "kind": "event", - "start": "2015-10-08T19:03:51.813Z" + "start": "2015-10-08T19:03:51.813Z", + "type": [ + "connection" + ] }, "flow": { "id": "Tw1iOKJ-dfE", @@ -296,6 +341,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.201", + "172.16.32.100" + ] + }, "source": { "bytes": 76, "ip": "172.16.32.201", @@ -318,12 +369,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-08T19:04:30Z", "duration": 0, "end": "2015-10-08T19:03:55.958Z", "kind": "event", - "start": "2015-10-08T19:03:55.958Z" + "start": "2015-10-08T19:03:55.958Z", + "type": [ + "connection" + ] }, "flow": { "id": "sNF38-obC7k", @@ -364,6 +421,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.100", + "172.16.32.202" + ] + }, "source": { "bytes": 76, "ip": "172.16.32.100", @@ -386,12 +449,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-08T19:04:30Z", "duration": 0, "end": "2015-10-08T19:03:55.958Z", "kind": "event", - "start": "2015-10-08T19:03:55.958Z" + "start": "2015-10-08T19:03:55.958Z", + "type": [ + "connection" + ] }, "flow": { "id": "458D6voFu3E", @@ -432,6 +501,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.202", + "172.16.32.100" + ] + }, "source": { "bytes": 76, "ip": "172.16.32.202", @@ -452,12 +527,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-08T19:04:30Z", "duration": 38081000000, "end": "2015-10-08T19:04:25.9Z", "kind": "event", - "start": "2015-10-08T19:03:47.819Z" + "start": "2015-10-08T19:03:47.819Z", + "type": [ + "connection" + ] }, "flow": { "id": "tYpw8DU5u10", @@ -518,12 +599,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-08T19:06:29Z", "duration": 5000000, "end": "2015-10-08T19:05:55.015Z", "kind": "event", - "start": "2015-10-08T19:05:55.01Z" + "start": "2015-10-08T19:05:55.01Z", + "type": [ + "connection" + ] }, "flow": { "id": "zQfsdfKgh-o", @@ -568,6 +655,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.201", + "172.16.32.1" + ] + }, "source": { "bytes": 200, "ip": "172.16.32.201", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-nprobe-DPI-L7.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-nprobe-DPI-L7.golden.json index 69e0a14a66c..abf9608ce15 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-nprobe-DPI-L7.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-nprobe-DPI-L7.golden.json @@ -12,9 +12,15 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "1970-01-01T00:08:22Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "oFN7CMNpOLQ", @@ -52,6 +58,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "0.0.0.0", + "0.0.0.0" + ] + }, "source": { "bytes": 82, "ip": "0.0.0.0", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-options-template-with-scope-fields.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-options-template-with-scope-fields.golden.json index 49732550717..ee3ffd12ab2 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-options-template-with-scope-fields.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-options-template-with-scope-fields.golden.json @@ -7,7 +7,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-08T19:06:29Z", "kind": "event" }, diff --git a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-template-with-0-length-fields.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-template-with-0-length-fields.golden.json index cf8c141f5c3..a1fa471443b 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-template-with-0-length-fields.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-template-with-0-length-fields.golden.json @@ -12,12 +12,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-23T01:35:31Z", "duration": 0, "end": "2016-12-23T01:34:48.299Z", "kind": "event", - "start": "2016-12-23T01:34:48.299Z" + "start": "2016-12-23T01:34:48.299Z", + "type": [ + "connection" + ] }, "flow": { "id": "BSsjrf_TZnk", @@ -62,6 +68,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "239.255.255.250", + "192.168.1.80" + ] + }, "source": { "bytes": 0, "ip": "239.255.255.250", @@ -84,12 +96,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-23T01:35:31Z", "duration": 0, "end": "2016-12-23T01:34:48.299Z", "kind": "event", - "start": "2016-12-23T01:34:48.299Z" + "start": "2016-12-23T01:34:48.299Z", + "type": [ + "connection" + ] }, "flow": { "id": "R1Sjz_ITbgo", @@ -134,6 +152,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.1.80", + "239.255.255.250" + ] + }, "source": { "bytes": 0, "ip": "192.168.1.80", @@ -156,12 +180,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-23T01:35:31Z", "duration": 0, "end": "2016-12-23T01:34:51.469Z", "kind": "event", - "start": "2016-12-23T01:34:51.469Z" + "start": "2016-12-23T01:34:51.469Z", + "type": [ + "connection" + ] }, "flow": { "id": "FpUgB2PIhjQ", @@ -206,6 +236,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "239.255.255.250", + "192.168.1.95" + ] + }, "source": { "bytes": 0, "ip": "239.255.255.250", @@ -228,12 +264,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-23T01:35:31Z", "duration": 0, "end": "2016-12-23T01:34:51.469Z", "kind": "event", - "start": "2016-12-23T01:34:51.469Z" + "start": "2016-12-23T01:34:51.469Z", + "type": [ + "connection" + ] }, "flow": { "id": "qN8iQExOvkc", @@ -278,6 +320,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.1.95", + "239.255.255.250" + ] + }, "source": { "bytes": 32, "ip": "192.168.1.95", @@ -300,12 +348,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-23T01:35:31Z", "duration": 0, "end": "2016-12-23T01:34:51.469Z", "kind": "event", - "start": "2016-12-23T01:34:51.469Z" + "start": "2016-12-23T01:34:51.469Z", + "type": [ + "connection" + ] }, "flow": { "id": "FpUgB2PIhjQ", @@ -350,6 +404,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "239.255.255.250", + "192.168.1.95" + ] + }, "source": { "bytes": 0, "ip": "239.255.255.250", @@ -372,12 +432,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-23T01:35:31Z", "duration": 0, "end": "2016-12-23T01:34:51.469Z", "kind": "event", - "start": "2016-12-23T01:34:51.469Z" + "start": "2016-12-23T01:34:51.469Z", + "type": [ + "connection" + ] }, "flow": { "id": "qN8iQExOvkc", @@ -422,6 +488,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.1.95", + "239.255.255.250" + ] + }, "source": { "bytes": 0, "ip": "192.168.1.95", @@ -444,12 +516,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-23T01:35:31Z", "duration": 0, "end": "2016-12-23T01:34:51.569Z", "kind": "event", - "start": "2016-12-23T01:34:51.569Z" + "start": "2016-12-23T01:34:51.569Z", + "type": [ + "connection" + ] }, "flow": { "id": "WuFpyBG1Gt0", @@ -494,6 +572,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "239.255.255.250", + "192.168.1.33" + ] + }, "source": { "bytes": 0, "ip": "239.255.255.250", @@ -516,12 +600,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-23T01:35:31Z", "duration": 0, "end": "2016-12-23T01:34:51.569Z", "kind": "event", - "start": "2016-12-23T01:34:51.569Z" + "start": "2016-12-23T01:34:51.569Z", + "type": [ + "connection" + ] }, "flow": { "id": "1aysHUs7BpA", @@ -566,6 +656,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.1.33", + "239.255.255.250" + ] + }, "source": { "bytes": 32, "ip": "192.168.1.33", @@ -588,12 +684,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-23T01:35:31Z", "duration": 0, "end": "2016-12-23T01:34:51.569Z", "kind": "event", - "start": "2016-12-23T01:34:51.569Z" + "start": "2016-12-23T01:34:51.569Z", + "type": [ + "connection" + ] }, "flow": { "id": "WuFpyBG1Gt0", @@ -638,6 +740,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "239.255.255.250", + "192.168.1.33" + ] + }, "source": { "bytes": 0, "ip": "239.255.255.250", @@ -660,12 +768,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-12-23T01:35:31Z", "duration": 0, "end": "2016-12-23T01:34:51.569Z", "kind": "event", - "start": "2016-12-23T01:34:51.569Z" + "start": "2016-12-23T01:34:51.569Z", + "type": [ + "connection" + ] }, "flow": { "id": "1aysHUs7BpA", @@ -710,6 +824,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "192.168.1.33", + "239.255.255.250" + ] + }, "source": { "bytes": 0, "ip": "192.168.1.33", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-valid-01.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-valid-01.golden.json index bcea73e1dfa..2138b0b63fd 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-valid-01.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/Netflow-9-valid-01.golden.json @@ -12,12 +12,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-08T19:04:30Z", "duration": 1000000, "end": "2015-10-08T19:03:46.141Z", "kind": "event", - "start": "2015-10-08T19:03:46.14Z" + "start": "2015-10-08T19:03:46.14Z", + "type": [ + "connection" + ] }, "flow": { "id": "1E-M5OJg_go", @@ -58,6 +64,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.100", + "172.16.32.248" + ] + }, "source": { "bytes": 76, "ip": "172.16.32.100", @@ -80,12 +92,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-08T19:04:30Z", "duration": 1000000, "end": "2015-10-08T19:03:46.141Z", "kind": "event", - "start": "2015-10-08T19:03:46.14Z" + "start": "2015-10-08T19:03:46.14Z", + "type": [ + "connection" + ] }, "flow": { "id": "yMxFd8CW_Ok", @@ -126,6 +144,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.248", + "172.16.32.100" + ] + }, "source": { "bytes": 76, "ip": "172.16.32.248", @@ -148,12 +172,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-08T19:04:30Z", "duration": 1000000, "end": "2015-10-08T19:03:51.814Z", "kind": "event", - "start": "2015-10-08T19:03:51.813Z" + "start": "2015-10-08T19:03:51.813Z", + "type": [ + "connection" + ] }, "flow": { "id": "NF1W3jyrHAA", @@ -194,6 +224,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.100", + "172.16.32.201" + ] + }, "source": { "bytes": 76, "ip": "172.16.32.100", @@ -216,12 +252,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-08T19:04:30Z", "duration": 1000000, "end": "2015-10-08T19:03:51.814Z", "kind": "event", - "start": "2015-10-08T19:03:51.813Z" + "start": "2015-10-08T19:03:51.813Z", + "type": [ + "connection" + ] }, "flow": { "id": "Tw1iOKJ-dfE", @@ -262,6 +304,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.201", + "172.16.32.100" + ] + }, "source": { "bytes": 76, "ip": "172.16.32.201", @@ -284,12 +332,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-08T19:04:30Z", "duration": 0, "end": "2015-10-08T19:03:55.958Z", "kind": "event", - "start": "2015-10-08T19:03:55.958Z" + "start": "2015-10-08T19:03:55.958Z", + "type": [ + "connection" + ] }, "flow": { "id": "sNF38-obC7k", @@ -330,6 +384,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.100", + "172.16.32.202" + ] + }, "source": { "bytes": 76, "ip": "172.16.32.100", @@ -352,12 +412,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-08T19:04:30Z", "duration": 0, "end": "2015-10-08T19:03:55.958Z", "kind": "event", - "start": "2015-10-08T19:03:55.958Z" + "start": "2015-10-08T19:03:55.958Z", + "type": [ + "connection" + ] }, "flow": { "id": "458D6voFu3E", @@ -398,6 +464,12 @@ "observer": { "ip": "192.0.2.1" }, + "related": { + "ip": [ + "172.16.32.202", + "172.16.32.100" + ] + }, "source": { "bytes": 76, "ip": "172.16.32.202", @@ -418,12 +490,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2015-10-08T19:04:30Z", "duration": 38081000000, "end": "2015-10-08T19:04:25.9Z", "kind": "event", - "start": "2015-10-08T19:03:47.819Z" + "start": "2015-10-08T19:03:47.819Z", + "type": [ + "connection" + ] }, "flow": { "id": "tYpw8DU5u10", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/Netflow9-Juniper-SRX-options-template-with-0-scope-field-length.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/Netflow9-Juniper-SRX-options-template-with-0-scope-field-length.golden.json index 23563687e00..1dc8829da2e 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/Netflow9-Juniper-SRX-options-template-with-0-scope-field-length.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/Netflow9-Juniper-SRX-options-template-with-0-scope-field-length.golden.json @@ -7,7 +7,10 @@ "Fields": { "event": { "action": "netflow_options", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2016-11-29T00:21:56Z", "kind": "event" }, diff --git a/x-pack/filebeat/input/netflow/testdata/golden/ipfix_cisco.pcap.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/ipfix_cisco.pcap.golden.json index 2ff196e7950..da28639ceae 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/ipfix_cisco.pcap.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/ipfix_cisco.pcap.golden.json @@ -17,7 +17,10 @@ "action": "netflow_flow", "category": "network_session", "created": "2018-07-03T10:47:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Vhs9T5k296w", @@ -102,7 +105,10 @@ "action": "netflow_flow", "category": "network_session", "created": "2018-07-03T10:47:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Vhs9T5k296w", @@ -187,7 +193,10 @@ "action": "netflow_flow", "category": "network_session", "created": "2018-07-03T10:47:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Vhs9T5k296w", @@ -272,7 +281,10 @@ "action": "netflow_flow", "category": "network_session", "created": "2018-07-03T10:47:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Vhs9T5k296w", @@ -357,7 +369,10 @@ "action": "netflow_flow", "category": "network_session", "created": "2018-07-03T10:47:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Vhs9T5k296w", @@ -442,7 +457,10 @@ "action": "netflow_flow", "category": "network_session", "created": "2018-07-03T10:47:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Vhs9T5k296w", @@ -527,7 +545,10 @@ "action": "netflow_flow", "category": "network_session", "created": "2018-07-03T10:47:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Vhs9T5k296w", @@ -612,7 +633,10 @@ "action": "netflow_flow", "category": "network_session", "created": "2018-07-03T10:47:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Vhs9T5k296w", @@ -697,7 +721,10 @@ "action": "netflow_flow", "category": "network_session", "created": "2018-07-03T10:47:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Vhs9T5k296w", @@ -782,7 +809,10 @@ "action": "netflow_flow", "category": "network_session", "created": "2018-07-03T10:47:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Vhs9T5k296w", @@ -867,7 +897,10 @@ "action": "netflow_flow", "category": "network_session", "created": "2018-07-03T10:47:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Vhs9T5k296w", @@ -952,7 +985,10 @@ "action": "netflow_flow", "category": "network_session", "created": "2018-07-03T10:47:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Vhs9T5k296w", @@ -1037,7 +1073,10 @@ "action": "netflow_flow", "category": "network_session", "created": "2018-07-03T10:47:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Vhs9T5k296w", @@ -1122,7 +1161,10 @@ "action": "netflow_flow", "category": "network_session", "created": "2018-07-03T10:47:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Vhs9T5k296w", @@ -1207,7 +1249,10 @@ "action": "netflow_flow", "category": "network_session", "created": "2018-07-03T10:47:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Vhs9T5k296w", @@ -1292,7 +1337,10 @@ "action": "netflow_flow", "category": "network_session", "created": "2018-07-03T10:47:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Vhs9T5k296w", @@ -1377,7 +1425,10 @@ "action": "netflow_flow", "category": "network_session", "created": "2018-07-03T10:47:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Vhs9T5k296w", @@ -1462,7 +1513,10 @@ "action": "netflow_flow", "category": "network_session", "created": "2018-07-03T10:47:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Vhs9T5k296w", @@ -1547,7 +1601,10 @@ "action": "netflow_flow", "category": "network_session", "created": "2018-07-03T10:47:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Vhs9T5k296w", @@ -1632,7 +1689,10 @@ "action": "netflow_flow", "category": "network_session", "created": "2018-07-03T10:47:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Vhs9T5k296w", @@ -1717,7 +1777,10 @@ "action": "netflow_flow", "category": "network_session", "created": "2018-07-03T10:47:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Vhs9T5k296w", @@ -1802,7 +1865,10 @@ "action": "netflow_flow", "category": "network_session", "created": "2018-07-03T10:47:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Vhs9T5k296w", @@ -1887,7 +1953,10 @@ "action": "netflow_flow", "category": "network_session", "created": "2018-07-03T10:47:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Vhs9T5k296w", @@ -1972,7 +2041,10 @@ "action": "netflow_flow", "category": "network_session", "created": "2018-07-03T10:47:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Vhs9T5k296w", @@ -2057,7 +2129,10 @@ "action": "netflow_flow", "category": "network_session", "created": "2018-07-03T10:47:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Vhs9T5k296w", @@ -2142,7 +2217,10 @@ "action": "netflow_flow", "category": "network_session", "created": "2018-07-03T10:47:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Vhs9T5k296w", @@ -2227,7 +2305,10 @@ "action": "netflow_flow", "category": "network_session", "created": "2018-07-03T10:47:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Vhs9T5k296w", @@ -2312,7 +2393,10 @@ "action": "netflow_flow", "category": "network_session", "created": "2018-07-03T10:47:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Vhs9T5k296w", @@ -2397,7 +2481,10 @@ "action": "netflow_flow", "category": "network_session", "created": "2018-07-03T10:47:00Z", - "kind": "event" + "kind": "event", + "type": [ + "connection" + ] }, "flow": { "id": "Vhs9T5k296w", diff --git a/x-pack/filebeat/input/netflow/testdata/golden/netflow9_ubiquiti_edgerouter.pcap.golden.json b/x-pack/filebeat/input/netflow/testdata/golden/netflow9_ubiquiti_edgerouter.pcap.golden.json index 3bd1907cc87..0f198716213 100644 --- a/x-pack/filebeat/input/netflow/testdata/golden/netflow9_ubiquiti_edgerouter.pcap.golden.json +++ b/x-pack/filebeat/input/netflow/testdata/golden/netflow9_ubiquiti_edgerouter.pcap.golden.json @@ -12,12 +12,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-08-09T16:49:04Z", "duration": 287000000, "end": "2018-08-09T16:43:00.307Z", "kind": "event", - "start": "2018-08-09T16:43:00.02Z" + "start": "2018-08-09T16:43:00.02Z", + "type": [ + "connection" + ] }, "flow": { "id": "NPZRWU1oZKQ", @@ -64,6 +70,12 @@ "observer": { "ip": "10.100.4.1" }, + "related": { + "ip": [ + "10.100.5.2", + "159.65.125.168" + ] + }, "source": { "bytes": 421, "ip": "10.100.5.2", @@ -86,12 +98,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-08-09T16:49:04Z", "duration": 30209000000, "end": "2018-08-09T16:43:01.317Z", "kind": "event", - "start": "2018-08-09T16:42:31.108Z" + "start": "2018-08-09T16:42:31.108Z", + "type": [ + "connection" + ] }, "flow": { "id": "wMmxEUF-2Sk", @@ -138,6 +156,12 @@ "observer": { "ip": "10.100.4.1" }, + "related": { + "ip": [ + "10.100.6.93", + "13.32.251.125" + ] + }, "source": { "bytes": 7621, "ip": "10.100.6.93", @@ -160,12 +184,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-08-09T16:49:04Z", "duration": 0, "end": "2018-08-09T16:43:01.41Z", "kind": "event", - "start": "2018-08-09T16:43:01.41Z" + "start": "2018-08-09T16:43:01.41Z", + "type": [ + "connection" + ] }, "flow": { "id": "2NG48p7EGpw", @@ -212,6 +242,12 @@ "observer": { "ip": "10.100.4.1" }, + "related": { + "ip": [ + "10.100.4.1", + "10.100.6.80" + ] + }, "source": { "bytes": 95, "ip": "10.100.4.1", @@ -234,12 +270,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-08-09T16:49:04Z", "duration": 59651000000, "end": "2018-08-09T16:43:02.334Z", "kind": "event", - "start": "2018-08-09T16:42:02.683Z" + "start": "2018-08-09T16:42:02.683Z", + "type": [ + "connection" + ] }, "flow": { "id": "f0LYEiUntL0", @@ -286,6 +328,12 @@ "observer": { "ip": "10.100.4.1" }, + "related": { + "ip": [ + "10.100.6.93", + "13.32.251.8" + ] + }, "source": { "bytes": 3162, "ip": "10.100.6.93", @@ -308,12 +356,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-08-09T16:49:04Z", "duration": 40015000000, "end": "2018-08-09T16:43:02.876Z", "kind": "event", - "start": "2018-08-09T16:42:22.861Z" + "start": "2018-08-09T16:42:22.861Z", + "type": [ + "connection" + ] }, "flow": { "id": "9ATz0HlBbIQ", @@ -360,6 +414,12 @@ "observer": { "ip": "10.100.4.1" }, + "related": { + "ip": [ + "10.100.6.80", + "52.22.76.61" + ] + }, "source": { "bytes": 2711, "ip": "10.100.6.80", @@ -382,12 +442,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-08-09T16:49:04Z", "duration": 37121000000, "end": "2018-08-09T16:43:02.43Z", "kind": "event", - "start": "2018-08-09T16:42:25.309Z" + "start": "2018-08-09T16:42:25.309Z", + "type": [ + "connection" + ] }, "flow": { "id": "vueGG5QVS_M", @@ -434,6 +500,12 @@ "observer": { "ip": "10.100.4.1" }, + "related": { + "ip": [ + "10.100.6.93", + "13.32.251.125" + ] + }, "source": { "bytes": 20855, "ip": "10.100.6.93", @@ -456,12 +528,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-08-09T16:49:04Z", "duration": 31322000000, "end": "2018-08-09T16:43:02.43Z", "kind": "event", - "start": "2018-08-09T16:42:31.108Z" + "start": "2018-08-09T16:42:31.108Z", + "type": [ + "connection" + ] }, "flow": { "id": "rJySLUBW94Y", @@ -508,6 +586,12 @@ "observer": { "ip": "10.100.4.1" }, + "related": { + "ip": [ + "10.100.6.93", + "13.32.251.125" + ] + }, "source": { "bytes": 7495, "ip": "10.100.6.93", @@ -530,12 +614,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-08-09T16:49:04Z", "duration": 31226000000, "end": "2018-08-09T16:43:02.334Z", "kind": "event", - "start": "2018-08-09T16:42:31.108Z" + "start": "2018-08-09T16:42:31.108Z", + "type": [ + "connection" + ] }, "flow": { "id": "pWQ3ZWUMRfU", @@ -582,6 +672,12 @@ "observer": { "ip": "10.100.4.1" }, + "related": { + "ip": [ + "10.100.6.93", + "13.32.251.125" + ] + }, "source": { "bytes": 7049, "ip": "10.100.6.93", @@ -604,12 +700,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-08-09T16:49:04Z", "duration": 30976000000, "end": "2018-08-09T16:43:02.334Z", "kind": "event", - "start": "2018-08-09T16:42:31.358Z" + "start": "2018-08-09T16:42:31.358Z", + "type": [ + "connection" + ] }, "flow": { "id": "M0l00u11bWc", @@ -656,6 +758,12 @@ "observer": { "ip": "10.100.4.1" }, + "related": { + "ip": [ + "10.100.6.93", + "13.32.251.126" + ] + }, "source": { "bytes": 1348, "ip": "10.100.6.93", @@ -678,12 +786,18 @@ }, "event": { "action": "netflow_flow", - "category": "network_traffic", + "category": [ + "network_traffic", + "network" + ], "created": "2018-08-09T16:49:04Z", "duration": 0, "end": "2018-08-09T16:43:06.28Z", "kind": "event", - "start": "2018-08-09T16:43:06.28Z" + "start": "2018-08-09T16:43:06.28Z", + "type": [ + "connection" + ] }, "flow": { "id": "lzKTutEyrKA", @@ -730,6 +844,12 @@ "observer": { "ip": "10.100.4.1" }, + "related": { + "ip": [ + "192.168.1.4", + "10.100.0.1" + ] + }, "source": { "bytes": 82, "ip": "192.168.1.4", From 2e4c7c382538cd06a20c3600c53ca68d2a603c08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Letterer?= <48132449+111andre111@users.noreply.github.com> Date: Mon, 4 May 2020 16:25:00 +0200 Subject: [PATCH 088/116] Mark decode_cef as GA in documentation (#17944) decode_syslog is no longer in beta. It is used by the cef module which is not beta. Co-authored-by: Andrew Kroh --- CHANGELOG.next.asciidoc | 1 + x-pack/filebeat/processors/decode_cef/decode_cef.go | 3 --- x-pack/filebeat/processors/decode_cef/docs/decode_cef.asciidoc | 2 -- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 78a1385e589..3981b084672 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -283,6 +283,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Added Unix stream socket support as an input source and a syslog input source. {pull}17492[17492] - Improve ECS categorization field mappings in postgresql module. {issue}16177[16177] {pull}17914[17914] - Improve ECS categorization field mappings in rabbitmq module. {issue}16178[16178] {pull}17916[17916] +- Make `decode_cef` processor GA. {pull}17944[17944] - Improve ECS categorization field mappings in redis module. {issue}16179[16179] {pull}17918[17918] - Improve ECS categorization field mappings for zeek module. {issue}16029[16029] {pull}17738[17738] - Improve ECS categorization field mappings for netflow module. {issue}16135[16135] {pull}18108[18108] diff --git a/x-pack/filebeat/processors/decode_cef/decode_cef.go b/x-pack/filebeat/processors/decode_cef/decode_cef.go index dc63c9cd195..1538fe2f417 100644 --- a/x-pack/filebeat/processors/decode_cef/decode_cef.go +++ b/x-pack/filebeat/processors/decode_cef/decode_cef.go @@ -14,7 +14,6 @@ import ( "github.com/elastic/beats/v7/libbeat/beat" "github.com/elastic/beats/v7/libbeat/common" - "github.com/elastic/beats/v7/libbeat/common/cfgwarn" "github.com/elastic/beats/v7/libbeat/logp" "github.com/elastic/beats/v7/libbeat/processors" "github.com/elastic/beats/v7/x-pack/filebeat/processors/decode_cef/cef" @@ -45,8 +44,6 @@ func New(cfg *common.Config) (processors.Processor, error) { } func newDecodeCEF(c config) (*processor, error) { - cfgwarn.Beta("The " + procName + " processor is a beta feature.") - log := logp.NewLogger(logName) if c.ID != "" { log = log.With("instance_id", c.ID) diff --git a/x-pack/filebeat/processors/decode_cef/docs/decode_cef.asciidoc b/x-pack/filebeat/processors/decode_cef/docs/decode_cef.asciidoc index dcde727efba..592439afabc 100644 --- a/x-pack/filebeat/processors/decode_cef/docs/decode_cef.asciidoc +++ b/x-pack/filebeat/processors/decode_cef/docs/decode_cef.asciidoc @@ -6,8 +6,6 @@ decode_cef ++++ -beta[] - The `decode_cef` processor decodes Common Event Format (CEF) messages. This processor is available in Filebeat. From 955bc46e691e56ed0030f1f3171ff1f10a4445a1 Mon Sep 17 00:00:00 2001 From: Anabella Cristaldi <33020901+janniten@users.noreply.github.com> Date: Mon, 4 May 2020 16:27:53 +0200 Subject: [PATCH 089/116] Add Kerberos Events - Other Logon Events - ECS event Categories and Types (#17517) Add support for event IDs 4673, 4674, 4697, 4698, 4699, 4700, 4701, 4702, 4768, 4769, 4770, 4771, 4776, 4778, 4779, 4964 to the Winlogbeat Security module. Co-authored-by: Lee E. Hinman --- CHANGELOG.next.asciidoc | 1 + winlogbeat/docs/modules/security.asciidoc | 42 +- .../module/security/_meta/docs.asciidoc | 42 +- .../security/config/winlogbeat-security.js | 871 +++++++++++++----- .../test/testdata/1100.evtx.golden.json | 5 +- .../test/testdata/1102.evtx.golden.json | 10 +- .../test/testdata/1104.evtx.golden.json | 5 +- .../test/testdata/1105.evtx.golden.json | 5 +- .../test/testdata/4719.evtx.golden.json | 8 +- .../test/testdata/4741.evtx.golden.json | 10 +- .../test/testdata/4742.evtx.golden.json | 10 +- .../test/testdata/4743.evtx.golden.json | 8 +- .../test/testdata/4744.evtx.golden.json | 8 +- .../test/testdata/4745.evtx.golden.json | 8 +- .../test/testdata/4746.evtx.golden.json | 13 +- .../test/testdata/4747.evtx.golden.json | 13 +- .../test/testdata/4748.evtx.golden.json | 8 +- .../test/testdata/4749.evtx.golden.json | 8 +- .../test/testdata/4750.evtx.golden.json | 8 +- .../test/testdata/4751.evtx.golden.json | 13 +- .../test/testdata/4752.evtx.golden.json | 13 +- .../test/testdata/4753.evtx.golden.json | 8 +- .../test/testdata/4759.evtx.golden.json | 8 +- .../test/testdata/4760.evtx.golden.json | 8 +- .../test/testdata/4761.evtx.golden.json | 13 +- .../test/testdata/4762.evtx.golden.json | 13 +- .../test/testdata/4763.evtx.golden.json | 8 +- .../testdata/security-windows2012_4673.evtx | Bin 0 -> 69632 bytes ...security-windows2012_4673.evtx.golden.json | 68 ++ .../testdata/security-windows2012_4674.evtx | Bin 0 -> 69632 bytes ...security-windows2012_4674.evtx.golden.json | 78 ++ .../testdata/security-windows2012_4697.evtx | Bin 0 -> 69632 bytes ...security-windows2012_4697.evtx.golden.json | 71 ++ .../testdata/security-windows2012_4698.evtx | Bin 0 -> 69632 bytes ...security-windows2012_4698.evtx.golden.json | 63 ++ .../testdata/security-windows2012_4699.evtx | Bin 0 -> 69632 bytes ...security-windows2012_4699.evtx.golden.json | 63 ++ .../testdata/security-windows2012_4700.evtx | Bin 0 -> 69632 bytes ...security-windows2012_4700.evtx.golden.json | 63 ++ .../testdata/security-windows2012_4701.evtx | Bin 0 -> 69632 bytes ...security-windows2012_4701.evtx.golden.json | 63 ++ .../testdata/security-windows2012_4702.evtx | Bin 0 -> 69632 bytes ...security-windows2012_4702.evtx.golden.json | 63 ++ .../testdata/security-windows2012_4768.evtx | Bin 0 -> 69632 bytes ...security-windows2012_4768.evtx.golden.json | 71 ++ .../testdata/security-windows2012_4769.evtx | Bin 0 -> 69632 bytes ...security-windows2012_4769.evtx.golden.json | 70 ++ .../testdata/security-windows2012_4770.evtx | Bin 0 -> 69632 bytes ...security-windows2012_4770.evtx.golden.json | 65 ++ .../testdata/security-windows2012_4771.evtx | Bin 0 -> 69632 bytes ...security-windows2012_4771.evtx.golden.json | 66 ++ .../testdata/security-windows2012_4776.evtx | Bin 0 -> 69632 bytes ...security-windows2012_4776.evtx.golden.json | 58 ++ .../testdata/security-windows2012_4778.evtx | Bin 0 -> 69632 bytes ...security-windows2012_4778.evtx.golden.json | 63 ++ .../testdata/security-windows2012_4779.evtx | Bin 0 -> 69632 bytes ...security-windows2012_4779.evtx.golden.json | 63 ++ ...urity-windows2012r2-logon.evtx.golden.json | 97 +- ...security-windows2016-4672.evtx.golden.json | 5 +- ...curity-windows2016-logoff.evtx.golden.json | 10 +- ...2016_4720_Account_Created.evtx.golden.json | 20 +- ...2016_4722_Account_Enabled.evtx.golden.json | 16 +- ...2016_4723_Password_Change.evtx.golden.json | 16 +- ...s2016_4724_Password_Reset.evtx.golden.json | 16 +- ...016_4725_Account_Disabled.evtx.golden.json | 16 +- ...2016_4726_Account_Deleted.evtx.golden.json | 16 +- ...security-windows2016_4727.evtx.golden.json | 8 +- ...security-windows2016_4728.evtx.golden.json | 8 +- ...security-windows2016_4729.evtx.golden.json | 8 +- ...security-windows2016_4730.evtx.golden.json | 8 +- ...security-windows2016_4731.evtx.golden.json | 10 +- ...security-windows2016_4732.evtx.golden.json | 8 +- ...security-windows2016_4733.evtx.golden.json | 8 +- ...security-windows2016_4734.evtx.golden.json | 8 +- ...security-windows2016_4735.evtx.golden.json | 8 +- ...security-windows2016_4737.evtx.golden.json | 8 +- ...2016_4738_Account_Changed.evtx.golden.json | 20 +- ...6_4740_Account_Locked_Out.evtx.golden.json | 8 +- ...security-windows2016_4754.evtx.golden.json | 8 +- ...security-windows2016_4755.evtx.golden.json | 8 +- ...security-windows2016_4756.evtx.golden.json | 8 +- ...security-windows2016_4757.evtx.golden.json | 8 +- ...security-windows2016_4758.evtx.golden.json | 8 +- ...security-windows2016_4764.evtx.golden.json | 8 +- ...016_4767_Account_Unlocked.evtx.golden.json | 8 +- ...2016_4781_Account_Renamed.evtx.golden.json | 16 +- ...security-windows2016_4798.evtx.golden.json | 8 +- ...security-windows2016_4799.evtx.golden.json | 8 +- .../testdata/security-windows2016_4964.evtx | Bin 0 -> 69632 bytes ...security-windows2016_4964.evtx.golden.json | 136 +++ ...2019_4688_Process_Created.evtx.golden.json | 11 +- ...s2019_4689_Process_Exited.evtx.golden.json | 18 +- 92 files changed, 2386 insertions(+), 344 deletions(-) create mode 100644 x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4673.evtx create mode 100644 x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4673.evtx.golden.json create mode 100644 x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4674.evtx create mode 100644 x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4674.evtx.golden.json create mode 100644 x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4697.evtx create mode 100644 x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4697.evtx.golden.json create mode 100644 x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4698.evtx create mode 100644 x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4698.evtx.golden.json create mode 100644 x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4699.evtx create mode 100644 x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4699.evtx.golden.json create mode 100644 x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4700.evtx create mode 100644 x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4700.evtx.golden.json create mode 100644 x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4701.evtx create mode 100644 x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4701.evtx.golden.json create mode 100644 x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4702.evtx create mode 100644 x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4702.evtx.golden.json create mode 100644 x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4768.evtx create mode 100644 x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4768.evtx.golden.json create mode 100644 x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4769.evtx create mode 100644 x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4769.evtx.golden.json create mode 100644 x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4770.evtx create mode 100644 x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4770.evtx.golden.json create mode 100644 x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4771.evtx create mode 100644 x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4771.evtx.golden.json create mode 100644 x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4776.evtx create mode 100644 x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4776.evtx.golden.json create mode 100644 x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4778.evtx create mode 100644 x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4778.evtx.golden.json create mode 100644 x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4779.evtx create mode 100644 x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4779.evtx.golden.json create mode 100644 x-pack/winlogbeat/module/security/test/testdata/security-windows2016_4964.evtx create mode 100644 x-pack/winlogbeat/module/security/test/testdata/security-windows2016_4964.evtx.golden.json diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 3981b084672..dcea2a1c3e0 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -376,6 +376,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Add more DNS error codes to the Sysmon module. {issue}15685[15685] - Add experimental event log reader implementation that should be faster in most cases. {issue}6585[6585] {pull}16849[16849] +- Add support for event IDs 4673,4674,4697,4698,4699,4700,4701,4702,4768,4769,4770,4771,4776,4778,4779,4964 to the Security module {pull}17517[17517] ==== Deprecated diff --git a/winlogbeat/docs/modules/security.asciidoc b/winlogbeat/docs/modules/security.asciidoc index c7e7415244e..30d2d04fe3a 100644 --- a/winlogbeat/docs/modules/security.asciidoc +++ b/winlogbeat/docs/modules/security.asciidoc @@ -19,8 +19,16 @@ The module has transformations for the following event IDs: * 4647 - User initiated logoff (interactive logon types). * 4648 - A logon was attempted using explicit credentials. * 4672 - Special privileges assigned to new logon. +* 4673 - A privileged service was called. +* 4674 - An operation was attempted on a privileged object. * 4688 - A new process has been created. * 4689 - A process has exited. +* 4697 - A service was installed in the system. +* 4698 - A scheduled task was created. +* 4699 - A scheduled task was deleted. +* 4700 - A scheduled task was enabled. +* 4701 - A scheduled task was disabled. +* 4702 - A scheduled task was updated. * 4719 - System audit policy was changed. * 4720 - A user account was created. * 4722 - A user account was enabled. @@ -32,7 +40,7 @@ The module has transformations for the following event IDs: * 4728 - A member was added to a security-enabled global group. * 4729 - A member was removed from a security-enabled global group. * 4730 - A security-enabled global group was deleted. -* 4731 - A security-enabled local group was created +* 4731 - A security-enabled local group was created. * 4732 - A member was added to a security-enabled local group. * 4733 - A member was removed from a security-enabled local group. * 4734 - A security-enabled local group was deleted. @@ -65,9 +73,41 @@ The module has transformations for the following event IDs: * 4763 - A security-disabled global group was deleted. * 4764 - A group's type was changed. * 4767 - An account was unlocked. +* 4741 - A computer account was created. +* 4742 - A computer account was changed. +* 4743 - A computer account was deleted. +* 4744 - A security-disabled local group was created. +* 4745 - A security-disabled local group was changed. +* 4746 - A member was added to a security-disabled local group. +* 4747 - A member was removed from a security-disabled local group. +* 4748 - A security-disabled local group was deleted. +* 4749 - A security-disabled global group was created. +* 4750 - A security-disabled global group was changed. +* 4751 - A member was added to a security-disabled global group. +* 4752 - A member was removed from a security-disabled global group. +* 4753 - A security-disabled global group was deleted. +* 4754 - A security-enabled universal group was created. +* 4755 - A security-enabled universal group was changed. +* 4756 - A member was added to a security-enabled universal group. +* 4757 - A member was removed from a security-enabled universal group. +* 4758 - A security-enabled universal group was deleted. +* 4759 - A security-disabled universal group was created. +* 4760 - A security-disabled universal group was changed. +* 4761 - A member was added to a security-disabled universal group. +* 4762 - A member was removed from a security-disabled universal group. +* 4763 - A security-disabled global group was deleted. +* 4764 - A group's type was changed. +* 4768 - A Kerberos authentication ticket TGT was requested. +* 4769 - A Kerberos service ticket was requested. +* 4770 - A Kerberos service ticket was renewed. +* 4771 - Kerberos pre-authentication failed. +* 4776 - The computer attempted to validate the credentials for an account. +* 4778 - A session was reconnected to a Window Station. +* 4779 - A session was disconnected from a Window Station. * 4781 - The name of an account was changed. * 4798 - A user's local group membership was enumerated. * 4799 - A security-enabled local group membership was enumerated. +* 4964 - Special groups have been assigned to a new logon. More event IDs will be added. diff --git a/x-pack/winlogbeat/module/security/_meta/docs.asciidoc b/x-pack/winlogbeat/module/security/_meta/docs.asciidoc index c7e7415244e..30d2d04fe3a 100644 --- a/x-pack/winlogbeat/module/security/_meta/docs.asciidoc +++ b/x-pack/winlogbeat/module/security/_meta/docs.asciidoc @@ -19,8 +19,16 @@ The module has transformations for the following event IDs: * 4647 - User initiated logoff (interactive logon types). * 4648 - A logon was attempted using explicit credentials. * 4672 - Special privileges assigned to new logon. +* 4673 - A privileged service was called. +* 4674 - An operation was attempted on a privileged object. * 4688 - A new process has been created. * 4689 - A process has exited. +* 4697 - A service was installed in the system. +* 4698 - A scheduled task was created. +* 4699 - A scheduled task was deleted. +* 4700 - A scheduled task was enabled. +* 4701 - A scheduled task was disabled. +* 4702 - A scheduled task was updated. * 4719 - System audit policy was changed. * 4720 - A user account was created. * 4722 - A user account was enabled. @@ -32,7 +40,7 @@ The module has transformations for the following event IDs: * 4728 - A member was added to a security-enabled global group. * 4729 - A member was removed from a security-enabled global group. * 4730 - A security-enabled global group was deleted. -* 4731 - A security-enabled local group was created +* 4731 - A security-enabled local group was created. * 4732 - A member was added to a security-enabled local group. * 4733 - A member was removed from a security-enabled local group. * 4734 - A security-enabled local group was deleted. @@ -65,9 +73,41 @@ The module has transformations for the following event IDs: * 4763 - A security-disabled global group was deleted. * 4764 - A group's type was changed. * 4767 - An account was unlocked. +* 4741 - A computer account was created. +* 4742 - A computer account was changed. +* 4743 - A computer account was deleted. +* 4744 - A security-disabled local group was created. +* 4745 - A security-disabled local group was changed. +* 4746 - A member was added to a security-disabled local group. +* 4747 - A member was removed from a security-disabled local group. +* 4748 - A security-disabled local group was deleted. +* 4749 - A security-disabled global group was created. +* 4750 - A security-disabled global group was changed. +* 4751 - A member was added to a security-disabled global group. +* 4752 - A member was removed from a security-disabled global group. +* 4753 - A security-disabled global group was deleted. +* 4754 - A security-enabled universal group was created. +* 4755 - A security-enabled universal group was changed. +* 4756 - A member was added to a security-enabled universal group. +* 4757 - A member was removed from a security-enabled universal group. +* 4758 - A security-enabled universal group was deleted. +* 4759 - A security-disabled universal group was created. +* 4760 - A security-disabled universal group was changed. +* 4761 - A member was added to a security-disabled universal group. +* 4762 - A member was removed from a security-disabled universal group. +* 4763 - A security-disabled global group was deleted. +* 4764 - A group's type was changed. +* 4768 - A Kerberos authentication ticket TGT was requested. +* 4769 - A Kerberos service ticket was requested. +* 4770 - A Kerberos service ticket was renewed. +* 4771 - Kerberos pre-authentication failed. +* 4776 - The computer attempted to validate the credentials for an account. +* 4778 - A session was reconnected to a Window Station. +* 4779 - A session was disconnected from a Window Station. * 4781 - The name of an account was changed. * 4798 - A user's local group membership was enumerated. * 4799 - A security-enabled local group membership was enumerated. +* 4964 - Special groups have been assigned to a new logon. More event IDs will be added. diff --git a/x-pack/winlogbeat/module/security/config/winlogbeat-security.js b/x-pack/winlogbeat/module/security/config/winlogbeat-security.js index b6cac040b74..f223b8f0b8d 100644 --- a/x-pack/winlogbeat/module/security/config/winlogbeat-security.js +++ b/x-pack/winlogbeat/module/security/config/winlogbeat-security.js @@ -7,6 +7,8 @@ var security = (function () { var processor = require("processor"); var winlogbeat = require("winlogbeat"); + // Logon Types + // https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/basic-audit-logon-events var logonTypes = { "2": "Interactive", "3": "Network", @@ -19,96 +21,237 @@ var security = (function () { "11": "CachedInteractive", }; + // ECS Allowed Event Outcome + // https://www.elastic.co/guide/en/ecs/current/ecs-allowed-values-event-outcome.html + var eventOutcomes = { + "Audit Success": "success", + "Audit Failure": "failure", + }; + // User Account Control Attributes Table // https://support.microsoft.com/es-us/help/305144/how-to-use-useraccountcontrol-to-manipulate-user-account-properties var uacFlags = [ - [0x0001, "SCRIPT"], - [0x0002, "ACCOUNTDISABLE"], - [0x0008, "HOMEDIR_REQUIRED"], - [0x0010, "LOCKOUT"], - [0x0020, "PASSWD_NOTREQD"], - [0x0040, "PASSWD_CANT_CHANGE"], - [0x0080, "ENCRYPTED_TEXT_PWD_ALLOWED"], - [0x0100, "TEMP_DUPLICATE_ACCOUNT"], - [0x0200, "NORMAL_ACCOUNT"], - [0x0800, "INTERDOMAIN_TRUST_ACCOUNT"], - [0x1000, "WORKSTATION_TRUST_ACCOUNT"], - [0x2000, "SERVER_TRUST_ACCOUNT"], - [0x10000, "DONT_EXPIRE_PASSWORD"], - [0x20000, "MNS_LOGON_ACCOUNT"], - [0x40000, "SMARTCARD_REQUIRED"], - [0x80000, "TRUSTED_FOR_DELEGATION"], - [0x100000, "NOT_DELEGATED"], - [0x200000, "USE_DES_KEY_ONLY"], - [0x400000, "DONT_REQ_PREAUTH"], - [0x800000, "PASSWORD_EXPIRED"], - [0x1000000, "TRUSTED_TO_AUTH_FOR_DELEGATION"], - [0x4000000, "PARTIAL_SECRETS_ACCOUNT"], + [0x0001, 'SCRIPT'], + [0x0002, 'ACCOUNTDISABLE'], + [0x0008, 'HOMEDIR_REQUIRED'], + [0x0010, 'LOCKOUT'], + [0x0020, 'PASSWD_NOTREQD'], + [0x0040, 'PASSWD_CANT_CHANGE'], + [0x0080, 'ENCRYPTED_TEXT_PWD_ALLOWED'], + [0x0100, 'TEMP_DUPLICATE_ACCOUNT'], + [0x0200, 'NORMAL_ACCOUNT'], + [0x0800, 'INTERDOMAIN_TRUST_ACCOUNT'], + [0x1000, 'WORKSTATION_TRUST_ACCOUNT'], + [0x2000, 'SERVER_TRUST_ACCOUNT'], + [0x10000, 'DONT_EXPIRE_PASSWORD'], + [0x20000, 'MNS_LOGON_ACCOUNT'], + [0x40000, 'SMARTCARD_REQUIRED'], + [0x80000, 'TRUSTED_FOR_DELEGATION'], + [0x100000, 'NOT_DELEGATED'], + [0x200000, 'USE_DES_KEY_ONLY'], + [0x400000, 'DONT_REQ_PREAUTH'], + [0x800000, 'PASSWORD_EXPIRED'], + [0x1000000, 'TRUSTED_TO_AUTH_FOR_DELEGATION'], + [0x04000000, 'PARTIAL_SECRETS_ACCOUNT'], ]; - // event.action Description Table - // event.action Description Table + // Kerberos TGT and TGS Ticket Options + // https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4768 + // https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4769 + var ticketOptions = [ + "Reserved", + "Forwardable", + "Forwarded", + "Proxiable", + "Proxy", + "Allow-postdate", + "Postdated", + "Invalid", + "Renewable", + "Initial", + "Pre-authent", + "Opt-hardware-auth", + "Transited-policy-checked", + "Ok-as-delegate", + "Request-anonymous", + "Name-canonicalize", + "Unused", + "Unused", + "Unused", + "Unused", + "Unused", + "Unused", + "Unused", + "Unused", + "Unused", + "Unused", + "Disable-transited-check", + "Renewable-ok", + "Enc-tkt-in-skey", + "Unused", + "Renew", + "Validate"]; + + // Kerberos Encryption Types + // https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4768 + // https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4768 + var ticketEncryptionTypes = { + "0x1": "DES-CBC-CRC", + "0x3": "DES-CBC-MD5", + "0x11": "AES128-CTS-HMAC-SHA1-96", + "0x12": "AES256-CTS-HMAC-SHA1-96", + "0x17": "RC4-HMAC", + "0x18": "RC4-HMAC-EXP", + "0xffffffff": "FAIL", + }; + + // Kerberos Result Status Codes + // https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4768 + // https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4768 + var kerberosTktStatusCodes = { + "0x0": "KDC_ERR_NONE", + "0x1": "KDC_ERR_NAME_EXP", + "0x2": "KDC_ERR_SERVICE_EXP", + "0x3": "KDC_ERR_BAD_PVNO", + "0x4": "KDC_ERR_C_OLD_MAST_KVNO", + "0x5": "KDC_ERR_S_OLD_MAST_KVNO", + "0x6": "KDC_ERR_C_PRINCIPAL_UNKNOWN", + "0x7": "KDC_ERR_S_PRINCIPAL_UNKNOWN", + "0x8": "KDC_ERR_PRINCIPAL_NOT_UNIQUE", + "0x9": "KDC_ERR_NULL_KEY", + "0xA": "KDC_ERR_CANNOT_POSTDATE", + "0xB": "KDC_ERR_NEVER_VALID", + "0xC": "KDC_ERR_POLICY", + "0xD": "KDC_ERR_BADOPTION", + "0xE": "KDC_ERR_ETYPE_NOTSUPP", + "0xF": "KDC_ERR_SUMTYPE_NOSUPP", + "0x10": "KDC_ERR_PADATA_TYPE_NOSUPP", + "0x11": "KDC_ERR_TRTYPE_NO_SUPP", + "0x12": "KDC_ERR_CLIENT_REVOKED", + "0x13": "KDC_ERR_SERVICE_REVOKED", + "0x14": "KDC_ERR_TGT_REVOKED", + "0x15": "KDC_ERR_CLIENT_NOTYET", + "0x16": "KDC_ERR_SERVICE_NOTYET", + "0x17": "KDC_ERR_KEY_EXPIRED", + "0x18": "KDC_ERR_PREAUTH_FAILED", + "0x19": "KDC_ERR_PREAUTH_REQUIRED", + "0x1A": "KDC_ERR_SERVER_NOMATCH", + "0x1B": "KDC_ERR_MUST_USE_USER2USER", + "0x1F": "KRB_AP_ERR_BAD_INTEGRITY", + "0x20": "KRB_AP_ERR_TKT_EXPIRED", + "0x21": "KRB_AP_ERR_TKT_NYV", + "0x22": "KRB_AP_ERR_REPEAT", + "0x23": "KRB_AP_ERR_NOT_US", + "0x24": "KRB_AP_ERR_BADMATCH", + "0x25": "KRB_AP_ERR_SKEW", + "0x26": "KRB_AP_ERR_BADADDR", + "0x27": "KRB_AP_ERR_BADVERSION", + "0x28": "KRB_AP_ERR_MSG_TYPE", + "0x29": "KRB_AP_ERR_MODIFIED", + "0x2A": "KRB_AP_ERR_BADORDER", + "0x2C": "KRB_AP_ERR_BADKEYVER", + "0x2D": "KRB_AP_ERR_NOKEY", + "0x2E": "KRB_AP_ERR_MUT_FAIL", + "0x2F": "KRB_AP_ERR_BADDIRECTION", + "0x30": "KRB_AP_ERR_METHOD", + "0x31": "KRB_AP_ERR_BADSEQ", + "0x32": "KRB_AP_ERR_INAPP_CKSUM", + "0x33": "KRB_AP_PATH_NOT_ACCEPTED", + "0x34": "KRB_ERR_RESPONSE_TOO_BIG", + "0x3C": "KRB_ERR_GENERIC", + "0x3D": "KRB_ERR_FIELD_TOOLONG", + "0x3E": "KDC_ERR_CLIENT_NOT_TRUSTED", + "0x3F": "KDC_ERR_KDC_NOT_TRUSTED", + "0x40": "KDC_ERR_INVALID_SIG", + "0x41": "KDC_ERR_KEY_TOO_WEAK", + "0x42": "KRB_AP_ERR_USER_TO_USER_REQUIRED", + "0x43": "KRB_AP_ERR_NO_TGT", + "0x44": "KDC_ERR_WRONG_REALM", + }; + + // event.category, event.type, event.action var eventActionTypes = { - "1100": "logging-service-shutdown", - "1102": "changed-audit-config", - "1104": "logging-full", - "1105": "auditlog-archieved", - "1108": "logging-processing-error", - "4624": "logged-in", - "4625": "logon-failed", - "4634": "logged-out", - "4672": "logged-in-special", - "4688": "created-process", - "4689": "exited-process", - "4719": "changed-audit-config", - "4720": "added-user-account", - "4722": "enabled-user-account", - "4723": "changed-password", - "4724": "reset-password", - "4725": "disabled-user-account", - "4726": "deleted-user-account", - "4727": "added-group-account", - "4728": "added-member-to-group", - "4729": "removed-member-from-group", - "4730": "deleted-group-account", - "4731": "added-member-to-group", - "4732": "added-member-to-group", - "4733": "removed-member-from-group", - "4734": "deleted-group-account", - "4735": "modified-group-account", - "4737": "modified-group-account", - "4738": "modified-user-account", - "4740": "locked-out-user-account", - "4741": "added-computer-account", - "4742": "changed-computer-account", - "4743": "deleted-computer-account", - "4744": "added-distribution-group-account", - "4745": "changed-distribution-group-account", - "4746": "added-member-to-distribution-group", - "4747": "removed-member-from-distribution-group", - "4748": "deleted-distribution-group-account", - "4749": "added-distribution-group-account", - "4750": "changed-distribution-group-account", - "4751": "added-member-to-distribution-group", - "4752": "removed-member-from-distribution-group", - "4753": "deleted-distribution-group-account", - "4754": "added-group-account", - "4755": "modified-group-account", - "4756": "added-member-to-group", - "4757": "removed-member-from-group", - "4758": "deleted-group-account", - "4759": "added-distribution-group-account", - "4760": "changed-distribution-group-account", - "4761": "added-member-to-distribution-group", - "4762": "removed-member-from-distribution-group", - "4763": "deleted-distribution-group-account", - "4764": "type-changed-group-account", - "4767": "unlocked-user-account", - "4781": "renamed-user-account", - "4798": "group-membership-enumerated", - "4799": "user-member-enumerated", + "1100": ["process","end","logging-service-shutdown"], + "1102": ["iam", "admin", "audit-log-cleared"], + "1104": ["iam","admin","logging-full"], + "1105": ["iam","admin","auditlog-archieved"], + "1108": ["iam","admin","logging-processing-error"], + "4624": ["authentication","start","logged-in"], + "4625": ["authentication","start","logon-failed"], + "4634": ["authentication","end","logged-out"], + "4647": ["authentication","end","logged-out"], + "4648": ["authentication","start","logged-in-explicit"], + "4672": ["iam","admin","logged-in-special"], + "4673": ["iam","admin","privileged-service-called"], + "4674": ["iam","admin","privileged-operation"], + "4688": ["process","start","created-process"], + "4689": ["process", "end", "exited-process"], + "4697": ["iam","admin","service-installed"], + "4698": ["iam","creation","scheduled-task-created"], + "4699": ["iam","deletion","scheduled-task-deleted"], + "4700": ["iam","change","scheduled-task-enabled"], + "4701": ["iam","change","scheduled-task-disabled"], + "4702": ["iam","change","scheduled-task-updated"], + "4719": ["iam","admin","changed-audit-config"], + "4720": ["iam","creation","added-user-account"], + "4722": ["iam","creation","enabled-user-account"], + "4723": ["iam","change","changed-password"], + "4724": ["iam","change","reset-password"], + "4725": ["iam","deletion","disabled-user-account"], + "4726": ["iam","deletion","deleted-user-account"], + "4727": ["iam","creation","added-group-account"], + "4728": ["iam","change","added-member-to-group"], + "4729": ["iam","change","removed-member-from-group"], + "4730": ["iam","deletion","deleted-group-account"], + "4731": ["iam","creation","added-group-account"], + "4732": ["iam","change","added-member-to-group"], + "4733": ["iam","change","removed-member-from-group"], + "4734": ["iam","deletion","deleted-group-account"], + "4735": ["iam","change","modified-group-account"], + "4737": ["iam","change","modified-group-account"], + "4738": ["iam","change","modified-user-account"], + "4740": ["iam","change","locked-out-user-account"], + "4741": ["iam","creation","added-computer-account"], + "4742": ["iam","change","changed-computer-account"], + "4743": ["iam","deletion","deleted-computer-account"], + "4744": ["iam","creation","added-distribution-group-account"], + "4745": ["iam","change","changed-distribution-group-account"], + "4746": ["iam","change","added-member-to-distribution-group"], + "4747": ["iam","change","removed-member-from-distribution-group"], + "4748": ["iam","deletion","deleted-distribution-group-account"], + "4749": ["iam","creation","added-distribution-group-account"], + "4750": ["iam","change","changed-distribution-group-account"], + "4751": ["iam","change","added-member-to-distribution-group"], + "4752": ["iam","change","removed-member-from-distribution-group"], + "4753": ["iam","deletion","deleted-distribution-group-account"], + "4754": ["iam","creation","added-group-account"], + "4755": ["iam","change","modified-group-account"], + "4756": ["iam","change","added-member-to-group"], + "4757": ["iam","change","removed-member-from-group"], + "4758": ["iam","deletion","deleted-group-account"], + "4759": ["iam","creation","added-distribution-group-account"], + "4760": ["iam","change","changed-distribution-group-account"], + "4761": ["iam","change","added-member-to-distribution-group"], + "4762": ["iam","change","removed-member-from-distribution-group"], + "4763": ["iam","deletion","deleted-distribution-group-account"], + "4764": ["iam","change","type-changed-group-account"], + "4767": ["iam","change","unlocked-user-account"], + "4768": ["authentication","start","kerberos-authentication-ticket-requested"], + "4769": ["authentication","start","kerberos-service-ticket-requested"], + "4770": ["authentication","start","kerberos-service-ticket-renewed"], + "4771": ["authentication","start","kerberos-preauth-failed"], + "4776": ["authentication","start","credential-validated"], + "4778": ["authentication","start","session-reconnected"], + "4779": ["authentication","end","session-disconnected"], + "4781": ["iam","change","renamed-user-account","dummy"], + "4798": ["iam","info","group-membership-enumerated"], + "4799": ["iam","info","user-member-enumerated","dummy"], + "4964": ["iam","admin","logged-in-special"], }; + + // Audit Policy Changes Table + // https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4719 var auditActions = { "8448": "Success Removed", "8450": "Failure Removed", @@ -116,68 +259,85 @@ var security = (function () { "8451": "Failure Added", }; + // Services Types + // https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4697 + var serviceTypes = { + "0x1": "Kernel Driver", + "0x2": "File System Driver", + "0x8": "Recognizer Driver", + "0x10": "Win32 Own Process", + "0x20": "Win32 Share Process", + "0x110": "Interactive Own Process", + "0x120": "Interactive Share Process", + }; + + + // Audit Categories Description + // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-gpac/77878370-0712-47cd-997d-b07053429f6d var auditDescription = { - "0CCE9210-69AE-11D9-BED3-505054503030": ["Security State Change", "System"], - "0CCE9211-69AE-11D9-BED3-505054503030": ["Security System Extension", "System"], - "0CCE9212-69AE-11D9-BED3-505054503030": ["System Integrity", "System"], - "0CCE9213-69AE-11D9-BED3-505054503030": ["IPsec Driver", "System"], - "0CCE9214-69AE-11D9-BED3-505054503030": ["Other System Events", "System"], - "0CCE9215-69AE-11D9-BED3-505054503030": ["Logon", "Logon/Logoff"], - "0CCE9216-69AE-11D9-BED3-505054503030": ["Logoff", "Logon/Logoff"], - "0CCE9217-69AE-11D9-BED3-505054503030": ["Account Lockout", "Logon/Logoff"], - "0CCE9218-69AE-11D9-BED3-505054503030": ["IPsec Main Mode", "Logon/Logoff"], - "0CCE9219-69AE-11D9-BED3-505054503030": ["IPsec Quick Mode", "Logon/Logoff"], - "0CCE921A-69AE-11D9-BED3-505054503030": ["IPsec Extended Mode", "Logon/Logoff"], - "0CCE921B-69AE-11D9-BED3-505054503030": ["Special Logon", "Logon/Logoff"], - "0CCE921C-69AE-11D9-BED3-505054503030": ["Other Logon/Logoff Events", "Logon/Logoff"], - "0CCE9243-69AE-11D9-BED3-505054503030": ["Network Policy Server", "Logon/Logoff"], - "0CCE9247-69AE-11D9-BED3-505054503030": ["User / Device Claims", "Logon/Logoff"], - "0CCE921D-69AE-11D9-BED3-505054503030": ["File System", "Object Access"], - "0CCE921E-69AE-11D9-BED3-505054503030": ["Registry", "Object Access"], - "0CCE921F-69AE-11D9-BED3-505054503030": ["Kernel Object", "Object Access"], - "0CCE9220-69AE-11D9-BED3-505054503030": ["SAM", "Object Access"], - "0CCE9221-69AE-11D9-BED3-505054503030": ["Certification Services", "Object Access"], - "0CCE9222-69AE-11D9-BED3-505054503030": ["Application Generated", "Object Access"], - "0CCE9223-69AE-11D9-BED3-505054503030": ["Handle Manipulation", "Object Access"], - "0CCE9224-69AE-11D9-BED3-505054503030": ["File Share", "Object Access"], - "0CCE9225-69AE-11D9-BED3-505054503030": ["Filtering Platform Packet Drop", "Object Access"], - "0CCE9226-69AE-11D9-BED3-505054503030": ["Filtering Platform Connection ", "Object Access"], - "0CCE9227-69AE-11D9-BED3-505054503030": ["Other Object Access Events", "Object Access"], - "0CCE9244-69AE-11D9-BED3-505054503030": ["Detailed File Share", "Object Access"], - "0CCE9245-69AE-11D9-BED3-505054503030": ["Removable Storage", "Object Access"], - "0CCE9246-69AE-11D9-BED3-505054503030": ["Central Policy Staging", "Object Access"], - "0CCE9228-69AE-11D9-BED3-505054503030": ["Sensitive Privilege Use", "Privilege Use"], - "0CCE9229-69AE-11D9-BED3-505054503030": ["Non Sensitive Privilege Use", "Privilege Use"], - "0CCE922A-69AE-11D9-BED3-505054503030": ["Other Privilege Use Events", "Privilege Use"], - "0CCE922B-69AE-11D9-BED3-505054503030": ["Process Creation", "Detailed Tracking"], - "0CCE922C-69AE-11D9-BED3-505054503030": ["Process Termination", "Detailed Tracking"], - "0CCE922D-69AE-11D9-BED3-505054503030": ["DPAPI Activity", "Detailed Tracking"], - "0CCE922E-69AE-11D9-BED3-505054503030": ["RPC Events", "Detailed Tracking"], - "0CCE9248-69AE-11D9-BED3-505054503030": ["Plug and Play Events", "Detailed Tracking"], - "0CCE922F-69AE-11D9-BED3-505054503030": ["Audit Policy Change", "Policy Change"], - "0CCE9230-69AE-11D9-BED3-505054503030": ["Authentication Policy Change", "Policy Change"], - "0CCE9231-69AE-11D9-BED3-505054503030": ["Authorization Policy Change", "Policy Change"], - "0CCE9232-69AE-11D9-BED3-505054503030": ["MPSSVC Rule-Level Policy Change", "Policy Change"], - "0CCE9233-69AE-11D9-BED3-505054503030": ["Filtering Platform Policy Change", "Policy Change"], - "0CCE9234-69AE-11D9-BED3-505054503030": ["Other Policy Change Events", "Policy Change"], - "0CCE9235-69AE-11D9-BED3-505054503030": ["User Account Management", "Account Management"], - "0CCE9236-69AE-11D9-BED3-505054503030": ["Computer Account Management", "Account Management"], - "0CCE9237-69AE-11D9-BED3-505054503030": ["Security Group Management", "Account Management"], - "0CCE9238-69AE-11D9-BED3-505054503030": ["Distribution Group Management", "Account Management"], - "0CCE9239-69AE-11D9-BED3-505054503030": ["Application Group Management", "Account Management"], - "0CCE923A-69AE-11D9-BED3-505054503030": ["Other Account Management Events", "Account Management"], - "0CCE923B-69AE-11D9-BED3-505054503030": ["Directory Service Access", "Account Management"], - "0CCE923C-69AE-11D9-BED3-505054503030": ["Directory Service Changes", "Account Management"], - "0CCE923D-69AE-11D9-BED3-505054503030": ["Directory Service Replication", "Account Management"], - "0CCE923E-69AE-11D9-BED3-505054503030": ["Detailed Directory Service Replication", "Account Management"], - "0CCE923F-69AE-11D9-BED3-505054503030": ["Credential Validation", "Account Logon"], - "0CCE9240-69AE-11D9-BED3-505054503030": ["Kerberos Service Ticket Operations", "Account Logon"], - "0CCE9241-69AE-11D9-BED3-505054503030": ["Other Account Logon Events", "Account Logon"], - "0CCE9242-69AE-11D9-BED3-505054503030": ["Kerberos Authentication Service", "Account Logon"], + "0CCE9210-69AE-11D9-BED3-505054503030":["Security State Change", "System"], + "0CCE9211-69AE-11D9-BED3-505054503030":["Security System Extension", "System"], + "0CCE9212-69AE-11D9-BED3-505054503030":["System Integrity", "System"], + "0CCE9213-69AE-11D9-BED3-505054503030":["IPsec Driver", "System"], + "0CCE9214-69AE-11D9-BED3-505054503030":["Other System Events", "System"], + "0CCE9215-69AE-11D9-BED3-505054503030":["Logon", "Logon/Logoff"], + "0CCE9216-69AE-11D9-BED3-505054503030":["Logoff","Logon/Logoff"], + "0CCE9217-69AE-11D9-BED3-505054503030":["Account Lockout","Logon/Logoff"], + "0CCE9218-69AE-11D9-BED3-505054503030":["IPsec Main Mode","Logon/Logoff"], + "0CCE9219-69AE-11D9-BED3-505054503030":["IPsec Quick Mode","Logon/Logoff"], + "0CCE921A-69AE-11D9-BED3-505054503030":["IPsec Extended Mode","Logon/Logoff"], + "0CCE921B-69AE-11D9-BED3-505054503030":["Special Logon","Logon/Logoff"], + "0CCE921C-69AE-11D9-BED3-505054503030":["Other Logon/Logoff Events","Logon/Logoff"], + "0CCE9243-69AE-11D9-BED3-505054503030":["Network Policy Server","Logon/Logoff"], + "0CCE9247-69AE-11D9-BED3-505054503030":["User / Device Claims","Logon/Logoff"], + "0CCE921D-69AE-11D9-BED3-505054503030":["File System","Object Access"], + "0CCE921E-69AE-11D9-BED3-505054503030":["Registry","Object Access"], + "0CCE921F-69AE-11D9-BED3-505054503030":["Kernel Object","Object Access"], + "0CCE9220-69AE-11D9-BED3-505054503030":["SAM","Object Access"], + "0CCE9221-69AE-11D9-BED3-505054503030":["Certification Services","Object Access"], + "0CCE9222-69AE-11D9-BED3-505054503030":["Application Generated","Object Access"], + "0CCE9223-69AE-11D9-BED3-505054503030":["Handle Manipulation","Object Access"], + "0CCE9224-69AE-11D9-BED3-505054503030":["File Share","Object Access"], + "0CCE9225-69AE-11D9-BED3-505054503030":["Filtering Platform Packet Drop","Object Access"], + "0CCE9226-69AE-11D9-BED3-505054503030":["Filtering Platform Connection ","Object Access"], + "0CCE9227-69AE-11D9-BED3-505054503030":["Other Object Access Events","Object Access"], + "0CCE9244-69AE-11D9-BED3-505054503030":["Detailed File Share","Object Access"], + "0CCE9245-69AE-11D9-BED3-505054503030":["Removable Storage","Object Access"], + "0CCE9246-69AE-11D9-BED3-505054503030":["Central Policy Staging","Object Access"], + "0CCE9228-69AE-11D9-BED3-505054503030":["Sensitive Privilege Use","Privilege Use"], + "0CCE9229-69AE-11D9-BED3-505054503030":["Non Sensitive Privilege Use","Privilege Use"], + "0CCE922A-69AE-11D9-BED3-505054503030":["Other Privilege Use Events","Privilege Use"], + "0CCE922B-69AE-11D9-BED3-505054503030":["Process Creation","Detailed Tracking"], + "0CCE922C-69AE-11D9-BED3-505054503030":["Process Termination","Detailed Tracking"], + "0CCE922D-69AE-11D9-BED3-505054503030":["DPAPI Activity","Detailed Tracking"], + "0CCE922E-69AE-11D9-BED3-505054503030":["RPC Events","Detailed Tracking"], + "0CCE9248-69AE-11D9-BED3-505054503030":["Plug and Play Events","Detailed Tracking"], + "0CCE922F-69AE-11D9-BED3-505054503030":["Audit Policy Change","Policy Change"], + "0CCE9230-69AE-11D9-BED3-505054503030":["Authentication Policy Change","Policy Change"], + "0CCE9231-69AE-11D9-BED3-505054503030":["Authorization Policy Change","Policy Change"], + "0CCE9232-69AE-11D9-BED3-505054503030":["MPSSVC Rule-Level Policy Change","Policy Change"], + "0CCE9233-69AE-11D9-BED3-505054503030":["Filtering Platform Policy Change","Policy Change"], + "0CCE9234-69AE-11D9-BED3-505054503030":["Other Policy Change Events","Policy Change"], + "0CCE9235-69AE-11D9-BED3-505054503030":["User Account Management","Account Management"], + "0CCE9236-69AE-11D9-BED3-505054503030":["Computer Account Management","Account Management"], + "0CCE9237-69AE-11D9-BED3-505054503030":["Security Group Management","Account Management"], + "0CCE9238-69AE-11D9-BED3-505054503030":["Distribution Group Management","Account Management"], + "0CCE9239-69AE-11D9-BED3-505054503030":["Application Group Management","Account Management"], + "0CCE923A-69AE-11D9-BED3-505054503030":["Other Account Management Events","Account Management"], + "0CCE923B-69AE-11D9-BED3-505054503030":["Directory Service Access","Account Management"], + "0CCE923C-69AE-11D9-BED3-505054503030":["Directory Service Changes","Account Management"], + "0CCE923D-69AE-11D9-BED3-505054503030":["Directory Service Replication","Account Management"], + "0CCE923E-69AE-11D9-BED3-505054503030":["Detailed Directory Service Replication","Account Management"], + "0CCE923F-69AE-11D9-BED3-505054503030":["Credential Validation","Account Logon"], + "0CCE9240-69AE-11D9-BED3-505054503030":["Kerberos Service Ticket Operations","Account Logon"], + "0CCE9241-69AE-11D9-BED3-505054503030":["Other Account Logon Events","Account Logon"], + "0CCE9242-69AE-11D9-BED3-505054503030":["Kerberos Authentication Service","Account Logon"], }; + // Descriptions of failure status codes. // https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4625 + // https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4776 var logonFailureStatus = { "0xc000005e": "There are currently no logon servers available to service the logon request.", "0xc0000064": "User logon with misspelled or bad user account", @@ -199,6 +359,7 @@ var security = (function () { "0xc0000234": "User logon with account locked", "0xc00002ee": "Failure Reason: An Error occurred during Logon", "0xc0000413": "Logon Failure: The machine you are logging onto is protected by an authentication firewall. The specified account is not allowed to authenticate to the machine.", + "0xc0000371": "The local account store does not contain secret material for the specified account", "0x0": "Status OK.", }; @@ -1187,17 +1348,31 @@ var security = (function () { return msobjsMessageTable[code]; }; - var addActionDesc = function(evt){ + var addEventFields = function(evt){ var code = evt.Get("event.code"); if (!code) { return; } - var eventActionDescription = eventActionTypes[code]; + var eventActionDescription = eventActionTypes[code][2]; if (eventActionDescription) { - evt.Put("event.action", eventActionDescription); + evt.AppendTo("event.category", eventActionTypes[code][0]); + evt.AppendTo("event.type", eventActionTypes[code][1]); + evt.Put("event.action", eventActionTypes[code][2]); } }; + var addEventOutcome = function(evt) { + var auditResult = evt.Get("winlog.keywords"); + if (!auditResult) { + return; + } + var eventOutcome = eventOutcomes[auditResult]; + if (eventOutcome === undefined) { + return; + } + evt.Put("event.outcome", eventOutcome); + }; + var addLogonType = function(evt) { var code = evt.Get("winlog.event_data.LogonType"); if (!code) { @@ -1242,38 +1417,30 @@ var security = (function () { evt.Put("winlog.logon.failure.sub_status", descriptiveFailureStatus); }; - var addUACDescription = function (evt) { + var addUACDescription = function(evt) { var code = evt.Get("winlog.event_data.NewUacValue"); if (!code) { return; } var uacCode = parseInt(code); - if (isNaN(uacCode)) { - return; - } var uacResult = []; for (var i = 0; i < uacFlags.length; i++) { if ((uacCode | uacFlags[i][0]) === uacCode) { uacResult.push(uacFlags[i][1]); } } - if (uacResult.length > 0) { - evt.Put("winlog.event_data.NewUacList", uacResult); + if (uacResult) { + evt.Put("winlog.event_data.NewUACList", uacResult); } - - // Parse list of values like "%%2080 %%2082 %%2084". - var uacList = evt.Get("winlog.event_data.UserAccountControl"); + var uacList = evt.Get("winlog.event_data.UserAccountControl").replace(/\s/g, '').split("%%").filter(String); if (!uacList) { return; } - uacList = uacList.replace(/\s/g, "").split("%%").filter(String); - if (uacList.length > 0) { - evt.Put("winlog.event_data.UserAccountControl", uacList); - } - }; + evt.Put("winlog.event_data.UserAccountControl", uacList); + }; - var addAuditInfo = function (evt) { - var subcategoryGuid = evt.Get("winlog.event_data.SubcategoryGuid").replace("{", "").replace("}", "").toUpperCase(); + var addAuditInfo = function(evt) { + var subcategoryGuid = evt.Get("winlog.event_data.SubcategoryGuid").replace("{", '').replace("}", '').toUpperCase(); if (!subcategoryGuid) { return; } @@ -1282,15 +1449,83 @@ var security = (function () { } evt.Put("winlog.event_data.Category", auditDescription[subcategoryGuid][1]); evt.Put("winlog.event_data.SubCategory", auditDescription[subcategoryGuid][0]); - var coded_actions = evt.Get("winlog.event_data.AuditPolicyChanges").split(","); - var action_results = []; - for (var j = 0; j < coded_actions.length; j++) { - var action_code = coded_actions[j].replace("%%", "").replace(" ", ""); - action_results.push(auditActions[action_code]); + var codedActions = evt.Get("winlog.event_data.AuditPolicyChanges").split(","); + var actionResults = []; + for (var j = 0; j < codedActions.length; j++) { + var actionCode = codedActions[j].replace("%%", '').replace(' ', ''); + actionResults.push(auditActions[actionCode]); } - evt.Put("winlog.event_data.AuditPolicyChangesDescription", action_results); + evt.Put("winlog.event_data.AuditPolicyChangesDescription", actionResults); }; + var addTicketOptionsDescription = function(evt) { + var code = evt.Get("winlog.event_data.TicketOptions"); + if (!code) { + return; + } + var tktCode = parseInt(code, 16).toString(2); + var tktResult = []; + var tktCodeLen = tktCode.length; + for (var i = tktCodeLen; i >= 0; i--) { + if (tktCode[i] == 1) { + tktResult.push(ticketOptions[(32-tktCodeLen)+i]); + } + } + if (tktResult) { + evt.Put("winlog.event_data.TicketOptionsDescription", tktResult); + } + }; + + var addTicketEncryptionType = function(evt) { + var code = evt.Get("winlog.event_data.TicketEncryptionType"); + if (!code) { + return; + } + var encTypeCode = code.toLowerCase(); + evt.Put("winlog.event_data.TicketEncryptionTypeDescription", ticketEncryptionTypes[encTypeCode]); + }; + + var addTicketStatus = function(evt) { + var code = evt.Get("winlog.event_data.Status"); + if (!code) { + return; + } + evt.Put("winlog.event_data.StatusDescription", kerberosTktStatusCodes[code]); + }; + + var addSessionData = new processor.Chain() + .Convert({ + fields: [ + {from: "winlog.event_data.AccountName", to: "user.name"}, + {from: "winlog.event_data.AccountDomain", to: "user.domain"}, + {from: "winlog.event_data.ClientAddress", to: "source.ip"}, + {from: "winlog.event_data.ClientName", to: "source.domain"}, + {from: "winlog.event_data.LogonID", to: "winlog.logon.id"}, + ], + ignore_missing: true, + }) + .Add(function(evt) { + var user = evt.Get("winlog.event_data.AccountName"); + evt.AppendTo('related.user', user); + }) + .Build(); + + var addServiceFields = new processor.Chain() + .Convert({ + fields: [ + {from: "winlog.event_data.ServiceName", to: "service.name"}, + ], + ignore_missing: true, + }) + .Add(function(evt) { + var code = evt.Get("winlog.event_data.ServiceType"); + if (!code) { + return; + } + evt.Put("service.type", serviceTypes[code]); + }) + .Build(); + var copyTargetUser = new processor.Chain() .Convert({ fields: [ @@ -1300,9 +1535,13 @@ var security = (function () { ], ignore_missing: true, }) - .Add(function (evt) { + .Add(function(evt) { var user = evt.Get("winlog.event_data.TargetUserName"); - evt.AppendTo("related.user", user); + if (/.@*/.test(user)) { + user = user.split('@')[0]; + evt.Put('user.name', user); + } + evt.AppendTo('related.user', user); }) .Build(); @@ -1348,7 +1587,7 @@ var security = (function () { }) .Add(function(evt) { var user = evt.Get("winlog.event_data.SubjectUserName"); - evt.AppendTo("related.user", user); + evt.AppendTo('related.user', user); }) .Build(); @@ -1361,9 +1600,9 @@ var security = (function () { ], ignore_missing: true, }) - .Add(function (evt) { + .Add(function(evt) { var user = evt.Get("winlog.user_data.SubjectUserName"); - evt.AppendTo("related.user", user); + evt.AppendTo('related.user', user); }) .Build(); @@ -1398,7 +1637,7 @@ var security = (function () { ignore_missing: true, fail_on_error: false, }) - .Add(function (evt) { + .Add(function(evt) { var name = evt.Get("process.name"); if (name) { return; @@ -1411,24 +1650,6 @@ var security = (function () { }) .Build(); - var addAuthSuccess = new processor.AddFields({ - fields: { - "event.category": "authentication", - "event.type": "authentication_success", - "event.outcome": "success", - }, - target: "", - }); - - var addAuthFailed = new processor.AddFields({ - fields: { - "event.category": "authentication", - "event.type": "authentication_failure", - "event.outcome": "failure", - }, - target: "", - }); - var renameNewProcessFields = new processor.Chain() .Convert({ fields: [ @@ -1440,7 +1661,7 @@ var security = (function () { ignore_missing: true, fail_on_error: false, }) - .Add(function (evt) { + .Add(function(evt) { var name = evt.Get("process.name"); if (name) { return; @@ -1451,7 +1672,7 @@ var security = (function () { } evt.Put("process.name", path.basename(exe)); }) - .Add(function (evt) { + .Add(function(evt) { var name = evt.Get("process.parent.name"); if (name) { return; @@ -1462,7 +1683,7 @@ var security = (function () { } evt.Put("process.parent.name", path.basename(exe)); }) - .Add(function (evt) { + .Add(function(evt) { var cl = evt.Get("winlog.event_data.CommandLine"); if (!cl) { return; @@ -1477,65 +1698,105 @@ var security = (function () { .Add(copyTargetUser) .Add(copyTargetUserLogonId) .Add(addLogonType) - .Add(addActionDesc) + .Add(addEventFields) + .Add(addEventOutcome) .Build(); - // Handles both 4624 and 4648. + // Handles both 4624 var logonSuccess = new processor.Chain() - .Add(addAuthSuccess) .Add(copyTargetUser) .Add(copyTargetUserLogonId) .Add(addLogonType) .Add(renameCommonAuthFields) - .Add(addActionDesc) + .Add(addEventFields) + .Add(addEventOutcome) + .Add(function(evt) { + var user = evt.Get("winlog.event_data.SubjectUserName"); + if (user) { + var res = /^-$/.test(user); + if (!res) { + evt.AppendTo('related.user', user); + } + } + }) + .Build(); + + // Handles both 4648 + var event4648 = new processor.Chain() + .Add(copyTargetUser) + .Add(copySubjectUserLogonId) + .Add(renameCommonAuthFields) + .Add(addEventFields) + .Add(addEventOutcome) + .Add(function(evt) { + var user = evt.Get("winlog.event_data.SubjectUserName"); + if (user) { + var res = /^-$/.test(user); + if (!res) { + evt.AppendTo('related.user', user); + } + } + }) .Build(); var event4625 = new processor.Chain() - .Add(addAuthFailed) .Add(copyTargetUser) - .Add(copyTargetUserLogonId) + .Add(copySubjectUserLogonId) .Add(addLogonType) .Add(addFailureCode) .Add(addFailureStatus) .Add(addFailureSubStatus) .Add(renameCommonAuthFields) - .Add(addActionDesc) + .Add(addEventFields) + .Add(addEventOutcome) .Build(); var event4672 = new processor.Chain() .Add(copySubjectUser) .Add(copySubjectUserLogonId) - .Add(function (evt) { + .Add(function(evt) { var privs = evt.Get("winlog.event_data.PrivilegeList"); if (!privs) { return; } evt.Put("winlog.event_data.PrivilegeList", privs.split(/\s+/)); }) - .Add(addActionDesc) + .Add(addEventFields) + .Add(addEventOutcome) .Build(); var event4688 = new processor.Chain() .Add(copySubjectUser) + .Add(copySubjectUserLogonId) .Add(renameNewProcessFields) - .Add(addActionDesc) - .Add(function (evt) { - evt.Put("event.category", "process"); - evt.Put("event.type", "process_start"); - }) - .Add(function (evt) { + .Add(addEventFields) + .Add(addEventOutcome) + .Add(function(evt) { var user = evt.Get("winlog.event_data.TargetUserName"); - evt.AppendTo("related.user", user); + var res = /^-$/.test(user); + if (!res) { + evt.AppendTo('related.user', user); + } }) .Build(); var event4689 = new processor.Chain() .Add(copySubjectUser) + .Add(copySubjectUserLogonId) + .Add(renameCommonAuthFields) + .Add(addEventFields) + .Add(addEventOutcome) + .Build(); + + var event4697 = new processor.Chain() + .Add(copySubjectUser) + .Add(copySubjectUserLogonId) .Add(renameCommonAuthFields) - .Add(addActionDesc) - .Add(function (evt) { - evt.Put("event.category", "process"); - evt.Put("event.type", "process_end"); + .Add(addServiceFields) + .Add(addEventFields) + .Add(addEventOutcome) + .Add(function(evt) { + evt.AppendTo("event.type", "change"); }) .Build(); @@ -1544,22 +1805,26 @@ var security = (function () { .Add(copySubjectUserLogonId) .Add(renameCommonAuthFields) .Add(addUACDescription) - .Add(addActionDesc) - .Add(function (evt) { + .Add(addEventFields) + .Add(addEventOutcome) + .Add(function(evt) { var user = evt.Get("winlog.event_data.TargetUserName"); - evt.AppendTo("related.user", user); + evt.AppendTo('related.user', user); + evt.AppendTo("event.type", "user"); }) .Build(); var userRenamed = new processor.Chain() .Add(copySubjectUser) .Add(copySubjectUserLogonId) - .Add(addActionDesc) - .Add(function (evt) { - var user_new = evt.Get("winlog.event_data.NewTargetUserName"); - evt.AppendTo("related.user", user_new); - var user_old = evt.Get("winlog.event_data.OldTargetUserName"); - evt.AppendTo("related.user", user_old); + .Add(addEventFields) + .Add(addEventOutcome) + .Add(function(evt) { + var userNew = evt.Get("winlog.event_data.NewTargetUserName"); + evt.AppendTo('related.user', userNew); + var userOld = evt.Get("winlog.event_data.OldTargetUserName"); + evt.AppendTo('related.user', userOld); + evt.AppendTo("event.type", "user"); }) .Build(); @@ -1568,14 +1833,28 @@ var security = (function () { .Add(copySubjectUserLogonId) .Add(copyTargetUserToGroup) .Add(renameCommonAuthFields) - .Add(addActionDesc) + .Add(addEventFields) + .Add(addEventOutcome) + .Add(function(evt) { + evt.AppendTo("event.type", "group"); + var member = evt.Get("winlog.event_data.MemberName"); + if (!member) { + return; + } + evt.AppendTo("related.user", member.split(',')[0].replace('CN=', '').replace('cn=', '')); + }) + .Build(); var auditLogCleared = new processor.Chain() .Add(copySubjectUserFromUserData) .Add(copySubjectUserLogonIdFromUserData) .Add(renameCommonAuthFields) - .Add(addActionDesc) + .Add(addEventFields) + .Add(addEventOutcome) + .Add(function(evt) { + evt.AppendTo("event.type", "change"); + }) .Build(); var auditChanged = new processor.Chain() @@ -1583,12 +1862,17 @@ var security = (function () { .Add(copySubjectUserLogonId) .Add(renameCommonAuthFields) .Add(addAuditInfo) - .Add(addActionDesc) + .Add(addEventFields) + .Add(addEventOutcome) + .Add(function(evt) { + evt.AppendTo("event.type", "change"); + }) .Build(); var auditLogMgmt = new processor.Chain() .Add(renameCommonAuthFields) - .Add(addActionDesc) + .Add(addEventFields) + .Add(addEventOutcome) .Build(); var computerMgmtEvts = new processor.Chain() @@ -1596,14 +1880,97 @@ var security = (function () { .Add(copySubjectUserLogonId) .Add(copyTargetUserToComputerObject) .Add(renameCommonAuthFields) - .Add(addActionDesc) .Add(addUACDescription) - .Add(function (evt) { + .Add(addEventFields) + .Add(addEventOutcome) + .Add(function(evt) { var privs = evt.Get("winlog.event_data.PrivilegeList"); if (!privs) { return; } evt.Put("winlog.event_data.PrivilegeList", privs.split(/\s+/)); + evt.AppendTo("event.type", "admin"); + }) + .Build(); + + var sessionEvts = new processor.Chain() + .Add(addSessionData) + .Add(addEventFields) + .Add(addEventOutcome) + .Build(); + + var event4964 = new processor.Chain() + .Add(copyTargetUser) + .Add(copyTargetUserLogonId) + .Add(addEventFields) + .Add(addEventOutcome) + .Add(function(evt) { + evt.AppendTo("event.type", "group"); + }) + .Build(); + + var kerberosTktEvts = new processor.Chain() + .Add(copyTargetUser) + .Add(renameCommonAuthFields) + .Add(addTicketOptionsDescription) + .Add(addTicketEncryptionType) + .Add(addTicketStatus) + .Add(addEventFields) + .Add(addEventOutcome) + .Add(function(evt) { + var ip = evt.Get("source.ip"); + if (/::ffff:/.test(ip)) { + evt.Put("source.ip", ip.replace("::ffff:", "")); + } + }) + .Build(); + + var event4776 = new processor.Chain() + .Add(copyTargetUser) + .Add(addFailureStatus) + .Add(addEventFields) + .Add(addEventOutcome) + .Build(); + + var scheduledTask = new processor.Chain() + .Add(copySubjectUser) + .Add(copySubjectUserLogonId) + .Add(addEventFields) + .Add(addEventOutcome) + .Add(function(evt) { + evt.AppendTo("event.type", "admin"); + }) + .Build(); + + var sensitivePrivilege = new processor.Chain() + .Add(copySubjectUser) + .Add(copySubjectUserLogonId) + .Add(renameCommonAuthFields) + .Add(addEventFields) + .Add(addEventOutcome) + .Add(function(evt) { + var privs = evt.Get("winlog.event_data.PrivilegeList"); + if (!privs) { + return; + } + evt.Put("winlog.event_data.PrivilegeList", privs.split(/\s+/)); + }) + .Add(function(evt){ + var maskCodes = evt.Get("winlog.event_data.AccessMask"); + if (!maskCodes) { + return; + } + var maskList = maskCodes.replace(/\s+/g, '').split("%%").filter(String); + evt.Put("winlog.event_data.AccessMask", maskList); + var maskResults = []; + for (var j = 0; j < maskList.length; j++) { + var description = msobjsMessageTable[maskList[j]]; + if (description === undefined) { + return; + } + maskResults.push(description); + } + evt.Put("winlog.event_data.AccessMaskDescription", maskResults); }) .Build(); @@ -1637,17 +2004,41 @@ var security = (function () { 4647: logoff.Run, // 4648 - A logon was attempted using explicit credentials. - 4648: logonSuccess.Run, + 4648: event4648.Run, // 4672 - Special privileges assigned to new logon. 4672: event4672.Run, + // 4673 - A privileged service was called. + 4673: sensitivePrivilege.Run, + + // 4674 - An operation was attempted on a privileged object. + 4674: sensitivePrivilege.Run, + // 4688 - A new process has been created. 4688: event4688.Run, // 4689 - A process has exited. 4689: event4689.Run, + // 4697 - A service was installed in the system. + 4697: event4697.Run, + + // 4698 - A scheduled task was created. + 4698: scheduledTask.Run, + + // 4699 - A scheduled task was deleted. + 4699: scheduledTask.Run, + + // 4700 - A scheduled task was enabled. + 4700: scheduledTask.Run, + + // 4701 - A scheduled task was disabled. + 4701: scheduledTask.Run, + + // 4702 - A scheduled task was updated. + 4702: scheduledTask.Run, + // 4719 - System audit policy was changed. 4719: auditChanged.Run, @@ -1780,6 +2171,27 @@ var security = (function () { // 4767 - A user account was unlocked. 4767: userMgmtEvts.Run, + // 4768 - A Kerberos authentication ticket TGT was requested. + 4768: kerberosTktEvts.Run, + + // 4769 - A Kerberos service ticket was requested. + 4769: kerberosTktEvts.Run, + + // 4770 - A Kerberos service ticket was renewed. + 4770: kerberosTktEvts.Run, + + // 4771 - Kerberos pre-authentication failed. + 4771: kerberosTktEvts.Run, + + // 4776 - The computer attempted to validate the credentials for an account. + 4776: event4776.Run, + + // 4778 - A session was reconnected to a Window Station. + 4778: sessionEvts.Run, + + // 4779 - A session was disconnected from a Window Station. + 4779: sessionEvts.Run, + // 4781 - The name of an account was changed. 4781: userRenamed.Run, @@ -1789,7 +2201,10 @@ var security = (function () { // 4799 - A security-enabled local group membership was enumerated. 4799: groupMgmtEvts.Run, - process: function (evt) { + // 4964 - Special groups have been assigned to a new logon. + 4964: event4964.Run, + + process: function(evt) { var eventId = evt.Get("winlog.event_id"); var processor = this[eventId]; if (processor === undefined) { diff --git a/x-pack/winlogbeat/module/security/test/testdata/1100.evtx.golden.json b/x-pack/winlogbeat/module/security/test/testdata/1100.evtx.golden.json index e981d6042ea..6a2e7aa85ea 100644 --- a/x-pack/winlogbeat/module/security/test/testdata/1100.evtx.golden.json +++ b/x-pack/winlogbeat/module/security/test/testdata/1100.evtx.golden.json @@ -3,10 +3,13 @@ "@timestamp": "2019-11-07T10:37:04.2260925Z", "event": { "action": "logging-service-shutdown", + "category": "process", "code": 1100, "kind": "event", "module": "security", - "provider": "Microsoft-Windows-Eventlog" + "outcome": "success", + "provider": "Microsoft-Windows-Eventlog", + "type": "end" }, "host": { "name": "WIN-41OB2LO92CR.wlbeat.local" diff --git a/x-pack/winlogbeat/module/security/test/testdata/1102.evtx.golden.json b/x-pack/winlogbeat/module/security/test/testdata/1102.evtx.golden.json index 16f6e120b8a..d124c8154dd 100644 --- a/x-pack/winlogbeat/module/security/test/testdata/1102.evtx.golden.json +++ b/x-pack/winlogbeat/module/security/test/testdata/1102.evtx.golden.json @@ -2,11 +2,17 @@ { "@timestamp": "2019-11-07T10:34:29.0559196Z", "event": { - "action": "changed-audit-config", + "action": "audit-log-cleared", + "category": "iam", "code": 1102, "kind": "event", "module": "security", - "provider": "Microsoft-Windows-Eventlog" + "outcome": "success", + "provider": "Microsoft-Windows-Eventlog", + "type": [ + "admin", + "change" + ] }, "host": { "name": "WIN-41OB2LO92CR.wlbeat.local" diff --git a/x-pack/winlogbeat/module/security/test/testdata/1104.evtx.golden.json b/x-pack/winlogbeat/module/security/test/testdata/1104.evtx.golden.json index e75caf10328..9e0b25160e0 100644 --- a/x-pack/winlogbeat/module/security/test/testdata/1104.evtx.golden.json +++ b/x-pack/winlogbeat/module/security/test/testdata/1104.evtx.golden.json @@ -3,10 +3,13 @@ "@timestamp": "2019-11-08T07:56:17.3217049Z", "event": { "action": "logging-full", + "category": "iam", "code": 1104, "kind": "event", "module": "security", - "provider": "Microsoft-Windows-Eventlog" + "outcome": "success", + "provider": "Microsoft-Windows-Eventlog", + "type": "admin" }, "host": { "name": "WIN-41OB2LO92CR.wlbeat.local" diff --git a/x-pack/winlogbeat/module/security/test/testdata/1105.evtx.golden.json b/x-pack/winlogbeat/module/security/test/testdata/1105.evtx.golden.json index ca72947620e..ae6ba7ee57c 100644 --- a/x-pack/winlogbeat/module/security/test/testdata/1105.evtx.golden.json +++ b/x-pack/winlogbeat/module/security/test/testdata/1105.evtx.golden.json @@ -3,10 +3,13 @@ "@timestamp": "2019-11-07T16:22:14.8425353Z", "event": { "action": "auditlog-archieved", + "category": "iam", "code": 1105, "kind": "event", "module": "security", - "provider": "Microsoft-Windows-Eventlog" + "outcome": "success", + "provider": "Microsoft-Windows-Eventlog", + "type": "admin" }, "host": { "name": "WIN-41OB2LO92CR.wlbeat.local" diff --git a/x-pack/winlogbeat/module/security/test/testdata/4719.evtx.golden.json b/x-pack/winlogbeat/module/security/test/testdata/4719.evtx.golden.json index 8780c91d12d..48e9297a3e0 100644 --- a/x-pack/winlogbeat/module/security/test/testdata/4719.evtx.golden.json +++ b/x-pack/winlogbeat/module/security/test/testdata/4719.evtx.golden.json @@ -3,10 +3,16 @@ "@timestamp": "2019-11-07T15:22:57.6553291Z", "event": { "action": "changed-audit-config", + "category": "iam", "code": 4719, "kind": "event", "module": "security", - "provider": "Microsoft-Windows-Security-Auditing" + "outcome": "success", + "provider": "Microsoft-Windows-Security-Auditing", + "type": [ + "admin", + "change" + ] }, "host": { "name": "WIN-41OB2LO92CR.wlbeat.local" diff --git a/x-pack/winlogbeat/module/security/test/testdata/4741.evtx.golden.json b/x-pack/winlogbeat/module/security/test/testdata/4741.evtx.golden.json index cd4bd32fb46..ead2058c418 100644 --- a/x-pack/winlogbeat/module/security/test/testdata/4741.evtx.golden.json +++ b/x-pack/winlogbeat/module/security/test/testdata/4741.evtx.golden.json @@ -3,10 +3,16 @@ "@timestamp": "2019-12-18T16:22:12.3112534Z", "event": { "action": "added-computer-account", + "category": "iam", "code": 4741, "kind": "event", "module": "security", - "provider": "Microsoft-Windows-Security-Auditing" + "outcome": "success", + "provider": "Microsoft-Windows-Security-Auditing", + "type": [ + "creation", + "admin" + ] }, "host": { "name": "DC_TEST2k12.TEST.SAAS" @@ -39,7 +45,7 @@ "HomeDirectory": "-", "HomePath": "-", "LogonHours": "%%1793", - "NewUacList": [ + "NewUACList": [ "SCRIPT", "ENCRYPTED_TEXT_PWD_ALLOWED" ], diff --git a/x-pack/winlogbeat/module/security/test/testdata/4742.evtx.golden.json b/x-pack/winlogbeat/module/security/test/testdata/4742.evtx.golden.json index 423f7e92280..6e6d21d1d9f 100644 --- a/x-pack/winlogbeat/module/security/test/testdata/4742.evtx.golden.json +++ b/x-pack/winlogbeat/module/security/test/testdata/4742.evtx.golden.json @@ -3,10 +3,16 @@ "@timestamp": "2019-12-18T16:22:12.3425087Z", "event": { "action": "changed-computer-account", + "category": "iam", "code": 4742, "kind": "event", "module": "security", - "provider": "Microsoft-Windows-Security-Auditing" + "outcome": "success", + "provider": "Microsoft-Windows-Security-Auditing", + "type": [ + "change", + "admin" + ] }, "host": { "name": "DC_TEST2k12.TEST.SAAS" @@ -40,7 +46,7 @@ "HomeDirectory": "-", "HomePath": "-", "LogonHours": "-", - "NewUacList": [ + "NewUACList": [ "ENCRYPTED_TEXT_PWD_ALLOWED" ], "NewUacValue": "0x84", diff --git a/x-pack/winlogbeat/module/security/test/testdata/4743.evtx.golden.json b/x-pack/winlogbeat/module/security/test/testdata/4743.evtx.golden.json index a64f1684596..c3dd849dfcf 100644 --- a/x-pack/winlogbeat/module/security/test/testdata/4743.evtx.golden.json +++ b/x-pack/winlogbeat/module/security/test/testdata/4743.evtx.golden.json @@ -3,10 +3,16 @@ "@timestamp": "2019-12-18T16:25:21.5781833Z", "event": { "action": "deleted-computer-account", + "category": "iam", "code": 4743, "kind": "event", "module": "security", - "provider": "Microsoft-Windows-Security-Auditing" + "outcome": "success", + "provider": "Microsoft-Windows-Security-Auditing", + "type": [ + "deletion", + "admin" + ] }, "host": { "name": "DC_TEST2k12.TEST.SAAS" diff --git a/x-pack/winlogbeat/module/security/test/testdata/4744.evtx.golden.json b/x-pack/winlogbeat/module/security/test/testdata/4744.evtx.golden.json index efad3a186bd..ee173fa174b 100644 --- a/x-pack/winlogbeat/module/security/test/testdata/4744.evtx.golden.json +++ b/x-pack/winlogbeat/module/security/test/testdata/4744.evtx.golden.json @@ -3,10 +3,16 @@ "@timestamp": "2019-12-18T16:26:46.8744233Z", "event": { "action": "added-distribution-group-account", + "category": "iam", "code": 4744, "kind": "event", "module": "security", - "provider": "Microsoft-Windows-Security-Auditing" + "outcome": "success", + "provider": "Microsoft-Windows-Security-Auditing", + "type": [ + "creation", + "group" + ] }, "group": { "domain": "TEST", diff --git a/x-pack/winlogbeat/module/security/test/testdata/4745.evtx.golden.json b/x-pack/winlogbeat/module/security/test/testdata/4745.evtx.golden.json index 115c5ba452f..6763c6e314b 100644 --- a/x-pack/winlogbeat/module/security/test/testdata/4745.evtx.golden.json +++ b/x-pack/winlogbeat/module/security/test/testdata/4745.evtx.golden.json @@ -3,10 +3,16 @@ "@timestamp": "2019-12-18T16:29:05.0175739Z", "event": { "action": "changed-distribution-group-account", + "category": "iam", "code": 4745, "kind": "event", "module": "security", - "provider": "Microsoft-Windows-Security-Auditing" + "outcome": "success", + "provider": "Microsoft-Windows-Security-Auditing", + "type": [ + "change", + "group" + ] }, "group": { "domain": "TEST", diff --git a/x-pack/winlogbeat/module/security/test/testdata/4746.evtx.golden.json b/x-pack/winlogbeat/module/security/test/testdata/4746.evtx.golden.json index bb1f2e0fe39..4f6767b86f1 100644 --- a/x-pack/winlogbeat/module/security/test/testdata/4746.evtx.golden.json +++ b/x-pack/winlogbeat/module/security/test/testdata/4746.evtx.golden.json @@ -3,10 +3,16 @@ "@timestamp": "2019-12-18T16:31:01.6117458Z", "event": { "action": "added-member-to-distribution-group", + "category": "iam", "code": 4746, "kind": "event", "module": "security", - "provider": "Microsoft-Windows-Security-Auditing" + "outcome": "success", + "provider": "Microsoft-Windows-Security-Auditing", + "type": [ + "change", + "group" + ] }, "group": { "domain": "TEST", @@ -19,7 +25,10 @@ "level": "information" }, "related": { - "user": "at_adm" + "user": [ + "at_adm", + "Administrator" + ] }, "user": { "domain": "TEST", diff --git a/x-pack/winlogbeat/module/security/test/testdata/4747.evtx.golden.json b/x-pack/winlogbeat/module/security/test/testdata/4747.evtx.golden.json index 734c1f25acc..1e49b60bf5a 100644 --- a/x-pack/winlogbeat/module/security/test/testdata/4747.evtx.golden.json +++ b/x-pack/winlogbeat/module/security/test/testdata/4747.evtx.golden.json @@ -3,10 +3,16 @@ "@timestamp": "2019-12-18T16:35:16.6816525Z", "event": { "action": "removed-member-from-distribution-group", + "category": "iam", "code": 4747, "kind": "event", "module": "security", - "provider": "Microsoft-Windows-Security-Auditing" + "outcome": "success", + "provider": "Microsoft-Windows-Security-Auditing", + "type": [ + "change", + "group" + ] }, "group": { "domain": "TEST", @@ -19,7 +25,10 @@ "level": "information" }, "related": { - "user": "at_adm" + "user": [ + "at_adm", + "Administrator" + ] }, "user": { "domain": "TEST", diff --git a/x-pack/winlogbeat/module/security/test/testdata/4748.evtx.golden.json b/x-pack/winlogbeat/module/security/test/testdata/4748.evtx.golden.json index 529c63c93fb..7028e3eabcf 100644 --- a/x-pack/winlogbeat/module/security/test/testdata/4748.evtx.golden.json +++ b/x-pack/winlogbeat/module/security/test/testdata/4748.evtx.golden.json @@ -3,10 +3,16 @@ "@timestamp": "2019-12-19T08:01:45.9824133Z", "event": { "action": "deleted-distribution-group-account", + "category": "iam", "code": 4748, "kind": "event", "module": "security", - "provider": "Microsoft-Windows-Security-Auditing" + "outcome": "success", + "provider": "Microsoft-Windows-Security-Auditing", + "type": [ + "deletion", + "group" + ] }, "group": { "domain": "TEST", diff --git a/x-pack/winlogbeat/module/security/test/testdata/4749.evtx.golden.json b/x-pack/winlogbeat/module/security/test/testdata/4749.evtx.golden.json index e00d62d4e0f..5d8b63f88fb 100644 --- a/x-pack/winlogbeat/module/security/test/testdata/4749.evtx.golden.json +++ b/x-pack/winlogbeat/module/security/test/testdata/4749.evtx.golden.json @@ -3,10 +3,16 @@ "@timestamp": "2019-12-19T08:03:42.7234679Z", "event": { "action": "added-distribution-group-account", + "category": "iam", "code": 4749, "kind": "event", "module": "security", - "provider": "Microsoft-Windows-Security-Auditing" + "outcome": "success", + "provider": "Microsoft-Windows-Security-Auditing", + "type": [ + "creation", + "group" + ] }, "group": { "domain": "TEST", diff --git a/x-pack/winlogbeat/module/security/test/testdata/4750.evtx.golden.json b/x-pack/winlogbeat/module/security/test/testdata/4750.evtx.golden.json index 5cc18e986c1..adc07bcf0bb 100644 --- a/x-pack/winlogbeat/module/security/test/testdata/4750.evtx.golden.json +++ b/x-pack/winlogbeat/module/security/test/testdata/4750.evtx.golden.json @@ -3,10 +3,16 @@ "@timestamp": "2019-12-19T08:10:57.4737631Z", "event": { "action": "changed-distribution-group-account", + "category": "iam", "code": 4750, "kind": "event", "module": "security", - "provider": "Microsoft-Windows-Security-Auditing" + "outcome": "success", + "provider": "Microsoft-Windows-Security-Auditing", + "type": [ + "change", + "group" + ] }, "group": { "domain": "TEST", diff --git a/x-pack/winlogbeat/module/security/test/testdata/4751.evtx.golden.json b/x-pack/winlogbeat/module/security/test/testdata/4751.evtx.golden.json index acad53e1f9d..19365fcd0b0 100644 --- a/x-pack/winlogbeat/module/security/test/testdata/4751.evtx.golden.json +++ b/x-pack/winlogbeat/module/security/test/testdata/4751.evtx.golden.json @@ -3,10 +3,16 @@ "@timestamp": "2019-12-19T08:20:29.0889568Z", "event": { "action": "added-member-to-distribution-group", + "category": "iam", "code": 4751, "kind": "event", "module": "security", - "provider": "Microsoft-Windows-Security-Auditing" + "outcome": "success", + "provider": "Microsoft-Windows-Security-Auditing", + "type": [ + "change", + "group" + ] }, "group": { "domain": "TEST", @@ -19,7 +25,10 @@ "level": "information" }, "related": { - "user": "at_adm" + "user": [ + "at_adm", + "Administrator" + ] }, "user": { "domain": "TEST", diff --git a/x-pack/winlogbeat/module/security/test/testdata/4752.evtx.golden.json b/x-pack/winlogbeat/module/security/test/testdata/4752.evtx.golden.json index 6daa89967bd..0ec7e223ca8 100644 --- a/x-pack/winlogbeat/module/security/test/testdata/4752.evtx.golden.json +++ b/x-pack/winlogbeat/module/security/test/testdata/4752.evtx.golden.json @@ -3,10 +3,16 @@ "@timestamp": "2019-12-19T08:21:23.6444225Z", "event": { "action": "removed-member-from-distribution-group", + "category": "iam", "code": 4752, "kind": "event", "module": "security", - "provider": "Microsoft-Windows-Security-Auditing" + "outcome": "success", + "provider": "Microsoft-Windows-Security-Auditing", + "type": [ + "change", + "group" + ] }, "group": { "domain": "TEST", @@ -19,7 +25,10 @@ "level": "information" }, "related": { - "user": "at_adm" + "user": [ + "at_adm", + "Administrator" + ] }, "user": { "domain": "TEST", diff --git a/x-pack/winlogbeat/module/security/test/testdata/4753.evtx.golden.json b/x-pack/winlogbeat/module/security/test/testdata/4753.evtx.golden.json index a202dced9ea..2522fe24547 100644 --- a/x-pack/winlogbeat/module/security/test/testdata/4753.evtx.golden.json +++ b/x-pack/winlogbeat/module/security/test/testdata/4753.evtx.golden.json @@ -3,10 +3,16 @@ "@timestamp": "2019-12-19T08:24:36.5952761Z", "event": { "action": "deleted-distribution-group-account", + "category": "iam", "code": 4753, "kind": "event", "module": "security", - "provider": "Microsoft-Windows-Security-Auditing" + "outcome": "success", + "provider": "Microsoft-Windows-Security-Auditing", + "type": [ + "deletion", + "group" + ] }, "group": { "domain": "TEST", diff --git a/x-pack/winlogbeat/module/security/test/testdata/4759.evtx.golden.json b/x-pack/winlogbeat/module/security/test/testdata/4759.evtx.golden.json index f7f1d4e03dd..ca734884d50 100644 --- a/x-pack/winlogbeat/module/security/test/testdata/4759.evtx.golden.json +++ b/x-pack/winlogbeat/module/security/test/testdata/4759.evtx.golden.json @@ -3,10 +3,16 @@ "@timestamp": "2019-12-19T08:26:26.1432582Z", "event": { "action": "added-distribution-group-account", + "category": "iam", "code": 4759, "kind": "event", "module": "security", - "provider": "Microsoft-Windows-Security-Auditing" + "outcome": "success", + "provider": "Microsoft-Windows-Security-Auditing", + "type": [ + "creation", + "group" + ] }, "group": { "domain": "TEST", diff --git a/x-pack/winlogbeat/module/security/test/testdata/4760.evtx.golden.json b/x-pack/winlogbeat/module/security/test/testdata/4760.evtx.golden.json index dee61d9d371..fd63349af6b 100644 --- a/x-pack/winlogbeat/module/security/test/testdata/4760.evtx.golden.json +++ b/x-pack/winlogbeat/module/security/test/testdata/4760.evtx.golden.json @@ -3,10 +3,16 @@ "@timestamp": "2019-12-19T08:28:21.0305977Z", "event": { "action": "changed-distribution-group-account", + "category": "iam", "code": 4760, "kind": "event", "module": "security", - "provider": "Microsoft-Windows-Security-Auditing" + "outcome": "success", + "provider": "Microsoft-Windows-Security-Auditing", + "type": [ + "change", + "group" + ] }, "group": { "domain": "TEST", diff --git a/x-pack/winlogbeat/module/security/test/testdata/4761.evtx.golden.json b/x-pack/winlogbeat/module/security/test/testdata/4761.evtx.golden.json index ded73373373..541326dabdc 100644 --- a/x-pack/winlogbeat/module/security/test/testdata/4761.evtx.golden.json +++ b/x-pack/winlogbeat/module/security/test/testdata/4761.evtx.golden.json @@ -3,10 +3,16 @@ "@timestamp": "2019-12-19T08:29:38.4487328Z", "event": { "action": "added-member-to-distribution-group", + "category": "iam", "code": 4761, "kind": "event", "module": "security", - "provider": "Microsoft-Windows-Security-Auditing" + "outcome": "success", + "provider": "Microsoft-Windows-Security-Auditing", + "type": [ + "change", + "group" + ] }, "group": { "domain": "TEST", @@ -19,7 +25,10 @@ "level": "information" }, "related": { - "user": "at_adm" + "user": [ + "at_adm", + "Administrator" + ] }, "user": { "domain": "TEST", diff --git a/x-pack/winlogbeat/module/security/test/testdata/4762.evtx.golden.json b/x-pack/winlogbeat/module/security/test/testdata/4762.evtx.golden.json index 4b346ef8e59..ff9647a360e 100644 --- a/x-pack/winlogbeat/module/security/test/testdata/4762.evtx.golden.json +++ b/x-pack/winlogbeat/module/security/test/testdata/4762.evtx.golden.json @@ -3,10 +3,16 @@ "@timestamp": "2019-12-19T08:33:25.9678735Z", "event": { "action": "removed-member-from-distribution-group", + "category": "iam", "code": 4762, "kind": "event", "module": "security", - "provider": "Microsoft-Windows-Security-Auditing" + "outcome": "success", + "provider": "Microsoft-Windows-Security-Auditing", + "type": [ + "change", + "group" + ] }, "group": { "domain": "TEST", @@ -19,7 +25,10 @@ "level": "information" }, "related": { - "user": "at_adm" + "user": [ + "at_adm", + "Administrator" + ] }, "user": { "domain": "TEST", diff --git a/x-pack/winlogbeat/module/security/test/testdata/4763.evtx.golden.json b/x-pack/winlogbeat/module/security/test/testdata/4763.evtx.golden.json index d4069947156..a600ede656d 100644 --- a/x-pack/winlogbeat/module/security/test/testdata/4763.evtx.golden.json +++ b/x-pack/winlogbeat/module/security/test/testdata/4763.evtx.golden.json @@ -3,10 +3,16 @@ "@timestamp": "2019-12-19T08:34:23.1623432Z", "event": { "action": "deleted-distribution-group-account", + "category": "iam", "code": 4763, "kind": "event", "module": "security", - "provider": "Microsoft-Windows-Security-Auditing" + "outcome": "success", + "provider": "Microsoft-Windows-Security-Auditing", + "type": [ + "deletion", + "group" + ] }, "group": { "domain": "TEST", diff --git a/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4673.evtx b/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4673.evtx new file mode 100644 index 0000000000000000000000000000000000000000..643fadac216e62cc3c0bc550d0ce463121288b36 GIT binary patch literal 69632 zcmeI5ZEzh`8ONW!Z@IZ?-mJw6rKD*aT9~8_eM_lO(li%h(o$`(mWfCpNt@=a(Gx_s-tkJ$ugCv(Gu_+21*P&a;iZUCHj=P6;j{$6-IVmV`yhte}@EAD7Key)orT z7y%_f2~Yx*03|>PPy&MIy61 zH{Wjip8UUF=Z~*H`OC~0u8fKNtw7|TQIT_z%rhdF#YJvFH^fAjQ)7gA4%gJ>N?cQy ze>?tPb==kMIkQmj|xV=ftI9Uv&02z;6<99t*^ye)6?cxt%tnM0f)7 z&0Maz26X<4)99 zeYre?UA;kFj?IWlqwJPW>6alKe;sRVhja4hrBT@{ebS4QgE$ejR>*dE89d*ianTzxmL)d63-aCRluq+1x9pN_~z zB(@JHMM_G34~u6-&ESOnqm4qc0v5KS;4sJ=GU05JraJ+RxQvEm=Dk+jk1bI9c z2W}DXI2Dx^c+|Y=HJ(I0g=7v6<~ZfF@!p8En!M;SUP5|7ODb@nRX~@cb7OKVGElQc zMWmOrG^VI5z=2zHcDx>!W}LnU`8EJKc0lgxH7c_r5c?(^JmJK+=3o&n+HGXo0I!`$ zfM!t#{21|QEz&aenR^qy9CQOgJTWN`TM3kwv@S`8i!-GCc|@p5)|A+i50xY&p=o&# zfn0@f_#Z{mV7zHMdQD1Pv9`fqGU?Rt#0LqfM~p*AxR#b7+^HRJNr9BcR4Hh-uXwFo z^wM@rdQUk56>D)xrTTCUSBbp*lS16UUX$=)b0Ir>Z_#uhH$~Hu_+6n>X77f_LHwLl z#3de6)v8(h;OaOeyxozlD}a(hiHEH+Q=C*8>vBJeXajt7M1=wMCz*L zLp5=@YDNtst+r4_u7Z!4tD(DEVsabqQm^OS=?s|}j+B(kyoh`PUQrm$in5DGn0;Sy znI&;4$5|A5?CBbpgz;w_@t4F=nUrF+pSs}{qbdK6YV{G;7e3UQ zjb#JU=~r3RvITDa$m;k!9jec5vhbM)TKBd-)pX?8jcdY-ju!`| zU@Pd}=a<#FDqZ(CJ@Dq4mtMK^saw|ms-*@MM13KjDzU3iR$bPAO$(8$px2uM-41uv zeT~@alUB>p!vhgyoK`$NyAW$kbzameLF$L)F4aeYb zmN+5jrLQ~#+AV#znh(2-_;w)_3YMld#!^1L2sSWp~S68K5gFIACEi$Bhg}q%F<LS_HvtT?qyXT=$;#c2uo>O(_TY^F{IQ#g(~vNkIwqZxuRC49B5 z6%yVF*?F~RQn94Vxl@F$=c4W?}L;BK_O zp^i3Yr9*3NR$h&+9}4ZbZpezuXuse+jjS^gw$7-}ibJ(dR(f*lks_lvicPzJ8s=RS)qXi=BwEd0$~y0vTH$+MpxKJxwWdkKNmhQJT)$cH{p zuk@U`-PFzCbFmsv*IwsQrws$Gmr5I}oO<34`shR%+k<@7Iyne^(~UBwdcm)OPdc%T zx9;zO2GNF#>IrSOXpFmY)y526pto3obv9N7A)R)Vzu^ViAeH9VJKK?dW6_Pe`TT9E^$Zf1DoJso2u4!A1+@{j9KT=!j zDiy#u+fbLo@2G93%itkro9UA{nWL?wc{@rQNn2ZEw2f|pm(kip<~GM}3mKV)$Jqwz z!!<{1`?TPEa*Q_5MB6&IA{K z@_J+*k|E31d2NS1$ zN~Iph?r80kNwSUGJPfo@njjoMeJPt~J?+NbY9Y;K$&NIaU7kpDmjcass+BYcw1r+J z%{e{YH$fPrIi*L^+yr`{vuW$4`!LDNJn_RZNZ4gI|dQM?>v-`s`o zn|pa$_RV>!l{811^Nm{|%?SqGkme>ho?vX6n@^e}&1Lr*Hq`ab;Pc+)yPY0guv zq&d@lC6igsVp?34GJ)&ZY{^f@l^0JcfMEf_PttJY&IJ?U*FP zW-6fN&KR~9S&R1UxV5agD9udQVt*!9G835z^K3?*e@$j0Gue8@$4nby!MO&J<^!h} zWH8fT*#pKNF!q442TYB*xHDqM+XIGqyYdKDW3~Yk9;=-Fe9j!Tjgz!t?sy~2wlrbp zW-Io$Vm{{vj1+9ZT;L|m;BLh6X3QT=;_McmLng!zS9S%JB%tE3o4Dhi!n+PyA^`|1(hmt$V+NbxMk zJlUZ8av0XpIkY3YFU#S+Jcs*=9PTS~xcl!{=lTZc`+ofMcD)B1-zM{IGT$ciZL-=M znu@KV?6=7`o%@Q9rO%P2$6HG5FES_p5&5`CvbELWa)7%vg z-RGmZv!prF9BGa;N16-T)10bJJx{fg=16m-Invzur@41YbEG-a9BGa;mv5T$R4ZwY zG)I~v&7FUmdxtbfnj_7T<}PNMTQDtc>UpY_G)I~v&5`EvPjipW-{*VN`WMn1X^u2U znj_8SyKl}@t)w~99BGa;mw%f3%^kx&n)@?pjx>uE_Xge{jh2Ns3eHebdYyw{|`&rhvXbb*(v~PZfeV8XnbEG-a9BFQCIL zVn3fY^`3QX>eb3hSq&>Hb+C+5hu`=tlMLbW*19yv^{^SU1vX>)r32P*_Q;^IDiiFJ z8klemaawEBs+(>SdAvd**`~$3N*>5Sf=Y!mmAgx}I(|Eb76-LbmrN>a& z*p~g&YF`U10jgcVPFVKaZT>4@6R;h8*Dl+f<4tDU1)AF>ede56K~#SO@YfB((=#q% zj%C{~XshqyAyD^m(8<0-z6s`=V7>{S>YL!P`wpKVfs#N;pd`@A5NM`7yi2?9&{d_=ojBKr}i+K-t1b>Sez9VEq(;z)6% zxXDo5rTKMXmnqXD4gFu+O}q2!!uI%49m#p2S=xKB(XI|znk-G0o+?Xce_i-r-+9Z| zBl;*=nk-G0CQDC-r89d(OE8~D=k(~@o_zO+?wpo(bv)Hdnj_7T=16nrpXMGR&5`Cv zbEG-aT+qJGRNr%Zs+BZHnj_7T=FUIOeTy_lnj_7T=16n-ra4cwlIBQrq&d=D{%LO2 zir@R*v_4FlBh8WKNOKo6&Gk;pH?5v(CC!oMNOPpQ^G|c%B+ZfLNOPn)(psr3@cXJL+^sTd>n{tWLG#=*0i??(R8fcXLio zk`pBDf6wgM-FM&HKJWWJ`+Kk3+Si@z>FW~z7IF;k$I+6oNQLFMGVSY%`D16M?Fl`g z1SkPYfD)htC;>`<5}*Vq0ZM=ppadv^2_?|l*WI#vpci#~d&_pbUvHyP;J_S_<%Me=QREM^xlOB=e5QWpR<4&L*=a_59S^KeM{CFd33TTn$P$Y%as~Ak3G`e%u?tafQ_3w-RS{vKs$gso;k_S0QU`>`kPV zn{I>cG1)G6!{-qbA~JaM*w_xl%T3XD?v2Yf6Z$@c)d^cexZ8w1>1M`Fha<8Dk?qG# zk+L$eVxM;M!_s*Xxm8AuOWZ1#@^Eul+F&f;7Gc7j72s zJROzIu&8O(XDo?Y3dvQtxXQ_=t@lQx-K0gYu@ce%EvdqV76ILkUlWt9NI*>%6_EyK zsZUW^gbTOi?0GdVn{fMXq}w3m*a^9-)u_yiKGmu5)&lkiZZtTC}AA1X^oLgVrv9JvbN z=-*4E#aPpL^qH8rV%-UQ$)r=l6YnIX89t66;+k7V@T9iCWkpgRQ>CEE-uP0bXwbG# z`b=3Im1=ew8|z0Ht`d3vXT^Ac11935CLlX`H)uSNnxb(@{HEB+vv-GS$XbWt1AR`T!cx$3nh!wHM#Ieeo^+1P4M)l_ATs~3)CQiZ7EeEl`}%}xbC)bW@|E@j?N4kxc4F8&?jMgdV%Xj-$W_qmO@{7(x$3@F9Q94B zdFi3S2og>Up59%IJ*GM@YLy}Oqw)pWW71_LCH(D(d8dR*MdoTUHadQp0&BZ0&*IY6 zpP^F3R^8s)|=se7xn>U8jW?>Rzu4jmkZJ% z&w+MJKdq+2kP+VyTp?p=d^Mgbx2w~x5*}CWPzHP9GE^GP(?$B3VzvvFqlR&LI*hn% zfCMyO^}%oZIf|qrYm8LWxJsc7E{9PPL#1)s|9Z#&g=zmwjUuttnTD_n`({X7UznlN zV%yzP$K91_cR>ZD5>D7Lt99&~LTu-Y5}(3KKYIznhzOVyFQvISS}3Ht&ohNm&!2U! z>u{`b&e!6Iw~ubodHB1gZb$b$ubjVp^w=}uw*Whusqe&pCvuRhl|I|_BM4UlB{PZV z)n{)3pzQRO)UYEN^pY7M>fy1w4AEyk~28l?d^R0^e5?EGBwm56DkxrXi6FN!_hlpjIq z*DYU345O4N%qhAlwsn~|7d%kjRGnfpgO{J8cx7jnwm`YL8ScEADGy)L`N=6uYXZKQ zTUB-^Cj4Xjbuqfz zan$|CI6sWtyza$c2Rn-*sdrEZdl{$xy4RQBLG1ru=c+-}#;ejC4zRf9W8Z{9U^ ztMThpn)k=6OPO~}o^_}z;7887(`B%bwa)Zm+{{u}(zMM{N7CBX1a+fzu#&4zWFB+k zx{#4+IL|szKf=sa_t}j5$qDK_ldbFA20ydaaaNn~a@B2e*J(6)bJk^&uu!l%%nEom ze%<9GxD&fLb(U8Hb(S*^t}d#!&RK4qWwEPrf}}EIbr$j0Q@-aYF>7q>F`VYApWF)J zdEfrZ>w))Eo;8$>0pFB?1HR>5YiSGkr*)u!f0L@oO!0}c)NWb=F*G$Subz!sdDM9F z<{#3WU%I5ZVD0PuM{_CFN}2=OLhq2~oR;o&5C&;Z>5()yi5BP}&Hc2x$wPCCNOPVx zYtkGv4QUQSxE6bW!|m?6tnJ}!n=|d~g>IXB-f2~vU2StI)k>Np&3VQxtb=MH&5`CN zIi6r*np;SkBh3Y=g>7@BIUPrmdt}7L>S>*wG?!AXq&dOV)97u4>f=+tj!@J=c+M95-jrN#eLU zjLr1VCE~a_ofE`ybI=u;bA8Mxf6rW><4^BCT9q+w?(_m@s4mCNnUVbLX+;)dPb-AL zp4LgV&9#en0&PKsJryc+}!<{ zn71nrV>in-V8Ww2)4PzP)9l4bc4BpoRxI1H5i>X2alXUM<8DUV!3LOZ!9AU;y%ox@ z9reU!V>_Rj-c@kh4!134B@10kMAyghF4VFUb4FX?V-j=1H_9zC;*6DZ=WRF0D$l&_ zwE6h+yc^_(aqX|fyx7e7->yIYdEsil5$h*4H)JtinZ>*@i+NKPbMN!!H&6Xy z&)ZGC*w~xQ-emSBvo~4S8=B5qL&4tUbr-(qVd)EGX|gm~nk+pPmcCfM$)Ate?F6%H zrbkM(lIBQrq&d=D;c4#5uiodOx$~qs(i~}yG)J2AuckTOHT6=el{811Bh8WKW}fEW zB+ZfLNOPn)(p;fwE~Q#YbEG-a9BFRmY3>cu9BGa;N1D5oX>QT1TvIQlT1j)HIno?y zuJAPX=)!wEJ*|Hx&5`CvbEG-aT%p_MQmU0SN17wek>(0dbHBP{)I)QBBF&NJNOPpQ zOPS_=Fe}^UT-Dn8LdLgfzl-yO{1$B&rnT@}v|JyCUoV@)*UNq$_!e!^e~mz&bJR`Y>y8UdT1|o^;mKtCuFMtx_keu`BMR{`(@ayD&udH8UBzu z9!#WH_Upj;M(kYKkL1Rc{rKJ4$$Z=5fvA6FKR>yBi{wUfBe{{>rb2FUkjt~!b!ERg z9A|>uk|3=8{Dw0J`+Ay;m=dSe4JaCf}t)pziCSi*1MO31&|) zdxEFi6FhO-;o~Gw5-16j1UeN0&0G)fgWY!Ms?il}J0!W0+(>TICAW#&4j&`Ak=#gb zB)6%M8`}=wZ*7OE*TG8};|`DE{1J{j~7~3(hQ(dVB$H6~-Rc;hSvr7=f5yukXeFBATmGyZCis zwj;6~k?n}nZAT1#UD!`?he&axI8q!bZYmV_!Th?g%arMnhTgC3rp?D6fyl252S3+c^^(0bb1BtInj_7T=16mer@2*)zxDLAK17-$&5`CvbC)vB_07tj)|6@` z&5`CvbELVMr@3#C=16m-Ino?yuF!3BDb-4vBh8WKNOOg!x$kd2>uH<&I%$qHN17we kk>(0bbFOOT*M;%Hz8dVwO@|}01>c0}#}{EP>eq$;2auXIIRF3v literal 0 HcmV?d00001 diff --git a/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4674.evtx.golden.json b/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4674.evtx.golden.json new file mode 100644 index 00000000000..8e0e6c2a6f5 --- /dev/null +++ b/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4674.evtx.golden.json @@ -0,0 +1,78 @@ +[ + { + "@timestamp": "2020-04-06T06:38:31.1087891Z", + "event": { + "action": "privileged-operation", + "category": "iam", + "code": 4674, + "kind": "event", + "module": "security", + "outcome": "success", + "provider": "Microsoft-Windows-Security-Auditing", + "type": "admin" + }, + "host": { + "name": "DC_TEST2k12.TEST.SAAS" + }, + "log": { + "level": "information" + }, + "process": { + "executable": "C:\\Windows\\System32\\svchost.exe", + "name": "svchost.exe", + "pid": 884 + }, + "related": { + "user": "at_adm" + }, + "user": { + "domain": "TEST", + "id": "S-1-5-21-1717121054-434620538-60925301-2794", + "name": "at_adm" + }, + "winlog": { + "api": "wineventlog", + "channel": "Security", + "computer_name": "DC_TEST2k12.TEST.SAAS", + "event_data": { + "AccessMask": [ + "1538", + "1542" + ], + "AccessMaskDescription": [ + "READ_CONTROL", + "ACCESS_SYS_SEC" + ], + "HandleId": "0x1ee0", + "ObjectName": "C:\\Windows\\System32\\Tasks\\Microsoft\\Windows\\PLA\\Server Manager Performance Monitor", + "ObjectServer": "Security", + "ObjectType": "File", + "PrivilegeList": [ + "SeSecurityPrivilege" + ], + "SubjectDomainName": "TEST", + "SubjectLogonId": "0x8aa365b", + "SubjectUserName": "at_adm", + "SubjectUserSid": "S-1-5-21-1717121054-434620538-60925301-2794" + }, + "event_id": 4674, + "keywords": [ + "Audit Success" + ], + "logon": { + "id": "0x8aa365b" + }, + "opcode": "Info", + "process": { + "pid": 496, + "thread": { + "id": 504 + } + }, + "provider_guid": "{54849625-5478-4994-a5ba-3e3b0328c30d}", + "provider_name": "Microsoft-Windows-Security-Auditing", + "record_id": 5109140, + "task": "Sensitive Privilege Use" + } + } +] \ No newline at end of file diff --git a/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4697.evtx b/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4697.evtx new file mode 100644 index 0000000000000000000000000000000000000000..b878c3bcd3abfc5c5826d0c73ce9e6d3d2d83903 GIT binary patch literal 69632 zcmeHw4SW^Fx&9;s2oN9$Dr(fIQBgrrqm7Ds_%zm_QNo8B6)*y7_$Wk04REkpspW6E zmR4G6jh5TeHn(@JRH;V5iWV)`VvB;7YHX>+ii(OYwo(4iGxN^b-Pt{d1EIOn%ko?H zJTuR{^Ugc(yfZty=VWN@gkjg$UZ>n6R}zxA=BRc`73R2Z$bJF)EwJAL z`z^5F0{bnn-vawBu-^jvEwJAL`z^5F0{bnn-vYa|z|h(WgC|Y75%>7!NOw=C^# z3Y2>4OAERi_MZRGdM=(i;>Mta!#gNdkgwE7?UmY+7tB$r6zS(;8)A2l7k5$SN2rs> z;i!|x--W+c(wX)E(k3|ioDaN1Hc1Y3>Xr{JG=ql{(ID z=s3krJQ01`nR`6uSaJT@zy4?bD>>bBa#TUiS4Vv>&z9upKfZF;h^7g@Kj+-%#}<2N z30VVss3rKw2J(2KpuHNZZdTW+DXI?mZk!!%kj$p;?bS>*S=AzQ1~S^`oS`OxR;Q+^ zv(+j1H$z>El+>XAo{Wi6#gBqc3h2%P2BOH9-&YhP-FWaLZ+@7y0b?rB!t{SkWBlk?4 z!>lu2^vyhVF_fK*Or^SYQ#l>Rh(DZgV4k`_-K0w*tGnvnZeTk#9JJen@$tBh0b^aL zh9K{FoE_+L6m!CBoz=v4cv>ZxmuSx*y$hqM+`RXEM-->>l3Om-o?xeO?2j;=-l>q-C z63@A}3o5-{+jKCf*FgbxQ4KiSJRC*zsR6fl4)vfd1oL!IJ=*1ThPU{!WLUed0c&3d zfmE5A7|Z8&>#RCcm%C8N284${?4ky18uh5vnpm?|gEwrLjPTAMbXEf)u?~vUTk6nK zBez>e)x86Q0=xZ;XL~4CZ6vWxX$p0vyX@RK8Fknovi#?r(14j*@tbr-rhCh&2YORc zm(CkHiO*gO;tc!~E%H@<2ZmO5?OkW(!@`rqw$=dMI;s42IfdGt7>s#*87?{)%yDp} zDOzuKTA|9x%h|b88vsOh+&NS7wV@`%6{wsUE4l4+iqvSRaIxx#A$lr2We^5z1+Hh| zIvBP%84gja#-jjx`DA>a3NzB<*%yPLgALLRC+DNP*=;vLb7Nwqjs&BFjn@+@I;fFo zGi#T27pR`?^1Agmknp=hp@|%g|CfD8mHH48yfHRJOe-n?-$92s6`AHn(dB)r<9gL3BL9w@#s>;|Set z)A7Gwx}(Zx=`^+pYRh=VzGc}z8)h*!U7f4_l#tDj_{SW~M=KvZUk*>p(E4?5heHa=% z4z8TrwLR8YkaQs=UD04xADmQakC}m>drX|x`D!4j(_kWYK7$Jb!@U@w=D=u79Uhix zJo050#>aM}K$LDD=W#`3|ZA4lL z_@+g6Rv!+Vv-4|pXhybPuW|z9TF3OraI}4f;)<0{VJ&O9BVijeAg+T9PP{ZM<~E#W zg_>QTQ(kdQnP7drk?-~hl61Q=`4c&=?o~sFSFVSLvoCqp_dqzsb;?sO9(QBU{83}N zeE8}o7j!#cruF&A%QT&zi=yFipeIDvQ#X#7iY2iAV5aBiS(Q6_yE-mtACF+#=9skN1ERb!NTYA@RwCRtxGRdJXJ>EfQnrh-@mAb*xp>PcMW0@U zI@v;=B|VK#KXp*)9r@)qU-sHZx!<_=jV~W5K7BPP|5Beey%g?~GU$}f*3^F~#w6n! z&%6VT{sZT)XngXoBS&r;*t6&6FVCzO{b!?%ne?Y$hsPwBfRXXkHR5A_9ODvOj~b4| zu&h(!WOd6JUsH?jtfgg&YPnryw4D>tEzN8;5*}Wm?kf-+U0y2s z=XT``9oN5;&}Te?bK^Wo3y#gkn6z~%C6JNHn#k6E>Dq|t zadSrzn#lzc&(#MRKOOznlG9G^d+Zf&-uj3Ce(;rL=Zc>$0wt55a>W$3zP^!j=Z?-g zK5^%cH|$)EnA<7w&{HE)4na!9+uPwLl3rkZF&am=qAfgv{g;1mj$RiqKDzaji*0O? zYGTm5z!+ZM+t_At1I3>cWm{j4UWa4Lrk9s(-M*v^n+?i>sZWQH%DDe z+4)GzX1sB4G@Vazj)>aNjM1n~;>TzM{E!~r5og|E#t%>V+UZL#9q_=YSBI_e*52`6 zfAPc8Hub|jh_~W<-0BI(io=l-8RhLmQC{YWOTf%@52QCa+^Ew{BV=U|XC;cQDm{0SHjh`IS|A+6K@_5e?e``Of-y2Q6ZWTYN-G!fIJ@1Ub zo&a}f{B_puuZ1(Mw^H|`S?}=-f-Y6qe05iXsdX?nYs0beNTft!V+Y8Y0ZC?4jB%_h zXVcwYKXFT%BRA|zlh8kIS~4bWiKKPKn6t-fNejlhjYy-fMEfHJHY z!8CV0w+KyQl;*kPDC2)8PI&az{~3SKrC-SV=e1wC;k{nsf6bt@-Fj{hdpyVVc-B!R zyn2iFdFj!n&x3tAM$&?PS&Ou6VnbY?FfLd<@KN9E=LP1E^~acg`Rvomk7{1|sIo)Z zlxw%&)2%@Ih1%>Z{gQQzimn403-sD}w?}ik)MyTm=aQ0W->#~L5#P;rd&>th&3~yi#6@U8 zI2v=jMWS(G+PiZ9ahj|nvL3H|JbIg6qvo1-5C%qcNwe0y7^Uj4>&W}98v_sY{MAIT ze-p$0IwG}ws0&)8hs;^Ey?SHGu=FDEkqvUgKal4@2%on zV`9e|qoAIDziXB;7m+ue^$0Mhq*ry(c z$BcXkV;*C7P3QTr&p+n-a`iGS3gEj+YuN!p!AV?W#ana?;8m1=CW z7J0UhOHVMi`A*)4rOnU2H~P-ClW!SbzUl?BO%14-`Z()V8hy_0+K1bTwglR_UD{L* zy^2wJx^l?Ub6o-rL8#Mb`H7}2kDRx$a^;EVjW}}CO^4ij?IR`9mLG$bsV&2iAC64C ztByn_^J>uy?6%b+Ciu@gqOHQ}G3)zsKc;g9+%e_%!G4~G_VEbLFN=`ISYYlyGwEbv zfnSfYUoRNXuHjlvGWL7u!JCeobk#SjcU<+{&(Hqkt24xYyz|LqzpNv>e_w5)9ACQ& zKI`T9(NAZ&GZF3)uIwT`asZ;oI6PhC%WOB{OeZzRNF5oG%5fTzdLUk(szn^iPB!1! zGouxvB<4e^D(h?dESNSnCT%{_*pIui4Q2MlnT=ND_Q#ohJB&|kLm6t#b5cL!6UmYj z|1!R2&FC#tH~*pI$R)3ePuvVjCZEXK)@Gb%(<9|KwMxdc6@7M^Qf&@d5-G38$|qI|Cp z`gh+`=sDqWP!*Fl6KUD{dJ-{bp zMhL=-%FvHC;Jse@^2VPRqMV%*?>yDC>)`J;Y##c?%a{N3qRp#Me{A3eY1ev)Y_oP@ zKD-w7h3&)<6R}f~(@xHA_O)QXHyIz{&2ulbS0ZuHevPmLk6_w5q-A5%1j>c&p1nS- zQ$N4Pl^XC|gk4)+*LB?X=o8C7tUB$@WnXz~bbVLplgp5msZVzM9u~Ex=B8l#8=;50 zr^5$t)_1(qjh?SOzTx(c3lF-o`o16C{n`VQzb|_Jau<4Ly`D1hI^ErjeVVD?8qZp$ zne#Hs%)wotokxQAWgF3M9>Fw)Cqax=yVlj*o9()O;}={CSbMji4%WP);{Kn$@`Im_ zy5pQT&U)dQ#czwf8<3UBFS6cGx39w5eIpZfFgmloU-4IwjLGKdqkq2=^s$mNj4gxj zzYRbd?@wGkkfhV2(v7WZ1GXwZ)7YxzOPja6_svyT%zCM1=?CvTYRAwjh{|NEJ(yLw z<2@d&ohi|02A*~C-UW8M6t#t;8egA|M75rQ8I1R4*I~uV@96OTZq5QGs!^_Q%i!~f zX=pQ#VA?{Yh3Ceoo-D(;(Vssv)oyIvh^p+GsQC-V*2m3yX!gBbXI;AGmdmGp|Hw=A z_m^^Z?%WE|d&SnK{qgT0rhIfaq95OJqb>EbtD(>`+(-S866vF^cy)OidW8|*d=1^cQ3Y3!>^?L@Z3WNNQeG0yZFnZ9ZUhZ^uKJj?Xe zgb!+7yYIR0R?nU@wbQ|We`=@15Pnf9Q(t8rU&Hg^Fui`^?!~QHtYu1|BV*~VWtjF= zqhU5imYr?dmw5BcVV@2<@Tyafedy#%)_r@5v~PJ^w-2L#FzO6@)=5Z-cvgWPe;nuL z73<)8&GgZzUyVguc?A7x9@4V4T`|gX%*WM9xm=elhhe$p!m|cD!K|_RzCV5YN9(_J z_3s|uH2JWvU$ahZI}dr8d?)MKBm7k-JHP8un)Urtx|!YXfteVJ-pTsio+?CSv$keb zo`~H=?m^0v=B($7Ok^Wd&yTA6CiCM8W@f)X`WGpd&<9~lsC(=mczIF{jW{= za{MP@LBH#Je$<~TW76tk(iR|%e&Egn;d|oje1*OiZvp?i3(-~?Un&pyQprH$OMf|W z!wGBtapx6}pZRfZ-|n@e#FvKb!k4lh*PuqL`5tNej_4~}23G~+K%bx-XF1pTXoqaH`lHwOgLbSD0*k+fjh zq@;E4W?6Wr4{86~{%Y2c{1Rk(i%?_wbqSge*VgCap~kOYdwu+{OCD{h{;%76Bu{wc ziU-86zXMvPHDcD!QvG=caf0(;*6TRl|C)PhbJu427EefH`*t9=UrfI)cfsEj=gWqLzlnwyVZjhoHzb%vC-=&^0~DUES>cn z7G0Z}_iRjb56AZeY{DaG=SoQnt~+aywmY`Zv_@Qm`dBN^BCG;={b=s;mJW9vH|if7 z+bx^->hyP{PsSrFQ=be(epah6es_!Cku>}J{_Yr}N6P<#dtv>d7kw+fobjzy$hYli zmUiTR@Qu$lfBMa<9$hr(^mp&+_aD-Zz4qO$DntgeFJ<2z;SNmve(84T#D0jDG5R+_ zc{?^rzi9g5?i24B^WX2@e@)~2J-aL!GVzb1|8|JU)DL@UZ4Gp)=igQjUpVajnR_=ei1It?yM|WcqIA z{F?ioUUBi|Kf37a7kv8inLpXvz84o)@YX|W`~=%S4LW6t0YUwXDn5Vu_lHiI^v7er z)ZAM<+~_~!_K^cOm;K@L<2rXduH}(uUlaR3jIPQwc3S17wEynR4V;DfQ!4(RGk0V< z#r9!2+Qp+&Cwe~5ed8P4@tA1R+@xD1&!5I#X8P#uKli(S$ytA?TzpKQldhOQ z%kIhS>iN?^sF2m#E$lmd*Vp!xe#-Z`>M7da9*bcg9>IQEE@{DjT7xvRPZnIs*mmxD zUj^59y{b%KZMfz5t#@yDXygyp9e(TOJ-*f~<7rpxyS>#{dnw+@8|JN=Ir+^4GrEF( zRtY=t2=-5{qy=N^0;FZ?vl7S%`)xR8vyOD}mG`aOp0M}!^G2G!8~^wFK1sa$!8KjK zc;8oEsh!hF`mPb8GR4BZWOLpr?ar(hw_5bypbgsBL;H9H?Ykant+nAg$fRVR^F|pP z=6rAI>3xS3jVx{XTVlvNi~lJ$oB&ED8)p5KI=p{81J6nL{ww!Ac%K&k-uzcHal}7(>A9V6fiD#}H*|PQ5v)}I3xlH=vPP8+VU$tsRk$W+|V-)tyqmUAL z@1UcsmHkz`%xVK8<M$BB=htNpZ1l2#;X!8KJ`OjA`;+ytF_ISar`njb1xVW+fAaky zeq5pZv+|isDhFRO6I|XG%h7+c)=&FR{}%l@mutoTy{<7jnjZ%}Tkpk8!2HL9y4!Sf z-^ZUojiyh<^EVz9Lq}CC!v+YzD8AEU-d(YI+(V+?)A5}l|Cc8DE@oJU`K52Z`^n!t#`qdI;-Uo|mEws zxwyQN8oWK7CT|S#x}?clh`fR{c}>W};x)FuX5{e^YMd_Wx*YswbbMY3^0)^PSFRj+ zJ<{aWAdf%U8CPx|^7yzgE^jsR8297yHX-l8G4Y40aGJbYw|LkcQV4i2^#z%c{A4HdBI-?&BPmwv1mt~d54+L zn7GQU!>&^B*GR)PTbb#&zSO>G3)c?*?>nY@v(Uy-v)-_AV-WP=lQOo52W?|#X3H6Y zHxKx0?dHeF`3kQ29n|m&+uXMg)onB1g693)B*Z5BXC4Ni9Haa#SU0f+{JHXU+73ii z<2vnTSc~y2*k>2&c*pM_@h7}*K@_C4agb)(7`6vd@YmgxDh7`l@WFSY$V zLu9!5ew}2PdPeA(q7JJ#`@5vU@6ehU%ss^ksD;l{?e_(2`LLehyIOL5dsU!)=I8FS zwHL>f+Xl)k##srS%o>LdWjdd~%*J2hH2#5682^ps~@$RjF#~mSi7#t^z#ttxI6k?Cbl%SOlK3d%?H5)Xfay; zR*d~|S__kZ(a_6B&&LGEKfJ;DmBDd!bQNGTX|r8QMewMa~}_+7v|l7 zQX&BJUSTOQ7bQ3_*A9GJ%n>xqjsKmDF6zB=ORAZw6=A6R9TIL%CFwYbY(6|_BigQch zof;=J#(YxPsBtodS8Hq(z`Rko#bB)f=8D3S$&_ytz}ly<%3!Sk_9zP%8>|&L9C)L_ z#VmxrDJ-0#%O{BO6HJ8_1}BM&fs+Pnz`19c#ZU=f`zTxw%rSk7{)Ps>bu=C^a3X#= z;#wWS7!T{S_6|X6E;n4F6h4;(G?cG|m7llGdd z%vy7e;o6nIuzmqfzWdDHw)ZFHS#vYj*(XL$+&YBw99IC-V0C(@`I}K@eztx_-{zki z;f&7}iCOWa8&AQpXnxNmIug^359|B(8!}uE)FIw0{C>H$VR+mJpJe%e6UfvUUSFA! z86N}E)q9N|2W&rQ8qU$iCP5wSIJ2ug{^^9^?4Hg}!MKy&PNiw;G%F6{HDN8yn!wld zW{l!=tAU~T^FTvUgATcv+h^bpdsIPt_B7X>^a)zutRL-tvsn>NgH`A)Tnq4*HyB~f zF?7_$Rbwwvs!oS{J)`yDeJg`Jjs%juUcAx6QH}d}9{hjZvK+M(S4=oP@GTdm79h1) zC4fD0cvOPFaIBff*9#wWIr#YqhQ*{LfJ1x_d`@DUg^w8&d>jm(;Nx?M&qV68Rrt`| z*9Uyy5TE*>PPI+=xDWUBDOL&K5TE*x-y(dpG^7u(mpq;tnD+8Yl>c*MAGEYbUJh3f z%uxwoZ*iX-wHA9cv`^hQv)@-Be5_@9ONGz1U|@VD zp?U21Dg%EJY{0c*kPqzjDUG&wfbg-FggW@Q-%%0oHt07Sw04@Q-o$z}}iK zQsXecLHH#OAK06R_efp-65$_zO3GEwXU?DfL4 zMIM$d0ast>ZMNs5n z`OI1`{I?xGu-90q=^_3`;s4d)1IO|=3ICmTv_EF8oFs)Nfwe!__^@`Y@#kjnY5%R7 zj}PP*t0ZvBf2{uOuPwrV*UFze7gM!L0(*TCbc6Dlv0eD@rQ{&Rl?NjiW z(dT`K59}@HhAxgEzfkzw96qqO9zo0H_Y(exxDrd3%?>y{4GIADgo>@A}I0*`okLV>2Je*KHWS4>{Xslw$G=(trh;oKA)5X zu%|at1AJO(qwp_r_`u$fvsl!ZPyQz1S2}!PZ`#?K9@4*A_?J3-V6XJND1Wo?FLU_7 z-hd0D{4K&C;qZaI@=K%qt-`Nz_`qJ@5mEj&;g59qz_I?nUHGFMKCqV@8I`|7_+!Y= zf&Z`4zMBB{nn(HZjsDO6Z4v(E^tap;KMCwD8;$;y^`zzJKW@g$6^=f@-nKzeeR_dU zf4kD*1AC2MjPiR6zZ&}FYJKL?Ka;@Tjxk1`;P@>PeXg?OC85K164>jH_VTd)&-RuI z|7u4cU~m3#O;7P@xrFHRh_z>OAV~>eukRI6dkzu)&&a3#!UxuT*PfNae^krYqV<2k zp1RVP&qV4|E&OF#zNYH`fW3-pzrUDB{#fDvoc77dX9`X%W5M5Xl`r3}=WD=cJbX<1 zgU<)ne5=3Z*9!k}EkD)XB(S&kYG1zfpE}_$*YbTnuvZAxtbEH)3jaw*KCriFd{q8? z;Ws(*fi>TizexB`Ir4$M`s~R{|x<&o}&`_KVYwN0zdlWi~hD1e2(|k4j(v{zfJhR zf59}3uDas!p{8yaz0>{qp3Gmt84GtgJ z+ZH2#i10T$eBfC5<-&i};RAaMXGHa{5dLcpAK3G5iSjFj|9jX+#xF?;;1oZ&|4;=! z{dp7Qb398tNdkLCx9S8nMAq9JFJnahAM(@z0e&%XEWaB3MFJG@8fK&Fd@+ohu z$bZ9;5A4mH71gI!hOWR1u^^v;lJzf zfxTrXMddFM{(BA|IF`Ru`0qP>U~kRIQTdI+-{$avy&?Ugd{6ivIDFt({xacz=8P*z+QQUUcrRsug$_g*x>`m^0x@Tx5EeaW)6?a-zxk=96oR?f1B_R zb@;&EV)^cJF#c>8{$UOu*h|RwWP|)2!Y{IXS$_h1Tjl$jL4J$y4|n*$-qNaQd(|&Y ze;v_2ynX|YjaPZ#Gaer4@PT9b`NA)D_`qIs%y=&l{!tDeIF?^1{G%N{a4f%<@Q-o$ zz_I+^!e7^s`w2blyIl!jujs4({ZXSl-z);3{{K=(?kA9+1X4+0uVKD_e?dOSOR>oR zE%{^$A6WC5Wc!!=KEnTzL)#bw9=l^8ukPU}e~Iw7Ieg$){!-z8pyNYlm7lAUkeUScyzl$* zhtGGo9%uxgZuOxfAJ}Vn2pKqn{{UGC_m|~w6#hTK&(-{70%8)tUi~t|*UxJppZ&W@_$>|}I3+*G z-y-}^9X_x(b9q$$cHw_U{ZXG{5QaX$vHqi;F!SS1$|uvzcL`vx{|+YLojJMxWqzLU z@hS%FgIihS&k10!?mvC|(4WaK5I(kgp%3kuOdvS{?3F!+PdKzci+y?tKNtLd-oK`FUq0i{GT~43`J^O(y*@AVqtB;4tAsx(#n+Ssa6~@svqt#Wr}#!6U~lbO zT{hKT>a$k(H>CJRK5&Fj`5T3Qqr(UG>Rt@XCx4UhYaKqYw{TsQ-z@ye4jsVz_Ioz6#lIaAK0rcqXqr(Mbu09w>f-Zui~sI zzqjyj$9!bwcRjw6z$ty~`jF$R2z<^*iyZmD-pqTDfg{+zeMJ7Z9X_zP_G?joU*Uhp z=O@8R0DJ1WFrP!CzwjR*AL|EM4<~@V#PeqTKz;0bxJ>vj`0^<<0qiX~C2F4`!f$c- zz+OFnh!IDyz2(CH)Zqii@+*Wtwo`b01?+8a(c(kv^Ge~5bNIli^_A@}YF8!v@eZHg zrLya>Ab*VTug#KQE&LjX5A1E;8P$KR@UL_Dz+QQdv1d@98sSeMKd?Uo?2X0Wqp}?+ynZm!(;RDCY zuNQu;!w2>XE{y7*6#nE+sqxNEN&ko7*BtLNA)oWhYVc|QXSDr&KCtH7_d_gy zo$yy{erMG#Kbcg;Dgo>jyyfe|_z!7nlklGvzM6OUL(~V@Tk=tukGFT!cHuuqKCty4 z;MjQEyUFPPEATlVsd*ifD3t*Air}wy{buzk68;+UjeN}q_Nwyfk^cO`@mMPS=Y2jY z31Dw=moT4bfbd_?d|w}6uc^JyxB4W6|7-B`a=M_s2Wt7iUQ>5J-sWNbNqdYD{#q@c ze9~0{*jsRbFQ0s(Ny7ghpPwWj*sJUr=Ci%E!hg}{BUL4Uy&VUJ`9yWX|4oW-^au8O z9USIU|Cz#H2Yz0zZf}zEfxX3t_}_=3{_LN5!hcE2M?${Rd|+=tkuM)Piux}W{%?Ig zvl74|{h7$pON9SBhY#%491)gJv{d-(9X_zPxj4#i6#mQLQ$LlT`8<#$WT2M);}Bl6b>|MyzH&j$|4xBRuj-vqvlUu{odZ~1Xi`>zxJAIJ~P zN5J0JO(x!QJ!$8c^}>H$^BE5*UL}CN(#<}f@sPD`6#g5IKEPUk77nggHwnKPe2xd} z&%j>cX-0p$9-}`r3;#`r4;;(iBK$u(eBfCAHsSxt*FTAB6Tseb(Z}jfeRc?cv%?3D z<*TPmeEu`_$vG(1Uf_^Fl#Mt0jrqcV%i#mZ@(YFk7e^o9Sbcg6e~ae(@fq05@96uF zjnBoxf1C3!+Asc)1ooO`zs2?!OP7Jq_1j-5KQJEwdt2K@=c554{~bqvU~fk!UmsRY z`&S8nt6o2(=HDc+o`3E7-0EK~`n)TAnSYbOdj1XaIseuP|2^AYGhZixy|OLT!he22 z{vzSOulWZfVC(rk3G8kDP}BAOR2BM{{3hXVbL0bi)qnH({6v1U@ITP{`|CI0*!5oC z3S<8d$Z~NEstAu}Cfs9}3g!?aG zZ~NQWAHCXqU)v;`|2ck-C*KS|CMJPH`WUA9pZq@L>jvwzBydO{mw$qz53tvJYkGZ7 zbo2qvsLx4`KEN6E>FdY`&M3d1BOf@U{FBM|jgSP^{B-s|#o+^I@z!~|ck{{?_ zV9mGj&{q{P4&XHM1N{rE`PScD{^^cBz+RvC()Vwv!w2^2-cQdz!{GyarQ6c;&m=$4 zUf_)F{Q~)c_5x>YZIHUZt9QnW*<)7`y2hJ$}97jHIM)?CA`M??FpXK5#~V9^&wUGxCQzd|+?Kw)Fl# zjQoH<17~dS1r8rLBfs3?183wH9xwBCu{LS~*sH+p8{?r}Z*#a-%luU^DMz*7cOU!W zI)EpBH;s6{qzChCy5$gOd$Jx|SFlWDpiX{fUbfdbt}AD-H*_ zt^Lfpwg&2It-N6QU_0#RBGgp@-wa;)**zEULZ`LxW1fxRWx84(#{)m}IzLdh0NCbP zZt!a52h-v{H;T+mvfOf%T_k#?`^+*{2P-=rH=A@TqqSG+l}-lrXmR?*w!@~ow%8LK ztjCt8PK|re7q)!dmGv+^c-;z|?OBWR^*G75pBcmFiww%8-i-g`^aUP(vjS%|PUbh^ zEI>YawqBQK^XxTFmd&Gl%C5(mk{6Ke<_EErVXwA4uhcb&clFuI*yAheqi)t#Ru1dU zWS`dB-<8dFH{mP@^wHkpSR2~5?ap;=pihG33ZSb!4$D(Vw!D0u8r$&O*FQsYp&G5P zHh<%j-+J>Oa}3Jyi>cDf?$_yoBwOTjaV>u9rBFN4DaH31(&c57e%hDMxlMgrB^Uk7 zw^OiOS(BfR-Lus3d9~lRi=NkZ8{66VLrUQ^A6jd_*OR}QrM{Eie*3EbHGkSR{`c49 zpJu5C(%Wxe^S=cb82>At**5n3Tk`L-)Z+B^s|<|aqXT2$^E!SPTx{&uyxG{#UIVI! zKDIV~-;w;rV4woUx|Y4X8Y>U&Z9t@*q3U%>zP z_;+d*-%7#n+%CT_c7C(vXlg!xvPb?W*^MX5Z|$`qr)})_MDo#D>cOb}=KUq!ejngP zRUYe_;_Vk~cgTJ(wXyxSWLIBn=igz;3udW?sQs)R_SyM&UEA32vgDPs)I(AG&D$F9 ze|G$OtKfsps$@tw@=a1sNwuyh8lkH}y z@5k6LjsMyBH|>}4{ulI{Q2hIAcK#47->o({w(UrFZLR%YoBO+Z^}~N3`>9Lh{_VZC z@xQm`{-qu-g#Fv>UriJHHDnntTI+uw&i!Y-`q96S{rV3u{@0M@eAQa}eQ)m9>(!6b z+izdJ|7a`w{dn&8>(x)v+izc+KUS5>{4uX>;@@L)AFo%9QTy3_6nFh+*Du@B?7sx( zx#0SxJj>@1!Sb#7-0*ta*l*C>i|W;XMeS$X>)P)`{jCF~8jk&cvHMrH9M{igr=~Sl z_}7yzhnQZ5l(=YA%Z=aoi+i3lFz}&;?)gw{+<;#9ay7;&1#Q6AU%l%)8f9K6TrC$9k zYCr4m``Y+T43qn}j%~C5_ri*2=BP*0+izcce%XJpv0w2!X8y2O>ksagMcjl;{{~Ow`jrL#eU9oeHdhFlNeq-Bc|L;31zA;BVp5A_I z0{i#5_yWj9-!$I>S@4Va{rg}n3Esa2)7#>+jeGX`ud4G0D}Fc!e;^>T{+lQ7f9dC16tRK1lXZNov)A(OdzhL~!&(F>u zg5_KDxuHvT^|ki?_vRJ1%u!8I`&r%h+4Wx=-GAh+=rTt=mEL~)+W0L#OYT4ZA@?70 zwf^8 z{-&Ehw(aKo8~H`q#dCLj?I|96M0WMH)_%WT`TN=G>8Sl|dtLk4=O2})q}l%yJ29^O zUwHoU*~<54t6xU#XZw9$n?L%WA@j%K#{Y`tYTM&pTRVT;v+~Qc)idesx3BG=PCMJ! zuenFtjNfmqynnV@o!)-?>ipYQ_Ir5cPiCuU)7x)f^S=eM{%bz0ZT#=zl`CiCcd8@% zFE&o3+rRRjzAyIqxh=iMYs(ygEE{$JtB`0;DYaa|A`zxfAb7ms%3 zFWKfd9GG2wt@XbnR(7AQUPy1heRco2;n23R--#=anXP^uwV&3Kd-1a%$)o690x>)s7rRr35i5jF%Qx&+Lh3jB-naUfubLVNGHk_QVMyTu5wdy7m Hz5f3LHvzL6 literal 0 HcmV?d00001 diff --git a/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4697.evtx.golden.json b/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4697.evtx.golden.json new file mode 100644 index 00000000000..4f95860bf30 --- /dev/null +++ b/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4697.evtx.golden.json @@ -0,0 +1,71 @@ +[ + { + "@timestamp": "2020-04-02T14:34:08.8896056Z", + "event": { + "action": "service-installed", + "category": "iam", + "code": 4697, + "kind": "event", + "module": "security", + "outcome": "success", + "provider": "Microsoft-Windows-Security-Auditing", + "type": [ + "admin", + "change" + ] + }, + "host": { + "name": "WIN-41OB2LO92CR.wlbeat.local" + }, + "log": { + "level": "information" + }, + "related": { + "user": "Administrator" + }, + "service": { + "name": "winlogbeat", + "type": "Win32 Own Process" + }, + "user": { + "domain": "WLBEAT", + "id": "S-1-5-21-101361758-2486510592-3018839910-500", + "name": "Administrator" + }, + "winlog": { + "activity_id": "{74b64d41-08ce-0000-454f-b674ce08d601}", + "api": "wineventlog", + "channel": "Security", + "computer_name": "WIN-41OB2LO92CR.wlbeat.local", + "event_data": { + "ServiceAccount": "LocalSystem", + "ServiceFileName": "\"C:\\Program Files\\Winlogbeat\\winlogbeat.exe\" -c \"C:\\Program Files\\Winlogbeat\\winlogbeat.yml\" -path.home \"C:\\Program Files\\Winlogbeat\" -path.data \"C:\\ProgramData\\winlogbeat\" -path.logs \"C:\\ProgramData\\winlogbeat\\logs\" -E logging.files.redirect_stderr=true", + "ServiceName": "winlogbeat", + "ServiceStartType": "2", + "ServiceType": "0x10", + "SubjectDomainName": "WLBEAT", + "SubjectLogonId": "0x4c323", + "SubjectUserName": "Administrator", + "SubjectUserSid": "S-1-5-21-101361758-2486510592-3018839910-500" + }, + "event_id": 4697, + "keywords": [ + "Audit Success" + ], + "logon": { + "id": "0x4c323" + }, + "opcode": "Info", + "process": { + "pid": 792, + "thread": { + "id": 2492 + } + }, + "provider_guid": "{54849625-5478-4994-a5ba-3e3b0328c30d}", + "provider_name": "Microsoft-Windows-Security-Auditing", + "record_id": 90108, + "task": "Security System Extension" + } + } +] \ No newline at end of file diff --git a/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4698.evtx b/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4698.evtx new file mode 100644 index 0000000000000000000000000000000000000000..ec779713044e54e6e2c3f13df07e2ccc454d3660 GIT binary patch literal 69632 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%0D`(=YKQh`{-%% z>3ZRGy!gG(yW!MY7EXOd%Bkj4Kc8+8PV1{?{i_>Jt!3fVSEQUOr#`2+V&E|u zb=vP|(C^KqInRHqa9Y3N*WYr(skJPe`ihiO&8L1o-6EXs+xwZSEH~p*&$4jpD^gCC zQ=ik@gwrFx`^CH5aB3|Jr@o>nIpu#0sq_5|p6}l-oQ@xU;)okgt!3fVSEQV3KK1kI zR?BJP=+NO`IN?+*3#Xze((m(YKF#p^PCh1_79ae_>#|*pPZQR%a4L!-NIBL2Czs*l(>sLI;!A&dqZ>}GW#Lp5DW{rGgM9jN;k3PN<^eaH zTFb(zC{j+9(}2@Eh0_Cl^S|qcQ)^i`6-AM9s{cT3%Sv+Hj88qw!l@`y zPBotf`EVII~V7<8J~KVg;P&8HcjPy2+^p6k9?KEuWMl&odp)K?TGr}}$+8P4f0;q>wCPwaKWskJPe`ihiO z{XW0{eg56T>1+3Y{b@IxTFb(zuShvnPJK@Mh0~=^K6ly;r`ED?>MM$pQ~kZZ4DI)+ zi%O}7%>R#~ZW_X;ozzLgc-@ZA-86uAgDBBMeAoW~)_CQ)b4)hTzVQExiQD5m zvHYt~|H&yAzZ)->Ec_ZPJN`>+#E6V|eDDvBcP_v`e2 z-AvD?vxL)*zLSUDaB3|Jr=mzX)p=BKJ+DwW9ee2AaW|Y=%fhKBQcjiAfYT!3^!V5E zSO3(-_%vZH3#XzeQcm@L-3*^c6$_^eUb-mZhEr=eQQ}b6-AM9s`u+=c)x#vaC*F9%{n)nTFb(zC{j+( z+k8rKD#0Us-E^*Sy5P=@54qvgS{6=4k#c(8ayo!-MGVs2VNO3HoOXQbj-zfkwU&ib zQ4}esdcSUF_4|`JEfG$~9`C>4w43p%XIVHEMarq>(_kKTp>TTq`Te)L;nZ3dPDPP& zs+3@IQhWCZaB4;g;P-!DW`gWS%&xf^Muom*4Da_B2&W77f8#4| zIJK6AQ&FUxYCa9}=|bVO;}4F$?uJuqSvVC%%BgZ1aC(X5lq$FW@XB{wj8Da~aOx|H z?!J>c())EYyx(6WoDSW0Vvie6t!3fVSEQVtzxPK?x*qjW;q=fS{_j(6IJK6AQ(uvC zs+{`w@s$dvr@k5gsT)qMW#QCU6eXv%u&)1aJ5&DOcAD>@EEY~HU%Rk&+|BsZvn-tY zij-5$r+z*y6HbTnm+W`LskJPe`ihiO<<#f&QsMN_)m4AzhEr=%K&AUt_TfO}{g9Pu|R9=Di_#GxPfUW%7HO_hvh1zVDoS zzk6ryOkvgRvGb}bMeyX43mP$3M2rxrO7JG_{+-&Z<)wBXq~ib`2y`IOfj|cW9SC$F z(1Ab)0v!l+Akcw82Lc@kbRZBGfx@cU`EzR)ppCChR`W($jZT4uY3K_0R_UQ_-S+>g zot|q>*B{}?I3d37B*YJXAwG@OuMr|GUWk*?4RNT`?lAkTgH4)_giV_E$?bQ@<+N-a zmdy_G=M?<@Np_MP9%Q3h9*g$9Q+}U_ZO7u64+b5Fw$V?|;QZS<9m}0Ihj4&FgTN`+ zWZnn;{`TG3@oy+8iXsw~^N;PnmXAu`pZRjrr~MahSo6VxinBN1OlYgTu3`T;9fr?X4#v~|5Gy9Z*=np5 zA~{(oac9bSIKF4B7$=sfdtz0JNQudd5fic5d2)*%XLizoHC_~8-9UWeDCFoc$G?>z zCgTt@0ogp9{yfzMf-X5$#QBnAL|m-WMfAr4j`UugJBi=O&E^5A8m#jrcM-9%;J7b- zaEMpz_KOMFkhrQ+HxjgwPxQf$J~B=Vm&J-Qm5T-HCO$H#h;;m*6rkyzzHy=$1QIO- z5t&oyNPck)esJc@e=%O1hSleSw_4y>0o-X*zvvkY*h}!^T6x4#tGmGHJQdS?Y+4Bi zh@uK?qryX3L@_mbS%QH<-U;x;iLNn90-_~QmW+)_(xH7i4oEJOCmQm;UaR45SbL!as@ z$h3AOIwy;rk|1#3z6+xI^zTyRL7U7hBSJM$Pch7$d`mSJIk1zg$)Bhrd!m=%0#%aAan_(q?2k)Up;F2ujF_?0SB@KMCw$^B@{>}hmBOzDjS_kZ>{m6YEittyg* zDEZd}ZPi5hX)Llb#1t&60oSz%{>38WpmWN=*)PF*Iz!rWju^0Jd09i*4dXXHIA&DL zaZe@%qo5Vg^LstIhpPJ{)!(9HXtk(@K1$nw6%4ODKrAfG$u2 zdM1`r*;j};)wC2#m)FLEI7&QPI{=?JD)anGGQ3|R%Ef$@m-<%lOW}Ei@FfY+ooE~? zkEJH8wr7gPo?L$VlHe`vJ5ug@LcnU*pnVH~B~=oHR$zahwttS?KZMs4u>Tx6sn9C=Zhmr{f0!GJ7CA;?}>kXhx~T-dC%_cy=3DpF`p+09J3xdG!wp7x8}`leUj8@ zo~Bx%YAj2P7N^K6HeDsT4xVe})yc@~EM(mf%+pn`v=Up3H(GlJjyx98nT0B@KaMj- zBq1e6x1N#J5wq1J1}b(sR(q>;&J|z zAyTR$TZjLr!uKV>tpYyRp*jnPQ4TQ5Rbwm%|FeP52o)(hmrzILa}Hov2&*$u##G^K zDqxqmT!Qa(q~LY>y=i^8Gu5`SLTj{Ic~VZy0-g)uMU@GfBnpk&q~mvmpvc$rsgKRT z*`2PU8qi*2R2@dqTraJYfS_|HHp$b?SwFNrjaKu3t#;N$I7gD! zW$IZ{nN1l@=WIMzdwUqIkcV{idgv-@EY=|BmSPFz43`>XaNb((Y8hm2AEO;D>xi)` zRm{&;W29a-U>4Z0+Lj|2eIRv0B|+%O*P@@9BaeAMDlLOc&`{gyt!qc6T4-@A;`(C; zV;pKr{c$PB*uT){H5wRmLA!=|&fXqIe}d~2eKrQKGwxZ0sH?yqMPV== z#&yR14vq{)B={(ywlp4d3hBOpGq@ot<|AnGz}I5ok%&*J=5R~l|8edwky^4I{6=b96mSAN&rct50Uq+ z8vc^5p>v?_wDx#N=VioR^PZM-J7^4AGisZd`Wazc>&82{Otx$SYbnz& zsM?*8JrTJ?L=Lvh}AusmxggV*G z7!b&h(0S5dDEd74yDNN{95{i!|h#mIKLq6>1i8UW~@_lKO~LIG_RK?G?B>bH5befrx(n@CeV%O5 zzz~d%d+igpXvW8K{kmJAU1z+SNQ^i)Oq~JDxrB zkQaM-GE}{u+SdAHVh^=Hh1>DlT;ap4J_*Ig9bJTA&phPAUY>;J!%m*4eN*uU-L+lv zvT5z3N7uTVCv4FwPuMdLd9j}-?a*DucKidb@L`rGq4*eqJYml~d?s~`-KFsnY6d$p; zd5%5vkPo|g5{eHqmV*1ca66$u|Gz#@9+tfPdvi+JVpsEoEn4LXd*&f8_VdKLzdl_1 z1o-Ro1bgNoANKRanh!g9BCq@D^JItQW&X6~RZCpW6SiojyVx@i zd9j-(*|=o? z#!|5Es>eMpxF1%ZCr?OTa`(;s)48tZ30t(v6ZXtQUhL*cjyg2ZjDhfZvNkP2U!Oeb z3Lj>95{i$}$P@O=Lq6>0Nhm(dSPIS);-K-v?)p60DS5g2UvF($=4zg>MXNkv&phPC zZl2H>k+#++8KOX+Cr`P;hgqJ4;-d%d?_$q9BgyO?ap3tz#G~v_d$9F%VvN;a(zrp8UTne3<1)C_Yx< zj%oJHLq6>1i8UW)ECuVXOWyzsJN=v|G=EO=Qh(joyUzH;sr%~Z%lo)vP@%~ixf|JH zpkH|ILfl-g-dF!u@WP&X$jcwkI=<(waT-GOz4w>HSC4pf;D8%;d~ z-6dlL1dUpkWBI#b6c3GJn2r%Jzrkk`Mr};T?;>?9cmc*kOu;gr9AhA~aTv_YdVuu2 zW>sAH&z<|va*UTc@Pe)^ad!$#(qo9A%h_T-`h}@P(Reu@oLqwsd*&f8)xSJ*WaW6x zJ^J3a>6TSfj*0)Wu=ljJr(v<=Oic$jc^F4mrsHQ4 zc$+}H(0vng0n`hcb+M=HmKNvu8KY?9QIwd#_y@Ll6H$gKm>>4cLw*KjuG{A)A~4bS zJN%6s1}*Eiy5gUYe@Dfw_*y86b> zgLz(TfzEm97?!uV{Mq)dSBoj0O@7Ts>n!-5mO!0g%?U*==ol$n#D%8n3GiA{QjMb z#$B=TgIx<6g&0`=^neNDk@|XygCP0r+Ip<@Z8vVo%<hXV5U)=w#Q|+o0$&DyKCjPPRIUM0{lO+7F3XJ>IE(V%blu*D+PKIv)=w?^6S zVb46oP2cXpz;niJ74UmadVSo6hxa>&+wWw&>2)GoJRDxo6pR~t<{@s}9wEvjF4v0$ z;m^9|brl047!@jV0?&J;K0$i20DbtWiPY*o)aMkK>2<{EOR=2kcpt;NO0|UP*X!+`p;8?lA6VdYzfJihK4fN^p;qUPn2`aZmNY zKP3LyTV8aq{AazP*XwN23V-&@L;Ur69oI9s-lX|IC2qB%&jIJ^O~y^Hr`h5OzOM(o z(Y&?juS1H?T=n$C1*A7fPqSwp;-=Trz)6qWTN1bRC%$z3bBDj~(DQg0H@&`Pi(g9* zg7pS_<{@qt`j&LU+tTa38ChJgb#b>A#RT9G@!j7o}YI*lz_@xY!%2@jFbX>z@y-*)z#rn~!b9a8F9w|dl@?3stSh0tjq zO5AcPc76Z(0H^c@$D3ZKvBf|=m+lP0!yMKicRBslbACf)u5I3W~?M87%`A+lCByO|% zt=_QYSU2@8TMWe8(BUochV=$}<{@q&^zG*oH|2+!yPd@?Sl_b6{&e9JQ;DMC_88*r zANa6m9^w{4-+rMvEpiuaeaSi8%=9f=93wpl#*IDm5H~0FEzVlsfBU5?JecX*a6GVQ zQNlwc^ew$X5^r(fdY<$awd84f=+*5G=7E{MWs6pMz@9}3{_WSdrGz)@TfXYWD5N-Y z-NH=YvPCQ0*|R9YJyQA>w+eFHliv773wyD3$O{g}J?k{RzGaJ6__Jpo;vYiaek*Z% z_xxTjJ6CTqZhC#o7V~$NH@{OCJNALXl^aH4v07oq6JF31j2nCAA#P6UTPQ5OzHQb(5R8g5 z^}LzBWs6okuxC-iLnQRAe193&^Q5?J3e%zo4yJUf(|8~DCJecX*a6GVQQNlwc^ldu&Z}9|QuIC$&2L~kn84dd#%mXuh z%NDKjfIW*6{M)Z@XA$14Z~x;8?q>Qn9PaE{l;9pIeT!#TbKH~OpewXA{d{@KFz3pD zGkwbzt>T_N^ALZdzKtQgxF1sBJ54za(_Fw!uW#AnX>-b(U+y)t=$hC8U%ZOJ*rYeo z(SOUHd5BvGeXB^^9(g5e>rPkoEn9q|^*Z}S#WV($bsBr-A#O%}+u9FFap#k`Rp+jF z-&x#(^(|WrJO@C-jqmGV&pgDgojAzTY5W%uAXlI_I`_g$I~ez7`j#zP#XWoGA^t{v zYv_lJmvKAVf1V4t>GdsJRGuksu6X~7Nw@st*7`-bxK8!vG0?Z{nTNQA(6^l>ZYg(N zurIa7&HEGBV)wYFZY>u<*h<~OdV@Xl5I3W~ZS997y_O(xd+6NHj_&FxZo&GNEv8Ek zN|dAOJ_LLV5pRSYd*&f-Mt$3QpFHWSF4F7AmR>f{Io!Jj-PyWi$u+?(lJwrCai?3su7 zhtRjFGHy5C_>~K|>GdsJJmS;x=F-y(C){*z$|gT9vXR~>fxczWJjBhYZ%0y`aldz0 ziJP*jaO-WCxv6j2V&MMUDHmU@(`oFPhqxK_ZL3Zr{JKfpO7efY=Uzu~3)Z)6@o24r zMERlN#_a<3%tPFa`nGjEBk8N|61S#p$KLB4Zf5$HE$(jBw?^F9GY@fdQs3e_l3w5T zaD@jmeH)Gk_AE+xh=jgXGc{1plfLaK@!#6}cn9;qOy9Cat2|)Oq6Gg4>)SL}a5vMp z;c#cqq6GJd>DwZq*SCjB{O>q%w1aVPrf=C|#g+@Mc>kB*7OsDM=r^^>vfH`dWY0Xr zKZL&RC2?DN?DykY z>3d7u?j4df@IJ?M+BX>IK(gYG>P-jg8zmaq;zud(9k}_nnsLF@`$}Z_*xt`B3#U6noL@EAj^7pB4`s7yLA5W{FNU z^3SpVO2)<7Lx>CZ%)_|I`enwupKQZLX1qy+z8`kpS!K^vmHt85@$uf90i=wJYw(rg zg5EbG7#9atHl2Ck0Sz|#{?6lTGhRObZ)v|PyJ&tS%ZoCU67-%ReYCh=UML(F za}g7LT#1X3Z746;GY{i}%M0qN;8NL6?#buM5x$gvG(S?}e4#(b!TS1SiL<`GW{YO^ z_035E&pgDL>uWvEc5A;3QL4weuPZp4#dA2E*)tDuZfiWNC8NKa9_OQ6!I|S(U!R1- znLYCmXRc4$63h*kl!qa|+po1g0C9ByXU3)mt>Jxv^Mj2nCAA#P4yF9;gXc#JDNm|ZUj z#{+v7B|JppdV$G!#sM6KQ=WA&4~n6Ssl!D7{=*V!(f`3~3m1)FUwGS$%&oVs{B{iI z0ecoD__zOh!K?;gO}e5H^J87X-RycnINaH@D8W5a*9&yx9H`_SDDkhF|F(m1pCaR4 z|GW;iXchPDnTPm?xL$Ca#I4KLfxVrpHyJnm^#Zo|&IjeqIlXd=uUa(r`K#iDpz`84 zbm6jR9^z)aUSQB^$4lIPxTx=2`FA;~)2N))zfXiMPHDYAq2KNFHf%0u*)tDuGwRzs z%14(@^q@r1aQiFbZ8JXXnTNPJsndd_~vh?|8@qw8`*WW4?9;wu;IdCn=l zLD%m|r(KAf;-tYwwsX*fS4tyK2SMqK$9jWi_3IpzlxLye-H;QS@yu|Yo0x4-JYFq zk3RYB+HX>8&|f3cM0b%OW(z+G=}J){W{Gk!Q_K)OMK{q|^blQ9bjP(mna6HP5NqH$ zSMvPhSoqd)K+I{vutgaqs?-uY8?!gyW>X6 zSkZ!=hKM9_q8Nm#IY#*6l(?8UpHK0{_Q80_={9aB==;~$qAKX%Wr?E9D8`)oiP$p_c{cX18TuuMN!(@~ zwQq>CxCLK7W{cA$vxDV)73#XR_^@Xl;%2;loT=}Z94>J?VacQ%=WsLYmt>1iOT0MV z7&rFJL)@I~m&E(DRsnmyjz*l&#HdgcB=YxLoApbwMJpcIvnb&q68(~SXs@APSl=%> z(iQxRWxGg!{Wu){>{*oH-~N8dP+2Y(M%bO28<(ss6 z-;|SIy$n|wp*2S1`Z0UvA#Nu9lGK5vPK~|ZE9#%$^jhnJ5@qujXJHlHYquRDd5yLm zZnDuJ_IJaAi2~2L!&4sB=is2JRgZqHDOgs6=2opZ2T#YzkUewg@1kd&)AJzlKk$q8 zuM|H{#&bHKmG{;AD*6^+1Xih#_Y=u|5|#U#rapu`L4%I&(pS*HQw;a*%j;Sq7mWb4 z6j7VVH)N{gxAMdUEDauUH3TEBwC%A{o36nLQar82{Eu=OqZ6qA(-QDB1*|2&=;K3M zQv2Z9aA;%3qkT!8Puftgc>GRKw@XA@g{C6>N);(+hbjQ9pUT4|JS0hWScT-H3HK$i zXCCsgGA;Lxo@i?8`QZLJV#p33W-MhQdTJ0mK@;&oybwQY<+|MJ2Tv+Q)H=gUrR3#2 z49Da=VT-&VI8WF!4|%blC&K}w83Tu$CpWsnhgqJ4;^R1se__u&Y&gw_S)PRA zqXhK{d*&e@_VdJ=4>Oi*p4{6ql&L;D+lk zD9#Mv;POTtP&P$z!{w>CFEj2-1jl`egQN2D{!?{Nx~i$_E|RXQp6^TL>r{7b=hXk7 zbMJp|)vYS5nmuM-Riy}?d~!h}=8A|BB2@|Aq}{($d$qjO?t^q3paX#p1UeAtK%fJG z4g@+7=s=(Yfer*Z5a>Xl1Az_%!Xi*uH9LQ9%>uOXwaIGUNUPB)urN)C+{tn15NO{- zc2=&5`$m71qvM45s*@1k`-S*4R=-Aww0I#-YlGDn!tApSHfcHh01r_IR#F@}md0oXCe5Dhl z>A^(5C=^RYrKl10`2Bl);^xbh52g4;gQyl&Sh*N0{K^n97n{|Kh2m5(2!D&kEG((S zHZ$=(6Z3gkQYUKht5(d$mYMini{*pFd01D2dA1mgzg&D9EQaHMWvltchF?CVrDY$u zV%}=Y;zX%9A4jfNEh514T3UV!znIZ)m&J<`)%sG{s=!utSUVJ-F&vC%{vlROgtOIH zDMWIzP~xV@csQ|VtQaensC!~nib#pcixCsB*#&ZoA7*yafi+GPVBHD$#8Jr6VNQH2 zK}^CSW&*N#IQ@C53j|$qtcdd^$B4LCrHkl~1sv(UI(HJkk( z{NNC;c)>5mV?*MqO5I4%Mn2I8Kl;cxEnF5W%2X~EsGIo6pd!-ogHnK|d;7+TVh~8Q z5JY57p(FXlarnWRGylbSaVA!u58i5lV+C-hP5q*0EMPCekL%?TN3QMyqw`cu^Ra0q z93YA+u#E~2Wf8^HsAUNT26-pI6DPXHC<%y`Kv^;-CP|0(SvVlMOrB`S`;rqx0{OB6 zJ8^-q@w+Y}U)_xSs8T&)Sf^pTF=J$gCw!YA@^Hj@I8M=0k2BSdo7_pH#8II@v=4c* zt02?bk?5Q(c1ogfX=$m34K74>ZtIK_Xiyzrq8ifZEh0a_rXXJu-s~)6b`~~Vj6dl` zyoisZqLrv!F(Mua&z0CR0Lh(2e2kK+h7%RWG+mAm&BvBA5lA(v-$YufP-2yqmWkMa zN;M*GWKFyZR5e0@oYQi}=T~|ntnFc^>{s1)+Zi4$8&y z`pkGNC8CzVR}EAlj=>gjTtd$tA1BVnS&}_@rir3^Ol)#j(JxkX!=?yE^@G?&6{ek6 zyyz+7MOUmv(Bm6@;zfeGow^YJ#^6_~NWn)De^2g5TW0@`4u~neu{r&%T~tzjgS4th zMgjQm@!F~h@Y7giWr)dGRs*hU5&Vlq#v$jFfwNzN^>l``R$u_u?Gz9e`{`;L(No*b~+HE7=gU`dq(p%vKQr|q93_YdLqc>eqWP_a|i60{5|on@8I9gzTlbty_am-5c64rz%lEQLo?xPb!*<--X}?o z=4q-Gs>ZU!C~>;1V$)TU>)^RoUY&-#&O+7=#ynm1N-MFoc%!vv27F%v+$!L69jdc%807$?Ts6jW@IM>)3|En&a|v}+KIZ^-g|IpkWlR;$ zrUG_}%O&_uM+#o2-<#HlJ5y~NE3`(bl_%xIEa15iUR0T&NutoWO*(#82#S0?pZeGg zoZVR}ssZgaTGe3`&Gphc2?#oOVw3zmbPxSmYrF+2K|+jc+e72cxZR=036zXw;CvxG z7>zPgE6FIMSmIQ|MlJG>$~W@SY(M?cX`4fF3q+v#dVM61LKIa3Bd#O$>og1|0NE;a z{X!K#@|#d4^`gn468KD7dX^f`c&_{UNj$rhH@~sx^kf&Ty$Q8t1L$u9iXe_A%PgvW^(5 zQpNmiHAd=X17?8@t8F=g(Fal|R1$=ad@cH!Ir5nIqtY_C1P!&F-nw>Fs)ZJ}BCbDn zFvg*_)E}2}jQtCJUZa39M^u18@}U4;k^VdM?CkAf^e4DZF~&~tTI0S&h`S2>Q5*&% zVq9n3@6gy_OoERRYD?oWCj${Gk*Q?X_6k0h*%roQ9NNO<8g)F*R8wE0QIh9?17gzN z_C_08JwVDxYtxN}rV5n*`sKVIt+kF7iW99~ss#&+p~3>{y8*Equ3jG1dKQ8Dh@xN1 z06JE+F4_j51fcSa+G}-a8cl=6KsQnjl+QGHL#59w=!N*Sx3Y+!7j%_6%xV>Su&ytsC#qHgN$;*HYOwu9IySt))!A zsK%}oKTO)R=*DNp6|GvZ?TZz@ioV)D>1t%~K8ec8wA?%Qqq$4XDRjt*G<^EvLyde| zGECL^xtLQ!k^3GppzOEbZ^)^UjqFiC&cDnd{{>UEgkt*sGz&iF&ajHcIu z`cch#BdBYXdm~cuJ-9alof+yei*N$e8_@&vO3@G7p+l)Ros>E?vPJ5@q^U&N`uRDS z-vktP!U%gN&?P{VpPaGNR}nd0?r)m9>q)d_^}HNu%1bVMGv_5si~xI?YP)Q>+M3No z8)Gn?k(^SA2jYWxxkoN!bhK_0?$?eKAM+$16VZq#4l#w~qY3>*?3ssr*v}JdKFnAO z&J(TAt3aP8_i8AKr?Wd>U+!w2u*K6}yZ_1^A1mUa{WO0~;XGl_JmkfGo=_*783O|O z5js!$3q_wNe|LosvpfmKM|_G9?3ssr*v}JdKFnAO&J)>Y*XPM*$;-T{7v5FjYM!t~ ztNMgJ^N<(&c`~fcd2+TGug{Y$uJB=&C!zRgg4kitJmkZEo>=o?Cr{L?I6k$0XWez5 z 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 G;Qs+tKvaDI literal 0 HcmV?d00001 diff --git a/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4701.evtx.golden.json b/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4701.evtx.golden.json new file mode 100644 index 00000000000..229ab491f58 --- /dev/null +++ b/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4701.evtx.golden.json @@ -0,0 +1,63 @@ +[ + { + "@timestamp": "2020-04-01T14:35:04.7030004Z", + "event": { + "action": "scheduled-task-disabled", + "category": "iam", + "code": 4701, + "kind": "event", + "module": "security", + "outcome": "success", + "provider": "Microsoft-Windows-Security-Auditing", + "type": [ + "change", + "admin" + ] + }, + "host": { + "name": "DC_TEST2k12.TEST.SAAS" + }, + "log": { + "level": "information" + }, + "related": { + "user": "at_adm" + }, + "user": { + "domain": "TEST", + "id": "S-1-5-21-1717121054-434620538-60925301-2794", + "name": "at_adm" + }, + "winlog": { + "api": "wineventlog", + "channel": "Security", + "computer_name": "DC_TEST2k12.TEST.SAAS", + "event_data": { + "SubjectDomainName": "TEST", + "SubjectLogonId": "0x60d1ca6", + "SubjectUserName": "at_adm", + "SubjectUserSid": "S-1-5-21-1717121054-434620538-60925301-2794", + "TaskContent": "\u003c?xml version=\"1.0\" encoding=\"UTF-16\"?\u003e\n\u003cTask version=\"1.2\" xmlns=\"http://schemas.microsoft.com/windows/2004/02/mit/task\"\u003e\n \u003cRegistrationInfo\u003e\n \u003cDate\u003e2020-04-01T16:34:34.574883\u003c/Date\u003e\n \u003cAuthor\u003eTEST\\at_adm\u003c/Author\u003e\n \u003c/RegistrationInfo\u003e\n \u003cTriggers\u003e\n \u003cTimeTrigger\u003e\n \u003cStartBoundary\u003e2020-04-01T16:33:41.3123848\u003c/StartBoundary\u003e\n \u003cEnabled\u003etrue\u003c/Enabled\u003e\n \u003c/TimeTrigger\u003e\n \u003c/Triggers\u003e\n \u003cPrincipals\u003e\n \u003cPrincipal id=\"Author\"\u003e\n \u003cRunLevel\u003eLeastPrivilege\u003c/RunLevel\u003e\n \u003cUserId\u003eTEST\\at_adm\u003c/UserId\u003e\n \u003cLogonType\u003eInteractiveToken\u003c/LogonType\u003e\n \u003c/Principal\u003e\n \u003c/Principals\u003e\n \u003cSettings\u003e\n \u003cMultipleInstancesPolicy\u003eIgnoreNew\u003c/MultipleInstancesPolicy\u003e\n \u003cDisallowStartIfOnBatteries\u003etrue\u003c/DisallowStartIfOnBatteries\u003e\n \u003cStopIfGoingOnBatteries\u003etrue\u003c/StopIfGoingOnBatteries\u003e\n \u003cAllowHardTerminate\u003etrue\u003c/AllowHardTerminate\u003e\n \u003cStartWhenAvailable\u003efalse\u003c/StartWhenAvailable\u003e\n \u003cRunOnlyIfNetworkAvailable\u003efalse\u003c/RunOnlyIfNetworkAvailable\u003e\n \u003cIdleSettings\u003e\n \u003cStopOnIdleEnd\u003etrue\u003c/StopOnIdleEnd\u003e\n \u003cRestartOnIdle\u003efalse\u003c/RestartOnIdle\u003e\n \u003c/IdleSettings\u003e\n \u003cAllowStartOnDemand\u003etrue\u003c/AllowStartOnDemand\u003e\n \u003cEnabled\u003efalse\u003c/Enabled\u003e\n \u003cHidden\u003efalse\u003c/Hidden\u003e\n \u003cRunOnlyIfIdle\u003efalse\u003c/RunOnlyIfIdle\u003e\n \u003cWakeToRun\u003efalse\u003c/WakeToRun\u003e\n \u003cExecutionTimeLimit\u003eP3D\u003c/ExecutionTimeLimit\u003e\n \u003cPriority\u003e7\u003c/Priority\u003e\n \u003c/Settings\u003e\n \u003cActions Context=\"Author\"\u003e\n \u003cExec\u003e\n \u003cCommand\u003e%windir%\\system32\\calc.exe\u003c/Command\u003e\n \u003c/Exec\u003e\n \u003cExec\u003e\n \u003cCommand\u003e%windir%\\system32\\mspaint.exe\u003c/Command\u003e\n \u003c/Exec\u003e\n \u003c/Actions\u003e\n\u003c/Task\u003e", + "TaskName": "\\test1" + }, + "event_id": 4701, + "keywords": [ + "Audit Success" + ], + "logon": { + "id": "0x60d1ca6" + }, + "opcode": "Info", + "process": { + "pid": 496, + "thread": { + "id": 3684 + } + }, + "provider_guid": "{54849625-5478-4994-a5ba-3e3b0328c30d}", + "provider_name": "Microsoft-Windows-Security-Auditing", + "record_id": 5043789, + "task": "Other Object Access Events" + } + } +] \ No newline at end of file diff --git a/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4702.evtx b/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4702.evtx new file mode 100644 index 0000000000000000000000000000000000000000..0f888825b632621620ca6a106d5baa526b12efd2 GIT binary patch literal 69632 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 literal 0 HcmV?d00001 diff --git a/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4768.evtx.golden.json b/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4768.evtx.golden.json new file mode 100644 index 00000000000..4cddbdcea1f --- /dev/null +++ b/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4768.evtx.golden.json @@ -0,0 +1,71 @@ +[ + { + "@timestamp": "2020-04-01T08:45:44.1717416Z", + "event": { + "action": "kerberos-authentication-ticket-requested", + "category": "authentication", + "code": 4768, + "kind": "event", + "module": "security", + "outcome": "success", + "provider": "Microsoft-Windows-Security-Auditing", + "type": "start" + }, + "host": { + "name": "DC_TEST2k12.TEST.SAAS" + }, + "log": { + "level": "information" + }, + "related": { + "user": "at_adm" + }, + "source": { + "ip": "::1", + "port": 0 + }, + "user": { + "domain": "TEST.SAAS", + "name": "at_adm" + }, + "winlog": { + "api": "wineventlog", + "channel": "Security", + "computer_name": "DC_TEST2k12.TEST.SAAS", + "event_data": { + "PreAuthType": "2", + "ServiceName": "krbtgt", + "ServiceSid": "S-1-5-21-1717121054-434620538-60925301-502", + "Status": "0x0", + "StatusDescription": "KDC_ERR_NONE", + "TargetDomainName": "TEST.SAAS", + "TargetSid": "S-1-5-21-1717121054-434620538-60925301-2794", + "TargetUserName": "at_adm", + "TicketEncryptionType": "0x12", + "TicketEncryptionTypeDescription": "AES256-CTS-HMAC-SHA1-96", + "TicketOptions": "0x40810010", + "TicketOptionsDescription": [ + "Renewable-ok", + "Name-canonicalize", + "Renewable", + "Forwardable" + ] + }, + "event_id": 4768, + "keywords": [ + "Audit Success" + ], + "opcode": "Info", + "process": { + "pid": 496, + "thread": { + "id": 2868 + } + }, + "provider_guid": "{54849625-5478-4994-a5ba-3e3b0328c30d}", + "provider_name": "Microsoft-Windows-Security-Auditing", + "record_id": 5040235, + "task": "Kerberos Authentication Service" + } + } +] \ No newline at end of file diff --git a/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4769.evtx b/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4769.evtx new file mode 100644 index 0000000000000000000000000000000000000000..ddc9436667ea5e288b83da2a415ad2c66c546d23 GIT binary patch literal 69632 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 literal 0 HcmV?d00001 diff --git a/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4769.evtx.golden.json b/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4769.evtx.golden.json new file mode 100644 index 00000000000..0e17ff381f6 --- /dev/null +++ b/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4769.evtx.golden.json @@ -0,0 +1,70 @@ +[ + { + "@timestamp": "2020-04-01T08:45:44.1717416Z", + "event": { + "action": "kerberos-service-ticket-requested", + "category": "authentication", + "code": 4769, + "kind": "event", + "module": "security", + "outcome": "success", + "provider": "Microsoft-Windows-Security-Auditing", + "type": "start" + }, + "host": { + "name": "DC_TEST2k12.TEST.SAAS" + }, + "log": { + "level": "information" + }, + "related": { + "user": "at_adm" + }, + "source": { + "ip": "::1", + "port": 0 + }, + "user": { + "domain": "TEST.SAAS", + "name": "at_adm" + }, + "winlog": { + "api": "wineventlog", + "channel": "Security", + "computer_name": "DC_TEST2k12.TEST.SAAS", + "event_data": { + "LogonGuid": "{46f85809-d26e-96f5-fbf2-73bd761a2d68}", + "ServiceName": "DC_TEST2K12$", + "ServiceSid": "S-1-5-21-1717121054-434620538-60925301-1110", + "Status": "0x0", + "StatusDescription": "KDC_ERR_NONE", + "TargetDomainName": "TEST.SAAS", + "TargetUserName": "at_adm@TEST.SAAS", + "TicketEncryptionType": "0x12", + "TicketEncryptionTypeDescription": "AES256-CTS-HMAC-SHA1-96", + "TicketOptions": "0x40810000", + "TicketOptionsDescription": [ + "Name-canonicalize", + "Renewable", + "Forwardable" + ], + "TransmittedServices": "-" + }, + "event_id": 4769, + "keywords": [ + "Audit Success" + ], + "opcode": "Info", + "process": { + "pid": 496, + "thread": { + "id": 2868 + } + }, + "provider_guid": "{54849625-5478-4994-a5ba-3e3b0328c30d}", + "provider_name": "Microsoft-Windows-Security-Auditing", + "record_id": 5040236, + "task": "Kerberos Service Ticket Operations" + } + } +] \ No newline at end of file diff --git a/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4770.evtx b/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4770.evtx new file mode 100644 index 0000000000000000000000000000000000000000..8a2f171f64a97b73325c2441a74a29bdd0b059a1 GIT binary patch literal 69632 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 literal 0 HcmV?d00001 diff --git a/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4770.evtx.golden.json b/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4770.evtx.golden.json new file mode 100644 index 00000000000..f41ce8ef476 --- /dev/null +++ b/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4770.evtx.golden.json @@ -0,0 +1,65 @@ +[ + { + "@timestamp": "2020-04-01T07:32:55.0104462Z", + "event": { + "action": "kerberos-service-ticket-renewed", + "category": "authentication", + "code": 4770, + "kind": "event", + "module": "security", + "outcome": "success", + "provider": "Microsoft-Windows-Security-Auditing", + "type": "start" + }, + "host": { + "name": "DC_TEST2k12.TEST.SAAS" + }, + "log": { + "level": "information" + }, + "related": { + "user": "DC_TEST2K12$" + }, + "source": { + "ip": "::1", + "port": 0 + }, + "user": { + "domain": "TEST.SAAS", + "name": "DC_TEST2K12$" + }, + "winlog": { + "api": "wineventlog", + "channel": "Security", + "computer_name": "DC_TEST2k12.TEST.SAAS", + "event_data": { + "ServiceName": "krbtgt", + "ServiceSid": "S-1-5-21-1717121054-434620538-60925301-502", + "TargetDomainName": "TEST.SAAS", + "TargetUserName": "DC_TEST2K12$@TEST.SAAS", + "TicketEncryptionType": "0x12", + "TicketEncryptionTypeDescription": "AES256-CTS-HMAC-SHA1-96", + "TicketOptions": "0x10002", + "TicketOptionsDescription": [ + "Renew", + "Name-canonicalize" + ] + }, + "event_id": 4770, + "keywords": [ + "Audit Success" + ], + "opcode": "Info", + "process": { + "pid": 496, + "thread": { + "id": 4468 + } + }, + "provider_guid": "{54849625-5478-4994-a5ba-3e3b0328c30d}", + "provider_name": "Microsoft-Windows-Security-Auditing", + "record_id": 5039598, + "task": "Kerberos Service Ticket Operations" + } + } +] \ No newline at end of file diff --git a/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4771.evtx b/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4771.evtx new file mode 100644 index 0000000000000000000000000000000000000000..d3e8a80a371d9a9cc64caed88bbac63ac6f58cb5 GIT binary patch literal 69632 zcmeI$S!`5Q9LMqhovqU;Go1=GsEB}qLec^X%Hmc_i-bj#Dv}sYp)J&Iq@5~?54god z6B8398WN)@G5TP9F&bigF$N!u`lN|ZKB*7Jh)XolWc>Zly_eF8;`aExliQhl&i|g} zbCx@uuHk|1!QpfHJ#|G5wfKmY**5I_I{1Q0*~0R#|0 z0D-d#bPW%5?i(G_|M>CoXYYqRTR#fSZ8Cdj&#w#P=zss$>pAy{1+Sc5!i5F1ZyU^h z$eVqgJ3U9SMYC)4GsHhVK03=iM^vXD7phJ_eoo@wP0~H>h|&gTwC6fq|C0Pja{i1u zMv}7p-dB^)TNT@_GLO$F$7BAlV@dj>&EF{Sgq;0YwoGH%=)dvAN8`)mZ7Ul-jhds# zW!WwjkK?oQ)@Aotzl~Z=*N^Ee z+?`~;)10>hHe$odoKQwST5bCjRoc*pQ?Uh+Epg1*T2+QrQh)b} zS30664;F2EsQn(*)u*U&84!RabFD`YBJE99ak zTcm_^rq69`u-lTbK}n4&FH>ogEBTx7xR*N+I28z zJz*?{LP*9djBJ4}dFQqq-^G5RQJ|WSt?*+c6wklCs{RxbE&`g_lm2tV1Q% z)Nr3IHT5(ux6)wE1>Y2W*jK;bVqSGzlK1IPZYuk9sn11HU;>u<^#8Wv>2d9G!?vY@mk5`qgGApPPb!o7Av|NX5 zrT(^`N@-Vmhh96(^>0w2>9+hrdr_%AP?KtFjB0j~A`0maJ+P%Jb+At7xrOv=-%R((99-MJt84kf=Z1x~f{U&d7FV7b+#Y<|Qvs zNA=M2kAGS6o$@;^s}EGrZ*Oghv$m?8)3>ZP+o`lsjrEu&|Ae*up-*k<{iO1}haS1t z79W13=RnWXn~oj7c767;_sTOB;tl%4#TRv?Te*K;d!psFA0Mu6UUv0^&mGbN@hFW` zO&|Tq>c@tMd=jM_^oB5@dzJ2cUzgtfN$Ydz@v)o+&KI8NF4kG_Jue?sG;Y1Z-YfFj zDf)B|%#;Q0!?-jl>w>8}8>ij5Lp|-YOgXaI9^aP4hrx|&Y6?!{VLAC^o~k1g*Re9W z=i;e4%BsWtheGRoUvg1jCljhGYjeCVkN1AIPTl8^0`F=~D-CdG89zkT6eUzVA2A&x&|Ydn5?*mr7*Y=|STNZLJZG&)uHxZ1r* zWlpH|el;akjPJZExzk4_8ON$tWqha4Eni*!l-Z^dMr?OT$&?%aM!mj|#P{qErTB9{ zQ`S<-RXy2gdV2Vg_aU0UV%Na^ zAAEJ*e;PY_Uc@&)3-VQVlbf|_}7N2J$SvZ(-rzsu})9T__a>E&f2i= ztkffXjpA49T&34)yF#xk!-HM7`S&3X1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{ z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 U009ILKmY**5I_I{1Q0O$9W()P2><{9 literal 0 HcmV?d00001 diff --git a/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4771.evtx.golden.json b/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4771.evtx.golden.json new file mode 100644 index 00000000000..7321a262d93 --- /dev/null +++ b/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4771.evtx.golden.json @@ -0,0 +1,66 @@ +[ + { + "@timestamp": "2020-03-31T07:50:27.1681182Z", + "event": { + "action": "kerberos-preauth-failed", + "category": "authentication", + "code": 4771, + "kind": "event", + "module": "security", + "outcome": "failure", + "provider": "Microsoft-Windows-Security-Auditing", + "type": "start" + }, + "host": { + "name": "DC_TEST2k12.TEST.SAAS" + }, + "log": { + "level": "information" + }, + "related": { + "user": "MPUIG" + }, + "source": { + "ip": "192.168.5.44", + "port": 53366 + }, + "user": { + "name": "MPUIG" + }, + "winlog": { + "api": "wineventlog", + "channel": "Security", + "computer_name": "DC_TEST2k12.TEST.SAAS", + "event_data": { + "PreAuthType": "0", + "ServiceName": "krbtgt/test.saas", + "Status": "0x12", + "StatusDescription": "KDC_ERR_CLIENT_REVOKED", + "TargetSid": "S-1-5-21-1717121054-434620538-60925301-3057", + "TargetUserName": "MPUIG", + "TicketOptions": "0x40810010", + "TicketOptionsDescription": [ + "Renewable-ok", + "Name-canonicalize", + "Renewable", + "Forwardable" + ] + }, + "event_id": 4771, + "keywords": [ + "Audit Failure" + ], + "opcode": "Info", + "process": { + "pid": 496, + "thread": { + "id": 4552 + } + }, + "provider_guid": "{54849625-5478-4994-a5ba-3e3b0328c30d}", + "provider_name": "Microsoft-Windows-Security-Auditing", + "record_id": 5027836, + "task": "Kerberos Authentication Service" + } + } +] \ No newline at end of file diff --git a/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4776.evtx b/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4776.evtx new file mode 100644 index 0000000000000000000000000000000000000000..e9017eff8582028f397b044396d48c2601b5b9d8 GIT binary patch literal 69632 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 DZwu3u literal 0 HcmV?d00001 diff --git a/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4776.evtx.golden.json b/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4776.evtx.golden.json new file mode 100644 index 00000000000..23a60fcb72e --- /dev/null +++ b/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4776.evtx.golden.json @@ -0,0 +1,58 @@ +[ + { + "@timestamp": "2020-04-01T08:45:42.1873153Z", + "event": { + "action": "credential-validated", + "category": "authentication", + "code": 4776, + "kind": "event", + "module": "security", + "outcome": "success", + "provider": "Microsoft-Windows-Security-Auditing", + "type": "start" + }, + "host": { + "name": "DC_TEST2k12.TEST.SAAS" + }, + "log": { + "level": "information" + }, + "related": { + "user": "at_adm" + }, + "user": { + "name": "at_adm" + }, + "winlog": { + "api": "wineventlog", + "channel": "Security", + "computer_name": "DC_TEST2k12.TEST.SAAS", + "event_data": { + "PackageName": "MICROSOFT_AUTHENTICATION_PACKAGE_V1_0", + "Status": "0x0", + "TargetUserName": "at_adm", + "Workstation": "EQP01777" + }, + "event_id": 4776, + "keywords": [ + "Audit Success" + ], + "logon": { + "failure": { + "status": "Status OK." + } + }, + "opcode": "Info", + "process": { + "pid": 496, + "thread": { + "id": 1864 + } + }, + "provider_guid": "{54849625-5478-4994-a5ba-3e3b0328c30d}", + "provider_name": "Microsoft-Windows-Security-Auditing", + "record_id": 5040222, + "task": "Credential Validation" + } + } +] \ No newline at end of file diff --git a/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4778.evtx b/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4778.evtx new file mode 100644 index 0000000000000000000000000000000000000000..5d8653c23ffdcb783a14be3c8e0bb1cddd1c4f39 GIT binary patch literal 69632 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 literal 0 HcmV?d00001 diff --git a/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4778.evtx.golden.json b/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4778.evtx.golden.json new file mode 100644 index 00000000000..f6723e5bada --- /dev/null +++ b/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4778.evtx.golden.json @@ -0,0 +1,63 @@ +[ + { + "@timestamp": "2020-04-05T16:33:32.3888253Z", + "event": { + "action": "session-reconnected", + "category": "authentication", + "code": 4778, + "kind": "event", + "module": "security", + "outcome": "success", + "provider": "Microsoft-Windows-Security-Auditing", + "type": "start" + }, + "host": { + "name": "DC_TEST2k12.TEST.SAAS" + }, + "log": { + "level": "information" + }, + "related": { + "user": "at_adm" + }, + "source": { + "domain": "EQP01777", + "ip": "10.100.150.9" + }, + "user": { + "domain": "TEST", + "name": "at_adm" + }, + "winlog": { + "api": "wineventlog", + "channel": "Security", + "computer_name": "DC_TEST2k12.TEST.SAAS", + "event_data": { + "AccountDomain": "TEST", + "AccountName": "at_adm", + "ClientAddress": "10.100.150.9", + "ClientName": "EQP01777", + "LogonID": "0x76fea87", + "SessionName": "RDP-Tcp#127" + }, + "event_id": 4778, + "keywords": [ + "Audit Success" + ], + "logon": { + "id": "0x76fea87" + }, + "opcode": "Info", + "process": { + "pid": 496, + "thread": { + "id": 4184 + } + }, + "provider_guid": "{54849625-5478-4994-a5ba-3e3b0328c30d}", + "provider_name": "Microsoft-Windows-Security-Auditing", + "record_id": 5101675, + "task": "Other Logon/Logoff Events" + } + } +] \ No newline at end of file diff --git a/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4779.evtx b/x-pack/winlogbeat/module/security/test/testdata/security-windows2012_4779.evtx new file mode 100644 index 0000000000000000000000000000000000000000..29bc4f77453f2b25e03e71e5638390832019f929 GIT binary patch literal 69632 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 Date: Mon, 4 May 2020 10:06:45 -0500 Subject: [PATCH 090/116] [Filebeat] Improve ECS categorization field mappings in system module (#18065) * Improve ECS categorization field mappings in system module - auth + event.kind + event.category (make array) + event.type (make array) + capture useradd, usermod, userdel + capture groupadd, groupmod, groupdel + related.ip + related.user - syslog + event.kind Closes #16031 --- CHANGELOG.next.asciidoc | 1 + .../module/system/auth/ingest/pipeline.json | 121 ---- .../module/system/auth/ingest/pipeline.yml | 145 ++++ filebeat/module/system/auth/manifest.yml | 2 +- .../test/auth-ubuntu1204.log-expected.json | 175 +++++ .../auth/test/secure-rhel7.log-expected.json | 625 ++++++++++++++++-- .../system/auth/test/test.log-expected.json | 101 ++- .../auth/test/timestamp.log-expected.json | 2 + .../module/system/syslog/ingest/pipeline.json | 71 -- .../module/system/syslog/ingest/pipeline.yml | 57 ++ filebeat/module/system/syslog/manifest.yml | 2 +- .../darwin-syslog-sample.log-expected.json | 3 + .../test/darwin-syslog.log-expected.json | 100 +++ .../syslog/test/suse-syslog.log-expected.json | 2 + .../syslog/test/tz-offset.log-expected.json | 3 + 15 files changed, 1138 insertions(+), 272 deletions(-) delete mode 100644 filebeat/module/system/auth/ingest/pipeline.json create mode 100644 filebeat/module/system/auth/ingest/pipeline.yml delete mode 100644 filebeat/module/system/syslog/ingest/pipeline.json create mode 100644 filebeat/module/system/syslog/ingest/pipeline.yml diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index dcea2a1c3e0..87fc9ce4615 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -287,6 +287,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Improve ECS categorization field mappings in redis module. {issue}16179[16179] {pull}17918[17918] - Improve ECS categorization field mappings for zeek module. {issue}16029[16029] {pull}17738[17738] - Improve ECS categorization field mappings for netflow module. {issue}16135[16135] {pull}18108[18108] +- Improve ECS categorization field mappings in system module. {issue}16031[16031] {pull}18065[18065] *Heartbeat* diff --git a/filebeat/module/system/auth/ingest/pipeline.json b/filebeat/module/system/auth/ingest/pipeline.json deleted file mode 100644 index 8df0a77e582..00000000000 --- a/filebeat/module/system/auth/ingest/pipeline.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "description": "Pipeline for parsing system authorisation/secure logs", - "processors": [ - { - "grok": { - "field": "message", - "ignore_missing": true, - "pattern_definitions" : { - "GREEDYMULTILINE" : "(.|\n)*", - "TIMESTAMP": "(?:%{TIMESTAMP_ISO8601}|%{SYSLOGTIMESTAMP})" - }, - "patterns": [ - "%{TIMESTAMP:system.auth.timestamp} %{SYSLOGHOST:host.hostname} %{DATA:process.name}(?:\\[%{POSINT:process.pid:long}\\])?: %{DATA:system.auth.ssh.event} %{DATA:system.auth.ssh.method} for (invalid user )?%{DATA:user.name} from %{IPORHOST:source.ip} port %{NUMBER:source.port:long} ssh2(: %{GREEDYDATA:system.auth.ssh.signature})?", - "%{TIMESTAMP:system.auth.timestamp} %{SYSLOGHOST:host.hostname} %{DATA:process.name}(?:\\[%{POSINT:process.pid:long}\\])?: %{DATA:system.auth.ssh.event} user %{DATA:user.name} from %{IPORHOST:source.ip}", - "%{TIMESTAMP:system.auth.timestamp} %{SYSLOGHOST:host.hostname} %{DATA:process.name}(?:\\[%{POSINT:process.pid:long}\\])?: Did not receive identification string from %{IPORHOST:system.auth.ssh.dropped_ip}", - "%{TIMESTAMP:system.auth.timestamp} %{SYSLOGHOST:host.hostname} %{DATA:process.name}(?:\\[%{POSINT:process.pid:long}\\])?: \\s*%{DATA:user.name} :( %{DATA:system.auth.sudo.error} ;)? TTY=%{DATA:system.auth.sudo.tty} ; PWD=%{DATA:system.auth.sudo.pwd} ; USER=%{DATA:system.auth.sudo.user} ; COMMAND=%{GREEDYDATA:system.auth.sudo.command}", - "%{TIMESTAMP:system.auth.timestamp} %{SYSLOGHOST:host.hostname} %{DATA:process.name}(?:\\[%{POSINT:process.pid:long}\\])?: new group: name=%{DATA:group.name}, GID=%{NUMBER:group.id}", - "%{TIMESTAMP:system.auth.timestamp} %{SYSLOGHOST:host.hostname} %{DATA:process.name}(?:\\[%{POSINT:process.pid:long}\\])?: new user: name=%{DATA:user.name}, UID=%{NUMBER:user.id}, GID=%{NUMBER:group.id}, home=%{DATA:system.auth.useradd.home}, shell=%{DATA:system.auth.useradd.shell}$", - "%{TIMESTAMP:system.auth.timestamp} %{SYSLOGHOST:host.hostname}? %{DATA:process.name}(?:\\[%{POSINT:process.pid:long}\\])?: %{GREEDYMULTILINE:system.auth.message}" - ] - } - }, - { - "remove": { - "field": "message" - } - }, - { - "rename": { - "field": "system.auth.message", - "target_field": "message", - "ignore_missing": true - } - }, - { - "set": { - "field": "source.ip", - "value": "{{system.auth.ssh.dropped_ip}}", - "if": "ctx.containsKey('system') && ctx.system.containsKey('auth') && ctx.system.auth.containsKey('ssh') && ctx.system.auth.ssh.containsKey('dropped_ip')" - } - }, - { - "date": { - "if": "ctx.event.timezone == null", - "field": "system.auth.timestamp", - "target_field": "@timestamp", - "formats": [ - "MMM d HH:mm:ss", - "MMM dd HH:mm:ss", - "ISO8601" - ], - "on_failure": [{"append": {"field": "error.message", "value": "{{ _ingest.on_failure_message }}"}}] - } - }, - { - "date": { - "if": "ctx.event.timezone != null", - "field": "system.auth.timestamp", - "target_field": "@timestamp", - "formats": [ - "MMM d HH:mm:ss", - "MMM dd HH:mm:ss", - "ISO8601" - ], - "timezone": "{{ event.timezone }}", - "on_failure": [{"append": {"field": "error.message", "value": "{{ _ingest.on_failure_message }}"}}] - } - }, - { - "remove": { - "field": "system.auth.timestamp" - } - }, - { - "geoip": { - "field": "source.ip", - "target_field": "source.geo", - "ignore_failure": true - } - }, - { - "geoip": { - "database_file": "GeoLite2-ASN.mmdb", - "field": "source.ip", - "target_field": "source.as", - "properties": [ - "asn", - "organization_name" - ], - "ignore_missing": true - } - }, - { - "rename": { - "field": "source.as.asn", - "target_field": "source.as.number", - "ignore_missing": true - } - }, - { - "rename": { - "field": "source.as.organization_name", - "target_field": "source.as.organization.name", - "ignore_missing": true - } - }, - { - "script": { - "lang": "painless", - "ignore_failure": true, - "source": "if (ctx.system.auth.ssh.event == \"Accepted\") { if (!ctx.containsKey(\"event\")) { ctx.event = [:]; } ctx.event.type = \"authentication_success\"; ctx.event.category = \"authentication\"; ctx.event.action = \"ssh_login\"; ctx.event.outcome = \"success\"; } else if (ctx.system.auth.ssh.event == \"Invalid\" || ctx.system.auth.ssh.event == \"Failed\") { if (!ctx.containsKey(\"event\")) { ctx.event = [:]; } ctx.event.type = \"authentication_failure\"; ctx.event.category = \"authentication\"; ctx.event.action = \"ssh_login\"; ctx.event.outcome = \"failure\"; }" - } - } - ], - "on_failure" : [{ - "set" : { - "field" : "error.message", - "value" : "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/filebeat/module/system/auth/ingest/pipeline.yml b/filebeat/module/system/auth/ingest/pipeline.yml new file mode 100644 index 00000000000..2cdd507f8cc --- /dev/null +++ b/filebeat/module/system/auth/ingest/pipeline.yml @@ -0,0 +1,145 @@ +description: Pipeline for parsing system authorisation/secure logs +processors: +- grok: + field: message + ignore_missing: true + pattern_definitions: + GREEDYMULTILINE: |- + (.| + )* + TIMESTAMP: (?:%{TIMESTAMP_ISO8601}|%{SYSLOGTIMESTAMP}) + patterns: + - '%{TIMESTAMP:system.auth.timestamp} %{SYSLOGHOST:host.hostname} %{DATA:process.name}(?:\[%{POSINT:process.pid:long}\])?: + %{DATA:system.auth.ssh.event} %{DATA:system.auth.ssh.method} for (invalid user + )?%{DATA:user.name} from %{IPORHOST:source.ip} port %{NUMBER:source.port:long} + ssh2(: %{GREEDYDATA:system.auth.ssh.signature})?' + - '%{TIMESTAMP:system.auth.timestamp} %{SYSLOGHOST:host.hostname} %{DATA:process.name}(?:\[%{POSINT:process.pid:long}\])?: + %{DATA:system.auth.ssh.event} user %{DATA:user.name} from %{IPORHOST:source.ip}' + - '%{TIMESTAMP:system.auth.timestamp} %{SYSLOGHOST:host.hostname} %{DATA:process.name}(?:\[%{POSINT:process.pid:long}\])?: + Did not receive identification string from %{IPORHOST:system.auth.ssh.dropped_ip}' + - '%{TIMESTAMP:system.auth.timestamp} %{SYSLOGHOST:host.hostname} %{DATA:process.name}(?:\[%{POSINT:process.pid:long}\])?: + \s*%{DATA:user.name} :( %{DATA:system.auth.sudo.error} ;)? TTY=%{DATA:system.auth.sudo.tty} + ; PWD=%{DATA:system.auth.sudo.pwd} ; USER=%{DATA:system.auth.sudo.user} ; COMMAND=%{GREEDYDATA:system.auth.sudo.command}' + - '%{TIMESTAMP:system.auth.timestamp} %{SYSLOGHOST:host.hostname} %{DATA:process.name}(?:\[%{POSINT:process.pid:long}\])?: + new group: name=%{DATA:group.name}, GID=%{NUMBER:group.id}' + - '%{TIMESTAMP:system.auth.timestamp} %{SYSLOGHOST:host.hostname} %{DATA:process.name}(?:\[%{POSINT:process.pid:long}\])?: + new user: name=%{DATA:user.name}, UID=%{NUMBER:user.id}, GID=%{NUMBER:group.id}, + home=%{DATA:system.auth.useradd.home}, shell=%{DATA:system.auth.useradd.shell}$' + - '%{TIMESTAMP:system.auth.timestamp} %{SYSLOGHOST:host.hostname}? %{DATA:process.name}(?:\[%{POSINT:process.pid:long}\])?: + %{GREEDYMULTILINE:system.auth.message}' +- remove: + field: message +- rename: + field: system.auth.message + target_field: message + ignore_missing: true +- set: + field: source.ip + value: '{{system.auth.ssh.dropped_ip}}' + if: "ctx?.system?.auth?.ssh?.dropped_ip != null" +- date: + if: ctx.event.timezone == null + field: system.auth.timestamp + target_field: '@timestamp' + formats: + - MMM d HH:mm:ss + - MMM dd HH:mm:ss + - ISO8601 + on_failure: + - append: + field: error.message + value: '{{ _ingest.on_failure_message }}' +- date: + if: ctx.event.timezone != null + field: system.auth.timestamp + target_field: '@timestamp' + formats: + - MMM d HH:mm:ss + - MMM dd HH:mm:ss + - ISO8601 + timezone: '{{ event.timezone }}' + on_failure: + - append: + field: error.message + value: '{{ _ingest.on_failure_message }}' +- remove: + field: system.auth.timestamp +- geoip: + field: source.ip + target_field: source.geo + ignore_failure: true +- geoip: + database_file: GeoLite2-ASN.mmdb + field: source.ip + target_field: source.as + properties: + - asn + - organization_name + ignore_missing: true +- rename: + field: source.as.asn + target_field: source.as.number + ignore_missing: true +- rename: + field: source.as.organization_name + target_field: source.as.organization.name + ignore_missing: true +- set: + field: event.kind + value: event +- script: + lang: painless + ignore_failure: true + source: >- + if (ctx.system.auth.ssh.event == "Accepted") { + ctx.event.type = ["authentication_success", "info"]; + ctx.event.category = ["authentication"]; + ctx.event.action = "ssh_login"; + ctx.event.outcome = "success"; + } else if (ctx.system.auth.ssh.event == "Invalid" || ctx.system.auth.ssh.event == "Failed") { + ctx.event.type = ["authentication_failure", "info"]; + ctx.event.category = ["authentication"]; + ctx.event.action = "ssh_login"; + ctx.event.outcome = "failure"; + } + +- append: + field: event.category + value: iam + if: "ctx?.process?.name != null && ['groupadd', 'groupdel', 'groupmod', 'useradd', 'userdel', 'usermod'].contains(ctx.process.name)" +- set: + field: event.outcome + value: success + if: "ctx?.process?.name != null && ['groupadd', 'groupdel', 'groupmod', 'useradd', 'userdel', 'usermod'].contains(ctx.process.name)" +- append: + field: event.type + value: user + if: "ctx?.process?.name != null && ['useradd', 'userdel', 'usermod'].contains(ctx.process.name)" +- append: + field: event.type + value: group + if: "ctx?.process?.name != null && ['groupadd', 'groupdel', 'groupmod'].contains(ctx.process.name)" +- append: + field: event.type + value: creation + if: "ctx?.process?.name != null && ['useradd', 'groupadd'].contains(ctx.process.name)" +- append: + field: event.type + value: deletion + if: "ctx?.process?.name != null && ['userdel', 'groupdel'].contains(ctx.process.name)" +- append: + field: event.type + value: change + if: "ctx?.process?.name != null && ['usermod', 'groupmod'].contains(ctx.process.name)" +- append: + field: related.user + value: "{{user.name}}" + if: "ctx?.user?.name != null" +- append: + field: related.ip + value: "{{source.ip}}" + if: "ctx?.source?.ip != null" +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/filebeat/module/system/auth/manifest.yml b/filebeat/module/system/auth/manifest.yml index ade9e03a69a..dd16ddafd65 100644 --- a/filebeat/module/system/auth/manifest.yml +++ b/filebeat/module/system/auth/manifest.yml @@ -11,5 +11,5 @@ var: - /var/log/secure.log* os.windows: [] -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/auth.yml diff --git a/filebeat/module/system/auth/test/auth-ubuntu1204.log-expected.json b/filebeat/module/system/auth/test/auth-ubuntu1204.log-expected.json index a7a3cee04e6..74654cb6dc1 100644 --- a/filebeat/module/system/auth/test/auth-ubuntu1204.log-expected.json +++ b/filebeat/module/system/auth/test/auth-ubuntu1204.log-expected.json @@ -1,6 +1,7 @@ [ { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -14,6 +15,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -21,6 +23,9 @@ "input.type": "log", "log.offset": 81, "process.name": "sudo", + "related.user": [ + "vagrant" + ], "service.type": "system", "system.auth.sudo.command": "/bin/sh -c echo BECOME-SUCCESS-lhspyyxxlfzpytwsebjoegenjxyjombo; LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 /usr/bin/python /home/vagrant/.ansible/tmp/ansible-tmp-1486675177.72-26828938879074/get_url; rm -rf /home/vagrant/.ansible/tmp/ansible-tmp-1486675177.72-26828938879074/ >/dev/null 2>&1", "system.auth.sudo.pwd": "/home/vagrant", @@ -30,6 +35,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -42,6 +48,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -54,6 +61,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -67,6 +75,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -74,6 +83,9 @@ "input.type": "log", "log.offset": 736, "process.name": "sudo", + "related.user": [ + "vagrant" + ], "service.type": "system", "system.auth.sudo.command": "/bin/sh -c echo BECOME-SUCCESS-xspkubktopzqiwiofvdhqaglconkrgwp; LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 /usr/bin/python /home/vagrant/.ansible/tmp/ansible-tmp-1486675181.24-158548606882799/get_url; rm -rf /home/vagrant/.ansible/tmp/ansible-tmp-1486675181.24-158548606882799/ >/dev/null 2>&1", "system.auth.sudo.pwd": "/home/vagrant", @@ -83,6 +95,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -95,6 +108,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -107,6 +121,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -120,6 +135,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -127,6 +143,9 @@ "input.type": "log", "log.offset": 1393, "process.name": "sudo", + "related.user": [ + "vagrant" + ], "service.type": "system", "system.auth.sudo.command": "/bin/sh -c echo BECOME-SUCCESS-vxcrqvczsrjrrsjcokculalhrgfsxqzl; LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 /usr/bin/python /home/vagrant/.ansible/tmp/ansible-tmp-1486675202.4-199750250589919/command; rm -rf /home/vagrant/.ansible/tmp/ansible-tmp-1486675202.4-199750250589919/ >/dev/null 2>&1", "system.auth.sudo.pwd": "/home/vagrant", @@ -136,6 +155,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -148,6 +168,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -160,6 +181,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -173,6 +195,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -180,6 +203,9 @@ "input.type": "log", "log.offset": 2048, "process.name": "sudo", + "related.user": [ + "vagrant" + ], "service.type": "system", "system.auth.sudo.command": "/bin/sh -c echo BECOME-SUCCESS-gruorqbeefuuhfprfoqzsftalatgwwvf; LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 /usr/bin/python /home/vagrant/.ansible/tmp/ansible-tmp-1486675203.3-59927285912173/file; rm -rf /home/vagrant/.ansible/tmp/ansible-tmp-1486675203.3-59927285912173/ >/dev/null 2>&1", "system.auth.sudo.pwd": "/home/vagrant", @@ -189,6 +215,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -201,6 +228,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -213,6 +241,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -226,6 +255,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -233,6 +263,9 @@ "input.type": "log", "log.offset": 2698, "process.name": "sudo", + "related.user": [ + "vagrant" + ], "service.type": "system", "system.auth.sudo.command": "/bin/sh -c echo BECOME-SUCCESS-fnthqelgspkbnpnxlsknzcbyxbqqxpmt; LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 /usr/bin/python /home/vagrant/.ansible/tmp/ansible-tmp-1486675204.07-135388534337396/command; rm -rf /home/vagrant/.ansible/tmp/ansible-tmp-1486675204.07-135388534337396/ >/dev/null 2>&1", "system.auth.sudo.pwd": "/home/vagrant", @@ -242,6 +275,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -254,6 +288,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -266,6 +301,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -279,6 +315,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -291,6 +328,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -298,6 +336,9 @@ "input.type": "log", "log.offset": 3414, "process.name": "sudo", + "related.user": [ + "vagrant" + ], "service.type": "system", "system.auth.sudo.command": "/bin/sh -c echo BECOME-SUCCESS-wagdvfiuqxtryvmyrqlfcwoxeqqrxejt; LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 /usr/bin/python /home/vagrant/.ansible/tmp/ansible-tmp-1486675206.28-198308747142204/async_wrapper 321853834469 45 /home/vagrant/.ansible/tmp/ansible-tmp-1486675206.28-198308747142204/command /home/vagrant/.ansible/tmp/ansible-tmp-1486675206.28-198308747142204/arguments; rm -rf /home/vagrant/.ansible/tmp/ansible-tmp-1486675206.28-198308747142204/ >/dev/null 2>&1", "system.auth.sudo.pwd": "/home/vagrant", @@ -307,6 +348,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -319,6 +361,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -331,6 +374,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -344,6 +388,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -351,6 +396,9 @@ "input.type": "log", "log.offset": 4249, "process.name": "sudo", + "related.user": [ + "vagrant" + ], "service.type": "system", "system.auth.sudo.command": "/bin/sh -c echo BECOME-SUCCESS-lkgydmrwiywdfvxfoxmgntufiumtzpmq; LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 /usr/bin/python /home/vagrant/.ansible/tmp/ansible-tmp-1486675212.66-81790186240643/command; rm -rf /home/vagrant/.ansible/tmp/ansible-tmp-1486675212.66-81790186240643/ >/dev/null 2>&1", "system.auth.sudo.pwd": "/home/vagrant", @@ -360,6 +408,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -372,6 +421,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -384,6 +434,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -397,6 +448,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -404,6 +456,9 @@ "input.type": "log", "log.offset": 4904, "process.name": "sudo", + "related.user": [ + "vagrant" + ], "service.type": "system", "system.auth.sudo.command": "/bin/sh -c echo BECOME-SUCCESS-mjsapklbglujaoktlsyytirwygexdily; LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 /usr/bin/python /home/vagrant/.ansible/tmp/ansible-tmp-1486675218.96-234174787135180/command; rm -rf /home/vagrant/.ansible/tmp/ansible-tmp-1486675218.96-234174787135180/ >/dev/null 2>&1", "system.auth.sudo.pwd": "/home/vagrant", @@ -413,6 +468,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -425,6 +481,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -437,6 +494,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -450,6 +508,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -457,6 +516,9 @@ "input.type": "log", "log.offset": 5561, "process.name": "sudo", + "related.user": [ + "vagrant" + ], "service.type": "system", "system.auth.sudo.command": "/bin/sh -c echo BECOME-SUCCESS-kvmafqtdnnvnyfyqlnoovickcavkqwdy; LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 /usr/bin/python /home/vagrant/.ansible/tmp/ansible-tmp-1486675219.83-99205535237718/setup; rm -rf /home/vagrant/.ansible/tmp/ansible-tmp-1486675219.83-99205535237718/ >/dev/null 2>&1", "system.auth.sudo.pwd": "/home/vagrant", @@ -466,6 +528,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -478,6 +541,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -490,6 +554,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -503,6 +568,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -510,6 +576,9 @@ "input.type": "log", "log.offset": 6214, "process.name": "sudo", + "related.user": [ + "vagrant" + ], "service.type": "system", "system.auth.sudo.command": "/bin/sh -c echo BECOME-SUCCESS-nhrnwbdpypmsmvcstuihfqfbcvpxrmys; LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 /usr/bin/python /home/vagrant/.ansible/tmp/ansible-tmp-1486675224.58-12467498973476/get_url; rm -rf /home/vagrant/.ansible/tmp/ansible-tmp-1486675224.58-12467498973476/ >/dev/null 2>&1", "system.auth.sudo.pwd": "/home/vagrant", @@ -519,6 +588,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -531,6 +601,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -543,6 +614,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -556,6 +628,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -563,6 +636,9 @@ "input.type": "log", "log.offset": 6869, "process.name": "sudo", + "related.user": [ + "vagrant" + ], "service.type": "system", "system.auth.sudo.command": "/bin/sh -c echo BECOME-SUCCESS-buzartmsbrirxgcoibjpsqjkldihhexh; LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 /usr/bin/python /home/vagrant/.ansible/tmp/ansible-tmp-1486675228.25-195852789001210/get_url; rm -rf /home/vagrant/.ansible/tmp/ansible-tmp-1486675228.25-195852789001210/ >/dev/null 2>&1", "system.auth.sudo.pwd": "/home/vagrant", @@ -572,6 +648,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -584,6 +661,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -596,6 +674,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -609,6 +688,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -616,6 +696,9 @@ "input.type": "log", "log.offset": 7526, "process.name": "sudo", + "related.user": [ + "vagrant" + ], "service.type": "system", "system.auth.sudo.command": "/bin/sh -c echo BECOME-SUCCESS-swwkpvmnxhcuduxerfbgclhsmgbhwzie; LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 /usr/bin/python /home/vagrant/.ansible/tmp/ansible-tmp-1486675247.78-128146395950020/command; rm -rf /home/vagrant/.ansible/tmp/ansible-tmp-1486675247.78-128146395950020/ >/dev/null 2>&1", "system.auth.sudo.pwd": "/home/vagrant", @@ -625,6 +708,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -637,6 +721,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -649,6 +734,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -662,6 +748,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -669,6 +756,9 @@ "input.type": "log", "log.offset": 8183, "process.name": "sudo", + "related.user": [ + "vagrant" + ], "service.type": "system", "system.auth.sudo.command": "/bin/sh -c echo BECOME-SUCCESS-raffykohamlcbnpxzipksbvfpjbfpagy; LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 /usr/bin/python /home/vagrant/.ansible/tmp/ansible-tmp-1486675250.82-190689706060358/apt; rm -rf /home/vagrant/.ansible/tmp/ansible-tmp-1486675250.82-190689706060358/ >/dev/null 2>&1", "system.auth.sudo.pwd": "/home/vagrant", @@ -678,6 +768,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -690,6 +781,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -702,6 +794,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -715,6 +808,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -722,6 +816,9 @@ "input.type": "log", "log.offset": 8836, "process.name": "sudo", + "related.user": [ + "vagrant" + ], "service.type": "system", "system.auth.sudo.command": "/bin/sh -c echo BECOME-SUCCESS-dfoxiractbmtavfiwfnhzfkftipjumph; LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 /usr/bin/python /home/vagrant/.ansible/tmp/ansible-tmp-1486675251.6-137767038423665/apt; rm -rf /home/vagrant/.ansible/tmp/ansible-tmp-1486675251.6-137767038423665/ >/dev/null 2>&1", "system.auth.sudo.pwd": "/home/vagrant", @@ -731,6 +828,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -743,6 +841,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -755,6 +854,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -768,6 +868,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -775,6 +876,9 @@ "input.type": "log", "log.offset": 9487, "process.name": "sudo", + "related.user": [ + "vagrant" + ], "service.type": "system", "system.auth.sudo.command": "/bin/sh -c echo BECOME-SUCCESS-jveaoynmhsmeodakzfhhaodihyroxobu; LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 /usr/bin/python /home/vagrant/.ansible/tmp/ansible-tmp-1486675261.29-208287411335817/file; rm -rf /home/vagrant/.ansible/tmp/ansible-tmp-1486675261.29-208287411335817/ >/dev/null 2>&1", "system.auth.sudo.pwd": "/home/vagrant", @@ -784,6 +888,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -796,6 +901,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -808,6 +914,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -815,6 +922,9 @@ "input.type": "log", "log.offset": 10060, "process.name": "sudo", + "related.user": [ + "vagrant" + ], "service.type": "system", "system.auth.sudo.command": "/bin/sh -c echo BECOME-SUCCESS-lwzhcvorajmjyxsrqydafzapoeescwaf; rc=flag; [ -r /etc/metricbeat/metricbeat.yml ] || rc=2; [ -f /etc/metricbeat/metricbeat.yml ] || rc=1; [ -d /etc/metricbeat/metricbeat.yml ] && rc=3; python -V 2>/dev/null || rc=4; [ x\"$rc\" != \"xflag\" ] && echo \"${rc} \"/etc/metricbeat/metricbeat.yml && exit 0; (python -c 'import hashlib; BLOCKSIZE = 65536; hasher = hashlib.sha1();#012afile = open(\"'/etc/metricbeat/metricbeat.yml'\", \"rb\")#012buf = afile.read(BLOCKSIZE)#012while len(buf) > 0:#012#011hasher.update(buf)#012#011buf = afile.read(BLOCKSIZE)#012afile.close()#012print(hasher.hexdigest())' 2>/dev/null) || (python -c 'import sha; BLOCKSIZE = 65536; hasher = sha.sha();#012afile = open(\"'/etc/metricbeat/metricbeat.yml'\", \"rb\")#012buf = afile.read(BLOCKSIZE)#012while len(buf) > 0:#012#011hasher.update(buf)#012#011buf = afile.read(BLOCKSIZE)#012afile.close()#012print(hasher.hexdigest())' 2>/dev/null) || (echo '0 ", "system.auth.sudo.pwd": "/home/vagrant", @@ -824,6 +934,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -836,6 +947,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -848,6 +960,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -860,6 +973,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -873,6 +987,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -886,6 +1001,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -893,6 +1009,9 @@ "input.type": "log", "log.offset": 11548, "process.name": "sudo", + "related.user": [ + "vagrant" + ], "service.type": "system", "system.auth.sudo.command": "/bin/sh -c echo BECOME-SUCCESS-yesyhegdrhiolusidthffdemrxphqdfm; LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 /usr/bin/python /home/vagrant/.ansible/tmp/ansible-tmp-1486675262.15-83340738940485/copy; rm -rf /home/vagrant/.ansible/tmp/ansible-tmp-1486675262.15-83340738940485/ >/dev/null 2>&1", "system.auth.sudo.pwd": "/home/vagrant", @@ -902,6 +1021,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -914,6 +1034,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -926,6 +1047,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -939,6 +1061,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -946,6 +1069,9 @@ "input.type": "log", "log.offset": 12200, "process.name": "sudo", + "related.user": [ + "vagrant" + ], "service.type": "system", "system.auth.sudo.command": "/bin/sh -c echo BECOME-SUCCESS-vqbyiylfjufyxlwvxcwusklrtmiekpia; LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 /usr/bin/python /home/vagrant/.ansible/tmp/ansible-tmp-1486675263.16-15325827909434/service; rm -rf /home/vagrant/.ansible/tmp/ansible-tmp-1486675263.16-15325827909434/ >/dev/null 2>&1", "system.auth.sudo.pwd": "/home/vagrant", @@ -955,6 +1081,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -967,6 +1094,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -979,6 +1107,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -992,6 +1121,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -999,6 +1129,9 @@ "input.type": "log", "log.offset": 12855, "process.name": "sudo", + "related.user": [ + "vagrant" + ], "service.type": "system", "system.auth.sudo.command": "/bin/sh -c echo BECOME-SUCCESS-osrbplljwskuafamtjuanhwfxqdxmfbj; LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 /usr/bin/python /home/vagrant/.ansible/tmp/ansible-tmp-1486675264.47-179299683847940/wait_for; rm -rf /home/vagrant/.ansible/tmp/ansible-tmp-1486675264.47-179299683847940/ >/dev/null 2>&1", "system.auth.sudo.pwd": "/home/vagrant", @@ -1008,6 +1141,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1020,6 +1154,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1032,6 +1167,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1045,6 +1181,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1052,6 +1189,9 @@ "input.type": "log", "log.offset": 13513, "process.name": "sudo", + "related.user": [ + "vagrant" + ], "service.type": "system", "system.auth.sudo.command": "/bin/sh -c echo BECOME-SUCCESS-xqypdfdxashhaekghbfnpdlcgsmfarmy; LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 /usr/bin/python /home/vagrant/.ansible/tmp/ansible-tmp-1486675265.39-273766954542007/service; rm -rf /home/vagrant/.ansible/tmp/ansible-tmp-1486675265.39-273766954542007/ >/dev/null 2>&1", "system.auth.sudo.pwd": "/home/vagrant", @@ -1061,6 +1201,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1073,6 +1214,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1085,6 +1227,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1098,6 +1241,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1105,6 +1249,9 @@ "input.type": "log", "log.offset": 14170, "process.name": "sudo", + "related.user": [ + "vagrant" + ], "service.type": "system", "system.auth.sudo.command": "/bin/sh -c echo BECOME-SUCCESS-ktkmpxhjivossxngupfgrqfobhopruzp; LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 /usr/bin/python /home/vagrant/.ansible/tmp/ansible-tmp-1486675266.58-47565152594552/apt; rm -rf /home/vagrant/.ansible/tmp/ansible-tmp-1486675266.58-47565152594552/ >/dev/null 2>&1", "system.auth.sudo.pwd": "/home/vagrant", @@ -1114,6 +1261,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1126,6 +1274,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1138,6 +1287,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1151,6 +1301,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1158,6 +1309,9 @@ "input.type": "log", "log.offset": 14821, "process.name": "sudo", + "related.user": [ + "vagrant" + ], "service.type": "system", "system.auth.sudo.command": "/bin/sh -c echo BECOME-SUCCESS-erpqyqrmifxazcclvbqytjwxgdplhtpy; LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 /usr/bin/python /home/vagrant/.ansible/tmp/ansible-tmp-1486675275.74-155140815824587/file; rm -rf /home/vagrant/.ansible/tmp/ansible-tmp-1486675275.74-155140815824587/ >/dev/null 2>&1", "system.auth.sudo.pwd": "/home/vagrant", @@ -1167,6 +1321,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1179,6 +1334,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1191,6 +1347,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1204,6 +1361,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1211,6 +1369,9 @@ "input.type": "log", "log.offset": 15475, "process.name": "sudo", + "related.user": [ + "vagrant" + ], "service.type": "system", "system.auth.sudo.command": "/bin/sh -c echo BECOME-SUCCESS-cfqjebskszjdqpksprlbjpbttastwzyp; LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 /usr/bin/python /home/vagrant/.ansible/tmp/ansible-tmp-1486675276.62-248748589735433/get_url; rm -rf /home/vagrant/.ansible/tmp/ansible-tmp-1486675276.62-248748589735433/ >/dev/null 2>&1", "system.auth.sudo.pwd": "/home/vagrant", @@ -1220,6 +1381,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1232,6 +1394,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1244,6 +1407,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1257,6 +1421,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1264,6 +1429,9 @@ "input.type": "log", "log.offset": 16132, "process.name": "sudo", + "related.user": [ + "vagrant" + ], "service.type": "system", "system.auth.sudo.command": "/bin/sh -c echo BECOME-SUCCESS-oxbowrzvfhsebemuiblilqwvdxvnwztv; LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 /usr/bin/python /home/vagrant/.ansible/tmp/ansible-tmp-1486675280.28-272460786101534/get_url; rm -rf /home/vagrant/.ansible/tmp/ansible-tmp-1486675280.28-272460786101534/ >/dev/null 2>&1", "system.auth.sudo.pwd": "/home/vagrant", @@ -1273,6 +1441,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1285,6 +1454,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1297,6 +1467,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1310,6 +1481,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1317,6 +1489,9 @@ "input.type": "log", "log.offset": 16789, "process.name": "sudo", + "related.user": [ + "vagrant" + ], "service.type": "system", "system.auth.sudo.command": "/bin/sh -c echo BECOME-SUCCESS-ohlhhhazvtawqawluadjlxglowwenmyc; LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 /usr/bin/python /home/vagrant/.ansible/tmp/ansible-tmp-1486675302.51-201837201796085/command; rm -rf /home/vagrant/.ansible/tmp/ansible-tmp-1486675302.51-201837201796085/ >/dev/null 2>&1", "system.auth.sudo.pwd": "/home/vagrant", diff --git a/filebeat/module/system/auth/test/secure-rhel7.log-expected.json b/filebeat/module/system/auth/test/secure-rhel7.log-expected.json index 331294ad81d..5242ff398d9 100644 --- a/filebeat/module/system/auth/test/secure-rhel7.log-expected.json +++ b/filebeat/module/system/auth/test/secure-rhel7.log-expected.json @@ -1,18 +1,30 @@ [ { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 0, "process.name": "sshd", "process.pid": 2738, + "related.ip": [ + "202.109.143.106" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 4134, "source.as.organization.name": "No.31,Jin-rong Street", @@ -30,6 +42,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -43,18 +56,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 209, "process.name": "sshd", "process.pid": 2738, + "related.ip": [ + "202.109.143.106" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 4134, "source.as.organization.name": "No.31,Jin-rong Street", @@ -72,6 +97,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -85,18 +111,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 418, "process.name": "sshd", "process.pid": 2738, + "related.ip": [ + "202.109.143.106" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 4134, "source.as.organization.name": "No.31,Jin-rong Street", @@ -114,6 +152,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -127,6 +166,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -140,6 +180,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -153,6 +194,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -166,6 +208,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -179,18 +222,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 1105, "process.name": "sshd", "process.pid": 2742, + "related.ip": [ + "202.109.143.106" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 4134, "source.as.organization.name": "No.31,Jin-rong Street", @@ -208,6 +263,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -221,18 +277,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 1314, "process.name": "sshd", "process.pid": 2742, + "related.ip": [ + "202.109.143.106" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 4134, "source.as.organization.name": "No.31,Jin-rong Street", @@ -250,6 +318,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -263,18 +332,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 1523, "process.name": "sshd", "process.pid": 2742, + "related.ip": [ + "202.109.143.106" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 4134, "source.as.organization.name": "No.31,Jin-rong Street", @@ -292,6 +373,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -305,18 +387,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 1732, "process.name": "sshd", "process.pid": 2742, + "related.ip": [ + "202.109.143.106" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 4134, "source.as.organization.name": "No.31,Jin-rong Street", @@ -334,6 +428,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -347,18 +442,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 1941, "process.name": "sshd", "process.pid": 2742, + "related.ip": [ + "202.109.143.106" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 4134, "source.as.organization.name": "No.31,Jin-rong Street", @@ -376,6 +483,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -389,6 +497,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -402,6 +511,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -415,6 +525,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -428,6 +539,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -441,6 +553,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -454,6 +567,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -467,18 +581,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 2889, "process.name": "sshd", "process.pid": 2754, + "related.ip": [ + "202.109.143.106" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 4134, "source.as.organization.name": "No.31,Jin-rong Street", @@ -496,6 +622,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -509,18 +636,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 3098, "process.name": "sshd", "process.pid": 2758, + "related.ip": [ + "116.31.116.27" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 134764, "source.as.organization.name": "CHINANET Guangdong province network", @@ -538,6 +677,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -551,18 +691,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 3306, "process.name": "sshd", "process.pid": 2754, + "related.ip": [ + "202.109.143.106" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 4134, "source.as.organization.name": "No.31,Jin-rong Street", @@ -580,6 +732,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -593,18 +746,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 3515, "process.name": "sshd", "process.pid": 2758, + "related.ip": [ + "116.31.116.27" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 134764, "source.as.organization.name": "CHINANET Guangdong province network", @@ -622,6 +787,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -635,18 +801,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 3723, "process.name": "sshd", "process.pid": 2754, + "related.ip": [ + "202.109.143.106" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 4134, "source.as.organization.name": "No.31,Jin-rong Street", @@ -664,6 +842,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -677,18 +856,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 3932, "process.name": "sshd", "process.pid": 2758, + "related.ip": [ + "116.31.116.27" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 134764, "source.as.organization.name": "CHINANET Guangdong province network", @@ -706,6 +897,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -719,6 +911,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -732,18 +925,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 4259, "process.name": "sshd", "process.pid": 2754, + "related.ip": [ + "202.109.143.106" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 4134, "source.as.organization.name": "No.31,Jin-rong Street", @@ -761,6 +966,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -774,18 +980,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 4468, "process.name": "sshd", "process.pid": 2754, + "related.ip": [ + "202.109.143.106" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 4134, "source.as.organization.name": "No.31,Jin-rong Street", @@ -803,6 +1021,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -816,6 +1035,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -829,6 +1049,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -842,6 +1063,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -855,6 +1077,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -868,18 +1091,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 5155, "process.name": "sshd", "process.pid": 2762, + "related.ip": [ + "202.109.143.106" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 4134, "source.as.organization.name": "No.31,Jin-rong Street", @@ -897,6 +1132,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -910,18 +1146,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 5364, "process.name": "sshd", "process.pid": 2762, + "related.ip": [ + "202.109.143.106" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 4134, "source.as.organization.name": "No.31,Jin-rong Street", @@ -939,6 +1187,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -952,18 +1201,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 5573, "process.name": "sshd", "process.pid": 2762, + "related.ip": [ + "202.109.143.106" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 4134, "source.as.organization.name": "No.31,Jin-rong Street", @@ -981,6 +1242,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -994,18 +1256,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 5782, "process.name": "sshd", "process.pid": 2762, + "related.ip": [ + "202.109.143.106" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 4134, "source.as.organization.name": "No.31,Jin-rong Street", @@ -1023,6 +1297,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1036,18 +1311,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 5991, "process.name": "sshd", "process.pid": 2762, + "related.ip": [ + "202.109.143.106" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 4134, "source.as.organization.name": "No.31,Jin-rong Street", @@ -1065,6 +1352,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1078,6 +1366,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1091,6 +1380,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1104,6 +1394,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1117,6 +1408,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1130,18 +1422,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 6678, "process.name": "sshd", "process.pid": 2766, + "related.ip": [ + "202.109.143.106" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 4134, "source.as.organization.name": "No.31,Jin-rong Street", @@ -1159,6 +1463,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1172,18 +1477,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 6887, "process.name": "sshd", "process.pid": 2766, + "related.ip": [ + "202.109.143.106" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 4134, "source.as.organization.name": "No.31,Jin-rong Street", @@ -1201,6 +1518,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1214,18 +1532,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 7096, "process.name": "sshd", "process.pid": 2766, + "related.ip": [ + "202.109.143.106" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 4134, "source.as.organization.name": "No.31,Jin-rong Street", @@ -1243,6 +1573,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1256,18 +1587,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 7305, "process.name": "sshd", "process.pid": 2766, + "related.ip": [ + "202.109.143.106" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 4134, "source.as.organization.name": "No.31,Jin-rong Street", @@ -1285,6 +1628,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1298,18 +1642,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 7514, "process.name": "sshd", "process.pid": 2766, + "related.ip": [ + "202.109.143.106" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 4134, "source.as.organization.name": "No.31,Jin-rong Street", @@ -1327,6 +1683,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1340,6 +1697,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1353,6 +1711,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1366,6 +1725,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1379,6 +1739,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1392,18 +1753,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 8199, "process.name": "sshd", "process.pid": 2778, + "related.ip": [ + "116.31.116.27" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 134764, "source.as.organization.name": "CHINANET Guangdong province network", @@ -1421,6 +1794,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1434,18 +1808,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 8407, "process.name": "sshd", "process.pid": 2778, + "related.ip": [ + "116.31.116.27" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 134764, "source.as.organization.name": "CHINANET Guangdong province network", @@ -1463,6 +1849,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1476,18 +1863,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 8615, "process.name": "sshd", "process.pid": 2778, + "related.ip": [ + "116.31.116.27" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 134764, "source.as.organization.name": "CHINANET Guangdong province network", @@ -1505,6 +1904,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1518,6 +1918,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1531,6 +1932,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1544,6 +1946,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1557,18 +1960,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 9205, "process.name": "sshd", "process.pid": 2785, + "related.ip": [ + "202.109.143.106" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 4134, "source.as.organization.name": "No.31,Jin-rong Street", @@ -1586,6 +2001,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1599,18 +2015,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 9414, "process.name": "sshd", "process.pid": 2785, + "related.ip": [ + "202.109.143.106" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 4134, "source.as.organization.name": "No.31,Jin-rong Street", @@ -1628,6 +2056,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1641,18 +2070,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 9623, "process.name": "sshd", "process.pid": 2785, + "related.ip": [ + "202.109.143.106" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 4134, "source.as.organization.name": "No.31,Jin-rong Street", @@ -1670,6 +2111,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1683,18 +2125,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 9832, "process.name": "sshd", "process.pid": 2785, + "related.ip": [ + "202.109.143.106" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 4134, "source.as.organization.name": "No.31,Jin-rong Street", @@ -1712,6 +2166,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1725,18 +2180,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 10041, "process.name": "sshd", "process.pid": 2785, + "related.ip": [ + "202.109.143.106" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 4134, "source.as.organization.name": "No.31,Jin-rong Street", @@ -1754,6 +2221,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1767,6 +2235,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1780,6 +2249,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1793,6 +2263,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1806,6 +2277,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -1819,18 +2291,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 10728, "process.name": "sshd", "process.pid": 2797, + "related.ip": [ + "202.109.143.106" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 4134, "source.as.organization.name": "No.31,Jin-rong Street", @@ -1848,6 +2332,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", diff --git a/filebeat/module/system/auth/test/test.log-expected.json b/filebeat/module/system/auth/test/test.log-expected.json index 5a2cf8fa0a2..0203b1a1f3b 100644 --- a/filebeat/module/system/auth/test/test.log-expected.json +++ b/filebeat/module/system/auth/test/test.log-expected.json @@ -1,18 +1,30 @@ [ { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "success", "event.timezone": "-02:00", - "event.type": "authentication_success", + "event.type": [ + "authentication_success", + "info" + ], "fileset.name": "auth", "host.hostname": "localhost", "input.type": "log", "log.offset": 0, "process.name": "sshd", "process.pid": 3402, + "related.ip": [ + "10.0.2.2" + ], + "related.user": [ + "vagrant" + ], "service.type": "system", "source.ip": "10.0.2.2", "source.port": 63673, @@ -23,18 +35,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "success", "event.timezone": "-02:00", - "event.type": "authentication_success", + "event.type": [ + "authentication_success", + "info" + ], "fileset.name": "auth", "host.hostname": "localhost", "input.type": "log", "log.offset": 152, "process.name": "sshd", "process.pid": 7483, + "related.ip": [ + "192.168.33.1" + ], + "related.user": [ + "vagrant" + ], "service.type": "system", "source.ip": "192.168.33.1", "source.port": 58803, @@ -44,18 +68,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "localhost", "input.type": "log", "log.offset": 254, "process.name": "sshd", "process.pid": 3430, + "related.ip": [ + "10.0.2.2" + ], + "related.user": [ + "test" + ], "service.type": "system", "source.ip": "10.0.2.2", "system.auth.ssh.event": "Invalid", @@ -63,18 +99,30 @@ }, { "event.action": "ssh_login", - "event.category": "authentication", + "event.category": [ + "authentication" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.outcome": "failure", "event.timezone": "-02:00", - "event.type": "authentication_failure", + "event.type": [ + "authentication_failure", + "info" + ], "fileset.name": "auth", "host.hostname": "slave22", "input.type": "log", "log.offset": 324, "process.name": "sshd", "process.pid": 5774, + "related.ip": [ + "116.31.116.24" + ], + "related.user": [ + "root" + ], "service.type": "system", "source.as.number": 134764, "source.as.organization.name": "CHINANET Guangdong province network", @@ -92,6 +140,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -99,6 +148,9 @@ "input.type": "log", "log.offset": 420, "process.name": "sudo", + "related.user": [ + "vagrant" + ], "service.type": "system", "system.auth.sudo.command": "/bin/ls", "system.auth.sudo.pwd": "/home/vagrant", @@ -108,6 +160,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -116,6 +169,9 @@ "log.offset": 522, "process.name": "sshd", "process.pid": 18406, + "related.ip": [ + "123.57.245.163" + ], "service.type": "system", "source.as.number": 37963, "source.as.organization.name": "Hangzhou Alibaba Advertising Co.,Ltd.", @@ -131,6 +187,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -138,6 +195,9 @@ "input.type": "log", "log.offset": 617, "process.name": "sudo", + "related.user": [ + "vagrant" + ], "service.type": "system", "system.auth.sudo.command": "/bin/cat /var/log/secure", "system.auth.sudo.pwd": "/home/vagrant", @@ -147,6 +207,7 @@ }, { "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -154,6 +215,9 @@ "input.type": "log", "log.offset": 736, "process.name": "sudo", + "related.user": [ + "tsg" + ], "service.type": "system", "system.auth.sudo.command": "/bin/ls", "system.auth.sudo.error": "user NOT in sudoers", @@ -163,9 +227,18 @@ "user.name": "tsg" }, { + "event.category": [ + "iam" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "group", + "creation" + ], "fileset.name": "auth", "group.id": "48", "group.name": "apache", @@ -177,9 +250,18 @@ "service.type": "system" }, { + "event.category": [ + "iam" + ], "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", + "event.outcome": "success", "event.timezone": "-02:00", + "event.type": [ + "user", + "creation" + ], "fileset.name": "auth", "group.id": "48", "host.hostname": "localhost", @@ -187,6 +269,9 @@ "log.offset": 934, "process.name": "useradd", "process.pid": 6995, + "related.user": [ + "apache" + ], "service.type": "system", "system.auth.useradd.home": "/usr/share/httpd", "system.auth.useradd.shell": "/sbin/nologin", diff --git a/filebeat/module/system/auth/test/timestamp.log-expected.json b/filebeat/module/system/auth/test/timestamp.log-expected.json index 80c07d4e9a8..8903b63e89e 100644 --- a/filebeat/module/system/auth/test/timestamp.log-expected.json +++ b/filebeat/module/system/auth/test/timestamp.log-expected.json @@ -2,6 +2,7 @@ { "@timestamp": "2019-06-14T10:40:20.912-02:00", "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", @@ -16,6 +17,7 @@ { "@timestamp": "2019-06-14T09:31:15.412-02:00", "event.dataset": "system.auth", + "event.kind": "event", "event.module": "system", "event.timezone": "-02:00", "fileset.name": "auth", diff --git a/filebeat/module/system/syslog/ingest/pipeline.json b/filebeat/module/system/syslog/ingest/pipeline.json deleted file mode 100644 index 0c614b8a957..00000000000 --- a/filebeat/module/system/syslog/ingest/pipeline.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "description": "Pipeline for parsing Syslog messages.", - "processors": [ - { - "grok": { - "field": "message", - "patterns": [ - "%{SYSLOGTIMESTAMP:system.syslog.timestamp} %{SYSLOGHOST:host.hostname} %{DATA:process.name}(?:\\[%{POSINT:process.pid:long}\\])?: %{GREEDYMULTILINE:system.syslog.message}", - "%{SYSLOGTIMESTAMP:system.syslog.timestamp} %{GREEDYMULTILINE:system.syslog.message}", - "%{TIMESTAMP_ISO8601:system.syslog.timestamp} %{SYSLOGHOST:host.hostname} %{DATA:process.name}(?:\\[%{POSINT:process.pid:long}\\])?: %{GREEDYMULTILINE:system.syslog.message}" - ], - "pattern_definitions" : { - "GREEDYMULTILINE" : "(.|\n)*" - }, - "ignore_missing": true - } - }, - { - "remove": { - "field": "message" - } - }, - { - "rename": { - "field": "system.syslog.message", - "target_field": "message", - "ignore_missing": true - } - }, - { - "date": { - "if": "ctx.event.timezone == null", - "field": "system.syslog.timestamp", - "target_field": "@timestamp", - "formats": [ - "MMM d HH:mm:ss", - "MMM dd HH:mm:ss", - "MMM d HH:mm:ss", - "ISO8601" - ], - "on_failure": [{"append": {"field": "error.message", "value": "{{ _ingest.on_failure_message }}"}}] - } - }, - { - "date": { - "if": "ctx.event.timezone != null", - "field": "system.syslog.timestamp", - "target_field": "@timestamp", - "formats": [ - "MMM d HH:mm:ss", - "MMM dd HH:mm:ss", - "MMM d HH:mm:ss", - "ISO8601" - ], - "timezone": "{{ event.timezone }}", - "on_failure": [{"append": {"field": "error.message", "value": "{{ _ingest.on_failure_message }}"}}] - } - }, - { - "remove": { - "field": "system.syslog.timestamp" - } - } - ], - "on_failure" : [{ - "set" : { - "field" : "error.message", - "value" : "{{ _ingest.on_failure_message }}" - } - }] -} diff --git a/filebeat/module/system/syslog/ingest/pipeline.yml b/filebeat/module/system/syslog/ingest/pipeline.yml new file mode 100644 index 00000000000..e0c80b9aad6 --- /dev/null +++ b/filebeat/module/system/syslog/ingest/pipeline.yml @@ -0,0 +1,57 @@ +description: Pipeline for parsing Syslog messages. +processors: +- grok: + field: message + patterns: + - '%{SYSLOGTIMESTAMP:system.syslog.timestamp} %{SYSLOGHOST:host.hostname} %{DATA:process.name}(?:\[%{POSINT:process.pid:long}\])?: + %{GREEDYMULTILINE:system.syslog.message}' + - '%{SYSLOGTIMESTAMP:system.syslog.timestamp} %{GREEDYMULTILINE:system.syslog.message}' + - '%{TIMESTAMP_ISO8601:system.syslog.timestamp} %{SYSLOGHOST:host.hostname} %{DATA:process.name}(?:\[%{POSINT:process.pid:long}\])?: + %{GREEDYMULTILINE:system.syslog.message}' + pattern_definitions: + GREEDYMULTILINE: |- + (.| + )* + ignore_missing: true +- remove: + field: message +- rename: + field: system.syslog.message + target_field: message + ignore_missing: true +- date: + if: ctx.event.timezone == null + field: system.syslog.timestamp + target_field: '@timestamp' + formats: + - MMM d HH:mm:ss + - MMM dd HH:mm:ss + - MMM d HH:mm:ss + - ISO8601 + on_failure: + - append: + field: error.message + value: '{{ _ingest.on_failure_message }}' +- date: + if: ctx.event.timezone != null + field: system.syslog.timestamp + target_field: '@timestamp' + formats: + - MMM d HH:mm:ss + - MMM dd HH:mm:ss + - MMM d HH:mm:ss + - ISO8601 + timezone: '{{ event.timezone }}' + on_failure: + - append: + field: error.message + value: '{{ _ingest.on_failure_message }}' +- remove: + field: system.syslog.timestamp +- set: + field: event.type + value: event +on_failure: +- set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/filebeat/module/system/syslog/manifest.yml b/filebeat/module/system/syslog/manifest.yml index fa0ec049135..39a34e56ca3 100644 --- a/filebeat/module/system/syslog/manifest.yml +++ b/filebeat/module/system/syslog/manifest.yml @@ -9,5 +9,5 @@ var: - /var/log/system.log* os.windows: [] -ingest_pipeline: ingest/pipeline.json +ingest_pipeline: ingest/pipeline.yml input: config/syslog.yml diff --git a/filebeat/module/system/syslog/test/darwin-syslog-sample.log-expected.json b/filebeat/module/system/syslog/test/darwin-syslog-sample.log-expected.json index 5b1165078bc..5a164aef94f 100644 --- a/filebeat/module/system/syslog/test/darwin-syslog-sample.log-expected.json +++ b/filebeat/module/system/syslog/test/darwin-syslog-sample.log-expected.json @@ -3,6 +3,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -19,6 +20,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -32,6 +34,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "input.type": "log", "log.offset": 1176, diff --git a/filebeat/module/system/syslog/test/darwin-syslog.log-expected.json b/filebeat/module/system/syslog/test/darwin-syslog.log-expected.json index fc057403a39..45d44816cd1 100644 --- a/filebeat/module/system/syslog/test/darwin-syslog.log-expected.json +++ b/filebeat/module/system/syslog/test/darwin-syslog.log-expected.json @@ -3,6 +3,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -16,6 +17,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -32,6 +34,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -45,6 +48,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -58,6 +62,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -74,6 +79,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -87,6 +93,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -100,6 +107,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -116,6 +124,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -129,6 +138,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -142,6 +152,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -155,6 +166,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -168,6 +180,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -181,6 +194,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -194,6 +208,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -207,6 +222,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -220,6 +236,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -233,6 +250,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -246,6 +264,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -259,6 +278,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -272,6 +292,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -285,6 +306,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -298,6 +320,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -314,6 +337,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -327,6 +351,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -343,6 +368,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -356,6 +382,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -369,6 +396,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -382,6 +410,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -395,6 +424,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -408,6 +438,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -421,6 +452,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -434,6 +466,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -450,6 +483,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -463,6 +497,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -479,6 +514,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -492,6 +528,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -505,6 +542,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -518,6 +556,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -531,6 +570,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -543,6 +583,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -556,6 +597,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -569,6 +611,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -582,6 +625,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -594,6 +638,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -606,6 +651,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -619,6 +665,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -631,6 +678,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -644,6 +692,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -657,6 +706,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -669,6 +719,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -682,6 +733,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -695,6 +747,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -708,6 +761,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -720,6 +774,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -733,6 +788,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -745,6 +801,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -758,6 +815,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -770,6 +828,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -783,6 +842,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -796,6 +856,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -809,6 +870,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -821,6 +883,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -834,6 +897,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -846,6 +910,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -859,6 +924,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -872,6 +938,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -885,6 +952,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -897,6 +965,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -910,6 +979,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -923,6 +993,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -935,6 +1006,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -948,6 +1020,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -961,6 +1034,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -973,6 +1047,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -986,6 +1061,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -999,6 +1075,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -1011,6 +1088,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -1024,6 +1102,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -1036,6 +1115,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -1049,6 +1129,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -1061,6 +1142,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -1074,6 +1156,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -1087,6 +1170,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -1100,6 +1184,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -1113,6 +1198,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -1125,6 +1211,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -1138,6 +1225,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -1151,6 +1239,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -1163,6 +1252,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -1176,6 +1266,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -1189,6 +1280,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -1201,6 +1293,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -1214,6 +1307,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -1226,6 +1320,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -1239,6 +1334,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -1252,6 +1348,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -1265,6 +1362,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -1277,6 +1375,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", @@ -1290,6 +1389,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "a-mac-with-esc-key", "input.type": "log", diff --git a/filebeat/module/system/syslog/test/suse-syslog.log-expected.json b/filebeat/module/system/syslog/test/suse-syslog.log-expected.json index 0230189feaf..f517557a26e 100644 --- a/filebeat/module/system/syslog/test/suse-syslog.log-expected.json +++ b/filebeat/module/system/syslog/test/suse-syslog.log-expected.json @@ -3,6 +3,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "linux-sqrz", "input.type": "log", @@ -16,6 +17,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "linux-sqrz", "input.type": "log", diff --git a/filebeat/module/system/syslog/test/tz-offset.log-expected.json b/filebeat/module/system/syslog/test/tz-offset.log-expected.json index 154e256b2ba..f2e167a1fd7 100644 --- a/filebeat/module/system/syslog/test/tz-offset.log-expected.json +++ b/filebeat/module/system/syslog/test/tz-offset.log-expected.json @@ -4,6 +4,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "rmbkmonitor04", "input.type": "log", @@ -19,6 +20,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "rmbkmonitor04", "input.type": "log", @@ -33,6 +35,7 @@ "event.dataset": "system.syslog", "event.module": "system", "event.timezone": "-02:00", + "event.type": "event", "fileset.name": "syslog", "host.hostname": "localhost", "input.type": "log", From 4bf5d6288db81fa6605168fe15deba9119d3c267 Mon Sep 17 00:00:00 2001 From: Andrew Stucki Date: Mon, 4 May 2020 11:09:57 -0400 Subject: [PATCH 091/116] [Auditbeat] Add system module process dataset ECS categorization fields (#18032) * [Auditbeat] Add system module process dataset ECS categorization fields * Add changelog entry --- CHANGELOG.next.asciidoc | 1 + .../module/system/process/process.go | 21 +++++++++++++++++-- .../module/system/process/process_test.go | 8 ++++--- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 87fc9ce4615..b60768970d1 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -220,6 +220,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Log to stderr when running using reference kubernetes manifests. {pull}17443[174443] - Fix syscall kprobe arguments for 32-bit systems in socket module. {pull}17500[17500] - Fix memory leak on when we miss socket close kprobe events. {pull}17500[17500] +- Add system module process dataset ECS categorization fields. {pull}18032[18032] *Filebeat* diff --git a/x-pack/auditbeat/module/system/process/process.go b/x-pack/auditbeat/module/system/process/process.go index 48c6914142f..6c9e5a7db6a 100644 --- a/x-pack/auditbeat/module/system/process/process.go +++ b/x-pack/auditbeat/module/system/process/process.go @@ -66,6 +66,21 @@ func (action eventAction) String() string { } } +func (action eventAction) Type() string { + switch action { + case eventActionExistingProcess: + return "info" + case eventActionProcessStarted: + return "start" + case eventActionProcessStopped: + return "end" + case eventActionProcessError: + return "info" + default: + return "info" + } +} + func init() { mb.Registry.MustAddMetricSet(moduleName, metricsetName, New, mb.DefaultMetricSet(), @@ -319,8 +334,10 @@ func (ms *MetricSet) processEvent(process *Process, eventType string, action eve event := mb.Event{ RootFields: common.MapStr{ "event": common.MapStr{ - "kind": eventType, - "action": action.String(), + "kind": eventType, + "category": []string{"process"}, + "type": []string{action.Type()}, + "action": action.String(), }, "process": process.toMapStr(), "message": processMessage(process, action), diff --git a/x-pack/auditbeat/module/system/process/process_test.go b/x-pack/auditbeat/module/system/process/process_test.go index 18e64998618..2a33022ddef 100644 --- a/x-pack/auditbeat/module/system/process/process_test.go +++ b/x-pack/auditbeat/module/system/process/process_test.go @@ -66,9 +66,11 @@ func TestProcessEvent(t *testing.T) { } expectedRootFields := map[string]interface{}{ - "event.kind": "event", - "event.action": "process_started", - "message": "Process zsh (PID: 9086) by user elastic STARTED", + "event.kind": "event", + "event.category": []string{"process"}, + "event.type": []string{"start"}, + "event.action": "process_started", + "message": "Process zsh (PID: 9086) by user elastic STARTED", "process.pid": 9086, "process.ppid": 9085, From 1c6b278fe51ce10bd2b36401e46b232537b800cf Mon Sep 17 00:00:00 2001 From: Michal Pristas Date: Mon, 4 May 2020 17:32:14 +0200 Subject: [PATCH 092/116] [Elastic-Agent] Follow home path for all config files (#18161) * fleet.yml at home * action_store * changelog --- x-pack/elastic-agent/CHANGELOG.asciidoc | 1 + .../pkg/agent/application/config.go | 11 ------- .../pkg/agent/application/enroll_cmd.go | 2 +- .../pkg/agent/application/info/agent_id.go | 33 ++++++++++++++----- .../pkg/agent/application/managed_mode.go | 6 ++-- 5 files changed, 29 insertions(+), 24 deletions(-) diff --git a/x-pack/elastic-agent/CHANGELOG.asciidoc b/x-pack/elastic-agent/CHANGELOG.asciidoc index 13f15a9f5b0..7c155d0eee5 100644 --- a/x-pack/elastic-agent/CHANGELOG.asciidoc +++ b/x-pack/elastic-agent/CHANGELOG.asciidoc @@ -47,3 +47,4 @@ - Use data subfolder as default for process logs {pull}17960[17960] - Do not require unnecessary configuration {pull}18003[18003] - Enable debug log level for Metricbeat and Filebeat when run under the Elastic Agent. {pull}17935[17935] +- Follow home path for all config files {pull}18161[18161] diff --git a/x-pack/elastic-agent/pkg/agent/application/config.go b/x-pack/elastic-agent/pkg/agent/application/config.go index 57945ba465b..bca9e615e17 100644 --- a/x-pack/elastic-agent/pkg/agent/application/config.go +++ b/x-pack/elastic-agent/pkg/agent/application/config.go @@ -8,7 +8,6 @@ import ( "fmt" "time" - "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application/info" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/errors" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/config" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/kibana" @@ -16,16 +15,6 @@ import ( logreporter "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/reporter/log" ) -// TODO(ph) correctly setup global path. -func fleetAgentConfigPath() string { - return info.AgentConfigFile -} - -// TODO(ph) correctly setup with global path. -func fleetActionStoreFile() string { - return info.AgentActionStoreFile -} - // Config define the configuration of the Agent. type Config struct { Management *config.Config `config:"management"` diff --git a/x-pack/elastic-agent/pkg/agent/application/enroll_cmd.go b/x-pack/elastic-agent/pkg/agent/application/enroll_cmd.go index 323937b080c..8a8624b1675 100644 --- a/x-pack/elastic-agent/pkg/agent/application/enroll_cmd.go +++ b/x-pack/elastic-agent/pkg/agent/application/enroll_cmd.go @@ -91,7 +91,7 @@ func NewEnrollCmd( store := storage.NewReplaceOnSuccessStore( configPath, DefaultAgentFleetConfig, - storage.NewEncryptedDiskStore(fleetAgentConfigPath(), []byte("")), + storage.NewEncryptedDiskStore(info.AgentConfigFile(), []byte("")), ) return NewEnrollCmdWithStore( diff --git a/x-pack/elastic-agent/pkg/agent/application/info/agent_id.go b/x-pack/elastic-agent/pkg/agent/application/info/agent_id.go index c20f60972f9..9e0084bc6f1 100644 --- a/x-pack/elastic-agent/pkg/agent/application/info/agent_id.go +++ b/x-pack/elastic-agent/pkg/agent/application/info/agent_id.go @@ -8,21 +8,23 @@ import ( "bytes" "fmt" "io" + "path/filepath" "github.com/gofrs/uuid" "gopkg.in/yaml.v2" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application/paths" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/errors" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/storage" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/config" ) -// AgentConfigFile is a name of file used to store agent information -const AgentConfigFile = "fleet.yml" +// defaultAgentConfigFile is a name of file used to store agent information +const defaultAgentConfigFile = "fleet.yml" const agentInfoKey = "agent_info" -// AgentActionStoreFile is the file that will contains the action that can be replayed after restart. -const AgentActionStoreFile = "action_store.yml" +// defaultAgentActionStoreFile is the file that will contains the action that can be replayed after restart. +const defaultAgentActionStoreFile = "action_store.yml" type persistentAgentInfo struct { ID string `json:"ID" yaml:"ID" config:"ID"` @@ -33,6 +35,16 @@ type ioStore interface { Load() (io.ReadCloser, error) } +// AgentConfigFile is a name of file used to store agent information +func AgentConfigFile() string { + return filepath.Join(paths.Home(), defaultAgentConfigFile) +} + +// AgentActionStoreFile is the file that will contains the action that can be replayed after restart. +func AgentActionStoreFile() string { + return filepath.Join(paths.Home(), defaultAgentActionStoreFile) +} + func generateAgentID() (string, error) { uid, err := uuid.NewV4() if err != nil { @@ -43,7 +55,8 @@ func generateAgentID() (string, error) { } func loadAgentInfo(forceUpdate bool) (*persistentAgentInfo, error) { - s := storage.NewEncryptedDiskStore(AgentConfigFile, []byte("")) + agentConfigFile := AgentConfigFile() + s := storage.NewEncryptedDiskStore(agentConfigFile, []byte("")) agentinfo, err := getInfoFromStore(s) if err != nil { @@ -67,6 +80,7 @@ func loadAgentInfo(forceUpdate bool) (*persistentAgentInfo, error) { } func getInfoFromStore(s ioStore) (*persistentAgentInfo, error) { + agentConfigFile := AgentConfigFile() reader, err := s.Load() if err != nil { return nil, err @@ -75,9 +89,9 @@ func getInfoFromStore(s ioStore) (*persistentAgentInfo, error) { cfg, err := config.NewConfigFrom(reader) if err != nil { return nil, errors.New(err, - fmt.Sprintf("fail to read configuration %s for the agent", AgentConfigFile), + fmt.Sprintf("fail to read configuration %s for the agent", agentConfigFile), errors.TypeFilesystem, - errors.M(errors.MetaKeyPath, AgentConfigFile)) + errors.M(errors.MetaKeyPath, agentConfigFile)) } if err := reader.Close(); err != nil { @@ -110,6 +124,7 @@ func getInfoFromStore(s ioStore) (*persistentAgentInfo, error) { } func updateAgentInfo(s ioStore, agentInfo *persistentAgentInfo) error { + agentConfigFile := AgentConfigFile() reader, err := s.Load() if err != nil { return err @@ -117,9 +132,9 @@ func updateAgentInfo(s ioStore, agentInfo *persistentAgentInfo) error { cfg, err := config.NewConfigFrom(reader) if err != nil { - return errors.New(err, fmt.Sprintf("fail to read configuration %s for the agent", AgentConfigFile), + return errors.New(err, fmt.Sprintf("fail to read configuration %s for the agent", agentConfigFile), errors.TypeFilesystem, - errors.M(errors.MetaKeyPath, AgentConfigFile)) + errors.M(errors.MetaKeyPath, agentConfigFile)) } if err := reader.Close(); err != nil { diff --git a/x-pack/elastic-agent/pkg/agent/application/managed_mode.go b/x-pack/elastic-agent/pkg/agent/application/managed_mode.go index c440c6e1243..98d45fe2c88 100644 --- a/x-pack/elastic-agent/pkg/agent/application/managed_mode.go +++ b/x-pack/elastic-agent/pkg/agent/application/managed_mode.go @@ -57,7 +57,7 @@ func newManaged( return nil, err } - path := fleetAgentConfigPath() + path := info.AgentConfigFile() // TODO(ph): Define the encryption password. store := storage.NewEncryptedDiskStore(path, []byte("")) @@ -145,9 +145,9 @@ func newManaged( batchedAcker := newLazyAcker(acker) // Create the action store that will persist the last good policy change on disk. - actionStore, err := newActionStore(log, storage.NewDiskStore(fleetActionStoreFile())) + actionStore, err := newActionStore(log, storage.NewDiskStore(info.AgentActionStoreFile())) if err != nil { - return nil, errors.New(err, fmt.Sprintf("fail to read action store '%s'", fleetActionStoreFile())) + return nil, errors.New(err, fmt.Sprintf("fail to read action store '%s'", info.AgentActionStoreFile())) } actionAcker := newActionStoreAcker(batchedAcker, actionStore) From a81bbda63c594f622473f0c68ee7ef327f021373 Mon Sep 17 00:00:00 2001 From: Andrew Cholakian Date: Mon, 4 May 2020 10:33:26 -0500 Subject: [PATCH 093/116] [Uptime] Fix nesting of x509 subject (#18139) In #17687 we added additional x509 fields. The mapping accidentally double nested subject fields as subject.subject. This fixes that. There are no tests here because we don't really have testing around mappings, and it's sort of difficult to test without being repetitive, so none are included here. --- heartbeat/docs/fields.asciidoc | 6 +++--- heartbeat/include/fields.go | 2 +- heartbeat/monitors/active/dialchain/_meta/fields.yml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/heartbeat/docs/fields.asciidoc b/heartbeat/docs/fields.asciidoc index bdcfda7fa0d..1ce19dc9375 100644 --- a/heartbeat/docs/fields.asciidoc +++ b/heartbeat/docs/fields.asciidoc @@ -8020,7 +8020,7 @@ example: SHA256-RSA -- -*`tls.server.x509.subject.subject.common_name`*:: +*`tls.server.x509.subject.common_name`*:: + -- List of common names (CN) of subject. @@ -8031,14 +8031,14 @@ example: r2.shared.global.fastly.net -- -*`tls.server.x509.subject.subject.common_name.text`*:: +*`tls.server.x509.subject.common_name.text`*:: + -- type: text -- -*`tls.server.x509.subject.subject.distinguished_name`*:: +*`tls.server.x509.subject.distinguished_name`*:: + -- Distinguished name (DN) of the certificate subject entity. diff --git a/heartbeat/include/fields.go b/heartbeat/include/fields.go index 137a53cce86..9261227de21 100644 --- a/heartbeat/include/fields.go +++ b/heartbeat/include/fields.go @@ -32,5 +32,5 @@ func init() { // AssetFieldsYml returns asset data. // This is the base64 encoded gzipped contents of fields.yml. func AssetFieldsYml() string { - return "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" + return "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" } diff --git a/heartbeat/monitors/active/dialchain/_meta/fields.yml b/heartbeat/monitors/active/dialchain/_meta/fields.yml index 288fe9edca7..eb7e66bd6f7 100644 --- a/heartbeat/monitors/active/dialchain/_meta/fields.yml +++ b/heartbeat/monitors/active/dialchain/_meta/fields.yml @@ -140,7 +140,7 @@ - name: subject type: group fields: - - name: subject.common_name + - name: common_name type: keyword ignore_above: 1024 description: List of common names (CN) of subject. @@ -150,7 +150,7 @@ - name: text type: text analyzer: simple - - name: subject.distinguished_name + - name: distinguished_name type: keyword ignore_above: 1024 description: Distinguished name (DN) of the certificate subject entity. From 6473a6ed36d8a15d75e8bfea8b09bd86b3f06710 Mon Sep 17 00:00:00 2001 From: Andrew Kroh Date: Mon, 4 May 2020 12:06:01 -0400 Subject: [PATCH 094/116] Add process.command_line to Sysmon module (#17823) * Add process.command_line to Sysmon module Adds the process.command_line field to Sysmon module that does not split the field into multiple values. * Handle process.parent.command_line Handle the parent process. Update golden files Co-authored-by: webhead404 --- CHANGELOG.next.asciidoc | 1 + .../module/sysmon/config/winlogbeat-sysmon.js | 14 +++++++------- .../test/testdata/sysmon-9.01.evtx.golden.json | 6 ++++++ 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index b60768970d1..ebe677a187d 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -378,6 +378,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Add more DNS error codes to the Sysmon module. {issue}15685[15685] - Add experimental event log reader implementation that should be faster in most cases. {issue}6585[6585] {pull}16849[16849] +- Set process.command_line and process.parent.command_line from Sysmon Event ID 1. {pull}17327[17327] - Add support for event IDs 4673,4674,4697,4698,4699,4700,4701,4702,4768,4769,4770,4771,4776,4778,4779,4964 to the Security module {pull}17517[17517] ==== Deprecated diff --git a/x-pack/winlogbeat/module/sysmon/config/winlogbeat-sysmon.js b/x-pack/winlogbeat/module/sysmon/config/winlogbeat-sysmon.js index 83f24193a66..b882df875fc 100644 --- a/x-pack/winlogbeat/module/sysmon/config/winlogbeat-sysmon.js +++ b/x-pack/winlogbeat/module/sysmon/config/winlogbeat-sysmon.js @@ -301,20 +301,20 @@ var sysmon = (function () { evt.Put(nameField, path.basename(exe)); }; - var splitCommandLine = function(evt, field) { - var commandLine = evt.Get(field); + var splitCommandLine = function(evt, source, target) { + var commandLine = evt.Get(source); if (!commandLine) { return; } - evt.Put(field, winlogbeat.splitCommandLine(commandLine)); + evt.Put(target, winlogbeat.splitCommandLine(commandLine)); }; var splitProcessArgs = function(evt) { - splitCommandLine(evt, "process.args"); + splitCommandLine(evt, "process.command_line", "process.args"); }; var splitParentProcessArgs = function(evt) { - splitCommandLine(evt, "process.parent.args"); + splitCommandLine(evt, "process.parent.command_line", "process.parent.args"); }; var addUser = function(evt) { @@ -468,12 +468,12 @@ var sysmon = (function () { {from: "winlog.event_data.ProcessGuid", to: "process.entity_id"}, {from: "winlog.event_data.ProcessId", to: "process.pid", type: "long"}, {from: "winlog.event_data.Image", to: "process.executable"}, - {from: "winlog.event_data.CommandLine", to: "process.args"}, + {from: "winlog.event_data.CommandLine", to: "process.command_line"}, {from: "winlog.event_data.CurrentDirectory", to: "process.working_directory"}, {from: "winlog.event_data.ParentProcessGuid", to: "process.parent.entity_id"}, {from: "winlog.event_data.ParentProcessId", to: "process.parent.pid", type: "long"}, {from: "winlog.event_data.ParentImage", to: "process.parent.executable"}, - {from: "winlog.event_data.ParentCommandLine", to: "process.parent.args"}, + {from: "winlog.event_data.ParentCommandLine", to: "process.parent.command_line"}, ], mode: "rename", ignore_missing: true, diff --git a/x-pack/winlogbeat/module/sysmon/test/testdata/sysmon-9.01.evtx.golden.json b/x-pack/winlogbeat/module/sysmon/test/testdata/sysmon-9.01.evtx.golden.json index 52d0cbafb74..b083f5aba41 100644 --- a/x-pack/winlogbeat/module/sysmon/test/testdata/sysmon-9.01.evtx.golden.json +++ b/x-pack/winlogbeat/module/sysmon/test/testdata/sysmon-9.01.evtx.golden.json @@ -101,6 +101,7 @@ "args": [ "C:\\Windows\\Sysmon.exe" ], + "command_line": "C:\\Windows\\Sysmon.exe", "entity_id": "{42f11c3b-ce01-5c8f-0000-0010c73e2a00}", "executable": "C:\\Windows\\Sysmon.exe", "name": "Sysmon.exe", @@ -108,6 +109,7 @@ "args": [ "C:\\Windows\\system32\\services.exe" ], + "command_line": "C:\\Windows\\system32\\services.exe", "entity_id": "{42f11c3b-6e1a-5c8c-0000-0010f14d0000}", "executable": "C:\\Windows\\System32\\services.exe", "name": "services.exe", @@ -177,6 +179,7 @@ "C:\\Windows\\system32\\wbem\\unsecapp.exe", "-Embedding" ], + "command_line": "C:\\Windows\\system32\\wbem\\unsecapp.exe -Embedding", "entity_id": "{42f11c3b-ce01-5c8f-0000-00102c412a00}", "executable": "C:\\Windows\\System32\\wbem\\unsecapp.exe", "name": "unsecapp.exe", @@ -186,6 +189,7 @@ "-k", "DcomLaunch" ], + "command_line": "C:\\Windows\\system32\\svchost.exe -k DcomLaunch", "entity_id": "{42f11c3b-6e1b-5c8c-0000-00102f610000}", "executable": "C:\\Windows\\System32\\svchost.exe", "name": "svchost.exe", @@ -345,6 +349,7 @@ "C:\\Windows\\system32\\wbem\\wmiprvse.exe", "-Embedding" ], + "command_line": "C:\\Windows\\system32\\wbem\\wmiprvse.exe -Embedding", "entity_id": "{42f11c3b-ce03-5c8f-0000-0010e9462a00}", "executable": "C:\\Windows\\System32\\wbem\\WmiPrvSE.exe", "name": "WmiPrvSE.exe", @@ -354,6 +359,7 @@ "-k", "DcomLaunch" ], + "command_line": "C:\\Windows\\system32\\svchost.exe -k DcomLaunch", "entity_id": "{42f11c3b-6e1b-5c8c-0000-00102f610000}", "executable": "C:\\Windows\\System32\\svchost.exe", "name": "svchost.exe", From f80f82c8dbe15f0215a7224c8b633312efbb4c71 Mon Sep 17 00:00:00 2001 From: Andrew Kroh Date: Mon, 4 May 2020 12:23:26 -0400 Subject: [PATCH 095/116] Skip add_host_metadata for forwarded event logs (#18153) Update config examples to use the "forwarded" tag to skip adding host metadata. Also disable host.name being added by libbeat. This field was overwritten by the winlog.computer_name so it didn't serve any purpose to have libbeat set it. Relates #13920 --- CHANGELOG.next.asciidoc | 1 + dev-tools/mage/config.go | 1 + libbeat/_meta/config.yml.tmpl | 3 ++- winlogbeat/_meta/beat.yml.tmpl | 17 +++++------------ winlogbeat/_meta/common.yml.tmpl | 6 ++++++ winlogbeat/cmd/root.go | 7 ++++++- winlogbeat/scripts/mage/config.go | 3 ++- winlogbeat/winlogbeat.reference.yml | 11 ++--------- winlogbeat/winlogbeat.yml | 8 ++++---- x-pack/winlogbeat/_meta/beat.yml.tmpl | 14 ++++++++++++++ x-pack/winlogbeat/winlogbeat.reference.yml | 14 ++++++++++++++ x-pack/winlogbeat/winlogbeat.yml | 20 ++++++++++++++++---- 12 files changed, 73 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index ebe677a187d..d0073444094 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -212,6 +212,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Add Kerberos support to Elasticsearch output. {pull}17927[17927] - Add support for fixed length extraction in `dissect` processor. {pull}17191[17191] - Set `agent.name` to the hostname by default. {issue}16377[16377] {pull}18000[18000] +- Add config example of how to skip the `add_host_metadata` processor when forwarding logs. {issue}13920[13920] {pull}18153[18153] *Auditbeat* diff --git a/dev-tools/mage/config.go b/dev-tools/mage/config.go index 668c71e1074..6cabac5c9b6 100644 --- a/dev-tools/mage/config.go +++ b/dev-tools/mage/config.go @@ -116,6 +116,7 @@ func Config(types ConfigFileType, args ConfigFileParams, targetDir string) error "UseDockerMetadataProcessor": true, "UseKubernetesMetadataProcessor": false, "ExcludeDashboards": false, + "UseProcessorsTemplate": false, } for k, v := range args.ExtraVars { params[k] = v diff --git a/libbeat/_meta/config.yml.tmpl b/libbeat/_meta/config.yml.tmpl index 2d5e510e33f..3cf0e3e00af 100644 --- a/libbeat/_meta/config.yml.tmpl +++ b/libbeat/_meta/config.yml.tmpl @@ -90,6 +90,7 @@ output.elasticsearch: #ssl.key: "/etc/pki/client/cert.key" {{end}} #================================ Processors ===================================== +{{if .UseProcessorsTemplate}}{{template "processors.yml.tmpl" .}}{{else -}} {{if not .UseObserverProcessor}} # Configure processors to enhance or manipulate events generated by the beat. @@ -112,7 +113,7 @@ processors: #name: us-east-1a # Lat, Lon " #location: "37.926868, -78.024902" -{{end}} +{{end}}{{end}} #================================ Logging ===================================== # Sets log level. The default log level is info. diff --git a/winlogbeat/_meta/beat.yml.tmpl b/winlogbeat/_meta/beat.yml.tmpl index 24b27d36648..093c6f69c04 100644 --- a/winlogbeat/_meta/beat.yml.tmpl +++ b/winlogbeat/_meta/beat.yml.tmpl @@ -2,18 +2,11 @@ winlogbeat.event_logs: - name: Application ignore_older: 72h -{{if .Reference}} - # Set to true to publish fields with null values in events. - #keep_null: false -{{end}} + - name: System -{{if .Reference}} - # Set to true to publish fields with null values in events. - #keep_null: false -{{end}} + - name: Security -{{if .Reference}} - # Set to true to publish fields with null values in events. - #keep_null: false -{{end}} + + - name: ForwardedEvents + tags: [forwarded] {{if not .Reference}}{{ template "elasticsearch_settings" . }}{{end}} diff --git a/winlogbeat/_meta/common.yml.tmpl b/winlogbeat/_meta/common.yml.tmpl index 7a7feaddf5a..63aa30fa0b5 100644 --- a/winlogbeat/_meta/common.yml.tmpl +++ b/winlogbeat/_meta/common.yml.tmpl @@ -34,3 +34,9 @@ setup.template.settings: #index.codec: best_compression #_source.enabled: false {{end -}} +{{define "processors.yml.tmpl"}} +processors: + - add_host_metadata: + when.not.contains.tags: forwarded + - add_cloud_metadata: ~ +{{end -}} diff --git a/winlogbeat/cmd/root.go b/winlogbeat/cmd/root.go index 7075a51aeb0..ecc3aa8e38f 100644 --- a/winlogbeat/cmd/root.go +++ b/winlogbeat/cmd/root.go @@ -20,6 +20,7 @@ package cmd import ( "github.com/elastic/beats/v7/libbeat/cmd" "github.com/elastic/beats/v7/libbeat/cmd/instance" + "github.com/elastic/beats/v7/libbeat/publisher/processing" "github.com/elastic/beats/v7/winlogbeat/beater" // Register fields. @@ -35,4 +36,8 @@ import ( var Name = "winlogbeat" // RootCmd to handle beats cli -var RootCmd = cmd.GenRootCmdWithSettings(beater.New, instance.Settings{Name: Name, HasDashboards: true}) +var RootCmd = cmd.GenRootCmdWithSettings(beater.New, instance.Settings{ + Name: Name, + HasDashboards: true, + Processing: processing.MakeDefaultSupport(true, processing.WithECS, processing.WithAgentMeta()), +}) diff --git a/winlogbeat/scripts/mage/config.go b/winlogbeat/scripts/mage/config.go index 1dae96510dd..70cc8cb43a6 100644 --- a/winlogbeat/scripts/mage/config.go +++ b/winlogbeat/scripts/mage/config.go @@ -54,7 +54,8 @@ func configFileParams() devtools.ConfigFileParams { devtools.LibbeatDir("_meta/config.docker.yml"), }, ExtraVars: map[string]interface{}{ - "GOOS": "windows", + "GOOS": "windows", + "UseProcessorsTemplate": true, }, } } diff --git a/winlogbeat/winlogbeat.reference.yml b/winlogbeat/winlogbeat.reference.yml index 471b6c4e7fc..39e22d25e34 100644 --- a/winlogbeat/winlogbeat.reference.yml +++ b/winlogbeat/winlogbeat.reference.yml @@ -26,19 +26,12 @@ winlogbeat.event_logs: - name: Application ignore_older: 72h - # Set to true to publish fields with null values in events. - #keep_null: false - - name: System - # Set to true to publish fields with null values in events. - #keep_null: false - - name: Security - # Set to true to publish fields with null values in events. - #keep_null: false - + - name: ForwardedEvents + tags: [forwarded] #================================ General ====================================== diff --git a/winlogbeat/winlogbeat.yml b/winlogbeat/winlogbeat.yml index 8887e8d75c7..d816327de99 100644 --- a/winlogbeat/winlogbeat.yml +++ b/winlogbeat/winlogbeat.yml @@ -25,6 +25,8 @@ winlogbeat.event_logs: - name: Security + - name: ForwardedEvents + tags: [forwarded] #==================== Elasticsearch template settings ========================== setup.template.settings: @@ -125,12 +127,10 @@ output.elasticsearch: #================================ Processors ===================================== -# Configure processors to enhance or manipulate events generated by the beat. - processors: - - add_host_metadata: ~ + - add_host_metadata: + when.not.contains.tags: forwarded - add_cloud_metadata: ~ - - add_docker_metadata: ~ #================================ Logging ===================================== diff --git a/x-pack/winlogbeat/_meta/beat.yml.tmpl b/x-pack/winlogbeat/_meta/beat.yml.tmpl index f2660df68bd..1ea8cdcc879 100644 --- a/x-pack/winlogbeat/_meta/beat.yml.tmpl +++ b/x-pack/winlogbeat/_meta/beat.yml.tmpl @@ -19,4 +19,18 @@ winlogbeat.event_logs: id: sysmon file: ${path.home}/module/sysmon/config/winlogbeat-sysmon.js + - name: ForwardedEvents + tags: [forwarded] + processors: + - script: + when.equals.winlog.channel: Security + lang: javascript + id: security + file: ${path.home}/module/security/config/winlogbeat-security.js + - script: + when.equals.winlog.channel: Microsoft-Windows-Sysmon/Operational + lang: javascript + id: sysmon + file: ${path.home}/module/sysmon/config/winlogbeat-sysmon.js + {{if not .Reference}}{{ template "elasticsearch_settings" . }}{{end}} diff --git a/x-pack/winlogbeat/winlogbeat.reference.yml b/x-pack/winlogbeat/winlogbeat.reference.yml index c8643d904ab..3fd2ffcba4f 100644 --- a/x-pack/winlogbeat/winlogbeat.reference.yml +++ b/x-pack/winlogbeat/winlogbeat.reference.yml @@ -42,6 +42,20 @@ winlogbeat.event_logs: id: sysmon file: ${path.home}/module/sysmon/config/winlogbeat-sysmon.js + - name: ForwardedEvents + tags: [forwarded] + processors: + - script: + when.equals.winlog.channel: Security + lang: javascript + id: security + file: ${path.home}/module/security/config/winlogbeat-security.js + - script: + when.equals.winlog.channel: Microsoft-Windows-Sysmon/Operational + lang: javascript + id: sysmon + file: ${path.home}/module/sysmon/config/winlogbeat-sysmon.js + #================================ General ====================================== diff --git a/x-pack/winlogbeat/winlogbeat.yml b/x-pack/winlogbeat/winlogbeat.yml index bc5dbc294d2..e718fb91d41 100644 --- a/x-pack/winlogbeat/winlogbeat.yml +++ b/x-pack/winlogbeat/winlogbeat.yml @@ -37,6 +37,20 @@ winlogbeat.event_logs: id: sysmon file: ${path.home}/module/sysmon/config/winlogbeat-sysmon.js + - name: ForwardedEvents + tags: [forwarded] + processors: + - script: + when.equals.winlog.channel: Security + lang: javascript + id: security + file: ${path.home}/module/security/config/winlogbeat-security.js + - script: + when.equals.winlog.channel: Microsoft-Windows-Sysmon/Operational + lang: javascript + id: sysmon + file: ${path.home}/module/sysmon/config/winlogbeat-sysmon.js + #==================== Elasticsearch template settings ========================== setup.template.settings: @@ -137,12 +151,10 @@ output.elasticsearch: #================================ Processors ===================================== -# Configure processors to enhance or manipulate events generated by the beat. - processors: - - add_host_metadata: ~ + - add_host_metadata: + when.not.contains.tags: forwarded - add_cloud_metadata: ~ - - add_docker_metadata: ~ #================================ Logging ===================================== From 6ea21a9c4b433ce4dff83d815f856fce17d16f4e Mon Sep 17 00:00:00 2001 From: Andrew Kroh Date: Mon, 4 May 2020 12:38:15 -0400 Subject: [PATCH 096/116] Add a disable_host option to Filebeat inputs (#18159) This adds a configuration option `publisher_pipeline.disable_host` to disable the addition of `host.name` in events. By default Filebeat adds `host.name` to all events and we want to be able to disable this for data sources that do not originate on the host (like cloud logs). Relates #13920 --- CHANGELOG.next.asciidoc | 2 ++ filebeat/_meta/common.reference.inputs.yml | 5 +++++ filebeat/channel/runner.go | 5 +++++ .../docs/inputs/input-common-options.asciidoc | 6 ++++++ filebeat/filebeat.reference.yml | 5 +++++ filebeat/tests/system/config/filebeat.yml.j2 | 5 +++++ filebeat/tests/system/test_input.py | 20 +++++++++++++++++++ libbeat/beat/pipeline.go | 3 +++ libbeat/publisher/processing/default.go | 6 ++++++ x-pack/filebeat/filebeat.reference.yml | 5 +++++ 10 files changed, 62 insertions(+) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index d0073444094..2e7c57ca634 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -289,6 +289,8 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Improve ECS categorization field mappings in redis module. {issue}16179[16179] {pull}17918[17918] - Improve ECS categorization field mappings for zeek module. {issue}16029[16029] {pull}17738[17738] - Improve ECS categorization field mappings for netflow module. {issue}16135[16135] {pull}18108[18108] +- Added an input option `publisher_pipeline.disable_host` to disable `host.name` + from being added to events by default. {pull}18159[18159] - Improve ECS categorization field mappings in system module. {issue}16031[16031] {pull}18065[18065] *Heartbeat* diff --git a/filebeat/_meta/common.reference.inputs.yml b/filebeat/_meta/common.reference.inputs.yml index b7de598be0b..db105f17ef0 100644 --- a/filebeat/_meta/common.reference.inputs.yml +++ b/filebeat/_meta/common.reference.inputs.yml @@ -65,6 +65,11 @@ filebeat.inputs: # Set to true to publish fields with null values in events. #keep_null: false + # By default, all events contain `host.name`. This option can be set to true + # to disable the addition of this field to all events. The default value is + # false. + #publisher_pipeline.disable_host: false + # Ignore files which were modified more then the defined timespan in the past. # ignore_older is disabled by default, so no files are ignored by setting it to 0. # Time strings like 2h (2 hours), 5m (5 minutes) can be used. diff --git a/filebeat/channel/runner.go b/filebeat/channel/runner.go index 51a17e17939..941e1cc8161 100644 --- a/filebeat/channel/runner.go +++ b/filebeat/channel/runner.go @@ -42,6 +42,10 @@ type commonInputConfig struct { Processors processors.PluginConfig `config:"processors"` KeepNull bool `config:"keep_null"` + PublisherPipeline struct { + DisableHost bool `config:"disable_host"` // Disable addition of host.name. + } `config:"publisher_pipeline"` + // implicit event fields Type string `config:"type"` // input.type ServiceType string `config:"service.type"` // service.type @@ -184,6 +188,7 @@ func newCommonConfigEditor( clientCfg.Processing.Fields = fields clientCfg.Processing.Processor = procs clientCfg.Processing.KeepNull = config.KeepNull + clientCfg.Processing.DisableHost = config.PublisherPipeline.DisableHost return clientCfg, nil }, nil diff --git a/filebeat/docs/inputs/input-common-options.asciidoc b/filebeat/docs/inputs/input-common-options.asciidoc index 8e08a8074e4..de22a519846 100644 --- a/filebeat/docs/inputs/input-common-options.asciidoc +++ b/filebeat/docs/inputs/input-common-options.asciidoc @@ -101,3 +101,9 @@ version and the event timestamp; for access to dynamic fields, use Example value: `"%{[agent.name]}-myindex-%{+yyyy.MM.dd}"` might expand to `"filebeat-myindex-2019.11.01"`. + +[float] +===== `publisher_pipeline.disable_host` + +By default, all events contain `host.name`. This option can be set to `true` to +disable the addition of this field to all events. The default value is `false`. diff --git a/filebeat/filebeat.reference.yml b/filebeat/filebeat.reference.yml index dfa8f631360..ea2ad11e672 100644 --- a/filebeat/filebeat.reference.yml +++ b/filebeat/filebeat.reference.yml @@ -451,6 +451,11 @@ filebeat.inputs: # Set to true to publish fields with null values in events. #keep_null: false + # By default, all events contain `host.name`. This option can be set to true + # to disable the addition of this field to all events. The default value is + # false. + #publisher_pipeline.disable_host: false + # Ignore files which were modified more then the defined timespan in the past. # ignore_older is disabled by default, so no files are ignored by setting it to 0. # Time strings like 2h (2 hours), 5m (5 minutes) can be used. diff --git a/filebeat/tests/system/config/filebeat.yml.j2 b/filebeat/tests/system/config/filebeat.yml.j2 index 0c48b34ebc2..2275d761bca 100644 --- a/filebeat/tests/system/config/filebeat.yml.j2 +++ b/filebeat/tests/system/config/filebeat.yml.j2 @@ -52,6 +52,11 @@ filebeat.{{input_config | default("inputs")}}: {{k}}: {{v}} {% endfor %} {% endif %} + {%- if publisher_pipeline %} + {%- for name, value in publisher_pipeline.items() %} + publisher_pipeline.{{name}}: {{value | tojson}} + {%- endfor %} + {% endif %} fields_under_root: {{"true" if fieldsUnderRoot else "false"}} diff --git a/filebeat/tests/system/test_input.py b/filebeat/tests/system/test_input.py index 0075329205b..684f4f852af 100644 --- a/filebeat/tests/system/test_input.py +++ b/filebeat/tests/system/test_input.py @@ -662,3 +662,23 @@ def test_disable_recursive_glob(self): "recursive glob disabled"), max_timeout=10) filebeat.check_kill_and_wait() + + def test_input_processing_pipeline_disable_host(self): + """ + Check processing_pipeline.disable_host in input config. + """ + self.render_config_template( + path=os.path.abspath(self.working_dir) + "/test.log", + publisher_pipeline={ + "disable_host": True, + }, + ) + with open(self.working_dir + "/test.log", "w") as f: + f.write("test message\n") + + filebeat = self.start_beat() + self.wait_until(lambda: self.output_has(lines=1)) + filebeat.check_kill_and_wait() + + output = self.read_output() + assert "host.name" not in output[0] diff --git a/libbeat/beat/pipeline.go b/libbeat/beat/pipeline.go index c4f4665af1b..9d6fbf04e86 100644 --- a/libbeat/beat/pipeline.go +++ b/libbeat/beat/pipeline.go @@ -110,6 +110,9 @@ type ProcessingConfig struct { // KeepNull determines whether published events will keep null values or omit them. KeepNull bool + // Disables the addition of host.name if it was enabled for the publisher. + DisableHost bool + // Private contains additional information to be passed to the processing // pipeline builder. Private interface{} diff --git a/libbeat/publisher/processing/default.go b/libbeat/publisher/processing/default.go index f020423b733..cf99e03d4d3 100644 --- a/libbeat/publisher/processing/default.go +++ b/libbeat/publisher/processing/default.go @@ -269,6 +269,12 @@ func (b *builder) Create(cfg beat.ProcessingConfig, drop bool) (beat.Processor, needsCopy := b.alwaysCopy || localProcessors != nil || b.processors != nil builtin := b.builtinMeta + if cfg.DisableHost { + tmp := builtin.Clone() + tmp.Delete("host") + builtin = tmp + } + var clientFields common.MapStr for _, mod := range b.modifiers { m := mod.ClientFields(b.info, cfg) diff --git a/x-pack/filebeat/filebeat.reference.yml b/x-pack/filebeat/filebeat.reference.yml index 9ac62c5e34f..115d8678fd7 100644 --- a/x-pack/filebeat/filebeat.reference.yml +++ b/x-pack/filebeat/filebeat.reference.yml @@ -1114,6 +1114,11 @@ filebeat.inputs: # Set to true to publish fields with null values in events. #keep_null: false + # By default, all events contain `host.name`. This option can be set to true + # to disable the addition of this field to all events. The default value is + # false. + #publisher_pipeline.disable_host: false + # Ignore files which were modified more then the defined timespan in the past. # ignore_older is disabled by default, so no files are ignored by setting it to 0. # Time strings like 2h (2 hours), 5m (5 minutes) can be used. From 1c2e4b9c27d7775933e34f494e18b475c9e4f525 Mon Sep 17 00:00:00 2001 From: Michal Pristas Date: Mon, 4 May 2020 18:40:23 +0200 Subject: [PATCH 097/116] [Elastic-Agent] Enable introspecting configuration (#18124) * introspection * changelog * mage fmt * introspect -> inspect * follow -c argument * conflicts --- x-pack/elastic-agent/CHANGELOG.asciidoc | 1 + .../pkg/agent/application/emitter.go | 6 +- .../application/introspect_config_cmd.go | 131 +++++++++++ .../application/introspect_output_cmd.go | 211 ++++++++++++++++++ x-pack/elastic-agent/pkg/agent/cmd/common.go | 1 + .../elastic-agent/pkg/agent/cmd/introspect.go | 69 ++++++ 6 files changed, 418 insertions(+), 1 deletion(-) create mode 100644 x-pack/elastic-agent/pkg/agent/application/introspect_config_cmd.go create mode 100644 x-pack/elastic-agent/pkg/agent/application/introspect_output_cmd.go create mode 100644 x-pack/elastic-agent/pkg/agent/cmd/introspect.go diff --git a/x-pack/elastic-agent/CHANGELOG.asciidoc b/x-pack/elastic-agent/CHANGELOG.asciidoc index 7c155d0eee5..78383de2f64 100644 --- a/x-pack/elastic-agent/CHANGELOG.asciidoc +++ b/x-pack/elastic-agent/CHANGELOG.asciidoc @@ -47,4 +47,5 @@ - Use data subfolder as default for process logs {pull}17960[17960] - Do not require unnecessary configuration {pull}18003[18003] - Enable debug log level for Metricbeat and Filebeat when run under the Elastic Agent. {pull}17935[17935] +- Enable introspecting configuration {pull}18124[18124] - Follow home path for all config files {pull}18161[18161] diff --git a/x-pack/elastic-agent/pkg/agent/application/emitter.go b/x-pack/elastic-agent/pkg/agent/application/emitter.go index 2279d78565d..249acdd213f 100644 --- a/x-pack/elastic-agent/pkg/agent/application/emitter.go +++ b/x-pack/elastic-agent/pkg/agent/application/emitter.go @@ -26,7 +26,11 @@ type configModifiers struct { Decorators []decoratorFunc } -func emitter(log *logger.Logger, router *router, modifiers *configModifiers, reloadables ...reloadable) emitterFunc { +type programsDispatcher interface { + Dispatch(id string, grpProg map[routingKey][]program.Program) error +} + +func emitter(log *logger.Logger, router programsDispatcher, modifiers *configModifiers, reloadables ...reloadable) emitterFunc { return func(c *config.Config) error { if err := InjectAgentConfig(c); err != nil { return err diff --git a/x-pack/elastic-agent/pkg/agent/application/introspect_config_cmd.go b/x-pack/elastic-agent/pkg/agent/application/introspect_config_cmd.go new file mode 100644 index 00000000000..f5ab634b84d --- /dev/null +++ b/x-pack/elastic-agent/pkg/agent/application/introspect_config_cmd.go @@ -0,0 +1,131 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package application + +import ( + "fmt" + + yaml "gopkg.in/yaml.v2" + + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application/info" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/errors" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/storage" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/config" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/fleetapi" +) + +// IntrospectConfigCmd is an introspect subcommand that shows configurations of the agent. +type IntrospectConfigCmd struct { + cfgPath string +} + +// NewIntrospectConfigCmd creates a new introspect command. +func NewIntrospectConfigCmd(configPath string, +) (*IntrospectConfigCmd, error) { + return &IntrospectConfigCmd{ + cfgPath: configPath, + }, nil +} + +// Execute introspects agent configuration. +func (c *IntrospectConfigCmd) Execute() error { + return c.introspectConfig() +} + +func (c *IntrospectConfigCmd) introspectConfig() error { + cfg, err := loadConfig(c.cfgPath) + if err != nil { + return err + } + + isLocal, err := isLocalMode(cfg) + if err != nil { + return err + } + + if isLocal { + return printConfig(cfg) + } + + fleetConfig, err := loadFleetConfig(cfg) + if err != nil { + return err + } else if fleetConfig == nil { + return errors.New("no fleet config retrieved yet") + } + + return printMapStringConfig(fleetConfig) +} + +func loadConfig(configPath string) (*config.Config, error) { + rawConfig, err := config.LoadYAML(configPath) + if err != nil { + return nil, err + } + + if err := InjectAgentConfig(rawConfig); err != nil { + return nil, err + } + + return rawConfig, nil +} + +func loadFleetConfig(cfg *config.Config) (map[string]interface{}, error) { + log, err := newErrorLogger() + if err != nil { + return nil, err + } + + as, err := newActionStore(log, storage.NewDiskStore(info.AgentActionStoreFile())) + if err != nil { + return nil, err + } + + for _, c := range as.Actions() { + cfgChange, ok := c.(*fleetapi.ActionConfigChange) + if !ok { + continue + } + + fmt.Println("Action ID:", cfgChange.ID()) + return cfgChange.Config, nil + } + return nil, nil +} + +func isLocalMode(rawConfig *config.Config) (bool, error) { + c := localDefaultConfig() + if err := rawConfig.Unpack(&c); err != nil { + return false, errors.New(err, "initiating application") + } + + managementConfig := struct { + Mode string `config:"mode" yaml:"mode"` + }{} + + if err := c.Management.Unpack(&managementConfig); err != nil { + return false, errors.New(err, "initiating application") + } + return managementConfig.Mode == "local", nil +} + +func printMapStringConfig(mapStr map[string]interface{}) error { + data, err := yaml.Marshal(mapStr) + if err != nil { + return errors.New(err, "could not marshal to YAML") + } + + fmt.Println(string(data)) + return nil +} + +func printConfig(cfg *config.Config) error { + mapStr, err := cfg.ToMapStr() + if err != nil { + return err + } + + return printMapStringConfig(mapStr) +} diff --git a/x-pack/elastic-agent/pkg/agent/application/introspect_output_cmd.go b/x-pack/elastic-agent/pkg/agent/application/introspect_output_cmd.go new file mode 100644 index 00000000000..26eb424fe97 --- /dev/null +++ b/x-pack/elastic-agent/pkg/agent/application/introspect_output_cmd.go @@ -0,0 +1,211 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package application + +import ( + "fmt" + + "github.com/urso/ecslog" + "github.com/urso/ecslog/backend" + "github.com/urso/ecslog/backend/appender" + "github.com/urso/ecslog/backend/layout" + + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application/filters" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/errors" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/program" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/config" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/core/logger" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/core/plugin/app/monitoring/noop" +) + +// IntrospectOutputCmd is an introspect subcommand that shows configurations of the agent. +type IntrospectOutputCmd struct { + cfgPath string + output string + program string +} + +// NewIntrospectOutputCmd creates a new introspect command. +func NewIntrospectOutputCmd(configPath, output, program string) (*IntrospectOutputCmd, error) { + return &IntrospectOutputCmd{ + cfgPath: configPath, + output: output, + program: program, + }, nil +} + +// Execute tries to enroll the agent into Fleet. +func (c *IntrospectOutputCmd) Execute() error { + if c.output == "" { + return c.introspectOutputs() + } + + return c.introspectOutput() +} + +func (c *IntrospectOutputCmd) introspectOutputs() error { + cfg, err := loadConfig(c.cfgPath) + if err != nil { + return err + } + + isLocal, err := isLocalMode(cfg) + if err != nil { + return err + } + + l, err := newErrorLogger() + if err != nil { + return err + } + + if isLocal { + return listOutputsFromConfig(l, cfg) + } + + fleetConfig, err := loadFleetConfig(cfg) + if err != nil { + return err + } else if fleetConfig == nil { + return errors.New("no fleet config retrieved yet") + } + + return listOutputsFromMap(l, fleetConfig) +} + +func listOutputsFromConfig(log *logger.Logger, cfg *config.Config) error { + programsGroup, err := getProgramsFromConfig(log, cfg) + if err != nil { + return err + + } + + for k := range programsGroup { + fmt.Println(k) + } + + return nil +} + +func listOutputsFromMap(log *logger.Logger, cfg map[string]interface{}) error { + c, err := config.NewConfigFrom(cfg) + if err != nil { + return err + } + + return listOutputsFromConfig(log, c) +} + +func (c *IntrospectOutputCmd) introspectOutput() error { + cfg, err := loadConfig(c.cfgPath) + if err != nil { + return err + } + + l, err := newErrorLogger() + if err != nil { + return err + } + + isLocal, err := isLocalMode(cfg) + if err != nil { + return err + } + + if isLocal { + return printOutputFromConfig(l, c.output, c.program, cfg) + } + + fleetConfig, err := loadFleetConfig(cfg) + if err != nil { + return err + } else if fleetConfig == nil { + return errors.New("no fleet config retrieved yet") + } + + return printOutputFromMap(l, c.output, c.program, fleetConfig) +} + +func printOutputFromConfig(log *logger.Logger, output, programName string, cfg *config.Config) error { + programsGroup, err := getProgramsFromConfig(log, cfg) + if err != nil { + return err + + } + + for k, programs := range programsGroup { + if k != output { + continue + } + + var programFound bool + for _, p := range programs { + if programName != "" && programName != p.Spec.Cmd { + continue + } + + programFound = true + fmt.Printf("[%s] %s:\n", k, p.Spec.Cmd) + printMapStringConfig(p.Configuration()) + fmt.Println("---") + } + + if !programFound { + fmt.Printf("program '%s' is not recognized within output '%s', try running `elastic-agent introspect output` to find available outputs.\n", + programName, + output) + } + return nil + } + + fmt.Printf("output '%s' is not recognized, try running `elastic-agent introspect output` to find available outputs.\n", output) + + return nil +} + +func printOutputFromMap(log *logger.Logger, output, programName string, cfg map[string]interface{}) error { + c, err := config.NewConfigFrom(cfg) + if err != nil { + return err + } + + return printOutputFromConfig(log, output, programName, c) +} + +func getProgramsFromConfig(log *logger.Logger, cfg *config.Config) (map[string][]program.Program, error) { + monitor := noop.NewMonitor() + router := &inmemRouter{} + emit := emitter( + log, + router, + &configModifiers{ + Decorators: []decoratorFunc{injectMonitoring}, + Filters: []filterFunc{filters.ConstraintFilter}, + }, + monitor, + ) + + if err := emit(cfg); err != nil { + return nil, err + } + return router.programs, nil +} + +type inmemRouter struct { + programs map[string][]program.Program +} + +func (r *inmemRouter) Dispatch(id string, grpProg map[routingKey][]program.Program) error { + r.programs = grpProg + return nil +} + +func newErrorLogger() (*logger.Logger, error) { + backend, err := appender.Console(backend.Error, layout.Text(true)) + if err != nil { + return nil, err + } + return ecslog.New(backend), nil +} diff --git a/x-pack/elastic-agent/pkg/agent/cmd/common.go b/x-pack/elastic-agent/pkg/agent/cmd/common.go index 5fe9947e34f..54b51202ef5 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/common.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/common.go @@ -61,6 +61,7 @@ func NewCommandWithArgs(args []string, streams *cli.IOStreams) *cobra.Command { cmd.AddCommand(basecmd.NewDefaultCommandsWithArgs(args, streams)...) cmd.AddCommand(newRunCommandWithArgs(flags, args, streams)) cmd.AddCommand(newEnrollCommandWithArgs(flags, args, streams)) + cmd.AddCommand(newIntrospectCommandWithArgs(flags, args, streams)) return cmd } diff --git a/x-pack/elastic-agent/pkg/agent/cmd/introspect.go b/x-pack/elastic-agent/pkg/agent/cmd/introspect.go new file mode 100644 index 00000000000..f6cb40e1894 --- /dev/null +++ b/x-pack/elastic-agent/pkg/agent/cmd/introspect.go @@ -0,0 +1,69 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package cmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/cli" +) + +func newIntrospectCommandWithArgs(flags *globalFlags, s []string, streams *cli.IOStreams) *cobra.Command { + cmd := &cobra.Command{ + Use: "inspect", + Short: "Shows configuration of the agent", + Long: "Shows current configuration of the agent", + Args: cobra.ExactArgs(0), + Run: func(c *cobra.Command, args []string) { + command, err := application.NewIntrospectConfigCmd(flags.Config()) + if err != nil { + fmt.Fprintf(streams.Err, "%v\n", err) + os.Exit(1) + } + + if err := command.Execute(); err != nil { + fmt.Fprintf(streams.Err, "%v\n", err) + os.Exit(1) + } + }, + } + + cmd.AddCommand(newIntrospectOutputCommandWithArgs(flags, s, streams)) + + return cmd +} + +func newIntrospectOutputCommandWithArgs(flags *globalFlags, _ []string, streams *cli.IOStreams) *cobra.Command { + cmd := &cobra.Command{ + Use: "output", + Short: "Displays configuration generated for output", + Long: "Displays configuration generated for output.\nIf no output is specified list of output is displayed", + Args: cobra.MaximumNArgs(2), + Run: func(c *cobra.Command, args []string) { + outName, _ := c.Flags().GetString("output") + program, _ := c.Flags().GetString("program") + + command, err := application.NewIntrospectOutputCmd(flags.Config(), outName, program) + if err != nil { + fmt.Fprintf(streams.Err, "%v\n", err) + os.Exit(1) + } + + if err := command.Execute(); err != nil { + fmt.Fprintf(streams.Err, "%v\n", err) + os.Exit(1) + } + }, + } + + cmd.Flags().StringP("output", "o", "", "name of the output to be introspected") + cmd.Flags().StringP("program", "p", "", "type of program to introspect, needs to be combined with output. e.g filebeat") + + return cmd +} From e83f7363cded21635bf9a66deeb0604743b67433 Mon Sep 17 00:00:00 2001 From: Pier-Hugues Pellerin Date: Mon, 4 May 2020 12:46:15 -0400 Subject: [PATCH 098/116] [Elastic Agent] Fix issues with the Gateway (#18135) * [Elastic Agent] Fix issues with the Gateway This PRs fixes a few issues: 1. Fix the flaky tests found in the Gateway (#17709) 2. Fix isses on shutdown (#17760): - when the gateway was stuck in a retry loop. - when mutliple signal (ctrl-c) were sent to the application. * Add changelog entry --- x-pack/elastic-agent/CHANGELOG.asciidoc | 1 + .../pkg/agent/application/fleet_gateway.go | 9 +++++---- .../pkg/agent/application/fleet_gateway_test.go | 17 +++++++++++------ .../pkg/agent/application/managed_mode.go | 1 - 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/x-pack/elastic-agent/CHANGELOG.asciidoc b/x-pack/elastic-agent/CHANGELOG.asciidoc index 78383de2f64..d7877bfc81f 100644 --- a/x-pack/elastic-agent/CHANGELOG.asciidoc +++ b/x-pack/elastic-agent/CHANGELOG.asciidoc @@ -29,6 +29,7 @@ - ECS compliant Elastic agent metadata sent to fleet {pull}18006[18006] - Use default output by default {pull}18091[18091] - Use /tmp for default monitoring endpoint location for libbeat {pull}18131[18131] +- Fix panic and flaky tests for the Agent. {pull}18135[18135] ==== New features diff --git a/x-pack/elastic-agent/pkg/agent/application/fleet_gateway.go b/x-pack/elastic-agent/pkg/agent/application/fleet_gateway.go index 51d103e4d65..ae6f2d21003 100644 --- a/x-pack/elastic-agent/pkg/agent/application/fleet_gateway.go +++ b/x-pack/elastic-agent/pkg/agent/application/fleet_gateway.go @@ -115,6 +115,9 @@ func newFleetGatewayWithScheduler( r fleetReporter, acker fleetAcker, ) (*fleetGateway, error) { + + // Backoff implementation doesn't support the using context as the shutdown mechanism. + // So we keep a done channel that will be closed when the current context is shutdown. done := make(chan struct{}) return &fleetGateway{ @@ -161,10 +164,8 @@ func (f *fleetGateway) worker() { } f.log.Debugf("FleetGateway is sleeping, next update in %s", f.settings.Duration) - case <-f.done: - return case <-f.bgContext.Done(): - f.Stop() + f.stop() return } } @@ -228,7 +229,7 @@ func (f *fleetGateway) Start() { }(&f.wg) } -func (f *fleetGateway) Stop() { +func (f *fleetGateway) stop() { f.log.Info("Fleet gateway is stopping") defer f.scheduler.Stop() close(f.done) diff --git a/x-pack/elastic-agent/pkg/agent/application/fleet_gateway_test.go b/x-pack/elastic-agent/pkg/agent/application/fleet_gateway_test.go index 91cf687aa54..4bc48b8fd98 100644 --- a/x-pack/elastic-agent/pkg/agent/application/fleet_gateway_test.go +++ b/x-pack/elastic-agent/pkg/agent/application/fleet_gateway_test.go @@ -114,8 +114,10 @@ func withGateway(agentInfo agentInfo, settings *fleetGatewaySettings, fn withGat log, _ := logger.New() rep := getReporter(agentInfo, log, t) + ctx, cancel := context.WithCancel(context.Background()) + gateway, err := newFleetGatewayWithScheduler( - context.Background(), + ctx, log, settings, agentInfo, @@ -127,7 +129,7 @@ func withGateway(agentInfo agentInfo, settings *fleetGatewaySettings, fn withGat ) go gateway.Start() - defer gateway.Stop() + defer cancel() require.NoError(t, err) @@ -240,9 +242,10 @@ func TestFleetGateway(t *testing.T) { client := newTestingClient() dispatcher := newTestingDispatcher() + ctx, cancel := context.WithCancel(context.Background()) log, _ := logger.New() gateway, err := newFleetGatewayWithScheduler( - context.Background(), + ctx, log, settings, agentInfo, @@ -254,7 +257,7 @@ func TestFleetGateway(t *testing.T) { ) go gateway.Start() - defer gateway.Stop() + defer cancel() require.NoError(t, err) @@ -324,9 +327,10 @@ func TestFleetGateway(t *testing.T) { client := newTestingClient() dispatcher := newTestingDispatcher() + ctx, cancel := context.WithCancel(context.Background()) log, _ := logger.New() gateway, err := newFleetGatewayWithScheduler( - context.Background(), + ctx, log, &fleetGatewaySettings{ Duration: d, @@ -370,8 +374,9 @@ func TestFleetGateway(t *testing.T) { // 1. Gateway will check the API on boot. // 2. WaitTick() will block for 20 minutes. // 3. Stop will should unblock the wait. - gateway.Stop() + cancel() }) + } func TestRetriesOnFailures(t *testing.T) { diff --git a/x-pack/elastic-agent/pkg/agent/application/managed_mode.go b/x-pack/elastic-agent/pkg/agent/application/managed_mode.go index 98d45fe2c88..269287446db 100644 --- a/x-pack/elastic-agent/pkg/agent/application/managed_mode.go +++ b/x-pack/elastic-agent/pkg/agent/application/managed_mode.go @@ -208,7 +208,6 @@ func (m *Managed) Start() error { func (m *Managed) Stop() error { defer m.log.Info("Agent is stopped") m.cancelCtxFn() - m.gateway.Stop() return nil } From d6edfe8474893fc57db8411229eaef57ac59b090 Mon Sep 17 00:00:00 2001 From: Steffen Siering Date: Mon, 4 May 2020 19:44:51 +0200 Subject: [PATCH 099/116] Skip flaky clean_x tests in filebeat (#18134) --- filebeat/tests/system/test_registrar.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/filebeat/tests/system/test_registrar.py b/filebeat/tests/system/test_registrar.py index 449f22da937..ed2e04d2c07 100644 --- a/filebeat/tests/system/test_registrar.py +++ b/filebeat/tests/system/test_registrar.py @@ -819,6 +819,7 @@ def test_clean_inactive(self): # Make sure the last file in the registry is the correct one and has the correct offset assert data[0]["offset"] == self.input_logs.size(file3) + @unittest.skipIf(os.name == 'nt', 'flaky test https://github.com/elastic/beats/issues/7690') def test_clean_removed(self): """ Checks that files which were removed, the state is removed @@ -868,6 +869,7 @@ def test_clean_removed(self): # Make sure the last file in the registry is the correct one and has the correct offset assert data[0]["offset"] == self.input_logs.size(file2) + @unittest.skipIf(os.name == 'nt', 'flaky test https://github.com/elastic/beats/issues/10606') def test_clean_removed_with_clean_inactive(self): """ Checks that files which were removed, the state is removed From 1e837c5681df3f93c7c0ec9a4a983914d7c0cc44 Mon Sep 17 00:00:00 2001 From: DeDe Morton Date: Mon, 4 May 2020 10:56:02 -0700 Subject: [PATCH 100/116] [docs] Fix indentation problems in processor examples (#18115) --- auditbeat/auditbeat.reference.yml | 150 +++++++++--------- filebeat/docs/filebeat-filtering.asciidoc | 12 +- ...ernetes-default-indexers-matchers.asciidoc | 6 +- filebeat/filebeat.reference.yml | 150 +++++++++--------- heartbeat/heartbeat.reference.yml | 150 +++++++++--------- journalbeat/journalbeat.reference.yml | 150 +++++++++--------- libbeat/_meta/config.reference.yml.tmpl | 150 +++++++++--------- libbeat/docs/processors-list.asciidoc | 12 +- libbeat/docs/processors-using.asciidoc | 120 +++++++------- libbeat/docs/shared-deduplication.asciidoc | 10 +- .../actions/docs/add_fields.asciidoc | 10 +- .../actions/docs/add_labels.asciidoc | 20 +-- .../processors/actions/docs/add_tags.asciidoc | 6 +- .../actions/docs/copy_fields.asciidoc | 12 +- .../actions/docs/decode_base64_field.asciidoc | 12 +- .../actions/docs/decode_json_fields.asciidoc | 14 +- .../docs/decompress_gzip_field.asciidoc | 12 +- .../actions/docs/drop_event.asciidoc | 4 +- .../actions/docs/drop_fields.asciidoc | 8 +- .../actions/docs/include_fields.asciidoc | 6 +- .../processors/actions/docs/rename.asciidoc | 12 +- .../processors/actions/docs/replace.asciidoc | 14 +- .../actions/docs/truncate_fields.asciidoc | 12 +- .../docs/add_cloud_metadata.asciidoc | 2 +- .../docs/add_docker_metadata.asciidoc | 28 ++-- .../docs/add_host_metadata.asciidoc | 20 +-- .../processors/add_id/docs/add_id.asciidoc | 2 +- .../docs/add_kubernetes_metadata.asciidoc | 36 ++--- .../add_locale/docs/add_locale.asciidoc | 6 +- .../docs/add_observer_metadata.asciidoc | 20 +-- .../docs/add_process_metadata.asciidoc | 6 +- .../docs/decode_csv_fields.asciidoc | 14 +- libbeat/processors/dns/docs/dns.asciidoc | 10 +- .../extract_array/docs/extract_array.asciidoc | 6 +- .../fingerprint/docs/fingerprint.asciidoc | 4 +- .../docs/registered_domain.asciidoc | 10 +- .../processors/script/docs/script.asciidoc | 52 +++--- .../timestamp/docs/timestamp.asciidoc | 20 +-- .../translate_sid/docs/translate_sid.asciidoc | 16 +- .../urldecode/docs/urldecode.asciidoc | 12 +- ...ernetes-default-indexers-matchers.asciidoc | 6 +- metricbeat/docs/metricbeat-filtering.asciidoc | 4 +- metricbeat/metricbeat.reference.yml | 150 +++++++++--------- .../system/filesystem/_meta/docs.asciidoc | 4 +- .../windows/service/_meta/docs.asciidoc | 4 +- packetbeat/docs/packetbeat-filtering.asciidoc | 14 +- packetbeat/packetbeat.reference.yml | 150 +++++++++--------- winlogbeat/docs/modules.asciidoc | 20 +-- winlogbeat/winlogbeat.reference.yml | 150 +++++++++--------- x-pack/auditbeat/auditbeat.reference.yml | 150 +++++++++--------- x-pack/filebeat/filebeat.reference.yml | 150 +++++++++--------- .../decode_cef/docs/decode_cef.asciidoc | 10 +- .../functionbeat/functionbeat.reference.yml | 150 +++++++++--------- .../docs/add_cloudfoundry_metadata.asciidoc | 22 +-- x-pack/metricbeat/metricbeat.reference.yml | 150 +++++++++--------- .../cockroachdb/status/_meta/docs.asciidoc | 1 + x-pack/winlogbeat/winlogbeat.reference.yml | 150 +++++++++--------- 57 files changed, 1303 insertions(+), 1298 deletions(-) diff --git a/auditbeat/auditbeat.reference.yml b/auditbeat/auditbeat.reference.yml index 3a76502c73e..5c866e2dc55 100644 --- a/auditbeat/auditbeat.reference.yml +++ b/auditbeat/auditbeat.reference.yml @@ -243,151 +243,151 @@ auditbeat.modules: # values: # #processors: -#- include_fields: -# fields: ["cpu"] -#- drop_fields: -# fields: ["cpu.user", "cpu.system"] +# - include_fields: +# fields: ["cpu"] +# - drop_fields: +# fields: ["cpu.user", "cpu.system"] # # The following example drops the events that have the HTTP response code 200: # #processors: -#- drop_event: -# when: -# equals: -# http.code: 200 +# - drop_event: +# when: +# equals: +# http.code: 200 # # The following example renames the field a to b: # #processors: -#- rename: -# fields: -# - from: "a" -# to: "b" +# - rename: +# fields: +# - from: "a" +# to: "b" # # The following example tokenizes the string into fields: # #processors: -#- dissect: -# tokenizer: "%{key1} - %{key2}" -# field: "message" -# target_prefix: "dissect" +# - dissect: +# tokenizer: "%{key1} - %{key2}" +# field: "message" +# target_prefix: "dissect" # # The following example enriches each event with metadata from the cloud # provider about the host machine. It works on EC2, GCE, DigitalOcean, # Tencent Cloud, and Alibaba Cloud. # #processors: -#- add_cloud_metadata: ~ +# - add_cloud_metadata: ~ # # The following example enriches each event with the machine's local time zone # offset from UTC. # #processors: -#- add_locale: -# format: offset +# - add_locale: +# format: offset # # The following example enriches each event with docker metadata, it matches # given fields to an existing container id and adds info from that container: # #processors: -#- add_docker_metadata: -# host: "unix:///var/run/docker.sock" -# match_fields: ["system.process.cgroup.id"] -# match_pids: ["process.pid", "process.ppid"] -# match_source: true -# match_source_index: 4 -# match_short_id: false -# cleanup_timeout: 60 -# labels.dedot: false -# # To connect to Docker over TLS you must specify a client and CA certificate. -# #ssl: -# # certificate_authority: "/etc/pki/root/ca.pem" -# # certificate: "/etc/pki/client/cert.pem" -# # key: "/etc/pki/client/cert.key" +# - add_docker_metadata: +# host: "unix:///var/run/docker.sock" +# match_fields: ["system.process.cgroup.id"] +# match_pids: ["process.pid", "process.ppid"] +# match_source: true +# match_source_index: 4 +# match_short_id: false +# cleanup_timeout: 60 +# labels.dedot: false +# # To connect to Docker over TLS you must specify a client and CA certificate. +# #ssl: +# # certificate_authority: "/etc/pki/root/ca.pem" +# # certificate: "/etc/pki/client/cert.pem" +# # key: "/etc/pki/client/cert.key" # # The following example enriches each event with docker metadata, it matches # container id from log path available in `source` field (by default it expects # it to be /var/lib/docker/containers/*/*.log). # #processors: -#- add_docker_metadata: ~ +# - add_docker_metadata: ~ # # The following example enriches each event with host metadata. # #processors: -#- add_host_metadata: ~ +# - add_host_metadata: ~ # # The following example enriches each event with process metadata using # process IDs included in the event. # #processors: -#- add_process_metadata: -# match_pids: ["system.process.ppid"] -# target: system.process.parent +# - add_process_metadata: +# match_pids: ["system.process.ppid"] +# target: system.process.parent # # The following example decodes fields containing JSON strings # and replaces the strings with valid JSON objects. # #processors: -#- decode_json_fields: -# fields: ["field1", "field2", ...] -# process_array: false -# max_depth: 1 -# target: "" -# overwrite_keys: false +# - decode_json_fields: +# fields: ["field1", "field2", ...] +# process_array: false +# max_depth: 1 +# target: "" +# overwrite_keys: false # #processors: -#- decompress_gzip_field: -# from: "field1" -# to: "field2" -# ignore_missing: false -# fail_on_error: true +# - decompress_gzip_field: +# from: "field1" +# to: "field2" +# ignore_missing: false +# fail_on_error: true # # The following example copies the value of message to message_copied # #processors: -#- copy_fields: -# fields: +# - copy_fields: +# fields: # - from: message # to: message_copied -# fail_on_error: true -# ignore_missing: false +# fail_on_error: true +# ignore_missing: false # # The following example truncates the value of message to 1024 bytes # #processors: -#- truncate_fields: -# fields: -# - message -# max_bytes: 1024 -# fail_on_error: false -# ignore_missing: true +# - truncate_fields: +# fields: +# - message +# max_bytes: 1024 +# fail_on_error: false +# ignore_missing: true # # The following example preserves the raw message under event.original # #processors: -#- copy_fields: -# fields: +# - copy_fields: +# fields: # - from: message # to: event.original -# fail_on_error: false -# ignore_missing: true -#- truncate_fields: -# fields: -# - event.original -# max_bytes: 1024 -# fail_on_error: false -# ignore_missing: true +# fail_on_error: false +# ignore_missing: true +# - truncate_fields: +# fields: +# - event.original +# max_bytes: 1024 +# fail_on_error: false +# ignore_missing: true # # The following example URL-decodes the value of field1 to field2 # #processors: -#- urldecode: -# fields: -# - from: "field1" -# to: "field2" -# ignore_missing: false -# fail_on_error: true +# - urldecode: +# fields: +# - from: "field1" +# to: "field2" +# ignore_missing: false +# fail_on_error: true #============================= Elastic Cloud ================================== diff --git a/filebeat/docs/filebeat-filtering.asciidoc b/filebeat/docs/filebeat-filtering.asciidoc index 87eaf2ce72e..b0d7efab0dc 100644 --- a/filebeat/docs/filebeat-filtering.asciidoc +++ b/filebeat/docs/filebeat-filtering.asciidoc @@ -37,10 +37,10 @@ The following configuration drops all the DEBUG messages. [source,yaml] ----------------------------------------------------- processors: - - drop_event: - when: + - drop_event: + when: regexp: - message: "^DBG:" + message: "^DBG:" ----------------------------------------------------- To drop all the log messages coming from a certain log file: @@ -48,10 +48,10 @@ To drop all the log messages coming from a certain log file: [source,yaml] ---------------- processors: - - drop_event: - when: + - drop_event: + when: contains: - source: "test" + source: "test" ---------------- [float] diff --git a/filebeat/docs/kubernetes-default-indexers-matchers.asciidoc b/filebeat/docs/kubernetes-default-indexers-matchers.asciidoc index 3d7f8655cc6..287b3bdbb3c 100644 --- a/filebeat/docs/kubernetes-default-indexers-matchers.asciidoc +++ b/filebeat/docs/kubernetes-default-indexers-matchers.asciidoc @@ -8,7 +8,7 @@ configuration: [source,yaml] ------------------------------------------------------------------------------- processors: -- add_kubernetes_metadata: - default_indexers.enabled: false - default_matchers.enabled: false + - add_kubernetes_metadata: + default_indexers.enabled: false + default_matchers.enabled: false ------------------------------------------------------------------------------- diff --git a/filebeat/filebeat.reference.yml b/filebeat/filebeat.reference.yml index ea2ad11e672..e337c1b0e9d 100644 --- a/filebeat/filebeat.reference.yml +++ b/filebeat/filebeat.reference.yml @@ -954,151 +954,151 @@ filebeat.inputs: # values: # #processors: -#- include_fields: -# fields: ["cpu"] -#- drop_fields: -# fields: ["cpu.user", "cpu.system"] +# - include_fields: +# fields: ["cpu"] +# - drop_fields: +# fields: ["cpu.user", "cpu.system"] # # The following example drops the events that have the HTTP response code 200: # #processors: -#- drop_event: -# when: -# equals: -# http.code: 200 +# - drop_event: +# when: +# equals: +# http.code: 200 # # The following example renames the field a to b: # #processors: -#- rename: -# fields: -# - from: "a" -# to: "b" +# - rename: +# fields: +# - from: "a" +# to: "b" # # The following example tokenizes the string into fields: # #processors: -#- dissect: -# tokenizer: "%{key1} - %{key2}" -# field: "message" -# target_prefix: "dissect" +# - dissect: +# tokenizer: "%{key1} - %{key2}" +# field: "message" +# target_prefix: "dissect" # # The following example enriches each event with metadata from the cloud # provider about the host machine. It works on EC2, GCE, DigitalOcean, # Tencent Cloud, and Alibaba Cloud. # #processors: -#- add_cloud_metadata: ~ +# - add_cloud_metadata: ~ # # The following example enriches each event with the machine's local time zone # offset from UTC. # #processors: -#- add_locale: -# format: offset +# - add_locale: +# format: offset # # The following example enriches each event with docker metadata, it matches # given fields to an existing container id and adds info from that container: # #processors: -#- add_docker_metadata: -# host: "unix:///var/run/docker.sock" -# match_fields: ["system.process.cgroup.id"] -# match_pids: ["process.pid", "process.ppid"] -# match_source: true -# match_source_index: 4 -# match_short_id: false -# cleanup_timeout: 60 -# labels.dedot: false -# # To connect to Docker over TLS you must specify a client and CA certificate. -# #ssl: -# # certificate_authority: "/etc/pki/root/ca.pem" -# # certificate: "/etc/pki/client/cert.pem" -# # key: "/etc/pki/client/cert.key" +# - add_docker_metadata: +# host: "unix:///var/run/docker.sock" +# match_fields: ["system.process.cgroup.id"] +# match_pids: ["process.pid", "process.ppid"] +# match_source: true +# match_source_index: 4 +# match_short_id: false +# cleanup_timeout: 60 +# labels.dedot: false +# # To connect to Docker over TLS you must specify a client and CA certificate. +# #ssl: +# # certificate_authority: "/etc/pki/root/ca.pem" +# # certificate: "/etc/pki/client/cert.pem" +# # key: "/etc/pki/client/cert.key" # # The following example enriches each event with docker metadata, it matches # container id from log path available in `source` field (by default it expects # it to be /var/lib/docker/containers/*/*.log). # #processors: -#- add_docker_metadata: ~ +# - add_docker_metadata: ~ # # The following example enriches each event with host metadata. # #processors: -#- add_host_metadata: ~ +# - add_host_metadata: ~ # # The following example enriches each event with process metadata using # process IDs included in the event. # #processors: -#- add_process_metadata: -# match_pids: ["system.process.ppid"] -# target: system.process.parent +# - add_process_metadata: +# match_pids: ["system.process.ppid"] +# target: system.process.parent # # The following example decodes fields containing JSON strings # and replaces the strings with valid JSON objects. # #processors: -#- decode_json_fields: -# fields: ["field1", "field2", ...] -# process_array: false -# max_depth: 1 -# target: "" -# overwrite_keys: false +# - decode_json_fields: +# fields: ["field1", "field2", ...] +# process_array: false +# max_depth: 1 +# target: "" +# overwrite_keys: false # #processors: -#- decompress_gzip_field: -# from: "field1" -# to: "field2" -# ignore_missing: false -# fail_on_error: true +# - decompress_gzip_field: +# from: "field1" +# to: "field2" +# ignore_missing: false +# fail_on_error: true # # The following example copies the value of message to message_copied # #processors: -#- copy_fields: -# fields: +# - copy_fields: +# fields: # - from: message # to: message_copied -# fail_on_error: true -# ignore_missing: false +# fail_on_error: true +# ignore_missing: false # # The following example truncates the value of message to 1024 bytes # #processors: -#- truncate_fields: -# fields: -# - message -# max_bytes: 1024 -# fail_on_error: false -# ignore_missing: true +# - truncate_fields: +# fields: +# - message +# max_bytes: 1024 +# fail_on_error: false +# ignore_missing: true # # The following example preserves the raw message under event.original # #processors: -#- copy_fields: -# fields: +# - copy_fields: +# fields: # - from: message # to: event.original -# fail_on_error: false -# ignore_missing: true -#- truncate_fields: -# fields: -# - event.original -# max_bytes: 1024 -# fail_on_error: false -# ignore_missing: true +# fail_on_error: false +# ignore_missing: true +# - truncate_fields: +# fields: +# - event.original +# max_bytes: 1024 +# fail_on_error: false +# ignore_missing: true # # The following example URL-decodes the value of field1 to field2 # #processors: -#- urldecode: -# fields: -# - from: "field1" -# to: "field2" -# ignore_missing: false -# fail_on_error: true +# - urldecode: +# fields: +# - from: "field1" +# to: "field2" +# ignore_missing: false +# fail_on_error: true #============================= Elastic Cloud ================================== diff --git a/heartbeat/heartbeat.reference.yml b/heartbeat/heartbeat.reference.yml index 306569f0a2c..31f343891fc 100644 --- a/heartbeat/heartbeat.reference.yml +++ b/heartbeat/heartbeat.reference.yml @@ -394,151 +394,151 @@ heartbeat.scheduler: # values: # #processors: -#- include_fields: -# fields: ["cpu"] -#- drop_fields: -# fields: ["cpu.user", "cpu.system"] +# - include_fields: +# fields: ["cpu"] +# - drop_fields: +# fields: ["cpu.user", "cpu.system"] # # The following example drops the events that have the HTTP response code 200: # #processors: -#- drop_event: -# when: -# equals: -# http.code: 200 +# - drop_event: +# when: +# equals: +# http.code: 200 # # The following example renames the field a to b: # #processors: -#- rename: -# fields: -# - from: "a" -# to: "b" +# - rename: +# fields: +# - from: "a" +# to: "b" # # The following example tokenizes the string into fields: # #processors: -#- dissect: -# tokenizer: "%{key1} - %{key2}" -# field: "message" -# target_prefix: "dissect" +# - dissect: +# tokenizer: "%{key1} - %{key2}" +# field: "message" +# target_prefix: "dissect" # # The following example enriches each event with metadata from the cloud # provider about the host machine. It works on EC2, GCE, DigitalOcean, # Tencent Cloud, and Alibaba Cloud. # #processors: -#- add_cloud_metadata: ~ +# - add_cloud_metadata: ~ # # The following example enriches each event with the machine's local time zone # offset from UTC. # #processors: -#- add_locale: -# format: offset +# - add_locale: +# format: offset # # The following example enriches each event with docker metadata, it matches # given fields to an existing container id and adds info from that container: # #processors: -#- add_docker_metadata: -# host: "unix:///var/run/docker.sock" -# match_fields: ["system.process.cgroup.id"] -# match_pids: ["process.pid", "process.ppid"] -# match_source: true -# match_source_index: 4 -# match_short_id: false -# cleanup_timeout: 60 -# labels.dedot: false -# # To connect to Docker over TLS you must specify a client and CA certificate. -# #ssl: -# # certificate_authority: "/etc/pki/root/ca.pem" -# # certificate: "/etc/pki/client/cert.pem" -# # key: "/etc/pki/client/cert.key" +# - add_docker_metadata: +# host: "unix:///var/run/docker.sock" +# match_fields: ["system.process.cgroup.id"] +# match_pids: ["process.pid", "process.ppid"] +# match_source: true +# match_source_index: 4 +# match_short_id: false +# cleanup_timeout: 60 +# labels.dedot: false +# # To connect to Docker over TLS you must specify a client and CA certificate. +# #ssl: +# # certificate_authority: "/etc/pki/root/ca.pem" +# # certificate: "/etc/pki/client/cert.pem" +# # key: "/etc/pki/client/cert.key" # # The following example enriches each event with docker metadata, it matches # container id from log path available in `source` field (by default it expects # it to be /var/lib/docker/containers/*/*.log). # #processors: -#- add_docker_metadata: ~ +# - add_docker_metadata: ~ # # The following example enriches each event with host metadata. # #processors: -#- add_host_metadata: ~ +# - add_host_metadata: ~ # # The following example enriches each event with process metadata using # process IDs included in the event. # #processors: -#- add_process_metadata: -# match_pids: ["system.process.ppid"] -# target: system.process.parent +# - add_process_metadata: +# match_pids: ["system.process.ppid"] +# target: system.process.parent # # The following example decodes fields containing JSON strings # and replaces the strings with valid JSON objects. # #processors: -#- decode_json_fields: -# fields: ["field1", "field2", ...] -# process_array: false -# max_depth: 1 -# target: "" -# overwrite_keys: false +# - decode_json_fields: +# fields: ["field1", "field2", ...] +# process_array: false +# max_depth: 1 +# target: "" +# overwrite_keys: false # #processors: -#- decompress_gzip_field: -# from: "field1" -# to: "field2" -# ignore_missing: false -# fail_on_error: true +# - decompress_gzip_field: +# from: "field1" +# to: "field2" +# ignore_missing: false +# fail_on_error: true # # The following example copies the value of message to message_copied # #processors: -#- copy_fields: -# fields: +# - copy_fields: +# fields: # - from: message # to: message_copied -# fail_on_error: true -# ignore_missing: false +# fail_on_error: true +# ignore_missing: false # # The following example truncates the value of message to 1024 bytes # #processors: -#- truncate_fields: -# fields: -# - message -# max_bytes: 1024 -# fail_on_error: false -# ignore_missing: true +# - truncate_fields: +# fields: +# - message +# max_bytes: 1024 +# fail_on_error: false +# ignore_missing: true # # The following example preserves the raw message under event.original # #processors: -#- copy_fields: -# fields: +# - copy_fields: +# fields: # - from: message # to: event.original -# fail_on_error: false -# ignore_missing: true -#- truncate_fields: -# fields: -# - event.original -# max_bytes: 1024 -# fail_on_error: false -# ignore_missing: true +# fail_on_error: false +# ignore_missing: true +# - truncate_fields: +# fields: +# - event.original +# max_bytes: 1024 +# fail_on_error: false +# ignore_missing: true # # The following example URL-decodes the value of field1 to field2 # #processors: -#- urldecode: -# fields: -# - from: "field1" -# to: "field2" -# ignore_missing: false -# fail_on_error: true +# - urldecode: +# fields: +# - from: "field1" +# to: "field2" +# ignore_missing: false +# fail_on_error: true #============================= Elastic Cloud ================================== diff --git a/journalbeat/journalbeat.reference.yml b/journalbeat/journalbeat.reference.yml index 0d995735fba..4bb34807f85 100644 --- a/journalbeat/journalbeat.reference.yml +++ b/journalbeat/journalbeat.reference.yml @@ -181,151 +181,151 @@ setup.template.settings: # values: # #processors: -#- include_fields: -# fields: ["cpu"] -#- drop_fields: -# fields: ["cpu.user", "cpu.system"] +# - include_fields: +# fields: ["cpu"] +# - drop_fields: +# fields: ["cpu.user", "cpu.system"] # # The following example drops the events that have the HTTP response code 200: # #processors: -#- drop_event: -# when: -# equals: -# http.code: 200 +# - drop_event: +# when: +# equals: +# http.code: 200 # # The following example renames the field a to b: # #processors: -#- rename: -# fields: -# - from: "a" -# to: "b" +# - rename: +# fields: +# - from: "a" +# to: "b" # # The following example tokenizes the string into fields: # #processors: -#- dissect: -# tokenizer: "%{key1} - %{key2}" -# field: "message" -# target_prefix: "dissect" +# - dissect: +# tokenizer: "%{key1} - %{key2}" +# field: "message" +# target_prefix: "dissect" # # The following example enriches each event with metadata from the cloud # provider about the host machine. It works on EC2, GCE, DigitalOcean, # Tencent Cloud, and Alibaba Cloud. # #processors: -#- add_cloud_metadata: ~ +# - add_cloud_metadata: ~ # # The following example enriches each event with the machine's local time zone # offset from UTC. # #processors: -#- add_locale: -# format: offset +# - add_locale: +# format: offset # # The following example enriches each event with docker metadata, it matches # given fields to an existing container id and adds info from that container: # #processors: -#- add_docker_metadata: -# host: "unix:///var/run/docker.sock" -# match_fields: ["system.process.cgroup.id"] -# match_pids: ["process.pid", "process.ppid"] -# match_source: true -# match_source_index: 4 -# match_short_id: false -# cleanup_timeout: 60 -# labels.dedot: false -# # To connect to Docker over TLS you must specify a client and CA certificate. -# #ssl: -# # certificate_authority: "/etc/pki/root/ca.pem" -# # certificate: "/etc/pki/client/cert.pem" -# # key: "/etc/pki/client/cert.key" +# - add_docker_metadata: +# host: "unix:///var/run/docker.sock" +# match_fields: ["system.process.cgroup.id"] +# match_pids: ["process.pid", "process.ppid"] +# match_source: true +# match_source_index: 4 +# match_short_id: false +# cleanup_timeout: 60 +# labels.dedot: false +# # To connect to Docker over TLS you must specify a client and CA certificate. +# #ssl: +# # certificate_authority: "/etc/pki/root/ca.pem" +# # certificate: "/etc/pki/client/cert.pem" +# # key: "/etc/pki/client/cert.key" # # The following example enriches each event with docker metadata, it matches # container id from log path available in `source` field (by default it expects # it to be /var/lib/docker/containers/*/*.log). # #processors: -#- add_docker_metadata: ~ +# - add_docker_metadata: ~ # # The following example enriches each event with host metadata. # #processors: -#- add_host_metadata: ~ +# - add_host_metadata: ~ # # The following example enriches each event with process metadata using # process IDs included in the event. # #processors: -#- add_process_metadata: -# match_pids: ["system.process.ppid"] -# target: system.process.parent +# - add_process_metadata: +# match_pids: ["system.process.ppid"] +# target: system.process.parent # # The following example decodes fields containing JSON strings # and replaces the strings with valid JSON objects. # #processors: -#- decode_json_fields: -# fields: ["field1", "field2", ...] -# process_array: false -# max_depth: 1 -# target: "" -# overwrite_keys: false +# - decode_json_fields: +# fields: ["field1", "field2", ...] +# process_array: false +# max_depth: 1 +# target: "" +# overwrite_keys: false # #processors: -#- decompress_gzip_field: -# from: "field1" -# to: "field2" -# ignore_missing: false -# fail_on_error: true +# - decompress_gzip_field: +# from: "field1" +# to: "field2" +# ignore_missing: false +# fail_on_error: true # # The following example copies the value of message to message_copied # #processors: -#- copy_fields: -# fields: +# - copy_fields: +# fields: # - from: message # to: message_copied -# fail_on_error: true -# ignore_missing: false +# fail_on_error: true +# ignore_missing: false # # The following example truncates the value of message to 1024 bytes # #processors: -#- truncate_fields: -# fields: -# - message -# max_bytes: 1024 -# fail_on_error: false -# ignore_missing: true +# - truncate_fields: +# fields: +# - message +# max_bytes: 1024 +# fail_on_error: false +# ignore_missing: true # # The following example preserves the raw message under event.original # #processors: -#- copy_fields: -# fields: +# - copy_fields: +# fields: # - from: message # to: event.original -# fail_on_error: false -# ignore_missing: true -#- truncate_fields: -# fields: -# - event.original -# max_bytes: 1024 -# fail_on_error: false -# ignore_missing: true +# fail_on_error: false +# ignore_missing: true +# - truncate_fields: +# fields: +# - event.original +# max_bytes: 1024 +# fail_on_error: false +# ignore_missing: true # # The following example URL-decodes the value of field1 to field2 # #processors: -#- urldecode: -# fields: -# - from: "field1" -# to: "field2" -# ignore_missing: false -# fail_on_error: true +# - urldecode: +# fields: +# - from: "field1" +# to: "field2" +# ignore_missing: false +# fail_on_error: true #============================= Elastic Cloud ================================== diff --git a/libbeat/_meta/config.reference.yml.tmpl b/libbeat/_meta/config.reference.yml.tmpl index a72aa80ba42..be7913679bf 100644 --- a/libbeat/_meta/config.reference.yml.tmpl +++ b/libbeat/_meta/config.reference.yml.tmpl @@ -124,151 +124,151 @@ # values: # #processors: -#- include_fields: -# fields: ["cpu"] -#- drop_fields: -# fields: ["cpu.user", "cpu.system"] +# - include_fields: +# fields: ["cpu"] +# - drop_fields: +# fields: ["cpu.user", "cpu.system"] # # The following example drops the events that have the HTTP response code 200: # #processors: -#- drop_event: -# when: -# equals: -# http.code: 200 +# - drop_event: +# when: +# equals: +# http.code: 200 # # The following example renames the field a to b: # #processors: -#- rename: -# fields: -# - from: "a" -# to: "b" +# - rename: +# fields: +# - from: "a" +# to: "b" # # The following example tokenizes the string into fields: # #processors: -#- dissect: -# tokenizer: "%{key1} - %{key2}" -# field: "message" -# target_prefix: "dissect" +# - dissect: +# tokenizer: "%{key1} - %{key2}" +# field: "message" +# target_prefix: "dissect" # # The following example enriches each event with metadata from the cloud # provider about the host machine. It works on EC2, GCE, DigitalOcean, # Tencent Cloud, and Alibaba Cloud. # #processors: -#- add_cloud_metadata: ~ +# - add_cloud_metadata: ~ # # The following example enriches each event with the machine's local time zone # offset from UTC. # #processors: -#- add_locale: -# format: offset +# - add_locale: +# format: offset # # The following example enriches each event with docker metadata, it matches # given fields to an existing container id and adds info from that container: # #processors: -#- add_docker_metadata: -# host: "unix:///var/run/docker.sock" -# match_fields: ["system.process.cgroup.id"] -# match_pids: ["process.pid", "process.ppid"] -# match_source: true -# match_source_index: 4 -# match_short_id: false -# cleanup_timeout: 60 -# labels.dedot: false -# # To connect to Docker over TLS you must specify a client and CA certificate. -# #ssl: -# # certificate_authority: "/etc/pki/root/ca.pem" -# # certificate: "/etc/pki/client/cert.pem" -# # key: "/etc/pki/client/cert.key" +# - add_docker_metadata: +# host: "unix:///var/run/docker.sock" +# match_fields: ["system.process.cgroup.id"] +# match_pids: ["process.pid", "process.ppid"] +# match_source: true +# match_source_index: 4 +# match_short_id: false +# cleanup_timeout: 60 +# labels.dedot: false +# # To connect to Docker over TLS you must specify a client and CA certificate. +# #ssl: +# # certificate_authority: "/etc/pki/root/ca.pem" +# # certificate: "/etc/pki/client/cert.pem" +# # key: "/etc/pki/client/cert.key" # # The following example enriches each event with docker metadata, it matches # container id from log path available in `source` field (by default it expects # it to be /var/lib/docker/containers/*/*.log). # #processors: -#- add_docker_metadata: ~ +# - add_docker_metadata: ~ # # The following example enriches each event with host metadata. # #processors: -#- add_host_metadata: ~ +# - add_host_metadata: ~ # # The following example enriches each event with process metadata using # process IDs included in the event. # #processors: -#- add_process_metadata: -# match_pids: ["system.process.ppid"] -# target: system.process.parent +# - add_process_metadata: +# match_pids: ["system.process.ppid"] +# target: system.process.parent # # The following example decodes fields containing JSON strings # and replaces the strings with valid JSON objects. # #processors: -#- decode_json_fields: -# fields: ["field1", "field2", ...] -# process_array: false -# max_depth: 1 -# target: "" -# overwrite_keys: false +# - decode_json_fields: +# fields: ["field1", "field2", ...] +# process_array: false +# max_depth: 1 +# target: "" +# overwrite_keys: false # #processors: -#- decompress_gzip_field: -# from: "field1" -# to: "field2" -# ignore_missing: false -# fail_on_error: true +# - decompress_gzip_field: +# from: "field1" +# to: "field2" +# ignore_missing: false +# fail_on_error: true # # The following example copies the value of message to message_copied # #processors: -#- copy_fields: -# fields: +# - copy_fields: +# fields: # - from: message # to: message_copied -# fail_on_error: true -# ignore_missing: false +# fail_on_error: true +# ignore_missing: false # # The following example truncates the value of message to 1024 bytes # #processors: -#- truncate_fields: -# fields: -# - message -# max_bytes: 1024 -# fail_on_error: false -# ignore_missing: true +# - truncate_fields: +# fields: +# - message +# max_bytes: 1024 +# fail_on_error: false +# ignore_missing: true # # The following example preserves the raw message under event.original # #processors: -#- copy_fields: -# fields: +# - copy_fields: +# fields: # - from: message # to: event.original -# fail_on_error: false -# ignore_missing: true -#- truncate_fields: -# fields: -# - event.original -# max_bytes: 1024 -# fail_on_error: false -# ignore_missing: true +# fail_on_error: false +# ignore_missing: true +# - truncate_fields: +# fields: +# - event.original +# max_bytes: 1024 +# fail_on_error: false +# ignore_missing: true # # The following example URL-decodes the value of field1 to field2 # #processors: -#- urldecode: -# fields: -# - from: "field1" -# to: "field2" -# ignore_missing: false -# fail_on_error: true +# - urldecode: +# fields: +# - from: "field1" +# to: "field2" +# ignore_missing: false +# fail_on_error: true #============================= Elastic Cloud ================================== diff --git a/libbeat/docs/processors-list.asciidoc b/libbeat/docs/processors-list.asciidoc index 169657aecc4..5dd95e2e3d5 100644 --- a/libbeat/docs/processors-list.asciidoc +++ b/libbeat/docs/processors-list.asciidoc @@ -95,12 +95,12 @@ endif::[] ifndef::no_timestamp_processor[] * <> endif::[] -ifndef::no_truncate_fields_processor[] -* <> -endif::[] ifndef::no_translate_sid_processor[] * <> endif::[] +ifndef::no_truncate_fields_processor[] +* <> +endif::[] ifndef::no_urldecode_processor[] * <> endif::[] @@ -201,12 +201,12 @@ endif::[] ifndef::no_timestamp_processor[] include::{libbeat-processors-dir}/timestamp/docs/timestamp.asciidoc[] endif::[] -ifndef::no_truncate_fields_processor[] -include::{libbeat-processors-dir}/actions/docs/truncate_fields.asciidoc[] -endif::[] ifndef::no_translate_sid_processor[] include::{libbeat-processors-dir}/translate_sid/docs/translate_sid.asciidoc[] endif::[] +ifndef::no_truncate_fields_processor[] +include::{libbeat-processors-dir}/actions/docs/truncate_fields.asciidoc[] +endif::[] ifndef::no_urldecode_processor[] include::{libbeat-processors-dir}/urldecode/docs/urldecode.asciidoc[] endif::[] diff --git a/libbeat/docs/processors-using.asciidoc b/libbeat/docs/processors-using.asciidoc index 81080dda5cb..eb5f391a558 100644 --- a/libbeat/docs/processors-using.asciidoc +++ b/libbeat/docs/processors-using.asciidoc @@ -8,15 +8,15 @@ optional condition, and a set of parameters: [source,yaml] ------ processors: -- : - when: - - + - : + when: + + -- : - when: - - + - : + when: + + ... ------ @@ -38,20 +38,20 @@ executed based on a single condition. [source,yaml] ---- processors: -- if: - - then: <1> - - : - - - : - - ... - else: <2> - - : - - - : - - ... + - if: + + then: <1> + - : + + - : + + ... + else: <2> + - : + + - : + + ... ---- <1> `then` must contain a single processor or a list of one or more processors to execute when the condition evaluates to true. @@ -99,10 +99,10 @@ ifeval::["{beatname_lc}"=="filebeat"] ------ - type: processors: - - : - when: - - + - : + when: + + ... ------ + @@ -116,10 +116,10 @@ ifeval::["{beatname_lc}"=="metricbeat"] - module: metricsets: [""] processors: - - : - when: - - + - : + when: + + ---- endif::[] ifeval::["{beatname_lc}"=="auditbeat"] @@ -129,10 +129,10 @@ ifeval::["{beatname_lc}"=="auditbeat"] auditbeat.modules: - module: processors: - - : - when: - - + - : + when: + + ---- endif::[] ifeval::["{beatname_lc}"=="packetbeat"] @@ -142,10 +142,10 @@ ifeval::["{beatname_lc}"=="packetbeat"] packetbeat.protocols: - type: processors: - - : - when: - - + - : + when: + + ---- * Under `packetbeat.flows`. The processor is applied to the data in @@ -155,10 +155,10 @@ packetbeat.protocols: ---- packetbeat.flows: processors: - - : - when: - - + - : + when: + + ---- endif::[] ifeval::["{beatname_lc}"=="heartbeat"] @@ -168,10 +168,10 @@ ifeval::["{beatname_lc}"=="heartbeat"] heartbeat.monitors: - type: processors: - - : - when: - - + - : + when: + + ---- endif::[] ifeval::["{beatname_lc}"=="winlogbeat"] @@ -181,10 +181,10 @@ ifeval::["{beatname_lc}"=="winlogbeat"] winlogbeat.event_logs: - name: processors: - - : - when: - - + - : + when: + + ---- endif::[] @@ -285,8 +285,8 @@ comparing the `http.response.code` field with 400. [source,yaml] ------ range: - http.response.code: - gte: 400 + http.response.code: + gte: 400 ------ This can also be written as: @@ -294,7 +294,7 @@ This can also be written as: [source,yaml] ---- range: - http.response.code.gte: 400 + http.response.code.gte: 400 ---- The following condition checks if the CPU usage in percentage has a value @@ -303,8 +303,8 @@ between 0.5 and 0.8. [source,yaml] ------ range: - system.cpu.user.pct.gte: 0.5 - system.cpu.user.pct.lt: 0.8 + system.cpu.user.pct.gte: 0.5 + system.cpu.user.pct.lt: 0.8 ------ [float] @@ -339,7 +339,7 @@ private address space. [source,yaml] ---- network: - source.ip: private + source.ip: private ---- This condition returns true if the `destination.ip` value is within the @@ -348,7 +348,7 @@ IPv4 range of `192.168.1.0` - `192.168.1.255`. [source,yaml] ---- network: - destination.ip: '192.168.1.0/24' + destination.ip: '192.168.1.0/24' ---- And this condition returns true when `destination.ip` is within any of the given @@ -357,7 +357,7 @@ subnets. [source,yaml] ---- network: - destination.ip: ['192.168.1.0/24', '10.0.0.0/8', loopback] + destination.ip: ['192.168.1.0/24', '10.0.0.0/8', loopback] ---- [float] @@ -438,8 +438,8 @@ To configure a condition like ` OR AND `: [source,yaml] ------ or: - - - - and: + - + - and: - - diff --git a/libbeat/docs/shared-deduplication.asciidoc b/libbeat/docs/shared-deduplication.asciidoc index 997f12c488b..eec45caaf97 100644 --- a/libbeat/docs/shared-deduplication.asciidoc +++ b/libbeat/docs/shared-deduplication.asciidoc @@ -73,11 +73,11 @@ from the JSON string and stores it in the `@metadata._id` field: [source,yaml] ---- processors: - - decode_json_fields: - document_id: "myid" - fields: ["message"] - max_depth: 1 - target: "" + - decode_json_fields: + document_id: "myid" + fields: ["message"] + max_depth: 1 + target: "" ---- + The resulting document ID is `100`. diff --git a/libbeat/processors/actions/docs/add_fields.asciidoc b/libbeat/processors/actions/docs/add_fields.asciidoc index dcf54ef842b..b133d2115c8 100644 --- a/libbeat/processors/actions/docs/add_fields.asciidoc +++ b/libbeat/processors/actions/docs/add_fields.asciidoc @@ -21,11 +21,11 @@ For example, this configuration: [source,yaml] ------------------------------------------------------------------------------ processors: -- add_fields: - target: project - fields: - name: myproject - id: '574734885120952459' + - add_fields: + target: project + fields: + name: myproject + id: '574734885120952459' ------------------------------------------------------------------------------ Adds these fields to any event: diff --git a/libbeat/processors/actions/docs/add_labels.asciidoc b/libbeat/processors/actions/docs/add_labels.asciidoc index fca768228bf..9e87ea05088 100644 --- a/libbeat/processors/actions/docs/add_labels.asciidoc +++ b/libbeat/processors/actions/docs/add_labels.asciidoc @@ -18,16 +18,16 @@ For example, this configuration: [source,yaml] ------------------------------------------------------------------------------ processors: -- add_labels: - labels: - number: 1 - with.dots: test - nested: - with.dots: nested - array: - - do - - re - - with.field: mi + - add_labels: + labels: + number: 1 + with.dots: test + nested: + with.dots: nested + array: + - do + - re + - with.field: mi ------------------------------------------------------------------------------ Adds these fields to every event: diff --git a/libbeat/processors/actions/docs/add_tags.asciidoc b/libbeat/processors/actions/docs/add_tags.asciidoc index c6015e749f5..e338d246668 100644 --- a/libbeat/processors/actions/docs/add_tags.asciidoc +++ b/libbeat/processors/actions/docs/add_tags.asciidoc @@ -17,9 +17,9 @@ For example, this configuration: [source,yaml] ------------------------------------------------------------------------------ processors: -- add_tags: - tags: [web, production] - target: "environment" + - add_tags: + tags: [web, production] + target: "environment" ------------------------------------------------------------------------------ Adds the environment field to every event: diff --git a/libbeat/processors/actions/docs/copy_fields.asciidoc b/libbeat/processors/actions/docs/copy_fields.asciidoc index ae3816bbce9..c958d3c6e82 100644 --- a/libbeat/processors/actions/docs/copy_fields.asciidoc +++ b/libbeat/processors/actions/docs/copy_fields.asciidoc @@ -20,12 +20,12 @@ For example, this configuration: [source,yaml] ------------------------------------------------------------------------------ processors: -- copy_fields: - fields: - - from: message - to: event.original - fail_on_error: false - ignore_missing: true + - copy_fields: + fields: + - from: message + to: event.original + fail_on_error: false + ignore_missing: true ------------------------------------------------------------------------------ Copies the original `message` field to `event.original`: diff --git a/libbeat/processors/actions/docs/decode_base64_field.asciidoc b/libbeat/processors/actions/docs/decode_base64_field.asciidoc index 17912cca1b7..a0c413006ef 100644 --- a/libbeat/processors/actions/docs/decode_base64_field.asciidoc +++ b/libbeat/processors/actions/docs/decode_base64_field.asciidoc @@ -15,12 +15,12 @@ processor to drop the field and then rename the field. [source,yaml] ------- processors: -- decode_base64_field: - field: - from: "field1" - to: "field2" - ignore_missing: false - fail_on_error: true + - decode_base64_field: + field: + from: "field1" + to: "field2" + ignore_missing: false + fail_on_error: true ------- In the example above: diff --git a/libbeat/processors/actions/docs/decode_json_fields.asciidoc b/libbeat/processors/actions/docs/decode_json_fields.asciidoc index 6c57dac73ba..c5aa15c2a3c 100644 --- a/libbeat/processors/actions/docs/decode_json_fields.asciidoc +++ b/libbeat/processors/actions/docs/decode_json_fields.asciidoc @@ -11,13 +11,13 @@ replaces the strings with valid JSON objects. [source,yaml] ----------------------------------------------------- processors: - - decode_json_fields: - fields: ["field1", "field2", ...] - process_array: false - max_depth: 1 - target: "" - overwrite_keys: false - add_error_key: true + - decode_json_fields: + fields: ["field1", "field2", ...] + process_array: false + max_depth: 1 + target: "" + overwrite_keys: false + add_error_key: true ----------------------------------------------------- The `decode_json_fields` processor has the following configuration settings: diff --git a/libbeat/processors/actions/docs/decompress_gzip_field.asciidoc b/libbeat/processors/actions/docs/decompress_gzip_field.asciidoc index 44ba25f7888..102244fd29b 100644 --- a/libbeat/processors/actions/docs/decompress_gzip_field.asciidoc +++ b/libbeat/processors/actions/docs/decompress_gzip_field.asciidoc @@ -15,12 +15,12 @@ processor to drop the field and then rename the field. [source,yaml] ------- processors: -- decompress_gzip_field: - field: - from: "field1" - to: "field2" - ignore_missing: false - fail_on_error: true + - decompress_gzip_field: + field: + from: "field1" + to: "field2" + ignore_missing: false + fail_on_error: true ------- In the example above: diff --git a/libbeat/processors/actions/docs/drop_event.asciidoc b/libbeat/processors/actions/docs/drop_event.asciidoc index 01016fc6290..5dba8ec24bb 100644 --- a/libbeat/processors/actions/docs/drop_event.asciidoc +++ b/libbeat/processors/actions/docs/drop_event.asciidoc @@ -12,8 +12,8 @@ are dropped. [source,yaml] ------ processors: - - drop_event: - when: + - drop_event: + when: condition ------ diff --git a/libbeat/processors/actions/docs/drop_fields.asciidoc b/libbeat/processors/actions/docs/drop_fields.asciidoc index 865da6549fb..9e543ea376a 100644 --- a/libbeat/processors/actions/docs/drop_fields.asciidoc +++ b/libbeat/processors/actions/docs/drop_fields.asciidoc @@ -13,11 +13,11 @@ be dropped, even if they show up in the `drop_fields` list. [source,yaml] ----------------------------------------------------- processors: - - drop_fields: - when: + - drop_fields: + when: condition - fields: ["field1", "field2", ...] - ignore_missing: false + fields: ["field1", "field2", ...] + ignore_missing: false ----------------------------------------------------- See <> for a list of supported conditions. diff --git a/libbeat/processors/actions/docs/include_fields.asciidoc b/libbeat/processors/actions/docs/include_fields.asciidoc index 9fbc620dfbd..86b2c70e6ff 100644 --- a/libbeat/processors/actions/docs/include_fields.asciidoc +++ b/libbeat/processors/actions/docs/include_fields.asciidoc @@ -13,10 +13,10 @@ always exported, even if they are not defined in the `include_fields` list. [source,yaml] ------- processors: - - include_fields: - when: + - include_fields: + when: condition - fields: ["field1", "field2", ...] + fields: ["field1", "field2", ...] ------- See <> for a list of supported conditions. diff --git a/libbeat/processors/actions/docs/rename.asciidoc b/libbeat/processors/actions/docs/rename.asciidoc index 81d098c7d2b..29d1495a8d2 100644 --- a/libbeat/processors/actions/docs/rename.asciidoc +++ b/libbeat/processors/actions/docs/rename.asciidoc @@ -25,12 +25,12 @@ before assigning values. [source,yaml] ------- processors: -- rename: - fields: - - from: "a.g" - to: "e.d" - ignore_missing: false - fail_on_error: true + - rename: + fields: + - from: "a.g" + to: "e.d" + ignore_missing: false + fail_on_error: true ------- The `rename` processor has the following configuration settings: diff --git a/libbeat/processors/actions/docs/replace.asciidoc b/libbeat/processors/actions/docs/replace.asciidoc index 3faf3e0bcce..b833e2e84bb 100644 --- a/libbeat/processors/actions/docs/replace.asciidoc +++ b/libbeat/processors/actions/docs/replace.asciidoc @@ -24,13 +24,13 @@ Following example will change path from /usr/bin to /usr/local/bin [source,yaml] ------- processors: -- replace: - fields: - - field: "file.path" - pattern: "/usr/" - replacement: "/usr/local/" - ignore_missing: false - fail_on_error: true + - replace: + fields: + - field: "file.path" + pattern: "/usr/" + replacement: "/usr/local/" + ignore_missing: false + fail_on_error: true ------- The `replace` processor has following configuration settings: diff --git a/libbeat/processors/actions/docs/truncate_fields.asciidoc b/libbeat/processors/actions/docs/truncate_fields.asciidoc index a9726818672..58ddc6ed3da 100644 --- a/libbeat/processors/actions/docs/truncate_fields.asciidoc +++ b/libbeat/processors/actions/docs/truncate_fields.asciidoc @@ -23,10 +23,10 @@ For example, this configuration truncates the field named `message` to 5 charact [source,yaml] ------------------------------------------------------------------------------ processors: -- truncate_fields: - fields: - - message - max_characters: 5 - fail_on_error: false - ignore_missing: true + - truncate_fields: + fields: + - message + max_characters: 5 + fail_on_error: false + ignore_missing: true ------------------------------------------------------------------------------ diff --git a/libbeat/processors/add_cloud_metadata/docs/add_cloud_metadata.asciidoc b/libbeat/processors/add_cloud_metadata/docs/add_cloud_metadata.asciidoc index d17fd326b87..9a5fcfcbf91 100644 --- a/libbeat/processors/add_cloud_metadata/docs/add_cloud_metadata.asciidoc +++ b/libbeat/processors/add_cloud_metadata/docs/add_cloud_metadata.asciidoc @@ -28,7 +28,7 @@ The simple configuration below enables the processor. [source,yaml] ------------------------------------------------------------------------------- processors: -- add_cloud_metadata: ~ + - add_cloud_metadata: ~ ------------------------------------------------------------------------------- The `add_cloud_metadata` processor has three optional configuration settings. diff --git a/libbeat/processors/add_docker_metadata/docs/add_docker_metadata.asciidoc b/libbeat/processors/add_docker_metadata/docs/add_docker_metadata.asciidoc index aa1229ff7cb..801437a4624 100644 --- a/libbeat/processors/add_docker_metadata/docs/add_docker_metadata.asciidoc +++ b/libbeat/processors/add_docker_metadata/docs/add_docker_metadata.asciidoc @@ -34,20 +34,20 @@ running as non-root inside the container. [source,yaml] ------------------------------------------------------------------------------- processors: -- add_docker_metadata: - host: "unix:///var/run/docker.sock" - #match_fields: ["system.process.cgroup.id"] - #match_pids: ["process.pid", "process.ppid"] - #match_source: true - #match_source_index: 4 - #match_short_id: true - #cleanup_timeout: 60 - #labels.dedot: false - # To connect to Docker over TLS you must specify a client and CA certificate. - #ssl: - # certificate_authority: "/etc/pki/root/ca.pem" - # certificate: "/etc/pki/client/cert.pem" - # key: "/etc/pki/client/cert.key" + - add_docker_metadata: + host: "unix:///var/run/docker.sock" + #match_fields: ["system.process.cgroup.id"] + #match_pids: ["process.pid", "process.ppid"] + #match_source: true + #match_source_index: 4 + #match_short_id: true + #cleanup_timeout: 60 + #labels.dedot: false + # To connect to Docker over TLS you must specify a client and CA certificate. + #ssl: + # certificate_authority: "/etc/pki/root/ca.pem" + # certificate: "/etc/pki/client/cert.pem" + # key: "/etc/pki/client/cert.key" ------------------------------------------------------------------------------- It has the following settings: diff --git a/libbeat/processors/add_host_metadata/docs/add_host_metadata.asciidoc b/libbeat/processors/add_host_metadata/docs/add_host_metadata.asciidoc index 4a651548ff6..0c71f10d200 100644 --- a/libbeat/processors/add_host_metadata/docs/add_host_metadata.asciidoc +++ b/libbeat/processors/add_host_metadata/docs/add_host_metadata.asciidoc @@ -8,16 +8,16 @@ [source,yaml] ------------------------------------------------------------------------------- processors: -- add_host_metadata: - cache.ttl: 5m - geo: - name: nyc-dc1-rack1 - location: 40.7128, -74.0060 - continent_name: North America - country_iso_code: US - region_name: New York - region_iso_code: NY - city_name: New York + - add_host_metadata: + cache.ttl: 5m + geo: + name: nyc-dc1-rack1 + location: 40.7128, -74.0060 + continent_name: North America + country_iso_code: US + region_name: New York + region_iso_code: NY + city_name: New York ------------------------------------------------------------------------------- It has the following settings: diff --git a/libbeat/processors/add_id/docs/add_id.asciidoc b/libbeat/processors/add_id/docs/add_id.asciidoc index a68d3e96123..1a051d6b29a 100644 --- a/libbeat/processors/add_id/docs/add_id.asciidoc +++ b/libbeat/processors/add_id/docs/add_id.asciidoc @@ -10,7 +10,7 @@ The `add_id` processor generates a unique ID for an event. [source,yaml] ----------------------------------------------------- processors: - - add_id: ~ + - add_id: ~ ----------------------------------------------------- The following settings are supported: diff --git a/libbeat/processors/add_kubernetes_metadata/docs/add_kubernetes_metadata.asciidoc b/libbeat/processors/add_kubernetes_metadata/docs/add_kubernetes_metadata.asciidoc index a46120a281c..a862ba42262 100644 --- a/libbeat/processors/add_kubernetes_metadata/docs/add_kubernetes_metadata.asciidoc +++ b/libbeat/processors/add_kubernetes_metadata/docs/add_kubernetes_metadata.asciidoc @@ -56,7 +56,7 @@ Kubernetes. [source,yaml] ------------------------------------------------------------------------------- processors: -- add_kubernetes_metadata: + - add_kubernetes_metadata: ------------------------------------------------------------------------------- The configuration below enables the processor on a Beat running as a process on @@ -65,11 +65,11 @@ the Kubernetes node. [source,yaml] ------------------------------------------------------------------------------- processors: -- add_kubernetes_metadata: - host: - # If kube_config is not set, KUBECONFIG environment variable will be checked - # and if not present it will fall back to InCluster - kube_config: ${HOME}/.kube/config + - add_kubernetes_metadata: + host: + # If kube_config is not set, KUBECONFIG environment variable will be checked + # and if not present it will fall back to InCluster + kube_config: ${HOME}/.kube/config ------------------------------------------------------------------------------- The configuration below has the default indexers and matchers disabled and @@ -78,18 +78,18 @@ enables ones that the user is interested in. [source,yaml] ------------------------------------------------------------------------------- processors: -- add_kubernetes_metadata: - host: - # If kube_config is not set, KUBECONFIG environment variable will be checked - # and if not present it will fall back to InCluster - kube_config: ~/.kube/config - default_indexers.enabled: false - default_matchers.enabled: false - indexers: - - ip_port: - matchers: - - fields: - lookup_fields: ["metricset.host"] + - add_kubernetes_metadata: + host: + # If kube_config is not set, KUBECONFIG environment variable will be checked + # and if not present it will fall back to InCluster + kube_config: ~/.kube/config + default_indexers.enabled: false + default_matchers.enabled: false + indexers: + - ip_port: + matchers: + - fields: + lookup_fields: ["metricset.host"] ------------------------------------------------------------------------------- The `add_kubernetes_metadata` processor has the following configuration settings: diff --git a/libbeat/processors/add_locale/docs/add_locale.asciidoc b/libbeat/processors/add_locale/docs/add_locale.asciidoc index 82c502cf0ac..a1a0455a69f 100644 --- a/libbeat/processors/add_locale/docs/add_locale.asciidoc +++ b/libbeat/processors/add_locale/docs/add_locale.asciidoc @@ -16,7 +16,7 @@ The configuration below enables the processor with the default settings. [source,yaml] ------------------------------------------------------------------------------- processors: -- add_locale: ~ + - add_locale: ~ ------------------------------------------------------------------------------- This configuration enables the processor and configures it to add the time zone @@ -25,8 +25,8 @@ abbreviation to events. [source,yaml] ------------------------------------------------------------------------------- processors: -- add_locale: - format: abbreviation + - add_locale: + format: abbreviation ------------------------------------------------------------------------------- NOTE: Please note that `add_locale` differentiates between daylight savings diff --git a/libbeat/processors/add_observer_metadata/docs/add_observer_metadata.asciidoc b/libbeat/processors/add_observer_metadata/docs/add_observer_metadata.asciidoc index 701e90741e0..ae98fa39dc5 100644 --- a/libbeat/processors/add_observer_metadata/docs/add_observer_metadata.asciidoc +++ b/libbeat/processors/add_observer_metadata/docs/add_observer_metadata.asciidoc @@ -10,16 +10,16 @@ beta[] [source,yaml] ------------------------------------------------------------------------------- processors: -- add_observer_metadata: - cache.ttl: 5m - geo: - name: nyc-dc1-rack1 - location: 40.7128, -74.0060 - continent_name: North America - country_iso_code: US - region_name: New York - region_iso_code: NY - city_name: New York + - add_observer_metadata: + cache.ttl: 5m + geo: + name: nyc-dc1-rack1 + location: 40.7128, -74.0060 + continent_name: North America + country_iso_code: US + region_name: New York + region_iso_code: NY + city_name: New York ------------------------------------------------------------------------------- It has the following settings: diff --git a/libbeat/processors/add_process_metadata/docs/add_process_metadata.asciidoc b/libbeat/processors/add_process_metadata/docs/add_process_metadata.asciidoc index 36acc68a407..3066107a009 100644 --- a/libbeat/processors/add_process_metadata/docs/add_process_metadata.asciidoc +++ b/libbeat/processors/add_process_metadata/docs/add_process_metadata.asciidoc @@ -11,9 +11,9 @@ processes, identified by their process ID (PID). [source,yaml] ------------------------------------------------------------------------------- processors: -- add_process_metadata: - match_pids: [system.process.ppid] - target: system.process.parent + - add_process_metadata: + match_pids: [system.process.ppid] + target: system.process.parent ------------------------------------------------------------------------------- The fields added to the event look as follows: diff --git a/libbeat/processors/decode_csv_fields/docs/decode_csv_fields.asciidoc b/libbeat/processors/decode_csv_fields/docs/decode_csv_fields.asciidoc index ead8551bca3..53cc3787543 100644 --- a/libbeat/processors/decode_csv_fields/docs/decode_csv_fields.asciidoc +++ b/libbeat/processors/decode_csv_fields/docs/decode_csv_fields.asciidoc @@ -14,14 +14,14 @@ This processor is available for Filebeat and Journalbeat. [source,yaml] ----------------------------------------------------- processors: - - decode_csv_fields: - fields: + - decode_csv_fields: + fields: message: decoded.csv - separator: "," - ignore_missing: false - overwrite_keys: true - trim_leading_space: false - fail_on_error: true + separator: "," + ignore_missing: false + overwrite_keys: true + trim_leading_space: false + fail_on_error: true ----------------------------------------------------- The `decode_csv_fields` has the following settings: diff --git a/libbeat/processors/dns/docs/dns.asciidoc b/libbeat/processors/dns/docs/dns.asciidoc index a6f92101033..b75fb8bf87a 100644 --- a/libbeat/processors/dns/docs/dns.asciidoc +++ b/libbeat/processors/dns/docs/dns.asciidoc @@ -30,11 +30,11 @@ in two fields. [source,yaml] ---- processors: -- dns: - type: reverse - fields: - source.ip: source.hostname - destination.ip: destination.hostname + - dns: + type: reverse + fields: + source.ip: source.hostname + destination.ip: destination.hostname ---- Next is a configuration example showing all options. diff --git a/libbeat/processors/extract_array/docs/extract_array.asciidoc b/libbeat/processors/extract_array/docs/extract_array.asciidoc index e30bf5c3d3c..0384a05dcca 100644 --- a/libbeat/processors/extract_array/docs/extract_array.asciidoc +++ b/libbeat/processors/extract_array/docs/extract_array.asciidoc @@ -15,9 +15,9 @@ the `my_array` field, `destination.ip` with the second element, and [source,yaml] ----------------------------------------------------- processors: - - extract_array: - field: my_array - mappings: + - extract_array: + field: my_array + mappings: source.ip: 0 destination.ip: 1 network.transport: 2 diff --git a/libbeat/processors/fingerprint/docs/fingerprint.asciidoc b/libbeat/processors/fingerprint/docs/fingerprint.asciidoc index 442fcfd8f7d..75509020c0a 100644 --- a/libbeat/processors/fingerprint/docs/fingerprint.asciidoc +++ b/libbeat/processors/fingerprint/docs/fingerprint.asciidoc @@ -11,8 +11,8 @@ specified subset of its fields. [source,yaml] ----------------------------------------------------- processors: - - fingerprint: - fields: ["field1", "field2", ...] + - fingerprint: + fields: ["field1", "field2", ...] ----------------------------------------------------- The following settings are supported: diff --git a/libbeat/processors/registered_domain/docs/registered_domain.asciidoc b/libbeat/processors/registered_domain/docs/registered_domain.asciidoc index 6e257954233..784811dca95 100644 --- a/libbeat/processors/registered_domain/docs/registered_domain.asciidoc +++ b/libbeat/processors/registered_domain/docs/registered_domain.asciidoc @@ -16,11 +16,11 @@ This processor uses the Mozilla Public Suffix list to determine the value. [source,yaml] ---- processors: -- registered_domain: - field: dns.question.name - target_field: dns.question.registered_domain - ignore_missing: true - ignore_failure: true + - registered_domain: + field: dns.question.name + target_field: dns.question.registered_domain + ignore_missing: true + ignore_failure: true ---- The `registered_domain` processor has the following configuration settings: diff --git a/libbeat/processors/script/docs/script.asciidoc b/libbeat/processors/script/docs/script.asciidoc index 2b89cbe3c8c..106a98a001b 100644 --- a/libbeat/processors/script/docs/script.asciidoc +++ b/libbeat/processors/script/docs/script.asciidoc @@ -16,13 +16,13 @@ file or by pointing the processor at external file(s). [source,yaml] ---- processors: -- script: - lang: javascript - id: my_filter - source: > - function process(event) { - event.Tag("js"); - } + - script: + lang: javascript + id: my_filter + source: > + function process(event) { + event.Tag("js"); + } ---- This loads `filter.js` from disk. @@ -30,10 +30,10 @@ This loads `filter.js` from disk. [source,yaml] ---- processors: -- script: - lang: javascript - id: my_filter - file: ${path.config}/filter.js + - script: + lang: javascript + id: my_filter + file: ${path.config}/filter.js ---- Parameters can be passed to the script by adding `params` to the config. @@ -43,21 +43,21 @@ code must define a `register(params)` function to receive the parameters. [source,yaml] ---- processors: -- script: - lang: javascript - id: my_filter - params: - threshold: 15 - source: > - var params = {threshold: 42}; - function register(scriptParams) { - params = scriptParams; - } - function process(event) { - if (event.Get("severity") < params.threshold) { - event.Cancel(); - } - } + - script: + lang: javascript + id: my_filter + params: + threshold: 15 + source: > + var params = {threshold: 42}; + function register(scriptParams) { + params = scriptParams; + } + function process(event) { + if (event.Get("severity") < params.threshold) { + event.Cancel(); + } + } ---- If the script defines a `test()` function it will be invoked when the processor diff --git a/libbeat/processors/timestamp/docs/timestamp.asciidoc b/libbeat/processors/timestamp/docs/timestamp.asciidoc index a8d569a9d9c..8c2ba6c0266 100644 --- a/libbeat/processors/timestamp/docs/timestamp.asciidoc +++ b/libbeat/processors/timestamp/docs/timestamp.asciidoc @@ -56,14 +56,14 @@ parse with this configuration. [source,yaml] ---- processors: -- timestamp: - field: start_time - layouts: - - '2006-01-02T15:04:05Z' - - '2006-01-02T15:04:05.999Z' - test: - - '2019-06-22T16:33:51Z' - - '2019-11-18T04:59:51.123Z' -- drop_fields: - fields: [start_time] + - timestamp: + field: start_time + layouts: + - '2006-01-02T15:04:05Z' + - '2006-01-02T15:04:05.999Z' + test: + - '2019-06-22T16:33:51Z' + - '2019-11-18T04:59:51.123Z' + - drop_fields: + fields: [start_time] ---- diff --git a/libbeat/processors/translate_sid/docs/translate_sid.asciidoc b/libbeat/processors/translate_sid/docs/translate_sid.asciidoc index 68f7fd2542d..9878e0667c0 100644 --- a/libbeat/processors/translate_sid/docs/translate_sid.asciidoc +++ b/libbeat/processors/translate_sid/docs/translate_sid.asciidoc @@ -1,6 +1,10 @@ [[processor-translate-sid]] === Translate SID +++++ +translate_sid +++++ + beta[] The `translate_sid` processor translates a Windows security identifier (SID) @@ -19,12 +23,12 @@ unless `ignore_failure` is set. [source,yaml] ---- processors: -- translate_sid: - field: winlog.event_data.MemberSid - account_name_target: user.name - domain_target: user.domain - ignore_missing: true - ignore_failure: true + - translate_sid: + field: winlog.event_data.MemberSid + account_name_target: user.name + domain_target: user.domain + ignore_missing: true + ignore_failure: true ---- The `translate_sid` processor has the following configuration settings: diff --git a/libbeat/processors/urldecode/docs/urldecode.asciidoc b/libbeat/processors/urldecode/docs/urldecode.asciidoc index 6a544749d2c..427090627bf 100644 --- a/libbeat/processors/urldecode/docs/urldecode.asciidoc +++ b/libbeat/processors/urldecode/docs/urldecode.asciidoc @@ -14,12 +14,12 @@ key, each entry contains a `from: source-field` and a `to: target-field` pair, w [source,yaml] ------- processors: -- urldecode: - fields: - - from: "field1" - to: "field2" - ignore_missing: false - fail_on_error: true + - urldecode: + fields: + - from: "field1" + to: "field2" + ignore_missing: false + fail_on_error: true ------- In the example above: diff --git a/metricbeat/docs/kubernetes-default-indexers-matchers.asciidoc b/metricbeat/docs/kubernetes-default-indexers-matchers.asciidoc index 2e1e4dde705..c3bf974d53d 100644 --- a/metricbeat/docs/kubernetes-default-indexers-matchers.asciidoc +++ b/metricbeat/docs/kubernetes-default-indexers-matchers.asciidoc @@ -8,7 +8,7 @@ configuration: [source,yaml] ------------------------------------------------------------------------------- processors: -- add_kubernetes_metadata: - default_indexers.enabled: false - default_matchers.enabled: false + - add_kubernetes_metadata: + default_indexers.enabled: false + default_matchers.enabled: false ------------------------------------------------------------------------------- diff --git a/metricbeat/docs/metricbeat-filtering.asciidoc b/metricbeat/docs/metricbeat-filtering.asciidoc index c7ea4c353c5..096948238ea 100644 --- a/metricbeat/docs/metricbeat-filtering.asciidoc +++ b/metricbeat/docs/metricbeat-filtering.asciidoc @@ -13,8 +13,8 @@ dropping the `agent.name` and `agent.version` fields under `beat` from all docum [source, yaml] ---- processors: - - drop_fields: - fields: ['agent'] + - drop_fields: + fields: ['agent'] ---- include::{libbeat-dir}/processors-using.asciidoc[] diff --git a/metricbeat/metricbeat.reference.yml b/metricbeat/metricbeat.reference.yml index 9dcfe20071b..1f9d6558620 100644 --- a/metricbeat/metricbeat.reference.yml +++ b/metricbeat/metricbeat.reference.yml @@ -996,151 +996,151 @@ metricbeat.modules: # values: # #processors: -#- include_fields: -# fields: ["cpu"] -#- drop_fields: -# fields: ["cpu.user", "cpu.system"] +# - include_fields: +# fields: ["cpu"] +# - drop_fields: +# fields: ["cpu.user", "cpu.system"] # # The following example drops the events that have the HTTP response code 200: # #processors: -#- drop_event: -# when: -# equals: -# http.code: 200 +# - drop_event: +# when: +# equals: +# http.code: 200 # # The following example renames the field a to b: # #processors: -#- rename: -# fields: -# - from: "a" -# to: "b" +# - rename: +# fields: +# - from: "a" +# to: "b" # # The following example tokenizes the string into fields: # #processors: -#- dissect: -# tokenizer: "%{key1} - %{key2}" -# field: "message" -# target_prefix: "dissect" +# - dissect: +# tokenizer: "%{key1} - %{key2}" +# field: "message" +# target_prefix: "dissect" # # The following example enriches each event with metadata from the cloud # provider about the host machine. It works on EC2, GCE, DigitalOcean, # Tencent Cloud, and Alibaba Cloud. # #processors: -#- add_cloud_metadata: ~ +# - add_cloud_metadata: ~ # # The following example enriches each event with the machine's local time zone # offset from UTC. # #processors: -#- add_locale: -# format: offset +# - add_locale: +# format: offset # # The following example enriches each event with docker metadata, it matches # given fields to an existing container id and adds info from that container: # #processors: -#- add_docker_metadata: -# host: "unix:///var/run/docker.sock" -# match_fields: ["system.process.cgroup.id"] -# match_pids: ["process.pid", "process.ppid"] -# match_source: true -# match_source_index: 4 -# match_short_id: false -# cleanup_timeout: 60 -# labels.dedot: false -# # To connect to Docker over TLS you must specify a client and CA certificate. -# #ssl: -# # certificate_authority: "/etc/pki/root/ca.pem" -# # certificate: "/etc/pki/client/cert.pem" -# # key: "/etc/pki/client/cert.key" +# - add_docker_metadata: +# host: "unix:///var/run/docker.sock" +# match_fields: ["system.process.cgroup.id"] +# match_pids: ["process.pid", "process.ppid"] +# match_source: true +# match_source_index: 4 +# match_short_id: false +# cleanup_timeout: 60 +# labels.dedot: false +# # To connect to Docker over TLS you must specify a client and CA certificate. +# #ssl: +# # certificate_authority: "/etc/pki/root/ca.pem" +# # certificate: "/etc/pki/client/cert.pem" +# # key: "/etc/pki/client/cert.key" # # The following example enriches each event with docker metadata, it matches # container id from log path available in `source` field (by default it expects # it to be /var/lib/docker/containers/*/*.log). # #processors: -#- add_docker_metadata: ~ +# - add_docker_metadata: ~ # # The following example enriches each event with host metadata. # #processors: -#- add_host_metadata: ~ +# - add_host_metadata: ~ # # The following example enriches each event with process metadata using # process IDs included in the event. # #processors: -#- add_process_metadata: -# match_pids: ["system.process.ppid"] -# target: system.process.parent +# - add_process_metadata: +# match_pids: ["system.process.ppid"] +# target: system.process.parent # # The following example decodes fields containing JSON strings # and replaces the strings with valid JSON objects. # #processors: -#- decode_json_fields: -# fields: ["field1", "field2", ...] -# process_array: false -# max_depth: 1 -# target: "" -# overwrite_keys: false +# - decode_json_fields: +# fields: ["field1", "field2", ...] +# process_array: false +# max_depth: 1 +# target: "" +# overwrite_keys: false # #processors: -#- decompress_gzip_field: -# from: "field1" -# to: "field2" -# ignore_missing: false -# fail_on_error: true +# - decompress_gzip_field: +# from: "field1" +# to: "field2" +# ignore_missing: false +# fail_on_error: true # # The following example copies the value of message to message_copied # #processors: -#- copy_fields: -# fields: +# - copy_fields: +# fields: # - from: message # to: message_copied -# fail_on_error: true -# ignore_missing: false +# fail_on_error: true +# ignore_missing: false # # The following example truncates the value of message to 1024 bytes # #processors: -#- truncate_fields: -# fields: -# - message -# max_bytes: 1024 -# fail_on_error: false -# ignore_missing: true +# - truncate_fields: +# fields: +# - message +# max_bytes: 1024 +# fail_on_error: false +# ignore_missing: true # # The following example preserves the raw message under event.original # #processors: -#- copy_fields: -# fields: +# - copy_fields: +# fields: # - from: message # to: event.original -# fail_on_error: false -# ignore_missing: true -#- truncate_fields: -# fields: -# - event.original -# max_bytes: 1024 -# fail_on_error: false -# ignore_missing: true +# fail_on_error: false +# ignore_missing: true +# - truncate_fields: +# fields: +# - event.original +# max_bytes: 1024 +# fail_on_error: false +# ignore_missing: true # # The following example URL-decodes the value of field1 to field2 # #processors: -#- urldecode: -# fields: -# - from: "field1" -# to: "field2" -# ignore_missing: false -# fail_on_error: true +# - urldecode: +# fields: +# - from: "field1" +# to: "field2" +# ignore_missing: false +# fail_on_error: true #============================= Elastic Cloud ================================== diff --git a/metricbeat/module/system/filesystem/_meta/docs.asciidoc b/metricbeat/module/system/filesystem/_meta/docs.asciidoc index dd10e665cf7..97029f24a83 100644 --- a/metricbeat/module/system/filesystem/_meta/docs.asciidoc +++ b/metricbeat/module/system/filesystem/_meta/docs.asciidoc @@ -48,6 +48,6 @@ metricbeat.modules: period: 30s metricsets: ["filesystem"] processors: - - drop_event.when.regexp: - system.filesystem.mount_point: '^/(sys|cgroup|proc|dev|etc|host)($|/)' + - drop_event.when.regexp: + system.filesystem.mount_point: '^/(sys|cgroup|proc|dev|etc|host)($|/)' ---- diff --git a/metricbeat/module/windows/service/_meta/docs.asciidoc b/metricbeat/module/windows/service/_meta/docs.asciidoc index bba2cd8e8f6..21ef4b45047 100644 --- a/metricbeat/module/windows/service/_meta/docs.asciidoc +++ b/metricbeat/module/windows/service/_meta/docs.asciidoc @@ -32,6 +32,6 @@ except for the events for the firewall service. See metricsets: ["service"] period: 60s processors: - - drop_event.when.not.equals: - windows.service.display_name: Windows Firewall + - drop_event.when.not.equals: + windows.service.display_name: Windows Firewall ---- diff --git a/packetbeat/docs/packetbeat-filtering.asciidoc b/packetbeat/docs/packetbeat-filtering.asciidoc index 3ac4d31f7c9..a3513659cf4 100644 --- a/packetbeat/docs/packetbeat-filtering.asciidoc +++ b/packetbeat/docs/packetbeat-filtering.asciidoc @@ -55,10 +55,10 @@ following configuration: [source,yaml] ---- processors: - - drop_event: - when: + - drop_event: + when: equals: - http.response.status_code: 200 + http.response.status_code: 200 ---- @@ -67,11 +67,11 @@ If you don't want to export raw data for the successful transactions: [source,yaml] ---- processors: - - drop_fields: - when: + - drop_fields: + when: equals: - http.response.status_code: 200 - fields: ["request", "response"] + http.response.status_code: 200 + fields: ["request", "response"] ---- include::{libbeat-dir}/processors-using.asciidoc[] diff --git a/packetbeat/packetbeat.reference.yml b/packetbeat/packetbeat.reference.yml index 30621ffbde4..3132c03bb9d 100644 --- a/packetbeat/packetbeat.reference.yml +++ b/packetbeat/packetbeat.reference.yml @@ -670,151 +670,151 @@ packetbeat.ignore_outgoing: false # values: # #processors: -#- include_fields: -# fields: ["cpu"] -#- drop_fields: -# fields: ["cpu.user", "cpu.system"] +# - include_fields: +# fields: ["cpu"] +# - drop_fields: +# fields: ["cpu.user", "cpu.system"] # # The following example drops the events that have the HTTP response code 200: # #processors: -#- drop_event: -# when: -# equals: -# http.code: 200 +# - drop_event: +# when: +# equals: +# http.code: 200 # # The following example renames the field a to b: # #processors: -#- rename: -# fields: -# - from: "a" -# to: "b" +# - rename: +# fields: +# - from: "a" +# to: "b" # # The following example tokenizes the string into fields: # #processors: -#- dissect: -# tokenizer: "%{key1} - %{key2}" -# field: "message" -# target_prefix: "dissect" +# - dissect: +# tokenizer: "%{key1} - %{key2}" +# field: "message" +# target_prefix: "dissect" # # The following example enriches each event with metadata from the cloud # provider about the host machine. It works on EC2, GCE, DigitalOcean, # Tencent Cloud, and Alibaba Cloud. # #processors: -#- add_cloud_metadata: ~ +# - add_cloud_metadata: ~ # # The following example enriches each event with the machine's local time zone # offset from UTC. # #processors: -#- add_locale: -# format: offset +# - add_locale: +# format: offset # # The following example enriches each event with docker metadata, it matches # given fields to an existing container id and adds info from that container: # #processors: -#- add_docker_metadata: -# host: "unix:///var/run/docker.sock" -# match_fields: ["system.process.cgroup.id"] -# match_pids: ["process.pid", "process.ppid"] -# match_source: true -# match_source_index: 4 -# match_short_id: false -# cleanup_timeout: 60 -# labels.dedot: false -# # To connect to Docker over TLS you must specify a client and CA certificate. -# #ssl: -# # certificate_authority: "/etc/pki/root/ca.pem" -# # certificate: "/etc/pki/client/cert.pem" -# # key: "/etc/pki/client/cert.key" +# - add_docker_metadata: +# host: "unix:///var/run/docker.sock" +# match_fields: ["system.process.cgroup.id"] +# match_pids: ["process.pid", "process.ppid"] +# match_source: true +# match_source_index: 4 +# match_short_id: false +# cleanup_timeout: 60 +# labels.dedot: false +# # To connect to Docker over TLS you must specify a client and CA certificate. +# #ssl: +# # certificate_authority: "/etc/pki/root/ca.pem" +# # certificate: "/etc/pki/client/cert.pem" +# # key: "/etc/pki/client/cert.key" # # The following example enriches each event with docker metadata, it matches # container id from log path available in `source` field (by default it expects # it to be /var/lib/docker/containers/*/*.log). # #processors: -#- add_docker_metadata: ~ +# - add_docker_metadata: ~ # # The following example enriches each event with host metadata. # #processors: -#- add_host_metadata: ~ +# - add_host_metadata: ~ # # The following example enriches each event with process metadata using # process IDs included in the event. # #processors: -#- add_process_metadata: -# match_pids: ["system.process.ppid"] -# target: system.process.parent +# - add_process_metadata: +# match_pids: ["system.process.ppid"] +# target: system.process.parent # # The following example decodes fields containing JSON strings # and replaces the strings with valid JSON objects. # #processors: -#- decode_json_fields: -# fields: ["field1", "field2", ...] -# process_array: false -# max_depth: 1 -# target: "" -# overwrite_keys: false +# - decode_json_fields: +# fields: ["field1", "field2", ...] +# process_array: false +# max_depth: 1 +# target: "" +# overwrite_keys: false # #processors: -#- decompress_gzip_field: -# from: "field1" -# to: "field2" -# ignore_missing: false -# fail_on_error: true +# - decompress_gzip_field: +# from: "field1" +# to: "field2" +# ignore_missing: false +# fail_on_error: true # # The following example copies the value of message to message_copied # #processors: -#- copy_fields: -# fields: +# - copy_fields: +# fields: # - from: message # to: message_copied -# fail_on_error: true -# ignore_missing: false +# fail_on_error: true +# ignore_missing: false # # The following example truncates the value of message to 1024 bytes # #processors: -#- truncate_fields: -# fields: -# - message -# max_bytes: 1024 -# fail_on_error: false -# ignore_missing: true +# - truncate_fields: +# fields: +# - message +# max_bytes: 1024 +# fail_on_error: false +# ignore_missing: true # # The following example preserves the raw message under event.original # #processors: -#- copy_fields: -# fields: +# - copy_fields: +# fields: # - from: message # to: event.original -# fail_on_error: false -# ignore_missing: true -#- truncate_fields: -# fields: -# - event.original -# max_bytes: 1024 -# fail_on_error: false -# ignore_missing: true +# fail_on_error: false +# ignore_missing: true +# - truncate_fields: +# fields: +# - event.original +# max_bytes: 1024 +# fail_on_error: false +# ignore_missing: true # # The following example URL-decodes the value of field1 to field2 # #processors: -#- urldecode: -# fields: -# - from: "field1" -# to: "field2" -# ignore_missing: false -# fail_on_error: true +# - urldecode: +# fields: +# - from: "field1" +# to: "field2" +# ignore_missing: false +# fail_on_error: true #============================= Elastic Cloud ================================== diff --git a/winlogbeat/docs/modules.asciidoc b/winlogbeat/docs/modules.asciidoc index a48f0e4ed11..f8d57950a12 100644 --- a/winlogbeat/docs/modules.asciidoc +++ b/winlogbeat/docs/modules.asciidoc @@ -35,16 +35,16 @@ winlogbeat.event_logs: - name: ForwardedEvents tags: [forwarded] processors: - - script: - when.equals.winlog.channel: Security - lang: javascript - id: security - file: ${path.home}/module/security/config/winlogbeat-security.js - - script: - when.equals.winlog.channel: Microsoft-Windows-Sysmon/Operational - lang: javascript - id: sysmon - file: ${path.home}/module/sysmon/config/winlogbeat-sysmon.js + - script: + when.equals.winlog.channel: Security + lang: javascript + id: security + file: ${path.home}/module/security/config/winlogbeat-security.js + - script: + when.equals.winlog.channel: Microsoft-Windows-Sysmon/Operational + lang: javascript + id: sysmon + file: ${path.home}/module/sysmon/config/winlogbeat-sysmon.js ---- [float] diff --git a/winlogbeat/winlogbeat.reference.yml b/winlogbeat/winlogbeat.reference.yml index 39e22d25e34..9dca185c13a 100644 --- a/winlogbeat/winlogbeat.reference.yml +++ b/winlogbeat/winlogbeat.reference.yml @@ -159,151 +159,151 @@ winlogbeat.event_logs: # values: # #processors: -#- include_fields: -# fields: ["cpu"] -#- drop_fields: -# fields: ["cpu.user", "cpu.system"] +# - include_fields: +# fields: ["cpu"] +# - drop_fields: +# fields: ["cpu.user", "cpu.system"] # # The following example drops the events that have the HTTP response code 200: # #processors: -#- drop_event: -# when: -# equals: -# http.code: 200 +# - drop_event: +# when: +# equals: +# http.code: 200 # # The following example renames the field a to b: # #processors: -#- rename: -# fields: -# - from: "a" -# to: "b" +# - rename: +# fields: +# - from: "a" +# to: "b" # # The following example tokenizes the string into fields: # #processors: -#- dissect: -# tokenizer: "%{key1} - %{key2}" -# field: "message" -# target_prefix: "dissect" +# - dissect: +# tokenizer: "%{key1} - %{key2}" +# field: "message" +# target_prefix: "dissect" # # The following example enriches each event with metadata from the cloud # provider about the host machine. It works on EC2, GCE, DigitalOcean, # Tencent Cloud, and Alibaba Cloud. # #processors: -#- add_cloud_metadata: ~ +# - add_cloud_metadata: ~ # # The following example enriches each event with the machine's local time zone # offset from UTC. # #processors: -#- add_locale: -# format: offset +# - add_locale: +# format: offset # # The following example enriches each event with docker metadata, it matches # given fields to an existing container id and adds info from that container: # #processors: -#- add_docker_metadata: -# host: "unix:///var/run/docker.sock" -# match_fields: ["system.process.cgroup.id"] -# match_pids: ["process.pid", "process.ppid"] -# match_source: true -# match_source_index: 4 -# match_short_id: false -# cleanup_timeout: 60 -# labels.dedot: false -# # To connect to Docker over TLS you must specify a client and CA certificate. -# #ssl: -# # certificate_authority: "/etc/pki/root/ca.pem" -# # certificate: "/etc/pki/client/cert.pem" -# # key: "/etc/pki/client/cert.key" +# - add_docker_metadata: +# host: "unix:///var/run/docker.sock" +# match_fields: ["system.process.cgroup.id"] +# match_pids: ["process.pid", "process.ppid"] +# match_source: true +# match_source_index: 4 +# match_short_id: false +# cleanup_timeout: 60 +# labels.dedot: false +# # To connect to Docker over TLS you must specify a client and CA certificate. +# #ssl: +# # certificate_authority: "/etc/pki/root/ca.pem" +# # certificate: "/etc/pki/client/cert.pem" +# # key: "/etc/pki/client/cert.key" # # The following example enriches each event with docker metadata, it matches # container id from log path available in `source` field (by default it expects # it to be /var/lib/docker/containers/*/*.log). # #processors: -#- add_docker_metadata: ~ +# - add_docker_metadata: ~ # # The following example enriches each event with host metadata. # #processors: -#- add_host_metadata: ~ +# - add_host_metadata: ~ # # The following example enriches each event with process metadata using # process IDs included in the event. # #processors: -#- add_process_metadata: -# match_pids: ["system.process.ppid"] -# target: system.process.parent +# - add_process_metadata: +# match_pids: ["system.process.ppid"] +# target: system.process.parent # # The following example decodes fields containing JSON strings # and replaces the strings with valid JSON objects. # #processors: -#- decode_json_fields: -# fields: ["field1", "field2", ...] -# process_array: false -# max_depth: 1 -# target: "" -# overwrite_keys: false +# - decode_json_fields: +# fields: ["field1", "field2", ...] +# process_array: false +# max_depth: 1 +# target: "" +# overwrite_keys: false # #processors: -#- decompress_gzip_field: -# from: "field1" -# to: "field2" -# ignore_missing: false -# fail_on_error: true +# - decompress_gzip_field: +# from: "field1" +# to: "field2" +# ignore_missing: false +# fail_on_error: true # # The following example copies the value of message to message_copied # #processors: -#- copy_fields: -# fields: +# - copy_fields: +# fields: # - from: message # to: message_copied -# fail_on_error: true -# ignore_missing: false +# fail_on_error: true +# ignore_missing: false # # The following example truncates the value of message to 1024 bytes # #processors: -#- truncate_fields: -# fields: -# - message -# max_bytes: 1024 -# fail_on_error: false -# ignore_missing: true +# - truncate_fields: +# fields: +# - message +# max_bytes: 1024 +# fail_on_error: false +# ignore_missing: true # # The following example preserves the raw message under event.original # #processors: -#- copy_fields: -# fields: +# - copy_fields: +# fields: # - from: message # to: event.original -# fail_on_error: false -# ignore_missing: true -#- truncate_fields: -# fields: -# - event.original -# max_bytes: 1024 -# fail_on_error: false -# ignore_missing: true +# fail_on_error: false +# ignore_missing: true +# - truncate_fields: +# fields: +# - event.original +# max_bytes: 1024 +# fail_on_error: false +# ignore_missing: true # # The following example URL-decodes the value of field1 to field2 # #processors: -#- urldecode: -# fields: -# - from: "field1" -# to: "field2" -# ignore_missing: false -# fail_on_error: true +# - urldecode: +# fields: +# - from: "field1" +# to: "field2" +# ignore_missing: false +# fail_on_error: true #============================= Elastic Cloud ================================== diff --git a/x-pack/auditbeat/auditbeat.reference.yml b/x-pack/auditbeat/auditbeat.reference.yml index 5e98dab34de..4514be47ddd 100644 --- a/x-pack/auditbeat/auditbeat.reference.yml +++ b/x-pack/auditbeat/auditbeat.reference.yml @@ -299,151 +299,151 @@ auditbeat.modules: # values: # #processors: -#- include_fields: -# fields: ["cpu"] -#- drop_fields: -# fields: ["cpu.user", "cpu.system"] +# - include_fields: +# fields: ["cpu"] +# - drop_fields: +# fields: ["cpu.user", "cpu.system"] # # The following example drops the events that have the HTTP response code 200: # #processors: -#- drop_event: -# when: -# equals: -# http.code: 200 +# - drop_event: +# when: +# equals: +# http.code: 200 # # The following example renames the field a to b: # #processors: -#- rename: -# fields: -# - from: "a" -# to: "b" +# - rename: +# fields: +# - from: "a" +# to: "b" # # The following example tokenizes the string into fields: # #processors: -#- dissect: -# tokenizer: "%{key1} - %{key2}" -# field: "message" -# target_prefix: "dissect" +# - dissect: +# tokenizer: "%{key1} - %{key2}" +# field: "message" +# target_prefix: "dissect" # # The following example enriches each event with metadata from the cloud # provider about the host machine. It works on EC2, GCE, DigitalOcean, # Tencent Cloud, and Alibaba Cloud. # #processors: -#- add_cloud_metadata: ~ +# - add_cloud_metadata: ~ # # The following example enriches each event with the machine's local time zone # offset from UTC. # #processors: -#- add_locale: -# format: offset +# - add_locale: +# format: offset # # The following example enriches each event with docker metadata, it matches # given fields to an existing container id and adds info from that container: # #processors: -#- add_docker_metadata: -# host: "unix:///var/run/docker.sock" -# match_fields: ["system.process.cgroup.id"] -# match_pids: ["process.pid", "process.ppid"] -# match_source: true -# match_source_index: 4 -# match_short_id: false -# cleanup_timeout: 60 -# labels.dedot: false -# # To connect to Docker over TLS you must specify a client and CA certificate. -# #ssl: -# # certificate_authority: "/etc/pki/root/ca.pem" -# # certificate: "/etc/pki/client/cert.pem" -# # key: "/etc/pki/client/cert.key" +# - add_docker_metadata: +# host: "unix:///var/run/docker.sock" +# match_fields: ["system.process.cgroup.id"] +# match_pids: ["process.pid", "process.ppid"] +# match_source: true +# match_source_index: 4 +# match_short_id: false +# cleanup_timeout: 60 +# labels.dedot: false +# # To connect to Docker over TLS you must specify a client and CA certificate. +# #ssl: +# # certificate_authority: "/etc/pki/root/ca.pem" +# # certificate: "/etc/pki/client/cert.pem" +# # key: "/etc/pki/client/cert.key" # # The following example enriches each event with docker metadata, it matches # container id from log path available in `source` field (by default it expects # it to be /var/lib/docker/containers/*/*.log). # #processors: -#- add_docker_metadata: ~ +# - add_docker_metadata: ~ # # The following example enriches each event with host metadata. # #processors: -#- add_host_metadata: ~ +# - add_host_metadata: ~ # # The following example enriches each event with process metadata using # process IDs included in the event. # #processors: -#- add_process_metadata: -# match_pids: ["system.process.ppid"] -# target: system.process.parent +# - add_process_metadata: +# match_pids: ["system.process.ppid"] +# target: system.process.parent # # The following example decodes fields containing JSON strings # and replaces the strings with valid JSON objects. # #processors: -#- decode_json_fields: -# fields: ["field1", "field2", ...] -# process_array: false -# max_depth: 1 -# target: "" -# overwrite_keys: false +# - decode_json_fields: +# fields: ["field1", "field2", ...] +# process_array: false +# max_depth: 1 +# target: "" +# overwrite_keys: false # #processors: -#- decompress_gzip_field: -# from: "field1" -# to: "field2" -# ignore_missing: false -# fail_on_error: true +# - decompress_gzip_field: +# from: "field1" +# to: "field2" +# ignore_missing: false +# fail_on_error: true # # The following example copies the value of message to message_copied # #processors: -#- copy_fields: -# fields: +# - copy_fields: +# fields: # - from: message # to: message_copied -# fail_on_error: true -# ignore_missing: false +# fail_on_error: true +# ignore_missing: false # # The following example truncates the value of message to 1024 bytes # #processors: -#- truncate_fields: -# fields: -# - message -# max_bytes: 1024 -# fail_on_error: false -# ignore_missing: true +# - truncate_fields: +# fields: +# - message +# max_bytes: 1024 +# fail_on_error: false +# ignore_missing: true # # The following example preserves the raw message under event.original # #processors: -#- copy_fields: -# fields: +# - copy_fields: +# fields: # - from: message # to: event.original -# fail_on_error: false -# ignore_missing: true -#- truncate_fields: -# fields: -# - event.original -# max_bytes: 1024 -# fail_on_error: false -# ignore_missing: true +# fail_on_error: false +# ignore_missing: true +# - truncate_fields: +# fields: +# - event.original +# max_bytes: 1024 +# fail_on_error: false +# ignore_missing: true # # The following example URL-decodes the value of field1 to field2 # #processors: -#- urldecode: -# fields: -# - from: "field1" -# to: "field2" -# ignore_missing: false -# fail_on_error: true +# - urldecode: +# fields: +# - from: "field1" +# to: "field2" +# ignore_missing: false +# fail_on_error: true #============================= Elastic Cloud ================================== diff --git a/x-pack/filebeat/filebeat.reference.yml b/x-pack/filebeat/filebeat.reference.yml index 115d8678fd7..e9503f55863 100644 --- a/x-pack/filebeat/filebeat.reference.yml +++ b/x-pack/filebeat/filebeat.reference.yml @@ -1692,151 +1692,151 @@ filebeat.inputs: # values: # #processors: -#- include_fields: -# fields: ["cpu"] -#- drop_fields: -# fields: ["cpu.user", "cpu.system"] +# - include_fields: +# fields: ["cpu"] +# - drop_fields: +# fields: ["cpu.user", "cpu.system"] # # The following example drops the events that have the HTTP response code 200: # #processors: -#- drop_event: -# when: -# equals: -# http.code: 200 +# - drop_event: +# when: +# equals: +# http.code: 200 # # The following example renames the field a to b: # #processors: -#- rename: -# fields: -# - from: "a" -# to: "b" +# - rename: +# fields: +# - from: "a" +# to: "b" # # The following example tokenizes the string into fields: # #processors: -#- dissect: -# tokenizer: "%{key1} - %{key2}" -# field: "message" -# target_prefix: "dissect" +# - dissect: +# tokenizer: "%{key1} - %{key2}" +# field: "message" +# target_prefix: "dissect" # # The following example enriches each event with metadata from the cloud # provider about the host machine. It works on EC2, GCE, DigitalOcean, # Tencent Cloud, and Alibaba Cloud. # #processors: -#- add_cloud_metadata: ~ +# - add_cloud_metadata: ~ # # The following example enriches each event with the machine's local time zone # offset from UTC. # #processors: -#- add_locale: -# format: offset +# - add_locale: +# format: offset # # The following example enriches each event with docker metadata, it matches # given fields to an existing container id and adds info from that container: # #processors: -#- add_docker_metadata: -# host: "unix:///var/run/docker.sock" -# match_fields: ["system.process.cgroup.id"] -# match_pids: ["process.pid", "process.ppid"] -# match_source: true -# match_source_index: 4 -# match_short_id: false -# cleanup_timeout: 60 -# labels.dedot: false -# # To connect to Docker over TLS you must specify a client and CA certificate. -# #ssl: -# # certificate_authority: "/etc/pki/root/ca.pem" -# # certificate: "/etc/pki/client/cert.pem" -# # key: "/etc/pki/client/cert.key" +# - add_docker_metadata: +# host: "unix:///var/run/docker.sock" +# match_fields: ["system.process.cgroup.id"] +# match_pids: ["process.pid", "process.ppid"] +# match_source: true +# match_source_index: 4 +# match_short_id: false +# cleanup_timeout: 60 +# labels.dedot: false +# # To connect to Docker over TLS you must specify a client and CA certificate. +# #ssl: +# # certificate_authority: "/etc/pki/root/ca.pem" +# # certificate: "/etc/pki/client/cert.pem" +# # key: "/etc/pki/client/cert.key" # # The following example enriches each event with docker metadata, it matches # container id from log path available in `source` field (by default it expects # it to be /var/lib/docker/containers/*/*.log). # #processors: -#- add_docker_metadata: ~ +# - add_docker_metadata: ~ # # The following example enriches each event with host metadata. # #processors: -#- add_host_metadata: ~ +# - add_host_metadata: ~ # # The following example enriches each event with process metadata using # process IDs included in the event. # #processors: -#- add_process_metadata: -# match_pids: ["system.process.ppid"] -# target: system.process.parent +# - add_process_metadata: +# match_pids: ["system.process.ppid"] +# target: system.process.parent # # The following example decodes fields containing JSON strings # and replaces the strings with valid JSON objects. # #processors: -#- decode_json_fields: -# fields: ["field1", "field2", ...] -# process_array: false -# max_depth: 1 -# target: "" -# overwrite_keys: false +# - decode_json_fields: +# fields: ["field1", "field2", ...] +# process_array: false +# max_depth: 1 +# target: "" +# overwrite_keys: false # #processors: -#- decompress_gzip_field: -# from: "field1" -# to: "field2" -# ignore_missing: false -# fail_on_error: true +# - decompress_gzip_field: +# from: "field1" +# to: "field2" +# ignore_missing: false +# fail_on_error: true # # The following example copies the value of message to message_copied # #processors: -#- copy_fields: -# fields: +# - copy_fields: +# fields: # - from: message # to: message_copied -# fail_on_error: true -# ignore_missing: false +# fail_on_error: true +# ignore_missing: false # # The following example truncates the value of message to 1024 bytes # #processors: -#- truncate_fields: -# fields: -# - message -# max_bytes: 1024 -# fail_on_error: false -# ignore_missing: true +# - truncate_fields: +# fields: +# - message +# max_bytes: 1024 +# fail_on_error: false +# ignore_missing: true # # The following example preserves the raw message under event.original # #processors: -#- copy_fields: -# fields: +# - copy_fields: +# fields: # - from: message # to: event.original -# fail_on_error: false -# ignore_missing: true -#- truncate_fields: -# fields: -# - event.original -# max_bytes: 1024 -# fail_on_error: false -# ignore_missing: true +# fail_on_error: false +# ignore_missing: true +# - truncate_fields: +# fields: +# - event.original +# max_bytes: 1024 +# fail_on_error: false +# ignore_missing: true # # The following example URL-decodes the value of field1 to field2 # #processors: -#- urldecode: -# fields: -# - from: "field1" -# to: "field2" -# ignore_missing: false -# fail_on_error: true +# - urldecode: +# fields: +# - from: "field1" +# to: "field2" +# ignore_missing: false +# fail_on_error: true #============================= Elastic Cloud ================================== diff --git a/x-pack/filebeat/processors/decode_cef/docs/decode_cef.asciidoc b/x-pack/filebeat/processors/decode_cef/docs/decode_cef.asciidoc index 592439afabc..3078bf3477b 100644 --- a/x-pack/filebeat/processors/decode_cef/docs/decode_cef.asciidoc +++ b/x-pack/filebeat/processors/decode_cef/docs/decode_cef.asciidoc @@ -16,11 +16,11 @@ renaming it to `event.original`. It is best to rename `message` to [source,yaml] ---- processors: -- rename: - fields: - - {from: "message", to: "event.original"} -- decode_cef: - field: event.original + - rename: + fields: + - {from: "message", to: "event.original"} + - decode_cef: + field: event.original ---- The `decode_cef` processor has the following configuration settings. diff --git a/x-pack/functionbeat/functionbeat.reference.yml b/x-pack/functionbeat/functionbeat.reference.yml index 9cf9d78c613..c3ab460ffe3 100644 --- a/x-pack/functionbeat/functionbeat.reference.yml +++ b/x-pack/functionbeat/functionbeat.reference.yml @@ -509,151 +509,151 @@ functionbeat.provider.gcp.functions: # values: # #processors: -#- include_fields: -# fields: ["cpu"] -#- drop_fields: -# fields: ["cpu.user", "cpu.system"] +# - include_fields: +# fields: ["cpu"] +# - drop_fields: +# fields: ["cpu.user", "cpu.system"] # # The following example drops the events that have the HTTP response code 200: # #processors: -#- drop_event: -# when: -# equals: -# http.code: 200 +# - drop_event: +# when: +# equals: +# http.code: 200 # # The following example renames the field a to b: # #processors: -#- rename: -# fields: -# - from: "a" -# to: "b" +# - rename: +# fields: +# - from: "a" +# to: "b" # # The following example tokenizes the string into fields: # #processors: -#- dissect: -# tokenizer: "%{key1} - %{key2}" -# field: "message" -# target_prefix: "dissect" +# - dissect: +# tokenizer: "%{key1} - %{key2}" +# field: "message" +# target_prefix: "dissect" # # The following example enriches each event with metadata from the cloud # provider about the host machine. It works on EC2, GCE, DigitalOcean, # Tencent Cloud, and Alibaba Cloud. # #processors: -#- add_cloud_metadata: ~ +# - add_cloud_metadata: ~ # # The following example enriches each event with the machine's local time zone # offset from UTC. # #processors: -#- add_locale: -# format: offset +# - add_locale: +# format: offset # # The following example enriches each event with docker metadata, it matches # given fields to an existing container id and adds info from that container: # #processors: -#- add_docker_metadata: -# host: "unix:///var/run/docker.sock" -# match_fields: ["system.process.cgroup.id"] -# match_pids: ["process.pid", "process.ppid"] -# match_source: true -# match_source_index: 4 -# match_short_id: false -# cleanup_timeout: 60 -# labels.dedot: false -# # To connect to Docker over TLS you must specify a client and CA certificate. -# #ssl: -# # certificate_authority: "/etc/pki/root/ca.pem" -# # certificate: "/etc/pki/client/cert.pem" -# # key: "/etc/pki/client/cert.key" +# - add_docker_metadata: +# host: "unix:///var/run/docker.sock" +# match_fields: ["system.process.cgroup.id"] +# match_pids: ["process.pid", "process.ppid"] +# match_source: true +# match_source_index: 4 +# match_short_id: false +# cleanup_timeout: 60 +# labels.dedot: false +# # To connect to Docker over TLS you must specify a client and CA certificate. +# #ssl: +# # certificate_authority: "/etc/pki/root/ca.pem" +# # certificate: "/etc/pki/client/cert.pem" +# # key: "/etc/pki/client/cert.key" # # The following example enriches each event with docker metadata, it matches # container id from log path available in `source` field (by default it expects # it to be /var/lib/docker/containers/*/*.log). # #processors: -#- add_docker_metadata: ~ +# - add_docker_metadata: ~ # # The following example enriches each event with host metadata. # #processors: -#- add_host_metadata: ~ +# - add_host_metadata: ~ # # The following example enriches each event with process metadata using # process IDs included in the event. # #processors: -#- add_process_metadata: -# match_pids: ["system.process.ppid"] -# target: system.process.parent +# - add_process_metadata: +# match_pids: ["system.process.ppid"] +# target: system.process.parent # # The following example decodes fields containing JSON strings # and replaces the strings with valid JSON objects. # #processors: -#- decode_json_fields: -# fields: ["field1", "field2", ...] -# process_array: false -# max_depth: 1 -# target: "" -# overwrite_keys: false +# - decode_json_fields: +# fields: ["field1", "field2", ...] +# process_array: false +# max_depth: 1 +# target: "" +# overwrite_keys: false # #processors: -#- decompress_gzip_field: -# from: "field1" -# to: "field2" -# ignore_missing: false -# fail_on_error: true +# - decompress_gzip_field: +# from: "field1" +# to: "field2" +# ignore_missing: false +# fail_on_error: true # # The following example copies the value of message to message_copied # #processors: -#- copy_fields: -# fields: +# - copy_fields: +# fields: # - from: message # to: message_copied -# fail_on_error: true -# ignore_missing: false +# fail_on_error: true +# ignore_missing: false # # The following example truncates the value of message to 1024 bytes # #processors: -#- truncate_fields: -# fields: -# - message -# max_bytes: 1024 -# fail_on_error: false -# ignore_missing: true +# - truncate_fields: +# fields: +# - message +# max_bytes: 1024 +# fail_on_error: false +# ignore_missing: true # # The following example preserves the raw message under event.original # #processors: -#- copy_fields: -# fields: +# - copy_fields: +# fields: # - from: message # to: event.original -# fail_on_error: false -# ignore_missing: true -#- truncate_fields: -# fields: -# - event.original -# max_bytes: 1024 -# fail_on_error: false -# ignore_missing: true +# fail_on_error: false +# ignore_missing: true +# - truncate_fields: +# fields: +# - event.original +# max_bytes: 1024 +# fail_on_error: false +# ignore_missing: true # # The following example URL-decodes the value of field1 to field2 # #processors: -#- urldecode: -# fields: -# - from: "field1" -# to: "field2" -# ignore_missing: false -# fail_on_error: true +# - urldecode: +# fields: +# - from: "field1" +# to: "field2" +# ignore_missing: false +# fail_on_error: true #============================= Elastic Cloud ================================== diff --git a/x-pack/libbeat/processors/add_cloudfoundry_metadata/docs/add_cloudfoundry_metadata.asciidoc b/x-pack/libbeat/processors/add_cloudfoundry_metadata/docs/add_cloudfoundry_metadata.asciidoc index 8de150048fd..7c5b2daba96 100644 --- a/x-pack/libbeat/processors/add_cloudfoundry_metadata/docs/add_cloudfoundry_metadata.asciidoc +++ b/x-pack/libbeat/processors/add_cloudfoundry_metadata/docs/add_cloudfoundry_metadata.asciidoc @@ -26,17 +26,17 @@ Each event is annotated with: [source,yaml] ------------------------------------------------------------------------------- processors: -- add_cloudfoundry_metadata: - api_address: https://api.dev.cfdev.sh - client_id: uaa-filebeat - client_secret: verysecret - ssl: - verification_mode: none - # To connect to Cloud Foundry over verified TLS you can specify a client and CA certificate. - #ssl: - # certificate_authorities: ["/etc/pki/cf/ca.pem"] - # certificate: "/etc/pki/cf/cert.pem" - # key: "/etc/pki/cf/cert.key" + - add_cloudfoundry_metadata: + api_address: https://api.dev.cfdev.sh + client_id: uaa-filebeat + client_secret: verysecret + ssl: + verification_mode: none + # To connect to Cloud Foundry over verified TLS you can specify a client and CA certificate. + #ssl: + # certificate_authorities: ["/etc/pki/cf/ca.pem"] + # certificate: "/etc/pki/cf/cert.pem" + # key: "/etc/pki/cf/cert.key" ------------------------------------------------------------------------------- It has the following settings: diff --git a/x-pack/metricbeat/metricbeat.reference.yml b/x-pack/metricbeat/metricbeat.reference.yml index be35d457ced..c02e6a43471 100644 --- a/x-pack/metricbeat/metricbeat.reference.yml +++ b/x-pack/metricbeat/metricbeat.reference.yml @@ -1421,151 +1421,151 @@ metricbeat.modules: # values: # #processors: -#- include_fields: -# fields: ["cpu"] -#- drop_fields: -# fields: ["cpu.user", "cpu.system"] +# - include_fields: +# fields: ["cpu"] +# - drop_fields: +# fields: ["cpu.user", "cpu.system"] # # The following example drops the events that have the HTTP response code 200: # #processors: -#- drop_event: -# when: -# equals: -# http.code: 200 +# - drop_event: +# when: +# equals: +# http.code: 200 # # The following example renames the field a to b: # #processors: -#- rename: -# fields: -# - from: "a" -# to: "b" +# - rename: +# fields: +# - from: "a" +# to: "b" # # The following example tokenizes the string into fields: # #processors: -#- dissect: -# tokenizer: "%{key1} - %{key2}" -# field: "message" -# target_prefix: "dissect" +# - dissect: +# tokenizer: "%{key1} - %{key2}" +# field: "message" +# target_prefix: "dissect" # # The following example enriches each event with metadata from the cloud # provider about the host machine. It works on EC2, GCE, DigitalOcean, # Tencent Cloud, and Alibaba Cloud. # #processors: -#- add_cloud_metadata: ~ +# - add_cloud_metadata: ~ # # The following example enriches each event with the machine's local time zone # offset from UTC. # #processors: -#- add_locale: -# format: offset +# - add_locale: +# format: offset # # The following example enriches each event with docker metadata, it matches # given fields to an existing container id and adds info from that container: # #processors: -#- add_docker_metadata: -# host: "unix:///var/run/docker.sock" -# match_fields: ["system.process.cgroup.id"] -# match_pids: ["process.pid", "process.ppid"] -# match_source: true -# match_source_index: 4 -# match_short_id: false -# cleanup_timeout: 60 -# labels.dedot: false -# # To connect to Docker over TLS you must specify a client and CA certificate. -# #ssl: -# # certificate_authority: "/etc/pki/root/ca.pem" -# # certificate: "/etc/pki/client/cert.pem" -# # key: "/etc/pki/client/cert.key" +# - add_docker_metadata: +# host: "unix:///var/run/docker.sock" +# match_fields: ["system.process.cgroup.id"] +# match_pids: ["process.pid", "process.ppid"] +# match_source: true +# match_source_index: 4 +# match_short_id: false +# cleanup_timeout: 60 +# labels.dedot: false +# # To connect to Docker over TLS you must specify a client and CA certificate. +# #ssl: +# # certificate_authority: "/etc/pki/root/ca.pem" +# # certificate: "/etc/pki/client/cert.pem" +# # key: "/etc/pki/client/cert.key" # # The following example enriches each event with docker metadata, it matches # container id from log path available in `source` field (by default it expects # it to be /var/lib/docker/containers/*/*.log). # #processors: -#- add_docker_metadata: ~ +# - add_docker_metadata: ~ # # The following example enriches each event with host metadata. # #processors: -#- add_host_metadata: ~ +# - add_host_metadata: ~ # # The following example enriches each event with process metadata using # process IDs included in the event. # #processors: -#- add_process_metadata: -# match_pids: ["system.process.ppid"] -# target: system.process.parent +# - add_process_metadata: +# match_pids: ["system.process.ppid"] +# target: system.process.parent # # The following example decodes fields containing JSON strings # and replaces the strings with valid JSON objects. # #processors: -#- decode_json_fields: -# fields: ["field1", "field2", ...] -# process_array: false -# max_depth: 1 -# target: "" -# overwrite_keys: false +# - decode_json_fields: +# fields: ["field1", "field2", ...] +# process_array: false +# max_depth: 1 +# target: "" +# overwrite_keys: false # #processors: -#- decompress_gzip_field: -# from: "field1" -# to: "field2" -# ignore_missing: false -# fail_on_error: true +# - decompress_gzip_field: +# from: "field1" +# to: "field2" +# ignore_missing: false +# fail_on_error: true # # The following example copies the value of message to message_copied # #processors: -#- copy_fields: -# fields: +# - copy_fields: +# fields: # - from: message # to: message_copied -# fail_on_error: true -# ignore_missing: false +# fail_on_error: true +# ignore_missing: false # # The following example truncates the value of message to 1024 bytes # #processors: -#- truncate_fields: -# fields: -# - message -# max_bytes: 1024 -# fail_on_error: false -# ignore_missing: true +# - truncate_fields: +# fields: +# - message +# max_bytes: 1024 +# fail_on_error: false +# ignore_missing: true # # The following example preserves the raw message under event.original # #processors: -#- copy_fields: -# fields: +# - copy_fields: +# fields: # - from: message # to: event.original -# fail_on_error: false -# ignore_missing: true -#- truncate_fields: -# fields: -# - event.original -# max_bytes: 1024 -# fail_on_error: false -# ignore_missing: true +# fail_on_error: false +# ignore_missing: true +# - truncate_fields: +# fields: +# - event.original +# max_bytes: 1024 +# fail_on_error: false +# ignore_missing: true # # The following example URL-decodes the value of field1 to field2 # #processors: -#- urldecode: -# fields: -# - from: "field1" -# to: "field2" -# ignore_missing: false -# fail_on_error: true +# - urldecode: +# fields: +# - from: "field1" +# to: "field2" +# ignore_missing: false +# fail_on_error: true #============================= Elastic Cloud ================================== diff --git a/x-pack/metricbeat/module/cockroachdb/status/_meta/docs.asciidoc b/x-pack/metricbeat/module/cockroachdb/status/_meta/docs.asciidoc index e58c7689222..71d2eff78dc 100644 --- a/x-pack/metricbeat/module/cockroachdb/status/_meta/docs.asciidoc +++ b/x-pack/metricbeat/module/cockroachdb/status/_meta/docs.asciidoc @@ -6,6 +6,7 @@ WARNING: This metricset collects a large number of metrics, what can significantly impact disk usage. Processors can be used to drop unused metrics before they are stored. For example the following configuration will drop all histogram buckets: + [source,yaml] ------------------------------------------------------------------------------ - module: cockroachdb diff --git a/x-pack/winlogbeat/winlogbeat.reference.yml b/x-pack/winlogbeat/winlogbeat.reference.yml index 3fd2ffcba4f..a84f5a41676 100644 --- a/x-pack/winlogbeat/winlogbeat.reference.yml +++ b/x-pack/winlogbeat/winlogbeat.reference.yml @@ -183,151 +183,151 @@ winlogbeat.event_logs: # values: # #processors: -#- include_fields: -# fields: ["cpu"] -#- drop_fields: -# fields: ["cpu.user", "cpu.system"] +# - include_fields: +# fields: ["cpu"] +# - drop_fields: +# fields: ["cpu.user", "cpu.system"] # # The following example drops the events that have the HTTP response code 200: # #processors: -#- drop_event: -# when: -# equals: -# http.code: 200 +# - drop_event: +# when: +# equals: +# http.code: 200 # # The following example renames the field a to b: # #processors: -#- rename: -# fields: -# - from: "a" -# to: "b" +# - rename: +# fields: +# - from: "a" +# to: "b" # # The following example tokenizes the string into fields: # #processors: -#- dissect: -# tokenizer: "%{key1} - %{key2}" -# field: "message" -# target_prefix: "dissect" +# - dissect: +# tokenizer: "%{key1} - %{key2}" +# field: "message" +# target_prefix: "dissect" # # The following example enriches each event with metadata from the cloud # provider about the host machine. It works on EC2, GCE, DigitalOcean, # Tencent Cloud, and Alibaba Cloud. # #processors: -#- add_cloud_metadata: ~ +# - add_cloud_metadata: ~ # # The following example enriches each event with the machine's local time zone # offset from UTC. # #processors: -#- add_locale: -# format: offset +# - add_locale: +# format: offset # # The following example enriches each event with docker metadata, it matches # given fields to an existing container id and adds info from that container: # #processors: -#- add_docker_metadata: -# host: "unix:///var/run/docker.sock" -# match_fields: ["system.process.cgroup.id"] -# match_pids: ["process.pid", "process.ppid"] -# match_source: true -# match_source_index: 4 -# match_short_id: false -# cleanup_timeout: 60 -# labels.dedot: false -# # To connect to Docker over TLS you must specify a client and CA certificate. -# #ssl: -# # certificate_authority: "/etc/pki/root/ca.pem" -# # certificate: "/etc/pki/client/cert.pem" -# # key: "/etc/pki/client/cert.key" +# - add_docker_metadata: +# host: "unix:///var/run/docker.sock" +# match_fields: ["system.process.cgroup.id"] +# match_pids: ["process.pid", "process.ppid"] +# match_source: true +# match_source_index: 4 +# match_short_id: false +# cleanup_timeout: 60 +# labels.dedot: false +# # To connect to Docker over TLS you must specify a client and CA certificate. +# #ssl: +# # certificate_authority: "/etc/pki/root/ca.pem" +# # certificate: "/etc/pki/client/cert.pem" +# # key: "/etc/pki/client/cert.key" # # The following example enriches each event with docker metadata, it matches # container id from log path available in `source` field (by default it expects # it to be /var/lib/docker/containers/*/*.log). # #processors: -#- add_docker_metadata: ~ +# - add_docker_metadata: ~ # # The following example enriches each event with host metadata. # #processors: -#- add_host_metadata: ~ +# - add_host_metadata: ~ # # The following example enriches each event with process metadata using # process IDs included in the event. # #processors: -#- add_process_metadata: -# match_pids: ["system.process.ppid"] -# target: system.process.parent +# - add_process_metadata: +# match_pids: ["system.process.ppid"] +# target: system.process.parent # # The following example decodes fields containing JSON strings # and replaces the strings with valid JSON objects. # #processors: -#- decode_json_fields: -# fields: ["field1", "field2", ...] -# process_array: false -# max_depth: 1 -# target: "" -# overwrite_keys: false +# - decode_json_fields: +# fields: ["field1", "field2", ...] +# process_array: false +# max_depth: 1 +# target: "" +# overwrite_keys: false # #processors: -#- decompress_gzip_field: -# from: "field1" -# to: "field2" -# ignore_missing: false -# fail_on_error: true +# - decompress_gzip_field: +# from: "field1" +# to: "field2" +# ignore_missing: false +# fail_on_error: true # # The following example copies the value of message to message_copied # #processors: -#- copy_fields: -# fields: +# - copy_fields: +# fields: # - from: message # to: message_copied -# fail_on_error: true -# ignore_missing: false +# fail_on_error: true +# ignore_missing: false # # The following example truncates the value of message to 1024 bytes # #processors: -#- truncate_fields: -# fields: -# - message -# max_bytes: 1024 -# fail_on_error: false -# ignore_missing: true +# - truncate_fields: +# fields: +# - message +# max_bytes: 1024 +# fail_on_error: false +# ignore_missing: true # # The following example preserves the raw message under event.original # #processors: -#- copy_fields: -# fields: +# - copy_fields: +# fields: # - from: message # to: event.original -# fail_on_error: false -# ignore_missing: true -#- truncate_fields: -# fields: -# - event.original -# max_bytes: 1024 -# fail_on_error: false -# ignore_missing: true +# fail_on_error: false +# ignore_missing: true +# - truncate_fields: +# fields: +# - event.original +# max_bytes: 1024 +# fail_on_error: false +# ignore_missing: true # # The following example URL-decodes the value of field1 to field2 # #processors: -#- urldecode: -# fields: -# - from: "field1" -# to: "field2" -# ignore_missing: false -# fail_on_error: true +# - urldecode: +# fields: +# - from: "field1" +# to: "field2" +# ignore_missing: false +# fail_on_error: true #============================= Elastic Cloud ================================== From 6cdc9b0d498c54931ed2c4134d23521e2f161bcd Mon Sep 17 00:00:00 2001 From: Pier-Hugues Pellerin Date: Mon, 4 May 2020 14:43:46 -0400 Subject: [PATCH 101/116] [Elastic Agent] Enable windows enroll cmd test (#18189) The windows tests should be fixed by now. Fixes: #16860 --- .../elastic-agent/pkg/agent/application/enroll_cmd_test.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/x-pack/elastic-agent/pkg/agent/application/enroll_cmd_test.go b/x-pack/elastic-agent/pkg/agent/application/enroll_cmd_test.go index d59cc620e1a..e22b2ec9eb0 100644 --- a/x-pack/elastic-agent/pkg/agent/application/enroll_cmd_test.go +++ b/x-pack/elastic-agent/pkg/agent/application/enroll_cmd_test.go @@ -13,7 +13,6 @@ import ( "net/http" "net/http/httptest" "os" - "runtime" "strconv" "testing" @@ -44,10 +43,6 @@ func (m *mockStore) Save(in io.Reader) error { } func TestEnroll(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("Disabled under windows: https://github.com/elastic/beats/issues/16860") - } - log, _ := logger.New() t.Run("fail to save is propagated", withTLSServer( From 7df6d010f2cdfdeced76e335049ae0fe78e4d1ea Mon Sep 17 00:00:00 2001 From: DeDe Morton Date: Mon, 4 May 2020 11:46:50 -0700 Subject: [PATCH 102/116] [docs] Change timezone to time zone (#18144) --- filebeat/docs/fields.asciidoc | 2 +- filebeat/docs/include/timezone-support.asciidoc | 12 ++++++------ .../module/elasticsearch/deprecation/config/log.yml | 2 +- filebeat/module/elasticsearch/server/config/log.yml | 4 ++-- .../module/elasticsearch/slowlog/config/slowlog.yml | 2 +- filebeat/module/logstash/log/config/log.yml | 2 +- filebeat/module/logstash/slowlog/config/slowlog.yml | 2 +- heartbeat/_meta/beat.reference.yml | 2 +- heartbeat/docs/heartbeat-scheduler.asciidoc | 2 +- heartbeat/heartbeat.reference.yml | 2 +- libbeat/processors/timestamp/docs/timestamp.asciidoc | 2 +- .../filebeat/processors/decode_cef/_meta/fields.yml | 2 +- x-pack/filebeat/processors/decode_cef/fields.go | 2 +- 13 files changed, 19 insertions(+), 19 deletions(-) diff --git a/filebeat/docs/fields.asciidoc b/filebeat/docs/fields.asciidoc index 0b93d699d2b..34d6b83ab7b 100644 --- a/filebeat/docs/fields.asciidoc +++ b/filebeat/docs/fields.asciidoc @@ -4152,7 +4152,7 @@ type: date *`cef.extensions.deviceTimeZone`*:: + -- -The timezone for the device generating the event. +The time zone for the device generating the event. type: keyword diff --git a/filebeat/docs/include/timezone-support.asciidoc b/filebeat/docs/include/timezone-support.asciidoc index e49bc860e73..50370e18a6c 100644 --- a/filebeat/docs/include/timezone-support.asciidoc +++ b/filebeat/docs/include/timezone-support.asciidoc @@ -1,17 +1,17 @@ [float] -==== Timezone support +==== Time zone support -This module parses logs that don't contain timezone information. For these logs, -Filebeat reads the local timezone and uses it when parsing to convert the -timestamp to UTC. The timezone to be used for parsing is included in the event +This module parses logs that don't contain time zone information. For these logs, +Filebeat reads the local time zone and uses it when parsing to convert the +timestamp to UTC. The time zone to be used for parsing is included in the event in the `event.timezone` field. To disable this conversion, the `event.timezone` field can be removed with the `drop_fields` processor. -If logs are originated from systems or applications with a different timezone to +If logs are originated from systems or applications with a different time zone to the local one, the `event.timezone` field can be overwritten with the original -timezone using the `add_fields` processor. +time zone using the `add_fields` processor. See <> for information about specifying processors in your config. diff --git a/filebeat/module/elasticsearch/deprecation/config/log.yml b/filebeat/module/elasticsearch/deprecation/config/log.yml index 14cdbbe9414..292a917e767 100644 --- a/filebeat/module/elasticsearch/deprecation/config/log.yml +++ b/filebeat/module/elasticsearch/deprecation/config/log.yml @@ -10,5 +10,5 @@ multiline: match: after processors: -# Locale for timezone is only needed in non-json logs +# Locale for time zone is only needed in non-json logs - add_locale.when.not.regexp.message: "^{" diff --git a/filebeat/module/elasticsearch/server/config/log.yml b/filebeat/module/elasticsearch/server/config/log.yml index 7d7e969a9b8..98be6d7adb6 100644 --- a/filebeat/module/elasticsearch/server/config/log.yml +++ b/filebeat/module/elasticsearch/server/config/log.yml @@ -10,5 +10,5 @@ multiline: match: after processors: -# Locale for timezone is only needed in non-json logs -- add_locale.when.not.regexp.message: "^{" +# Locale for time zone is only needed in non-json logs +- add_locale.when.not.regexp.message: "^{" diff --git a/filebeat/module/elasticsearch/slowlog/config/slowlog.yml b/filebeat/module/elasticsearch/slowlog/config/slowlog.yml index d6a75034b2d..03c14625b28 100644 --- a/filebeat/module/elasticsearch/slowlog/config/slowlog.yml +++ b/filebeat/module/elasticsearch/slowlog/config/slowlog.yml @@ -11,5 +11,5 @@ multiline: match: after processors: -# Locale for timezone is only needed in non-json logs +# Locale for time zone is only needed in non-json logs - add_locale.when.not.regexp.message: "^{" diff --git a/filebeat/module/logstash/log/config/log.yml b/filebeat/module/logstash/log/config/log.yml index d90907fb16c..c32b22b82d5 100644 --- a/filebeat/module/logstash/log/config/log.yml +++ b/filebeat/module/logstash/log/config/log.yml @@ -13,5 +13,5 @@ multiline: {{ end }} processors: -# Locale for timezone is only needed in non-json logs +# Locale for time zone is only needed in non-json logs - add_locale.when.not.regexp.message: "^{" diff --git a/filebeat/module/logstash/slowlog/config/slowlog.yml b/filebeat/module/logstash/slowlog/config/slowlog.yml index e8c035e32cc..270ba255609 100644 --- a/filebeat/module/logstash/slowlog/config/slowlog.yml +++ b/filebeat/module/logstash/slowlog/config/slowlog.yml @@ -6,5 +6,5 @@ paths: exclude_files: [".gz$"] processors: -# Locale for timezone is only needed in non-json logs +# Locale for time zone is only needed in non-json logs - add_locale.when.not.regexp.message: "^{" diff --git a/heartbeat/_meta/beat.reference.yml b/heartbeat/_meta/beat.reference.yml index e84bd9e99c4..efc94b31ed9 100644 --- a/heartbeat/_meta/beat.reference.yml +++ b/heartbeat/_meta/beat.reference.yml @@ -266,5 +266,5 @@ heartbeat.scheduler: # disabled if set to 0. The default is 0. #limit: 0 - # Set the scheduler it's timezone + # Set the scheduler it's time zone #location: '' diff --git a/heartbeat/docs/heartbeat-scheduler.asciidoc b/heartbeat/docs/heartbeat-scheduler.asciidoc index ac2f647f6ee..363928f1e58 100644 --- a/heartbeat/docs/heartbeat-scheduler.asciidoc +++ b/heartbeat/docs/heartbeat-scheduler.asciidoc @@ -37,5 +37,5 @@ specify for `limit` should be below the configured ulimit. [[heartbeat-scheduler-location]] ==== `location` -The timezone for the scheduler. By default the scheduler uses localtime. +The time zone for the scheduler. By default the scheduler uses localtime. diff --git a/heartbeat/heartbeat.reference.yml b/heartbeat/heartbeat.reference.yml index 31f343891fc..d3d6dfbd7a2 100644 --- a/heartbeat/heartbeat.reference.yml +++ b/heartbeat/heartbeat.reference.yml @@ -266,7 +266,7 @@ heartbeat.scheduler: # disabled if set to 0. The default is 0. #limit: 0 - # Set the scheduler it's timezone + # Set the scheduler it's time zone #location: '' #================================ General ====================================== diff --git a/libbeat/processors/timestamp/docs/timestamp.asciidoc b/libbeat/processors/timestamp/docs/timestamp.asciidoc index 8c2ba6c0266..83421c584a7 100644 --- a/libbeat/processors/timestamp/docs/timestamp.asciidoc +++ b/libbeat/processors/timestamp/docs/timestamp.asciidoc @@ -41,7 +41,7 @@ If a layout does not contain a year then the current year in the specified | `field` | yes | | Source field containing the time to be parsed. | | `target_field` | no | @timestamp | Target field for the parsed time value. The target value is always written as UTC. | | `layouts` | yes | | Timestamp layouts that define the expected time value format. In addition layouts, `UNIX` and `UNIX_MS` are accepted. | -| `timezone` | no | UTC | Timezone (e.g. America/New_York) to use when parsing a timestamp not containing a timezone. | +| `timezone` | no | UTC | Time zone (e.g. America/New_York) to use when parsing a timestamp not containing a time zone. | | `ignore_missing` | no | false | Ignore errors when the source field is missing. | | `ignore_failure` | no | false | Ignore all errors produced by the processor. | | `test` | no | | A list of timestamps that must parse successfully when loading the processor. | diff --git a/x-pack/filebeat/processors/decode_cef/_meta/fields.yml b/x-pack/filebeat/processors/decode_cef/_meta/fields.yml index 64b0a32cd02..6ac2ce6ea1c 100644 --- a/x-pack/filebeat/processors/decode_cef/_meta/fields.yml +++ b/x-pack/filebeat/processors/decode_cef/_meta/fields.yml @@ -452,7 +452,7 @@ - name: deviceTimeZone type: keyword - description: The timezone for the device generating the event. + description: The time zone for the device generating the event. - name: deviceTranslatedAddress type: ip diff --git a/x-pack/filebeat/processors/decode_cef/fields.go b/x-pack/filebeat/processors/decode_cef/fields.go index d885d6210e1..dd5080f8251 100644 --- a/x-pack/filebeat/processors/decode_cef/fields.go +++ b/x-pack/filebeat/processors/decode_cef/fields.go @@ -19,5 +19,5 @@ func init() { // AssetDecodeCef returns asset data. // This is the base64 encoded gzipped contents of processors/decode_cef. func AssetDecodeCef() string { - return "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" + return "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" } From 64f6e809ed8427741a9adfaa4bcde7c39245f02a Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Mon, 4 May 2020 14:59:54 -0400 Subject: [PATCH 103/116] Add more kubernetes integration tests (#18138) * Performing testing on kubelet metrics. * Add integration tests for kubelet, proxy, and scheduler. * mage fmt * Update to use GoVersion in pods create. * Fix kubelet hosts string to connect with kind. --- dev-tools/mage/kuberemote.go | 612 ------------------ dev-tools/mage/{ => kubernetes}/kubectl.go | 2 +- dev-tools/mage/kubernetes/kuberemote.go | 17 +- dev-tools/mage/kubernetes/kubernetes.go | 6 +- .../container/container_integration_test.go | 39 ++ .../kubernetes/node/node_integration_test.go | 39 ++ .../kubernetes/pod/pod_integration_test.go | 39 ++ .../proxy/proxy_integration_test.go | 39 ++ .../scheduler/scheduler_integration_test.go | 39 ++ .../system/system_integration_test.go | 39 ++ .../module/kubernetes/test/integration.go | 37 ++ .../volume/volume_integration_test.go | 39 ++ 12 files changed, 327 insertions(+), 620 deletions(-) delete mode 100644 dev-tools/mage/kuberemote.go rename dev-tools/mage/{ => kubernetes}/kubectl.go (99%) create mode 100644 metricbeat/module/kubernetes/container/container_integration_test.go create mode 100644 metricbeat/module/kubernetes/node/node_integration_test.go create mode 100644 metricbeat/module/kubernetes/pod/pod_integration_test.go create mode 100644 metricbeat/module/kubernetes/proxy/proxy_integration_test.go create mode 100644 metricbeat/module/kubernetes/scheduler/scheduler_integration_test.go create mode 100644 metricbeat/module/kubernetes/system/system_integration_test.go create mode 100644 metricbeat/module/kubernetes/volume/volume_integration_test.go diff --git a/dev-tools/mage/kuberemote.go b/dev-tools/mage/kuberemote.go deleted file mode 100644 index 078680fbf0b..00000000000 --- a/dev-tools/mage/kuberemote.go +++ /dev/null @@ -1,612 +0,0 @@ -// Licensed to Elasticsearch B.V. under one or more contributor -// license agreements. See the NOTICE file distributed with -// this work for additional information regarding copyright -// ownership. Elasticsearch B.V. licenses this file to you under -// the Apache License, Version 2.0 (the "License"); you may -// not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -package mage - -import ( - "bufio" - "context" - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "encoding/hex" - "encoding/pem" - "fmt" - "io" - "io/ioutil" - "net" - "net/http" - "net/url" - "os/exec" - "strings" - "time" - - "github.com/pkg/errors" - "golang.org/x/crypto/ssh" - - apiv1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/watch" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - "k8s.io/client-go/tools/portforward" - watchtools "k8s.io/client-go/tools/watch" - "k8s.io/client-go/transport/spdy" -) - -const sshBitSize = 4096 - -var mode = int32(256) - -// KubeRemote rsyncs the passed directory to a pod and runs the command inside of that pod. -type KubeRemote struct { - cfg *rest.Config - cs *kubernetes.Clientset - namespace string - name string - workDir string - destDir string - syncDir string - - svcAccName string - secretName string - privateKey []byte - publicKey []byte -} - -// NewKubeRemote creates a new kubernetes remote runner. -func NewKubeRemote(kubeconfig string, namespace string, name string, workDir string, destDir string, syncDir string) (*KubeRemote, error) { - config, err := clientcmd.BuildConfigFromFlags("", kubeconfig) - if err != nil { - return nil, err - } - cs, err := kubernetes.NewForConfig(config) - if err != nil { - return nil, err - } - name = strings.Replace(name, "_", "-", -1) - svcAccName := fmt.Sprintf("%s-sa", name) - secretName := fmt.Sprintf("%s-ssh-key", name) - privateKey, publicKey, err := generateSSHKeyPair() - if err != nil { - return nil, err - } - return &KubeRemote{config, cs, namespace, name, workDir, destDir, syncDir, svcAccName, secretName, privateKey, publicKey}, nil -} - -// Run runs the command remotely on the kubernetes cluster. -func (r *KubeRemote) Run(env map[string]string, stdout io.Writer, stderr io.Writer, args ...string) error { - if err := r.syncSSHKey(); err != nil { - return errors.Wrap(err, "failed to sync SSH secret") - } - defer r.deleteSSHKey() - if err := r.syncServiceAccount(); err != nil { - return err - } - defer r.deleteServiceAccount() - _, err := r.createPod(env, args...) - if err != nil { - return errors.Wrap(err, "failed to create execute pod") - } - defer r.deletePod() - - // wait for SSH to be up inside the init container. - _, err = r.waitForPod(5*time.Minute, podInitReady) - if err != nil { - return errors.Wrap(err, "execute pod init container never started") - } - time.Sleep(1 * time.Second) // SSH inside of container can take a moment - - // forward the SSH port so rsync can be ran. - randomPort, err := getFreePort() - if err != nil { - return errors.Wrap(err, "failed to find a free port") - } - stopChannel := make(chan struct{}, 1) - readyChannel := make(chan struct{}, 1) - f, err := r.portForward([]string{fmt.Sprintf("%d:%d", randomPort, 22)}, stopChannel, readyChannel, stderr, stderr) - if err != nil { - return err - } - go f.ForwardPorts() - <-readyChannel - - // perform the rsync - r.rsync(randomPort, stderr, stderr) - - // stop port forwarding - close(stopChannel) - - // wait for exec container to be running - _, err = r.waitForPod(5*time.Minute, containerRunning("exec")) - if err != nil { - return errors.Wrap(err, "execute pod container never started") - } - - // stream the logs of the container - err = r.streamLogs("exec", stdout) - if err != nil { - return errors.Wrap(err, "failed to stream the logs") - } - - // wait for exec container to be completely done - pod, err := r.waitForPod(30*time.Second, podDone) - if err != nil { - return errors.Wrap(err, "execute pod didn't terminate after 30 seconds of log stream") - } - - // return error on failure - if pod.Status.Phase == apiv1.PodFailed { - return fmt.Errorf("execute pod test failed") - } - return nil -} - -// deleteSSHKey deletes SSH key from the cluster. -func (r *KubeRemote) deleteSSHKey() { - _ = r.cs.CoreV1().Secrets(r.namespace).Delete(r.secretName, &metav1.DeleteOptions{}) -} - -// syncSSHKey syncs the SSH key to the cluster. -func (r *KubeRemote) syncSSHKey() error { - // delete before create - r.deleteSSHKey() - _, err := r.cs.CoreV1().Secrets(r.namespace).Create(createSecretManifest(r.secretName, r.publicKey)) - if err != nil { - return err - } - return nil -} - -// deleteServiceAccount syncs required service account. -func (r *KubeRemote) deleteServiceAccount() { - _ = r.cs.RbacV1().ClusterRoleBindings().Delete(r.name, &metav1.DeleteOptions{}) - _ = r.cs.RbacV1().ClusterRoles().Delete(r.name, &metav1.DeleteOptions{}) - _ = r.cs.CoreV1().ServiceAccounts(r.namespace).Delete(r.svcAccName, &metav1.DeleteOptions{}) -} - -// syncServiceAccount syncs required service account. -func (r *KubeRemote) syncServiceAccount() error { - // delete before create - r.deleteServiceAccount() - _, err := r.cs.CoreV1().ServiceAccounts(r.namespace).Create(createServiceAccountManifest(r.svcAccName)) - if err != nil { - return errors.Wrap(err, "failed to create service account") - } - _, err = r.cs.RbacV1().ClusterRoles().Create(createClusterRoleManifest(r.name)) - if err != nil { - return errors.Wrap(err, "failed to create cluster role") - } - _, err = r.cs.RbacV1().ClusterRoleBindings().Create(createClusterRoleBindingManifest(r.name, r.namespace, r.svcAccName)) - if err != nil { - return errors.Wrap(err, "failed to create cluster role binding") - } - return nil -} - -// createPod creates the pod. -func (r *KubeRemote) createPod(env map[string]string, cmd ...string) (*apiv1.Pod, error) { - r.deletePod() // ensure it doesn't already exist - return r.cs.CoreV1().Pods(r.namespace).Create(createPodManifest(r.name, "golang:1.13.9", env, cmd, r.workDir, r.destDir, r.secretName, r.svcAccName)) -} - -// deletePod deletes the pod. -func (r *KubeRemote) deletePod() { - _ = r.cs.CoreV1().Pods(r.namespace).Delete(r.name, &metav1.DeleteOptions{}) -} - -// waitForPod waits for the created pod to match the given condition. -func (r *KubeRemote) waitForPod(wait time.Duration, condition watchtools.ConditionFunc) (*apiv1.Pod, error) { - w, err := r.cs.CoreV1().Pods(r.namespace).Watch(metav1.SingleObject(metav1.ObjectMeta{Name: r.name})) - if err != nil { - return nil, err - } - - ctx, _ := watchtools.ContextWithOptionalTimeout(context.Background(), wait) - ev, err := watchtools.UntilWithoutRetry(ctx, w, func(ev watch.Event) (bool, error) { - return condition(ev) - }) - if ev != nil { - return ev.Object.(*apiv1.Pod), err - } - return nil, err -} - -// portFoward runs the port forwarding so SSH rsync can be ran into the pod. -func (r *KubeRemote) portForward(ports []string, stopChannel, readyChannel chan struct{}, stdout, stderr io.Writer) (*portforward.PortForwarder, error) { - roundTripper, upgrader, err := spdy.RoundTripperFor(r.cfg) - if err != nil { - return nil, err - } - - path := fmt.Sprintf("/api/v1/namespaces/%s/pods/%s/portforward", r.namespace, r.name) - hostIP := strings.TrimLeft(r.cfg.Host, "https://") - serverURL := url.URL{Scheme: "https", Path: path, Host: hostIP} - dialer := spdy.NewDialer(upgrader, &http.Client{Transport: roundTripper}, http.MethodPost, &serverURL) - return portforward.New(dialer, ports, stopChannel, readyChannel, stdout, stderr) -} - -// rsync performs the rsync of sync directory to destination directory inside of the pod. -func (r *KubeRemote) rsync(port uint16, stdout, stderr io.Writer) error { - privateKeyFile, err := createTempFile(r.privateKey) - if err != nil { - return err - } - - rsh := fmt.Sprintf("/usr/bin/ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -p %d -i %s", port, privateKeyFile) - args := []string{ - "--rsh", rsh, - "-a", fmt.Sprintf("%s/", r.syncDir), - fmt.Sprintf("root@localhost:%s", r.destDir), - } - cmd := exec.Command("rsync", args...) - cmd.Stdout = stdout - cmd.Stderr = stderr - return cmd.Run() -} - -// streamLogs streams the logs from the execution pod until the pod is terminated. -func (r *KubeRemote) streamLogs(container string, stdout io.Writer) error { - req := r.cs.CoreV1().Pods(r.namespace).GetLogs(r.name, &apiv1.PodLogOptions{ - Container: container, - Follow: true, - }) - logs, err := req.Stream() - if err != nil { - return err - } - defer logs.Close() - - reader := bufio.NewReader(logs) - for { - bytes, err := reader.ReadBytes('\n') - if _, err := stdout.Write(bytes); err != nil { - return err - } - if err != nil { - if err != io.EOF { - return err - } - return nil - } - } -} - -// generateSSHKeyPair generates a new SSH key pair. -func generateSSHKeyPair() ([]byte, []byte, error) { - private, err := rsa.GenerateKey(rand.Reader, sshBitSize) - if err != nil { - return nil, nil, err - } - if err = private.Validate(); err != nil { - return nil, nil, err - } - public, err := ssh.NewPublicKey(&private.PublicKey) - if err != nil { - return nil, nil, err - } - return encodePrivateKeyToPEM(private), ssh.MarshalAuthorizedKey(public), nil -} - -// encodePrivateKeyToPEM encodes private key from RSA to PEM format. -func encodePrivateKeyToPEM(privateKey *rsa.PrivateKey) []byte { - privDER := x509.MarshalPKCS1PrivateKey(privateKey) - privBlock := pem.Block{ - Type: "RSA PRIVATE KEY", - Headers: nil, - Bytes: privDER, - } - return pem.EncodeToMemory(&privBlock) -} - -// getFreePort finds a free port. -func getFreePort() (uint16, error) { - addr, err := net.ResolveTCPAddr("tcp", "localhost:0") - if err != nil { - return 0, err - } - l, err := net.ListenTCP("tcp", addr) - if err != nil { - return 0, err - } - defer l.Close() - return uint16(l.Addr().(*net.TCPAddr).Port), nil -} - -// createSecretManifest creates the secret object to create in the cluster. -// -// This is the public key that the sshd uses as the authorized key. -func createSecretManifest(name string, publicKey []byte) *apiv1.Secret { - return &apiv1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - StringData: map[string]string{ - "authorized_keys": string(publicKey), - }, - } -} - -// createServiceAccountManifest creates the service account the pod will used. -func createServiceAccountManifest(name string) *apiv1.ServiceAccount { - return &apiv1.ServiceAccount{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - } -} - -// createClusterRoleManifest creates the cluster role the pod will used. -// -// This gives the pod all permissions on everything! -func createClusterRoleManifest(name string) *rbacv1.ClusterRole { - return &rbacv1.ClusterRole{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - Rules: []rbacv1.PolicyRule{ - rbacv1.PolicyRule{ - Verbs: []string{"*"}, - APIGroups: []string{"*"}, - Resources: []string{"*"}, - }, - rbacv1.PolicyRule{ - Verbs: []string{"*"}, - NonResourceURLs: []string{"*"}, - }, - }, - } -} - -// createClusterRoleBindingManifest creates the cluster role binding the pod will used. -// -// This binds the service account to the cluster role. -func createClusterRoleBindingManifest(name string, namespace string, svcAccName string) *rbacv1.ClusterRoleBinding { - return &rbacv1.ClusterRoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - Subjects: []rbacv1.Subject{ - rbacv1.Subject{ - Kind: "ServiceAccount", - Name: svcAccName, - Namespace: namespace, - }, - }, - RoleRef: rbacv1.RoleRef{ - APIGroup: "rbac.authorization.k8s.io", - Kind: "ClusterRole", - Name: name, - }, - } -} - -// createPodManifest creates the pod inside of the cluster that will be used for remote execution. -// -// Creates a pod with an init container that runs sshd-rsync, once the first connection closes the init container -// exits then the exec container starts using the rsync'd directory as its work directory. -func createPodManifest(name string, image string, env map[string]string, cmd []string, workDir string, destDir string, secretName string, svcAccName string) *apiv1.Pod { - execEnv := []apiv1.EnvVar{ - apiv1.EnvVar{ - Name: "NODE_NAME", - ValueFrom: &apiv1.EnvVarSource{ - FieldRef: &apiv1.ObjectFieldSelector{ - FieldPath: "spec.nodeName", - }, - }, - }, - } - for k, v := range env { - execEnv = append(execEnv, apiv1.EnvVar{ - Name: k, - Value: v, - }) - } - return &apiv1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - Spec: apiv1.PodSpec{ - ServiceAccountName: svcAccName, - RestartPolicy: apiv1.RestartPolicyNever, - InitContainers: []apiv1.Container{ - { - Name: "sync-init", - Image: "ernoaapa/sshd-rsync", - Ports: []apiv1.ContainerPort{ - { - Name: "ssh", - Protocol: apiv1.ProtocolTCP, - ContainerPort: 22, - }, - }, - Env: []apiv1.EnvVar{ - { - Name: "ONE_TIME", - Value: "true", - }, - }, - VolumeMounts: []apiv1.VolumeMount{ - { - Name: "ssh-config", - MountPath: "/root/.ssh/authorized_keys", - SubPath: "authorized_keys", - }, - { - Name: "destdir", - MountPath: destDir, - }, - }, - }, - }, - Containers: []apiv1.Container{ - apiv1.Container{ - Name: "exec", - Image: image, - Command: cmd, - WorkingDir: workDir, - Env: execEnv, - VolumeMounts: []apiv1.VolumeMount{ - { - Name: "destdir", - MountPath: destDir, - }, - }, - }, - }, - Volumes: []apiv1.Volume{ - { - Name: "ssh-config", - VolumeSource: apiv1.VolumeSource{ - Secret: &apiv1.SecretVolumeSource{ - SecretName: secretName, - DefaultMode: &mode, - }, - }, - }, - { - Name: "destdir", - VolumeSource: apiv1.VolumeSource{ - EmptyDir: &apiv1.EmptyDirVolumeSource{}, - }, - }, - }, - }, - } -} - -func podInitReady(event watch.Event) (bool, error) { - switch event.Type { - case watch.Deleted: - return false, k8serrors.NewNotFound(schema.GroupResource{Resource: "pods"}, "") - } - switch t := event.Object.(type) { - case *apiv1.Pod: - switch t.Status.Phase { - case apiv1.PodFailed, apiv1.PodSucceeded: - return false, nil - case apiv1.PodRunning: - return false, nil - case apiv1.PodPending: - return isInitContainersReady(t), nil - } - } - return false, nil -} - -func isInitContainersReady(pod *apiv1.Pod) bool { - if isScheduled(pod) && isInitContainersRunning(pod) { - return true - } - return false -} - -func isScheduled(pod *apiv1.Pod) bool { - if &pod.Status != nil && len(pod.Status.Conditions) > 0 { - for _, condition := range pod.Status.Conditions { - if condition.Type == apiv1.PodScheduled && - condition.Status == apiv1.ConditionTrue { - return true - } - } - } - return false -} - -func isInitContainersRunning(pod *apiv1.Pod) bool { - if &pod.Status != nil { - if len(pod.Spec.InitContainers) != len(pod.Status.InitContainerStatuses) { - return false - } - for _, status := range pod.Status.InitContainerStatuses { - if status.State.Running == nil { - return false - } - } - return true - } - return false -} - -func containerRunning(containerName string) func(watch.Event) (bool, error) { - return func(event watch.Event) (bool, error) { - switch event.Type { - case watch.Deleted: - return false, k8serrors.NewNotFound(schema.GroupResource{Resource: "pods"}, "") - } - switch t := event.Object.(type) { - case *apiv1.Pod: - switch t.Status.Phase { - case apiv1.PodFailed, apiv1.PodSucceeded: - return false, nil - case apiv1.PodRunning: - return isContainerRunning(t, containerName) - } - } - return false, nil - } -} - -func isContainerRunning(pod *apiv1.Pod, containerName string) (bool, error) { - for _, status := range pod.Status.ContainerStatuses { - if status.Name == containerName { - if status.State.Waiting != nil { - return false, nil - } else if status.State.Running != nil { - return true, nil - } else if status.State.Terminated != nil { - return false, nil - } else { - return false, fmt.Errorf("Unknown container state") - } - } - } - return false, nil -} - -func podDone(event watch.Event) (bool, error) { - switch event.Type { - case watch.Deleted: - return false, k8serrors.NewNotFound(schema.GroupResource{Resource: "pods"}, "") - } - switch t := event.Object.(type) { - case *apiv1.Pod: - switch t.Status.Phase { - case apiv1.PodFailed, apiv1.PodSucceeded: - return true, nil - } - } - return false, nil -} - -func createTempFile(content []byte) (string, error) { - randBytes := make([]byte, 16) - rand.Read(randBytes) - tmpfile, err := ioutil.TempFile("", hex.EncodeToString(randBytes)) - if err != nil { - return "", err - } - defer tmpfile.Close() - if _, err := tmpfile.Write(content); err != nil { - return "", err - } - return tmpfile.Name(), nil -} diff --git a/dev-tools/mage/kubectl.go b/dev-tools/mage/kubernetes/kubectl.go similarity index 99% rename from dev-tools/mage/kubectl.go rename to dev-tools/mage/kubernetes/kubectl.go index df42bc8049d..d2899ef6a8f 100644 --- a/dev-tools/mage/kubectl.go +++ b/dev-tools/mage/kubernetes/kubectl.go @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -package mage +package kubernetes import ( "fmt" diff --git a/dev-tools/mage/kubernetes/kuberemote.go b/dev-tools/mage/kubernetes/kuberemote.go index 4e98b536a10..67a28aacdd8 100644 --- a/dev-tools/mage/kubernetes/kuberemote.go +++ b/dev-tools/mage/kubernetes/kuberemote.go @@ -50,6 +50,8 @@ import ( "k8s.io/client-go/tools/portforward" watchtools "k8s.io/client-go/tools/watch" "k8s.io/client-go/transport/spdy" + + "github.com/elastic/beats/v7/dev-tools/mage" ) const sshBitSize = 4096 @@ -72,8 +74,8 @@ type KubeRemote struct { publicKey []byte } -// New creates a new kubernetes remote runner. -func New(kubeconfig string, namespace string, name string, workDir string, destDir string, syncDir string) (*KubeRemote, error) { +// NewKubeRemote creates a new kubernetes remote runner. +func NewKubeRemote(kubeconfig string, namespace string, name string, workDir string, destDir string, syncDir string) (*KubeRemote, error) { config, err := clientcmd.BuildConfigFromFlags("", kubeconfig) if err != nil { return nil, err @@ -204,8 +206,13 @@ func (r *KubeRemote) syncServiceAccount() error { // createPod creates the pod. func (r *KubeRemote) createPod(env map[string]string, cmd ...string) (*apiv1.Pod, error) { + version, err := mage.GoVersion() + if err != nil { + return nil, err + } + image := fmt.Sprintf("golang:%s", version) r.deletePod() // ensure it doesn't already exist - return r.cs.CoreV1().Pods(r.namespace).Create(createPodManifest(r.name, "golang:1.13.9", env, cmd, r.workDir, r.destDir, r.secretName, r.svcAccName)) + return r.cs.CoreV1().Pods(r.namespace).Create(createPodManifest(r.name, image, env, cmd, r.workDir, r.destDir, r.secretName, r.svcAccName)) } // deletePod deletes the pod. @@ -426,6 +433,8 @@ func createPodManifest(name string, image string, env map[string]string, cmd []s }, Spec: apiv1.PodSpec{ ServiceAccountName: svcAccName, + HostNetwork: true, + DNSPolicy: apiv1.DNSClusterFirstWithHostNet, RestartPolicy: apiv1.RestartPolicyNever, InitContainers: []apiv1.Container{ { @@ -458,7 +467,7 @@ func createPodManifest(name string, image string, env map[string]string, cmd []s }, }, Containers: []apiv1.Container{ - apiv1.Container{ + { Name: "exec", Image: image, Command: cmd, diff --git a/dev-tools/mage/kubernetes/kubernetes.go b/dev-tools/mage/kubernetes/kubernetes.go index 2f929da9e16..ec2cf95ddd4 100644 --- a/dev-tools/mage/kubernetes/kubernetes.go +++ b/dev-tools/mage/kubernetes/kubernetes.go @@ -102,14 +102,14 @@ func (d *KubernetesIntegrationTester) Test(dir string, mageTarget string, env ma // Apply the manifest from the dir. This is the requirements for the tests that will // run inside the cluster. - if err := mage.KubectlApply(env, stdOut, stdErr, manifestPath); err != nil { + if err := KubectlApply(env, stdOut, stdErr, manifestPath); err != nil { return errors.Wrapf(err, "failed to apply manifest %s", manifestPath) } defer func() { if mg.Verbose() { fmt.Println(">> Deleting module manifest from cluster...") } - if err := mage.KubectlDelete(env, stdOut, stdErr, manifestPath); err != nil { + if err := KubectlDelete(env, stdOut, stdErr, manifestPath); err != nil { log.Printf("%s", errors.Wrapf(err, "failed to apply manifest %s", manifestPath)) } }() @@ -125,7 +125,7 @@ func (d *KubernetesIntegrationTester) Test(dir string, mageTarget string, env ma destDir := filepath.Join("/go/src", repo.CanonicalRootImportPath) workDir := filepath.Join(destDir, repo.SubDir) - remote, err := mage.NewKubeRemote(kubeConfig, "default", kubernetesPodName(), workDir, destDir, repo.RootDir) + remote, err := NewKubeRemote(kubeConfig, "default", kubernetesPodName(), workDir, destDir, repo.RootDir) if err != nil { return err } diff --git a/metricbeat/module/kubernetes/container/container_integration_test.go b/metricbeat/module/kubernetes/container/container_integration_test.go new file mode 100644 index 00000000000..18df4cdd2d1 --- /dev/null +++ b/metricbeat/module/kubernetes/container/container_integration_test.go @@ -0,0 +1,39 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build integration,linux + +package container + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing" + "github.com/elastic/beats/v7/metricbeat/module/kubernetes/test" +) + +func TestFetchMetricset(t *testing.T) { + config := test.GetKubeletConfig(t, "container") + metricSet := mbtest.NewFetcher(t, config) + events, errs := metricSet.FetchEvents() + if len(errs) > 0 { + t.Fatalf("Expected 0 error, had %d. %v\n", len(errs), errs) + } + assert.NotEmpty(t, events) +} diff --git a/metricbeat/module/kubernetes/node/node_integration_test.go b/metricbeat/module/kubernetes/node/node_integration_test.go new file mode 100644 index 00000000000..7d1c73bc20b --- /dev/null +++ b/metricbeat/module/kubernetes/node/node_integration_test.go @@ -0,0 +1,39 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build integration,linux + +package node + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing" + "github.com/elastic/beats/v7/metricbeat/module/kubernetes/test" +) + +func TestFetchMetricset(t *testing.T) { + config := test.GetKubeletConfig(t, "node") + metricSet := mbtest.NewFetcher(t, config) + events, errs := metricSet.FetchEvents() + if len(errs) > 0 { + t.Fatalf("Expected 0 error, had %d. %v\n", len(errs), errs) + } + assert.NotEmpty(t, events) +} diff --git a/metricbeat/module/kubernetes/pod/pod_integration_test.go b/metricbeat/module/kubernetes/pod/pod_integration_test.go new file mode 100644 index 00000000000..9202891f84d --- /dev/null +++ b/metricbeat/module/kubernetes/pod/pod_integration_test.go @@ -0,0 +1,39 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build integration,linux + +package pod + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing" + "github.com/elastic/beats/v7/metricbeat/module/kubernetes/test" +) + +func TestFetchMetricset(t *testing.T) { + config := test.GetKubeletConfig(t, "pod") + metricSet := mbtest.NewFetcher(t, config) + events, errs := metricSet.FetchEvents() + if len(errs) > 0 { + t.Fatalf("Expected 0 error, had %d. %v\n", len(errs), errs) + } + assert.NotEmpty(t, events) +} diff --git a/metricbeat/module/kubernetes/proxy/proxy_integration_test.go b/metricbeat/module/kubernetes/proxy/proxy_integration_test.go new file mode 100644 index 00000000000..d596fb545f8 --- /dev/null +++ b/metricbeat/module/kubernetes/proxy/proxy_integration_test.go @@ -0,0 +1,39 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build integration,linux + +package proxy + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing" + "github.com/elastic/beats/v7/metricbeat/module/kubernetes/test" +) + +func TestFetchMetricset(t *testing.T) { + config := test.GetKubeProxyConfig(t, "proxy") + metricSet := mbtest.NewFetcher(t, config) + events, errs := metricSet.FetchEvents() + if len(errs) > 0 { + t.Fatalf("Expected 0 error, had %d. %v\n", len(errs), errs) + } + assert.NotEmpty(t, events) +} diff --git a/metricbeat/module/kubernetes/scheduler/scheduler_integration_test.go b/metricbeat/module/kubernetes/scheduler/scheduler_integration_test.go new file mode 100644 index 00000000000..90d986404dc --- /dev/null +++ b/metricbeat/module/kubernetes/scheduler/scheduler_integration_test.go @@ -0,0 +1,39 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build integration,linux + +package scheduler + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing" + "github.com/elastic/beats/v7/metricbeat/module/kubernetes/test" +) + +func TestFetchMetricset(t *testing.T) { + config := test.GetSchedulerConfig(t, "scheduler") + metricSet := mbtest.NewFetcher(t, config) + events, errs := metricSet.FetchEvents() + if len(errs) > 0 { + t.Fatalf("Expected 0 error, had %d. %v\n", len(errs), errs) + } + assert.NotEmpty(t, events) +} diff --git a/metricbeat/module/kubernetes/system/system_integration_test.go b/metricbeat/module/kubernetes/system/system_integration_test.go new file mode 100644 index 00000000000..5b876f0caff --- /dev/null +++ b/metricbeat/module/kubernetes/system/system_integration_test.go @@ -0,0 +1,39 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build integration,linux + +package system + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing" + "github.com/elastic/beats/v7/metricbeat/module/kubernetes/test" +) + +func TestFetchMetricset(t *testing.T) { + config := test.GetKubeletConfig(t, "system") + metricSet := mbtest.NewFetcher(t, config) + events, errs := metricSet.FetchEvents() + if len(errs) > 0 { + t.Fatalf("Expected 0 error, had %d. %v\n", len(errs), errs) + } + assert.NotEmpty(t, events) +} diff --git a/metricbeat/module/kubernetes/test/integration.go b/metricbeat/module/kubernetes/test/integration.go index 3e6dd7f11ea..64dfc99ab65 100644 --- a/metricbeat/module/kubernetes/test/integration.go +++ b/metricbeat/module/kubernetes/test/integration.go @@ -48,3 +48,40 @@ func GetKubeStateMetricsConfig(t *testing.T, metricSetName string) map[string]in "hosts": []string{"kube-state-metrics:8080"}, } } + +// GetKubeletConfig function returns configuration for talking to Kubelet API. +func GetKubeletConfig(t *testing.T, metricSetName string) map[string]interface{} { + t.Helper() + return map[string]interface{}{ + "module": "kubernetes", + "metricsets": []string{metricSetName}, + "host": "${NODE_NAME}", + "hosts": []string{"https://localhost:10250"}, + "bearer_token_file": "/var/run/secrets/kubernetes.io/serviceaccount/token", + "ssl": map[string]interface{}{ + "verification_mode": "none", + }, + } +} + +// GetKubeProxyConfig function returns configuration for talking to kube-proxy. +func GetKubeProxyConfig(t *testing.T, metricSetName string) map[string]interface{} { + t.Helper() + return map[string]interface{}{ + "module": "kubernetes", + "metricsets": []string{metricSetName}, + "host": "${NODE_NAME}", + "hosts": []string{"localhost:10252"}, + } +} + +// GetSchedulerConfig function returns configuration for talking to kube-proxy. +func GetSchedulerConfig(t *testing.T, metricSetName string) map[string]interface{} { + t.Helper() + return map[string]interface{}{ + "module": "kubernetes", + "metricsets": []string{metricSetName}, + "host": "${NODE_NAME}", + "hosts": []string{"localhost:10251"}, + } +} diff --git a/metricbeat/module/kubernetes/volume/volume_integration_test.go b/metricbeat/module/kubernetes/volume/volume_integration_test.go new file mode 100644 index 00000000000..c0934a8cae0 --- /dev/null +++ b/metricbeat/module/kubernetes/volume/volume_integration_test.go @@ -0,0 +1,39 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build integration,linux + +package volume + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing" + "github.com/elastic/beats/v7/metricbeat/module/kubernetes/test" +) + +func TestFetchMetricset(t *testing.T) { + config := test.GetKubeletConfig(t, "volume") + metricSet := mbtest.NewFetcher(t, config) + events, errs := metricSet.FetchEvents() + if len(errs) > 0 { + t.Fatalf("Expected 0 error, had %d. %v\n", len(errs), errs) + } + assert.NotEmpty(t, events) +} From c81adccfa1eac0ef6600763f8bd52df32a87d0f2 Mon Sep 17 00:00:00 2001 From: Andrew Kroh Date: Mon, 4 May 2020 16:12:55 -0400 Subject: [PATCH 104/116] Build config files with Go text/template (#18148) Rather than relying on file concatenation followed by Go text/template we'll just use Go text/template. This makes is easier to customize a template without having to modify libbeat since you can just overwrite a template section (like inject your own processors section). This works by loading all of the templates from `libbeat/_meta/config/*.tmpl` followed by templates from `$mybeat/_meta/config/*.tmpl`. Files loaded last take precedence (it uses go text/template ParseGlob). Finally each template is created from `libbeat/_meta/config/default{., reference, docker}.yml.tmpl` (which can be changed if needed, see elastic/agent). --- CHANGELOG-developer.next.asciidoc | 5 + auditbeat/_meta/common.reference.yml | 31 - .../config/auditbeat.config.modules.yml.tmpl | 18 + .../_meta/config/auditbeat.modules.yml.tmpl | 2 + .../beat.docker.yml.tmpl} | 0 .../_meta/config/beat.reference.yml.tmpl | 4 + auditbeat/_meta/config/beat.yml.tmpl | 4 + .../_meta/config/header.reference.yml.tmpl | 8 + .../{common.p1.yml => config/header.yml.tmpl} | 4 - .../_meta/config/setup.template.yml.tmpl | 5 + auditbeat/auditbeat.docker.yml | 1 + auditbeat/auditbeat.reference.yml | 58 +- auditbeat/auditbeat.yml | 32 +- auditbeat/scripts/mage/config.go | 41 +- dev-tools/mage/common.go | 2 +- dev-tools/mage/config.go | 198 +-- .../beat.docker.yml.tmpl} | 1 - filebeat/_meta/config/beat.reference.yml.tmpl | 5 + filebeat/_meta/config/beat.yml.tmpl | 4 + .../filebeat.autodiscover.reference.yml.tmpl | 16 + .../config/filebeat.config.modules.yml.tmpl | 11 + .../filebeat.global.reference.yml.tmpl} | 19 +- .../filebeat.inputs.reference.yml.tmpl} | 0 .../filebeat.inputs.yml.tmpl} | 25 +- .../header.reference.yml.tmpl} | 1 - .../{common.p1.yml => config/header.yml.tmpl} | 2 + .../_meta/config/setup.template.yml.tmpl | 2 +- filebeat/filebeat.reference.yml | 60 +- filebeat/filebeat.yml | 35 +- filebeat/scripts/mage/config.go | 40 +- .../beat.docker.yml.tmpl} | 0 .../beat.reference.yml.tmpl} | 0 .../_meta/{beat.yml => config/beat.yml.tmpl} | 0 generator/_templates/beat/{beat}/magefile.go | 4 +- .../beat.docker.yml.tmpl} | 0 .../beat.reference.yml.tmpl} | 0 .../_meta/{short.yml => config/beat.yml.tmpl} | 0 .../_templates/metricbeat/{beat}/magefile.go | 10 +- .../beat.docker.yml.tmpl} | 1 - .../beat.reference.yml.tmpl} | 0 .../_meta/{beat.yml => config/beat.yml.tmpl} | 2 +- heartbeat/heartbeat.reference.yml | 53 +- heartbeat/heartbeat.yml | 26 +- heartbeat/scripts/mage/config.go | 25 +- .../beat.docker.yml.tmpl} | 1 - .../beat.reference.yml.tmpl} | 6 +- .../_meta/{beat.yml => config/beat.yml.tmpl} | 6 +- journalbeat/journalbeat.reference.yml | 59 +- journalbeat/journalbeat.yml | 33 +- journalbeat/magefile.go | 4 +- libbeat/_meta/config.reference.yml.tmpl | 1352 ----------------- .../default.docker.yml.tmpl} | 1 + .../_meta/config/default.reference.yml.tmpl | 22 + libbeat/_meta/config/default.short.yml.tmpl | 12 + libbeat/_meta/config/elastic-cloud.yml.tmpl | 12 + .../_meta/config/general.reference.yml.tmpl | 107 ++ libbeat/_meta/config/general.yml.tmpl | 14 + libbeat/_meta/config/http.reference.yml.tmpl | 24 + .../_meta/config/keystore.reference.yml.tmpl | 4 + .../_meta/config/logging.reference.yml.tmpl | 67 + libbeat/_meta/config/logging.yml.tmpl | 10 + libbeat/_meta/config/migration.yml.tmpl | 4 + .../config/monitoring.reference.yml.tmpl | 141 ++ libbeat/_meta/config/monitoring.yml.tmpl | 21 + .../config/output-console.reference.yml.tmpl | 12 + .../output-elasticsearch.reference.yml.tmpl | 140 ++ .../config/output-elasticsearch.yml.tmpl | 12 + .../config/output-file.reference.yml.tmpl | 33 + .../config/output-kafka.reference.yml.tmpl | 178 +++ .../config/output-logstash.reference.yml.tmpl | 113 ++ libbeat/_meta/config/output-logstash.yml.tmpl | 14 + .../config/output-redis.reference.yml.tmpl | 117 ++ libbeat/_meta/config/outputs.yml.tmpl | 3 + libbeat/_meta/config/paths.reference.yml.tmpl | 25 + .../config/processors.reference.yml.tmpl | 162 ++ libbeat/_meta/config/processors.yml.tmpl | 24 + .../_meta/config/seccomp.reference.yml.tmpl | 4 + .../setup.dashboards.reference.yml.tmpl | 44 + .../_meta/config/setup.dashboards.yml.tmpl | 11 + .../_meta/config/setup.ilm.reference.yml.tmpl | 34 + .../config/setup.kibana.reference.yml.tmpl | 54 + libbeat/_meta/config/setup.kibana.yml.tmpl | 16 + .../config/setup.template.reference.yml.tmpl | 53 + libbeat/magefile.go | 2 +- .../beat.docker.yml.tmpl} | 1 - .../_meta/config/beat.reference.yml.tmpl | 2 + metricbeat/_meta/config/beat.yml.tmpl | 3 + .../header.reference.yml.tmpl} | 0 .../{common.yml => config/header.yml.tmpl} | 0 .../config/metricbeat.config.modules.yml.tmpl | 11 + .../_meta/config/setup.template.yml.tmpl | 6 + metricbeat/_meta/setup.yml | 19 - metricbeat/metricbeat.reference.yml | 55 +- metricbeat/metricbeat.yml | 32 +- metricbeat/scripts/mage/config.go | 37 +- .../beat.docker.yml.tmpl} | 1 - .../beat.reference.yml.tmpl} | 8 +- .../_meta/{beat.yml => config/beat.yml.tmpl} | 8 +- packetbeat/packetbeat.reference.yml | 61 +- packetbeat/packetbeat.yml | 35 +- packetbeat/scripts/mage/config.go | 24 +- winlogbeat/_meta/beat.reference.yml | 34 - .../_meta/config/beat.reference.yml.tmpl | 2 + winlogbeat/_meta/config/beat.yml.tmpl | 3 + .../header.yml.tmpl} | 20 +- winlogbeat/_meta/config/processors.yml.tmpl | 5 + .../_meta/config/setup.template.yml.tmpl | 6 + .../winlogbeat.event_logs.yml.tmpl} | 2 - winlogbeat/scripts/mage/config.go | 35 +- winlogbeat/winlogbeat.reference.yml | 56 +- winlogbeat/winlogbeat.yml | 33 +- x-pack/auditbeat/auditbeat.docker.yml | 1 + x-pack/auditbeat/auditbeat.reference.yml | 58 +- x-pack/auditbeat/auditbeat.yml | 32 +- .../common.p1.yml.tmpl} | 0 .../common.p2.yml.tmpl} | 0 .../common.reference.p1.yml.tmpl} | 0 .../common.reference.p2.yml.tmpl} | 0 .../elastic-agent.docker.yml.tmpl} | 0 .../config/elastic-agent.reference.yml.tmpl | 2 + .../_meta/config/elastic-agent.yml.tmpl | 2 + .../elastic-agent/elastic-agent.reference.yml | 2 + x-pack/elastic-agent/elastic-agent.yml | 2 + x-pack/elastic-agent/magefile.go | 19 +- .../_meta/config/beat.reference.yml.tmpl | 6 + .../filebeat.inputs.reference.xpack.yml.tmpl} | 0 x-pack/filebeat/filebeat.reference.yml | 61 +- x-pack/filebeat/filebeat.yml | 35 +- .../beat.reference.yml.tmpl} | 4 +- .../_meta/{beat.yml => config/beat.yml.tmpl} | 4 +- .../functionbeat/functionbeat.reference.yml | 53 +- x-pack/functionbeat/functionbeat.yml | 31 +- x-pack/functionbeat/scripts/mage/config.go | 25 +- x-pack/metricbeat/metricbeat.reference.yml | 55 +- x-pack/metricbeat/metricbeat.yml | 32 +- .../winlogbeat.event_logs.yml.tmpl} | 3 - x-pack/winlogbeat/winlogbeat.reference.yml | 57 +- x-pack/winlogbeat/winlogbeat.yml | 32 +- 138 files changed, 2386 insertions(+), 2335 deletions(-) delete mode 100644 auditbeat/_meta/common.reference.yml create mode 100644 auditbeat/_meta/config/auditbeat.config.modules.yml.tmpl create mode 100644 auditbeat/_meta/config/auditbeat.modules.yml.tmpl rename auditbeat/_meta/{beat.docker.yml => config/beat.docker.yml.tmpl} (100%) create mode 100644 auditbeat/_meta/config/beat.reference.yml.tmpl create mode 100644 auditbeat/_meta/config/beat.yml.tmpl create mode 100644 auditbeat/_meta/config/header.reference.yml.tmpl rename auditbeat/_meta/{common.p1.yml => config/header.yml.tmpl} (80%) create mode 100644 auditbeat/_meta/config/setup.template.yml.tmpl rename filebeat/_meta/{beat.docker.yml => config/beat.docker.yml.tmpl} (98%) create mode 100644 filebeat/_meta/config/beat.reference.yml.tmpl create mode 100644 filebeat/_meta/config/beat.yml.tmpl create mode 100644 filebeat/_meta/config/filebeat.autodiscover.reference.yml.tmpl create mode 100644 filebeat/_meta/config/filebeat.config.modules.yml.tmpl rename filebeat/_meta/{common.reference.p2.yml => config/filebeat.global.reference.yml.tmpl} (74%) rename filebeat/_meta/{common.reference.inputs.yml => config/filebeat.inputs.reference.yml.tmpl} (100%) rename filebeat/_meta/{common.p2.yml => config/filebeat.inputs.yml.tmpl} (72%) rename filebeat/_meta/{common.reference.p1.yml => config/header.reference.yml.tmpl} (99%) rename filebeat/_meta/{common.p1.yml => config/header.yml.tmpl} (79%) rename auditbeat/_meta/common.p2.yml => filebeat/_meta/config/setup.template.yml.tmpl (58%) rename generator/_templates/beat/{beat}/_meta/{beat.docker.yml => config/beat.docker.yml.tmpl} (100%) rename generator/_templates/beat/{beat}/_meta/{beat.reference.yml => config/beat.reference.yml.tmpl} (100%) rename generator/_templates/beat/{beat}/_meta/{beat.yml => config/beat.yml.tmpl} (100%) rename generator/_templates/metricbeat/{beat}/_meta/{docker.yml => config/beat.docker.yml.tmpl} (100%) rename generator/_templates/metricbeat/{beat}/_meta/{reference.yml => config/beat.reference.yml.tmpl} (100%) rename generator/_templates/metricbeat/{beat}/_meta/{short.yml => config/beat.yml.tmpl} (100%) rename heartbeat/_meta/{beat.docker.yml => config/beat.docker.yml.tmpl} (99%) rename heartbeat/_meta/{beat.reference.yml => config/beat.reference.yml.tmpl} (100%) rename heartbeat/_meta/{beat.yml => config/beat.yml.tmpl} (94%) rename journalbeat/_meta/{beat.docker.yml => config/beat.docker.yml.tmpl} (97%) rename journalbeat/_meta/{beat.reference.yml => config/beat.reference.yml.tmpl} (89%) rename journalbeat/_meta/{beat.yml => config/beat.yml.tmpl} (87%) delete mode 100644 libbeat/_meta/config.reference.yml.tmpl rename libbeat/_meta/{config.docker.yml => config/default.docker.yml.tmpl} (83%) create mode 100644 libbeat/_meta/config/default.reference.yml.tmpl create mode 100644 libbeat/_meta/config/default.short.yml.tmpl create mode 100644 libbeat/_meta/config/elastic-cloud.yml.tmpl create mode 100644 libbeat/_meta/config/general.reference.yml.tmpl create mode 100644 libbeat/_meta/config/general.yml.tmpl create mode 100644 libbeat/_meta/config/http.reference.yml.tmpl create mode 100644 libbeat/_meta/config/keystore.reference.yml.tmpl create mode 100644 libbeat/_meta/config/logging.reference.yml.tmpl create mode 100644 libbeat/_meta/config/logging.yml.tmpl create mode 100644 libbeat/_meta/config/migration.yml.tmpl create mode 100644 libbeat/_meta/config/monitoring.reference.yml.tmpl create mode 100644 libbeat/_meta/config/monitoring.yml.tmpl create mode 100644 libbeat/_meta/config/output-console.reference.yml.tmpl create mode 100644 libbeat/_meta/config/output-elasticsearch.reference.yml.tmpl create mode 100644 libbeat/_meta/config/output-elasticsearch.yml.tmpl create mode 100644 libbeat/_meta/config/output-file.reference.yml.tmpl create mode 100644 libbeat/_meta/config/output-kafka.reference.yml.tmpl create mode 100644 libbeat/_meta/config/output-logstash.reference.yml.tmpl create mode 100644 libbeat/_meta/config/output-logstash.yml.tmpl create mode 100644 libbeat/_meta/config/output-redis.reference.yml.tmpl create mode 100644 libbeat/_meta/config/outputs.yml.tmpl create mode 100644 libbeat/_meta/config/paths.reference.yml.tmpl create mode 100644 libbeat/_meta/config/processors.reference.yml.tmpl create mode 100644 libbeat/_meta/config/processors.yml.tmpl create mode 100644 libbeat/_meta/config/seccomp.reference.yml.tmpl create mode 100644 libbeat/_meta/config/setup.dashboards.reference.yml.tmpl create mode 100644 libbeat/_meta/config/setup.dashboards.yml.tmpl create mode 100644 libbeat/_meta/config/setup.ilm.reference.yml.tmpl create mode 100644 libbeat/_meta/config/setup.kibana.reference.yml.tmpl create mode 100644 libbeat/_meta/config/setup.kibana.yml.tmpl create mode 100644 libbeat/_meta/config/setup.template.reference.yml.tmpl rename metricbeat/_meta/{beat.docker.yml => config/beat.docker.yml.tmpl} (98%) create mode 100644 metricbeat/_meta/config/beat.reference.yml.tmpl create mode 100644 metricbeat/_meta/config/beat.yml.tmpl rename metricbeat/_meta/{common.reference.yml => config/header.reference.yml.tmpl} (100%) rename metricbeat/_meta/{common.yml => config/header.yml.tmpl} (100%) create mode 100644 metricbeat/_meta/config/metricbeat.config.modules.yml.tmpl create mode 100644 metricbeat/_meta/config/setup.template.yml.tmpl delete mode 100644 metricbeat/_meta/setup.yml rename packetbeat/_meta/{beat.docker.yml => config/beat.docker.yml.tmpl} (99%) rename packetbeat/_meta/{beat.reference.yml => config/beat.reference.yml.tmpl} (98%) rename packetbeat/_meta/{beat.yml => config/beat.yml.tmpl} (91%) delete mode 100644 winlogbeat/_meta/beat.reference.yml create mode 100644 winlogbeat/_meta/config/beat.reference.yml.tmpl create mode 100644 winlogbeat/_meta/config/beat.yml.tmpl rename winlogbeat/_meta/{common.yml.tmpl => config/header.yml.tmpl} (66%) create mode 100644 winlogbeat/_meta/config/processors.yml.tmpl create mode 100644 winlogbeat/_meta/config/setup.template.yml.tmpl rename winlogbeat/_meta/{beat.yml.tmpl => config/winlogbeat.event_logs.yml.tmpl} (61%) rename x-pack/elastic-agent/_meta/{common.p1.yml => config/common.p1.yml.tmpl} (100%) rename x-pack/elastic-agent/_meta/{common.p2.yml => config/common.p2.yml.tmpl} (100%) rename x-pack/elastic-agent/_meta/{common.reference.p1.yml => config/common.reference.p1.yml.tmpl} (100%) rename x-pack/elastic-agent/_meta/{common.reference.p2.yml => config/common.reference.p2.yml.tmpl} (100%) rename x-pack/elastic-agent/_meta/{elastic-agent.docker.yml => config/elastic-agent.docker.yml.tmpl} (100%) create mode 100644 x-pack/elastic-agent/_meta/config/elastic-agent.reference.yml.tmpl create mode 100644 x-pack/elastic-agent/_meta/config/elastic-agent.yml.tmpl create mode 100644 x-pack/filebeat/_meta/config/beat.reference.yml.tmpl rename x-pack/filebeat/_meta/{common.reference.inputs.yml => config/filebeat.inputs.reference.xpack.yml.tmpl} (100%) rename x-pack/functionbeat/_meta/{beat.reference.yml => config/beat.reference.yml.tmpl} (99%) rename x-pack/functionbeat/_meta/{beat.yml => config/beat.yml.tmpl} (99%) rename x-pack/winlogbeat/_meta/{beat.yml.tmpl => config/winlogbeat.event_logs.yml.tmpl} (90%) diff --git a/CHANGELOG-developer.next.asciidoc b/CHANGELOG-developer.next.asciidoc index f2313ccdb94..9fb3f6bf1ea 100644 --- a/CHANGELOG-developer.next.asciidoc +++ b/CHANGELOG-developer.next.asciidoc @@ -41,6 +41,11 @@ The list below covers the major changes between 7.0.0-rc2 and master only. - Rename `queue.BufferConfig.Events` to `queue.BufferConfig.MaxEvents`. {pull}17622[17622] - Remove `queue.Feature` and replace `queue.RegisterType` with `queue.RegisterQueueType`. {pull}17666[17666] - Introduce APM libbeat instrumentation. `Publish` method on `Client` interface now takes a Context as first argument. {pull}17938[17938] +- The way configuration files are generated has changed to make it easier to customize parts + of the config without requiring changes to libbeat config templates. Generation is now + fully based on Go text/template and no longer uses file concatenation to generate the config. + Your magefile.go will require a change to adapt the devtool API. See the pull request for + more details. {pull}18148[18148] ==== Bugfixes diff --git a/auditbeat/_meta/common.reference.yml b/auditbeat/_meta/common.reference.yml deleted file mode 100644 index 8c9ae27e5db..00000000000 --- a/auditbeat/_meta/common.reference.yml +++ /dev/null @@ -1,31 +0,0 @@ -########################## Auditbeat Configuration ############################# - -# This is a reference configuration file documenting all non-deprecated options -# in comments. For a shorter configuration example that contains only the most -# common options, please see auditbeat.yml in the same directory. -# -# You can find the full configuration reference here: -# https://www.elastic.co/guide/en/beats/auditbeat/index.html - -#============================ Config Reloading ================================ - -# Config reloading allows to dynamically load modules. Each file which is -# monitored must contain one or multiple modules as a list. -auditbeat.config.modules: - - # Glob pattern for configuration reloading - path: ${path.config}/modules.d/*.yml - - # Period on which files under path should be checked for changes - reload.period: 10s - - # Set to true to enable config reloading - reload.enabled: false - -# Maximum amount of time to randomly delay the start of a dataset. Use 0 to -# disable startup delay. -auditbeat.max_start_delay: 10s - -#========================== Modules configuration ============================= -auditbeat.modules: - diff --git a/auditbeat/_meta/config/auditbeat.config.modules.yml.tmpl b/auditbeat/_meta/config/auditbeat.config.modules.yml.tmpl new file mode 100644 index 00000000000..8108f3edf92 --- /dev/null +++ b/auditbeat/_meta/config/auditbeat.config.modules.yml.tmpl @@ -0,0 +1,18 @@ +{{header "Config Reloading"}} + +# Config reloading allows to dynamically load modules. Each file which is +# monitored must contain one or multiple modules as a list. +auditbeat.config.modules: + + # Glob pattern for configuration reloading + path: ${path.config}/modules.d/*.yml + + # Period on which files under path should be checked for changes + reload.period: 10s + + # Set to true to enable config reloading + reload.enabled: false + +# Maximum amount of time to randomly delay the start of a dataset. Use 0 to +# disable startup delay. +auditbeat.max_start_delay: 10s diff --git a/auditbeat/_meta/config/auditbeat.modules.yml.tmpl b/auditbeat/_meta/config/auditbeat.modules.yml.tmpl new file mode 100644 index 00000000000..6265552a9c1 --- /dev/null +++ b/auditbeat/_meta/config/auditbeat.modules.yml.tmpl @@ -0,0 +1,2 @@ +{{header "Modules configuration"}} +auditbeat.modules: diff --git a/auditbeat/_meta/beat.docker.yml b/auditbeat/_meta/config/beat.docker.yml.tmpl similarity index 100% rename from auditbeat/_meta/beat.docker.yml rename to auditbeat/_meta/config/beat.docker.yml.tmpl diff --git a/auditbeat/_meta/config/beat.reference.yml.tmpl b/auditbeat/_meta/config/beat.reference.yml.tmpl new file mode 100644 index 00000000000..ce2e0f72664 --- /dev/null +++ b/auditbeat/_meta/config/beat.reference.yml.tmpl @@ -0,0 +1,4 @@ +{{template "header.reference.yml.tmpl" .}} +{{template "auditbeat.config.modules.yml.tmpl" .}} +{{template "auditbeat.modules.yml.tmpl" .}} +{{template "config.modules.yml.tmpl" .}} diff --git a/auditbeat/_meta/config/beat.yml.tmpl b/auditbeat/_meta/config/beat.yml.tmpl new file mode 100644 index 00000000000..b90667b2b4e --- /dev/null +++ b/auditbeat/_meta/config/beat.yml.tmpl @@ -0,0 +1,4 @@ +{{template "header.yml.tmpl" .}} +{{template "auditbeat.modules.yml.tmpl" .}} +{{template "config.modules.yml.tmpl" .}} +{{template "setup.template.yml.tmpl" .}} diff --git a/auditbeat/_meta/config/header.reference.yml.tmpl b/auditbeat/_meta/config/header.reference.yml.tmpl new file mode 100644 index 00000000000..355f8c2b50e --- /dev/null +++ b/auditbeat/_meta/config/header.reference.yml.tmpl @@ -0,0 +1,8 @@ +########################## Auditbeat Configuration ############################# + +# This is a reference configuration file documenting all non-deprecated options +# in comments. For a shorter configuration example that contains only the most +# common options, please see auditbeat.yml in the same directory. +# +# You can find the full configuration reference here: +# https://www.elastic.co/guide/en/beats/auditbeat/index.html diff --git a/auditbeat/_meta/common.p1.yml b/auditbeat/_meta/config/header.yml.tmpl similarity index 80% rename from auditbeat/_meta/common.p1.yml rename to auditbeat/_meta/config/header.yml.tmpl index 9fc4f5ccbc5..9f703af2140 100644 --- a/auditbeat/_meta/common.p1.yml +++ b/auditbeat/_meta/config/header.yml.tmpl @@ -6,7 +6,3 @@ # # You can find the full configuration reference here: # https://www.elastic.co/guide/en/beats/auditbeat/index.html - -#========================== Modules configuration ============================= -auditbeat.modules: - diff --git a/auditbeat/_meta/config/setup.template.yml.tmpl b/auditbeat/_meta/config/setup.template.yml.tmpl new file mode 100644 index 00000000000..a241fcbd983 --- /dev/null +++ b/auditbeat/_meta/config/setup.template.yml.tmpl @@ -0,0 +1,5 @@ +{{header "Elasticsearch template setting"}} +setup.template.settings: + index.number_of_shards: 1 + #index.codec: best_compression + #_source.enabled: false diff --git a/auditbeat/auditbeat.docker.yml b/auditbeat/auditbeat.docker.yml index a012bbb6aad..19c9bd1b477 100644 --- a/auditbeat/auditbeat.docker.yml +++ b/auditbeat/auditbeat.docker.yml @@ -12,6 +12,7 @@ auditbeat.modules: - /sbin - /usr/sbin - /etc + processors: - add_cloud_metadata: ~ - add_docker_metadata: ~ diff --git a/auditbeat/auditbeat.reference.yml b/auditbeat/auditbeat.reference.yml index 5c866e2dc55..d0a5ee0dae3 100644 --- a/auditbeat/auditbeat.reference.yml +++ b/auditbeat/auditbeat.reference.yml @@ -7,7 +7,7 @@ # You can find the full configuration reference here: # https://www.elastic.co/guide/en/beats/auditbeat/index.html -#============================ Config Reloading ================================ +# ============================== Config Reloading ============================== # Config reloading allows to dynamically load modules. Each file which is # monitored must contain one or multiple modules as a list. @@ -26,7 +26,7 @@ auditbeat.config.modules: # disable startup delay. auditbeat.max_start_delay: 10s -#========================== Modules configuration ============================= +# =========================== Modules configuration ============================ auditbeat.modules: # The auditd module collects events from the audit framework in the Linux @@ -118,7 +118,8 @@ auditbeat.modules: #keep_null: false -#================================ General ====================================== + +# ================================== General =================================== # The name of the shipper that publishes the network data. It can be used to group # all the transactions sent by a single shipper in the web interface. @@ -226,7 +227,7 @@ auditbeat.modules: # default is the number of logical CPUs available in the system. #max_procs: -#================================ Processors =================================== +# ================================= Processors ================================= # Processors are used to reduce the number of fields in the exported event or to # enhance the event with external metadata. This section defines a list of @@ -389,7 +390,7 @@ auditbeat.modules: # ignore_missing: false # fail_on_error: true -#============================= Elastic Cloud ================================== +# =============================== Elastic Cloud ================================ # These settings simplify using Auditbeat with the Elastic Cloud (https://cloud.elastic.co/). @@ -402,11 +403,11 @@ auditbeat.modules: # `output.elasticsearch.password` settings. The format is `:`. #cloud.auth: -#================================ Outputs ====================================== +# ================================== Outputs =================================== # Configure what output to use when sending the data collected by the beat. -#-------------------------- Elasticsearch output ------------------------------- +# ---------------------------- Elasticsearch Output ---------------------------- output.elasticsearch: # Boolean flag to enable or disable the output module. #enabled: true @@ -547,7 +548,7 @@ output.elasticsearch: # Kerberos realm. #kerberos.realm: ELASTIC -#----------------------------- Logstash output --------------------------------- +# ------------------------------ Logstash Output ------------------------------- #output.logstash: # Boolean flag to enable or disable the output module. #enabled: true @@ -661,7 +662,7 @@ output.elasticsearch: # timing out. The default is 30s. #timeout: 30s -#------------------------------- Kafka output ---------------------------------- +# -------------------------------- Kafka Output -------------------------------- #output.kafka: # Boolean flag to enable or disable the output module. #enabled: true @@ -840,7 +841,7 @@ output.elasticsearch: # Kerberos realm. #kerberos.realm: ELASTIC -#------------------------------- Redis output ---------------------------------- +# -------------------------------- Redis Output -------------------------------- #output.redis: # Boolean flag to enable or disable the output module. #enabled: true @@ -958,7 +959,7 @@ output.elasticsearch: # never, once, and freely. Default is never. #ssl.renegotiation: never -#------------------------------- File output ----------------------------------- +# -------------------------------- File Output --------------------------------- #output.file: # Boolean flag to enable or disable the output module. #enabled: true @@ -992,7 +993,7 @@ output.elasticsearch: # Permissions to use for file creation. The default is 0600. #permissions: 0600 -#----------------------------- Console output --------------------------------- +# ------------------------------- Console Output ------------------------------- #output.console: # Boolean flag to enable or disable the output module. #enabled: true @@ -1005,7 +1006,7 @@ output.elasticsearch: # Configure escaping HTML symbols in strings. #escape_html: false -#================================= Paths ====================================== +# =================================== Paths ==================================== # The home path for the Auditbeat installation. This is the default base path # for all other path settings and for miscellaneous files that come with the @@ -1031,11 +1032,13 @@ output.elasticsearch: # the default for the logs path is a logs subdirectory inside the home path. #path.logs: ${path.home}/logs -#================================ Keystore ========================================== +# ================================== Keystore ================================== + # Location of the Keystore containing the keys and their sensitive values. #keystore.path: "${path.config}/beats.keystore" -#============================== Dashboards ===================================== +# ================================= Dashboards ================================= + # These settings control loading the sample dashboards to the Kibana index. Loading # the dashboards are disabled by default and can be enabled either by setting the # options here, or by using the `-setup` CLI flag or the `setup` command. @@ -1079,8 +1082,7 @@ output.elasticsearch: # Maximum number of retries before exiting with an error, 0 for unlimited retrying. #setup.dashboards.retry.maximum: 0 - -#============================== Template ===================================== +# ================================== Template ================================== # A template is used to set the mapping in Elasticsearch # By default template loading is enabled and the template is loaded. @@ -1134,7 +1136,7 @@ setup.template.settings: #_source: #enabled: false -#============================== Setup ILM ===================================== +# ====================== Index Lifecycle Management (ILM) ====================== # Configure index lifecycle management (ILM). These settings create a write # alias and add additional settings to the index template. When ILM is enabled, @@ -1148,13 +1150,13 @@ setup.template.settings: # Set the prefix used in the index lifecycle write alias name. The default alias # name is 'auditbeat-%{[agent.version]}'. -#setup.ilm.rollover_alias: "auditbeat" +#setup.ilm.rollover_alias: 'auditbeat' # Set the rollover index pattern. The default is "%{now/d}-000001". #setup.ilm.pattern: "{now/d}-000001" # Set the lifecycle policy name. The default policy name is -# 'auditbeat'. +# 'beatname'. #setup.ilm.policy_name: "mypolicy" # The path to a JSON file that contains a lifecycle policy configuration. Used @@ -1169,7 +1171,7 @@ setup.template.settings: # Overwrite the lifecycle policy at startup. The default is false. #setup.ilm.overwrite: false -#============================== Kibana ===================================== +# =================================== Kibana =================================== # Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API. # This requires a Kibana endpoint configuration. @@ -1224,9 +1226,8 @@ setup.kibana: # Configure curve types for ECDHE-based cipher suites #ssl.curve_types: [] +# ================================== Logging =================================== - -#================================ Logging ====================================== # There are four options for the log output: file, stderr, syslog, eventlog # The file output is the default. @@ -1293,8 +1294,7 @@ logging.files: # Set to true to log messages in JSON format. #logging.json: false - -#============================== X-Pack Monitoring =============================== +# ============================= X-Pack Monitoring ============================== # Auditbeat can export internal metrics to a central Elasticsearch monitoring # cluster. This requires xpack monitoring to be enabled in Elasticsearch. The # reporting is disabled by default. @@ -1436,7 +1436,8 @@ logging.files: # and `monitoring.elasticsearch.password` settings. The format is `:`. #monitoring.cloud.auth: -#================================ HTTP Endpoint ====================================== +# =============================== HTTP Endpoint ================================ + # Each beat can expose internal metrics through a HTTP endpoint. For security # reasons the endpoint is disabled by default. This feature is currently experimental. # Stats can be access through http://localhost:5066/stats . For pretty JSON output @@ -1460,12 +1461,13 @@ logging.files: # `http.user`. #http.named_pipe.security_descriptor: -#============================= Process Security ================================ +# ============================== Process Security ============================== # Enable or disable seccomp system call filtering on Linux. Default is enabled. #seccomp.enabled: true -#================================= Migration ================================== +# ================================= Migration ================================== # This allows to enable 6.7 migration aliases #migration.6_to_7.enabled: false + diff --git a/auditbeat/auditbeat.yml b/auditbeat/auditbeat.yml index 0aa50de30ce..79cca537a8a 100644 --- a/auditbeat/auditbeat.yml +++ b/auditbeat/auditbeat.yml @@ -7,7 +7,7 @@ # You can find the full configuration reference here: # https://www.elastic.co/guide/en/beats/auditbeat/index.html -#========================== Modules configuration ============================= +# =========================== Modules configuration ============================ auditbeat.modules: - module: auditd @@ -48,13 +48,14 @@ auditbeat.modules: - /etc -#==================== Elasticsearch template setting ========================== +# ======================= Elasticsearch template setting ======================= setup.template.settings: index.number_of_shards: 1 #index.codec: best_compression #_source.enabled: false -#================================ General ===================================== + +# ================================== General =================================== # The name of the shipper that publishes the network data. It can be used to group # all the transactions sent by a single shipper in the web interface. @@ -69,8 +70,7 @@ setup.template.settings: #fields: # env: staging - -#============================== Dashboards ===================================== +# ================================= Dashboards ================================= # These settings control loading the sample dashboards to the Kibana index. Loading # the dashboards is disabled by default and can be enabled either by setting the # options here or by using the `setup` command. @@ -82,7 +82,7 @@ setup.template.settings: # website. #setup.dashboards.url: -#============================== Kibana ===================================== +# =================================== Kibana =================================== # Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API. # This requires a Kibana endpoint configuration. @@ -99,7 +99,7 @@ setup.kibana: # the Default Space will be used. #space.id: -#============================= Elastic Cloud ================================== +# =============================== Elastic Cloud ================================ # These settings simplify using Auditbeat with the Elastic Cloud (https://cloud.elastic.co/). @@ -112,11 +112,11 @@ setup.kibana: # `output.elasticsearch.password` settings. The format is `:`. #cloud.auth: -#================================ Outputs ===================================== +# ================================== Outputs =================================== # Configure what output to use when sending the data collected by the beat. -#-------------------------- Elasticsearch output ------------------------------ +# ---------------------------- Elasticsearch Output ---------------------------- output.elasticsearch: # Array of hosts to connect to. hosts: ["localhost:9200"] @@ -129,7 +129,7 @@ output.elasticsearch: #username: "elastic" #password: "changeme" -#----------------------------- Logstash output -------------------------------- +# ------------------------------ Logstash Output ------------------------------- #output.logstash: # The Logstash hosts #hosts: ["localhost:5044"] @@ -144,7 +144,7 @@ output.elasticsearch: # Client Certificate Key #ssl.key: "/etc/pki/client/cert.key" -#================================ Processors ===================================== +# ================================= Processors ================================= # Configure processors to enhance or manipulate events generated by the beat. @@ -153,7 +153,8 @@ processors: - add_cloud_metadata: ~ - add_docker_metadata: ~ -#================================ Logging ===================================== + +# ================================== Logging =================================== # Sets log level. The default log level is info. # Available log levels are: error, warning, info, debug @@ -164,8 +165,8 @@ processors: # "publish", "service". #logging.selectors: ["*"] -#============================== X-Pack Monitoring =============================== -# auditbeat can export internal metrics to a central Elasticsearch monitoring +# ============================= X-Pack Monitoring ============================== +# Auditbeat can export internal metrics to a central Elasticsearch monitoring # cluster. This requires xpack monitoring to be enabled in Elasticsearch. The # reporting is disabled by default. @@ -186,7 +187,8 @@ processors: # uncomment the following line. #monitoring.elasticsearch: -#================================= Migration ================================== +# ================================= Migration ================================== # This allows to enable 6.7 migration aliases #migration.6_to_7.enabled: true + diff --git a/auditbeat/scripts/mage/config.go b/auditbeat/scripts/mage/config.go index 91f70446123..d6a1f6b1424 100644 --- a/auditbeat/scripts/mage/config.go +++ b/auditbeat/scripts/mage/config.go @@ -61,27 +61,15 @@ func configFileParams(dirs ...string) (devtools.ConfigFileParams, error) { if len(configFiles) == 0 { return devtools.ConfigFileParams{}, errors.Errorf("no config files found in %v", globs) } + devtools.MustFileConcat("build/config.modules.yml.tmpl", 0644, configFiles...) - return devtools.ConfigFileParams{ - ShortParts: join( - devtools.OSSBeatDir("_meta/common.p1.yml"), - configFiles, - devtools.OSSBeatDir("_meta/common.p2.yml"), - devtools.LibbeatDir("_meta/config.yml.tmpl"), - ), - ReferenceParts: join( - devtools.OSSBeatDir("_meta/common.reference.yml"), - configFiles, - devtools.LibbeatDir("_meta/config.reference.yml.tmpl"), - ), - DockerParts: []string{ - devtools.OSSBeatDir("_meta/beat.docker.yml"), - devtools.LibbeatDir("_meta/config.docker.yml"), - }, - ExtraVars: map[string]interface{}{ - "ArchBits": archBits, - }, - }, nil + p := devtools.DefaultConfigFileParams() + p.Templates = append(p.Templates, devtools.OSSBeatDir("_meta/config/*.tmpl")) + p.Templates = append(p.Templates, "build/config.modules.yml.tmpl") + p.ExtraVars = map[string]interface{}{ + "ArchBits": archBits, + } + return p, nil } // archBits returns the number of bit width of the GOARCH architecture value. @@ -95,16 +83,3 @@ func archBits(goarch string) int { return 64 } } - -func join(items ...interface{}) []string { - var out []string - for _, item := range items { - switch v := item.(type) { - case string: - out = append(out, v) - case []string: - out = append(out, v...) - } - } - return out -} diff --git a/dev-tools/mage/common.go b/dev-tools/mage/common.go index 9fdb189e3fa..91d291f5387 100644 --- a/dev-tools/mage/common.go +++ b/dev-tools/mage/common.go @@ -115,7 +115,7 @@ func joinMaps(args ...map[string]interface{}) map[string]interface{} { return args[0] } - var out map[string]interface{} + out := map[string]interface{}{} for _, m := range args { for k, v := range m { out[k] = v diff --git a/dev-tools/mage/config.go b/dev-tools/mage/config.go index 6cabac5c9b6..677b307926f 100644 --- a/dev-tools/mage/config.go +++ b/dev-tools/mage/config.go @@ -20,12 +20,11 @@ package mage import ( "fmt" "io/ioutil" - "log" "os" "path/filepath" - "regexp" "sort" "strings" + "text/template" "github.com/magefile/mage/mg" @@ -64,44 +63,87 @@ func (t ConfigFileType) IsDocker() bool { return t&DockerConfigType > 0 } // ConfigFileParams defines the files that make up each config file. type ConfigFileParams struct { - ShortParts []string // List of files or globs. - ShortDeps []interface{} - ReferenceParts []string // List of files or globs. - ReferenceDeps []interface{} - DockerParts []string // List of files or globs. - DockerDeps []interface{} - ExtraVars map[string]interface{} + Templates []string // List of files or globs to load. + ExtraVars map[string]interface{} + Short, Reference, Docker ConfigParams } -// Empty checks if configuration files are set. -func (c ConfigFileParams) Empty() bool { - return len(c.ShortParts) == len(c.ReferenceDeps) && len(c.ReferenceParts) == len(c.DockerParts) && len(c.DockerParts) == 0 +type ConfigParams struct { + Template string + Deps []interface{} +} + +func DefaultConfigFileParams() ConfigFileParams { + return ConfigFileParams{ + Templates: []string{LibbeatDir("_meta/config/*.tmpl")}, + ExtraVars: map[string]interface{}{}, + Short: ConfigParams{ + Template: LibbeatDir("_meta/config/default.short.yml.tmpl"), + }, + Reference: ConfigParams{ + Template: LibbeatDir("_meta/config/default.reference.yml.tmpl"), + }, + Docker: ConfigParams{ + Template: LibbeatDir("_meta/config/default.docker.yml.tmpl"), + }, + } } // Config generates config files. Set DEV_OS and DEV_ARCH to change the target // host for the generated configs. Defaults to linux/amd64. func Config(types ConfigFileType, args ConfigFileParams, targetDir string) error { - if args.Empty() { - args = ConfigFileParams{ - ShortParts: []string{ - OSSBeatDir("_meta/beat.yml"), - LibbeatDir("_meta/config.yml.tmpl"), - }, - ReferenceParts: []string{ - OSSBeatDir("_meta/beat.reference.yml"), - LibbeatDir("_meta/config.reference.yml.tmpl"), - }, - DockerParts: []string{ - OSSBeatDir("_meta/beat.docker.yml"), - LibbeatDir("_meta/config.docker.yml"), - }, + // Short + if types.IsShort() { + file := filepath.Join(targetDir, BeatName+".yml") + if err := makeConfigTemplate(file, 0600, args, ShortConfigType); err != nil { + return errors.Wrap(err, "failed making short config") } } - if err := makeConfigTemplates(types, args); err != nil { - return errors.Wrap(err, "failed making config templates") + // Reference + if types.IsReference() { + file := filepath.Join(targetDir, BeatName+".reference.yml") + if err := makeConfigTemplate(file, 0644, args, ReferenceConfigType); err != nil { + return errors.Wrap(err, "failed making reference config") + } + } + + // Docker + if types.IsDocker() { + file := filepath.Join(targetDir, BeatName+".docker.yml") + if err := makeConfigTemplate(file, 0600, args, DockerConfigType); err != nil { + return errors.Wrap(err, "failed making docker config") + } } + return nil +} + +func makeConfigTemplate(destination string, mode os.FileMode, confParams ConfigFileParams, typ ConfigFileType) error { + // Determine what type to build and set some parameters. + var confFile ConfigParams + var tmplParams map[string]interface{} + switch typ { + case ShortConfigType: + confFile = confParams.Short + tmplParams = map[string]interface{}{} + case ReferenceConfigType: + confFile = confParams.Reference + tmplParams = map[string]interface{}{"Reference": true} + case DockerConfigType: + confFile = confParams.Docker + tmplParams = map[string]interface{}{"Docker": true} + default: + panic(errors.Errorf("Invalid config file type: %v", typ)) + } + + // Build the dependencies. + mg.SerialDeps(confFile.Deps...) + + // Set variables that are available in templates. + // Rather than adding more "ExcludeX"/"UseX" options consider overwriting + // one of the libbeat templates in your project by adding a file with the + // same name to your _meta/config directory. params := map[string]interface{}{ "GOOS": EnvOr("DEV_OS", "linux"), "GOARCH": EnvOr("DEV_ARCH", "amd64"), @@ -116,90 +158,58 @@ func Config(types ConfigFileType, args ConfigFileParams, targetDir string) error "UseDockerMetadataProcessor": true, "UseKubernetesMetadataProcessor": false, "ExcludeDashboards": false, - "UseProcessorsTemplate": false, - } - for k, v := range args.ExtraVars { - params[k] = v - } - - // Short - if types.IsShort() { - file := filepath.Join(targetDir, BeatName+".yml") - fmt.Printf(">> Building %v for %v/%v\n", file, params["GOOS"], params["GOARCH"]) - if err := ExpandFile(shortTemplate, file, params); err != nil { - return errors.Wrapf(err, "failed building %v", file) - } } + params = joinMaps(params, confParams.ExtraVars, tmplParams) + funcs := joinMaps(FuncMap, template.FuncMap{ + "header": header, + "subheader": subheader, + }) - // Reference - if types.IsReference() { - file := filepath.Join(targetDir, BeatName+".reference.yml") - params["Reference"] = true - fmt.Printf(">> Building %v for %v/%v\n", file, params["GOOS"], params["GOARCH"]) - if err := ExpandFile(referenceTemplate, file, params); err != nil { - return errors.Wrapf(err, "failed building %v", file) + fmt.Printf(">> Building %v for %v/%v\n", destination, params["GOOS"], params["GOARCH"]) + var err error + tmpl := template.New("config").Option("missingkey=error").Funcs(funcs) + for _, templateGlob := range confParams.Templates { + if tmpl, err = tmpl.ParseGlob(templateGlob); err != nil { + return errors.Wrapf(err, "failed to parse config templates in %q", templateGlob) } } - // Docker - if types.IsDocker() { - file := filepath.Join(targetDir, BeatName+".docker.yml") - params["Reference"] = false - params["Docker"] = true - fmt.Printf(">> Building %v for %v/%v\n", file, params["GOOS"], params["GOARCH"]) - if err := ExpandFile(dockerTemplate, file, params); err != nil { - return errors.Wrapf(err, "failed building %v", file) - } + data, err := ioutil.ReadFile(confFile.Template) + if err != nil { + return errors.Wrapf(err, "failed to read config template %q", confFile.Template) } - return nil -} - -func makeConfigTemplates(types ConfigFileType, args ConfigFileParams) error { - var err error - - if types.IsShort() { - mg.SerialDeps(args.ShortDeps...) - if err = makeConfigTemplate(shortTemplate, 0600, args.ShortParts...); err != nil { - return err - } + tmpl, err = tmpl.Parse(string(data)) + if err != nil { + return errors.Wrap(err, "failed to parse template") } - if types.IsReference() { - mg.SerialDeps(args.ReferenceDeps...) - if err = makeConfigTemplate(referenceTemplate, 0644, args.ReferenceParts...); err != nil { - return err - } + out, err := os.OpenFile(CreateDir(destination), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, mode) + if err != nil { + return err } + defer out.Close() - if types.IsDocker() { - mg.SerialDeps(args.DockerDeps...) - if err = makeConfigTemplate(dockerTemplate, 0600, args.DockerParts...); err != nil { - return err - } + if err = tmpl.Execute(out, EnvMap(params)); err != nil { + return errors.Wrapf(err, "failed building %v", destination) } return nil } -func makeConfigTemplate(destination string, mode os.FileMode, parts ...string) error { - configFiles, err := FindFiles(parts...) - if err != nil { - return errors.Wrap(err, "failed to find config templates") - } +func header(title string) string { + return makeHeading(title, "=") +} - if IsUpToDate(destination, configFiles...) { - return nil - } +func subheader(title string) string { + return makeHeading(title, "-") +} - log.Println(">> Building", destination) - if err = FileConcat(destination, mode, configFiles...); err != nil { - return err - } - if err = FindReplace(destination, regexp.MustCompile("beatname"), "{{.BeatName}}"); err != nil { - return err - } - return FindReplace(destination, regexp.MustCompile("beat-index-prefix"), "{{.BeatIndexPrefix}}") +func makeHeading(title, separator string) string { + const line = 80 + leftEquals := (line - len("# ") - len(title) - 2*len(" ")) / 2 + rightEquals := leftEquals + len(title)%2 + return "# " + strings.Repeat(separator, leftEquals) + " " + title + " " + strings.Repeat(separator, rightEquals) } const moduleConfigTemplate = ` diff --git a/filebeat/_meta/beat.docker.yml b/filebeat/_meta/config/beat.docker.yml.tmpl similarity index 98% rename from filebeat/_meta/beat.docker.yml rename to filebeat/_meta/config/beat.docker.yml.tmpl index 756c2df5217..3af3b45289c 100644 --- a/filebeat/_meta/beat.docker.yml +++ b/filebeat/_meta/config/beat.docker.yml.tmpl @@ -2,4 +2,3 @@ filebeat.config: modules: path: ${path.config}/modules.d/*.yml reload.enabled: false - diff --git a/filebeat/_meta/config/beat.reference.yml.tmpl b/filebeat/_meta/config/beat.reference.yml.tmpl new file mode 100644 index 00000000000..542892789b1 --- /dev/null +++ b/filebeat/_meta/config/beat.reference.yml.tmpl @@ -0,0 +1,5 @@ +{{template "header.reference.yml.tmpl" .}} +{{template "config.modules.yml.tmpl" .}} +{{template "filebeat.inputs.reference.yml.tmpl" .}} +{{template "filebeat.autodiscover.reference.yml.tmpl" .}} +{{template "filebeat.global.reference.yml.tmpl" .}} diff --git a/filebeat/_meta/config/beat.yml.tmpl b/filebeat/_meta/config/beat.yml.tmpl new file mode 100644 index 00000000000..95a6019ee70 --- /dev/null +++ b/filebeat/_meta/config/beat.yml.tmpl @@ -0,0 +1,4 @@ +{{template "header.yml.tmpl" .}} +{{template "filebeat.inputs.yml.tmpl" .}} +{{template "filebeat.config.modules.yml.tmpl" .}} +{{template "setup.template.yml.tmpl" .}} diff --git a/filebeat/_meta/config/filebeat.autodiscover.reference.yml.tmpl b/filebeat/_meta/config/filebeat.autodiscover.reference.yml.tmpl new file mode 100644 index 00000000000..003559be0dc --- /dev/null +++ b/filebeat/_meta/config/filebeat.autodiscover.reference.yml.tmpl @@ -0,0 +1,16 @@ +{{header "Filebeat autodiscover"}} + +# Autodiscover allows you to detect changes in the system and spawn new modules +# or inputs as they happen. + +#filebeat.autodiscover: + # List of enabled autodiscover providers +# providers: +# - type: docker +# templates: +# - condition: +# equals.docker.container.image: busybox +# config: +# - type: container +# paths: +# - /var/lib/docker/containers/${data.docker.container.id}/*.log diff --git a/filebeat/_meta/config/filebeat.config.modules.yml.tmpl b/filebeat/_meta/config/filebeat.config.modules.yml.tmpl new file mode 100644 index 00000000000..58214008d2b --- /dev/null +++ b/filebeat/_meta/config/filebeat.config.modules.yml.tmpl @@ -0,0 +1,11 @@ +{{header "Filebeat modules"}} + +filebeat.config.modules: + # Glob pattern for configuration loading + path: ${path.config}/modules.d/*.yml + + # Set to true to enable config reloading + reload.enabled: false + + # Period on which files under path should be checked for changes + #reload.period: 10s diff --git a/filebeat/_meta/common.reference.p2.yml b/filebeat/_meta/config/filebeat.global.reference.yml.tmpl similarity index 74% rename from filebeat/_meta/common.reference.p2.yml rename to filebeat/_meta/config/filebeat.global.reference.yml.tmpl index eb3c1c9cca4..dccfc790a7c 100644 --- a/filebeat/_meta/common.reference.p2.yml +++ b/filebeat/_meta/config/filebeat.global.reference.yml.tmpl @@ -1,21 +1,4 @@ -#========================== Filebeat autodiscover ============================== - -# Autodiscover allows you to detect changes in the system and spawn new modules -# or inputs as they happen. - -#filebeat.autodiscover: - # List of enabled autodiscover providers -# providers: -# - type: docker -# templates: -# - condition: -# equals.docker.container.image: busybox -# config: -# - type: container -# paths: -# - /var/lib/docker/containers/${data.docker.container.id}/*.log - -#========================= Filebeat global options ============================ +{{header "Filebeat global options"}} # Registry data path. If a relative path is used, it is considered relative to the # data path. diff --git a/filebeat/_meta/common.reference.inputs.yml b/filebeat/_meta/config/filebeat.inputs.reference.yml.tmpl similarity index 100% rename from filebeat/_meta/common.reference.inputs.yml rename to filebeat/_meta/config/filebeat.inputs.reference.yml.tmpl diff --git a/filebeat/_meta/common.p2.yml b/filebeat/_meta/config/filebeat.inputs.yml.tmpl similarity index 72% rename from filebeat/_meta/common.p2.yml rename to filebeat/_meta/config/filebeat.inputs.yml.tmpl index b423f0b0494..a7bd1b5eaa6 100644 --- a/filebeat/_meta/common.p2.yml +++ b/filebeat/_meta/config/filebeat.inputs.yml.tmpl @@ -1,7 +1,4 @@ -# For more available modules and options, please see the filebeat.reference.yml sample -# configuration file. - -#=========================== Filebeat inputs ============================= +{{header "Filebeat inputs"}} filebeat.inputs: @@ -52,23 +49,3 @@ filebeat.inputs: # that was (not) matched before or after or as long as a pattern is not matched based on negate. # Note: After is the equivalent to previous and before is the equivalent to to next in Logstash #multiline.match: after - - -#============================= Filebeat modules =============================== - -filebeat.config.modules: - # Glob pattern for configuration loading - path: ${path.config}/modules.d/*.yml - - # Set to true to enable config reloading - reload.enabled: false - - # Period on which files under path should be checked for changes - #reload.period: 10s - -#==================== Elasticsearch template setting ========================== - -setup.template.settings: - index.number_of_shards: 1 - #index.codec: best_compression - #_source.enabled: false diff --git a/filebeat/_meta/common.reference.p1.yml b/filebeat/_meta/config/header.reference.yml.tmpl similarity index 99% rename from filebeat/_meta/common.reference.p1.yml rename to filebeat/_meta/config/header.reference.yml.tmpl index c02e11deacb..9c9e00b66b2 100644 --- a/filebeat/_meta/common.reference.p1.yml +++ b/filebeat/_meta/config/header.reference.yml.tmpl @@ -6,4 +6,3 @@ # # You can find the full configuration reference here: # https://www.elastic.co/guide/en/beats/filebeat/index.html - diff --git a/filebeat/_meta/common.p1.yml b/filebeat/_meta/config/header.yml.tmpl similarity index 79% rename from filebeat/_meta/common.p1.yml rename to filebeat/_meta/config/header.yml.tmpl index 10b4ef6956d..d0351dc0ff1 100644 --- a/filebeat/_meta/common.p1.yml +++ b/filebeat/_meta/config/header.yml.tmpl @@ -7,3 +7,5 @@ # You can find the full configuration reference here: # https://www.elastic.co/guide/en/beats/filebeat/index.html +# For more available modules and options, please see the filebeat.reference.yml sample +# configuration file. diff --git a/auditbeat/_meta/common.p2.yml b/filebeat/_meta/config/setup.template.yml.tmpl similarity index 58% rename from auditbeat/_meta/common.p2.yml rename to filebeat/_meta/config/setup.template.yml.tmpl index 468cc1d45a9..290fbc27ace 100644 --- a/auditbeat/_meta/common.p2.yml +++ b/filebeat/_meta/config/setup.template.yml.tmpl @@ -1,5 +1,5 @@ +{{header "Elasticsearch template setting"}} -#==================== Elasticsearch template setting ========================== setup.template.settings: index.number_of_shards: 1 #index.codec: best_compression diff --git a/filebeat/filebeat.reference.yml b/filebeat/filebeat.reference.yml index e337c1b0e9d..e34e85fd810 100644 --- a/filebeat/filebeat.reference.yml +++ b/filebeat/filebeat.reference.yml @@ -384,6 +384,7 @@ filebeat.modules: #input: + #=========================== Filebeat inputs ============================= # List of inputs to fetch data. @@ -765,7 +766,8 @@ filebeat.inputs: # Configure stream to filter to a specific stream: stdout, stderr or all (default) #stream: all -#========================== Filebeat autodiscover ============================== + +# =========================== Filebeat autodiscover ============================ # Autodiscover allows you to detect changes in the system and spawn new modules # or inputs as they happen. @@ -782,7 +784,7 @@ filebeat.inputs: # paths: # - /var/lib/docker/containers/${data.docker.container.id}/*.log -#========================= Filebeat global options ============================ +# ========================== Filebeat global options =========================== # Registry data path. If a relative path is used, it is considered relative to the # data path. @@ -829,7 +831,8 @@ filebeat.inputs: #reload.enabled: true #reload.period: 10s -#================================ General ====================================== + +# ================================== General =================================== # The name of the shipper that publishes the network data. It can be used to group # all the transactions sent by a single shipper in the web interface. @@ -937,7 +940,7 @@ filebeat.inputs: # default is the number of logical CPUs available in the system. #max_procs: -#================================ Processors =================================== +# ================================= Processors ================================= # Processors are used to reduce the number of fields in the exported event or to # enhance the event with external metadata. This section defines a list of @@ -1100,7 +1103,7 @@ filebeat.inputs: # ignore_missing: false # fail_on_error: true -#============================= Elastic Cloud ================================== +# =============================== Elastic Cloud ================================ # These settings simplify using Filebeat with the Elastic Cloud (https://cloud.elastic.co/). @@ -1113,11 +1116,11 @@ filebeat.inputs: # `output.elasticsearch.password` settings. The format is `:`. #cloud.auth: -#================================ Outputs ====================================== +# ================================== Outputs =================================== # Configure what output to use when sending the data collected by the beat. -#-------------------------- Elasticsearch output ------------------------------- +# ---------------------------- Elasticsearch Output ---------------------------- output.elasticsearch: # Boolean flag to enable or disable the output module. #enabled: true @@ -1258,7 +1261,7 @@ output.elasticsearch: # Kerberos realm. #kerberos.realm: ELASTIC -#----------------------------- Logstash output --------------------------------- +# ------------------------------ Logstash Output ------------------------------- #output.logstash: # Boolean flag to enable or disable the output module. #enabled: true @@ -1372,7 +1375,7 @@ output.elasticsearch: # timing out. The default is 30s. #timeout: 30s -#------------------------------- Kafka output ---------------------------------- +# -------------------------------- Kafka Output -------------------------------- #output.kafka: # Boolean flag to enable or disable the output module. #enabled: true @@ -1551,7 +1554,7 @@ output.elasticsearch: # Kerberos realm. #kerberos.realm: ELASTIC -#------------------------------- Redis output ---------------------------------- +# -------------------------------- Redis Output -------------------------------- #output.redis: # Boolean flag to enable or disable the output module. #enabled: true @@ -1669,7 +1672,7 @@ output.elasticsearch: # never, once, and freely. Default is never. #ssl.renegotiation: never -#------------------------------- File output ----------------------------------- +# -------------------------------- File Output --------------------------------- #output.file: # Boolean flag to enable or disable the output module. #enabled: true @@ -1703,7 +1706,7 @@ output.elasticsearch: # Permissions to use for file creation. The default is 0600. #permissions: 0600 -#----------------------------- Console output --------------------------------- +# ------------------------------- Console Output ------------------------------- #output.console: # Boolean flag to enable or disable the output module. #enabled: true @@ -1716,7 +1719,7 @@ output.elasticsearch: # Configure escaping HTML symbols in strings. #escape_html: false -#================================= Paths ====================================== +# =================================== Paths ==================================== # The home path for the Filebeat installation. This is the default base path # for all other path settings and for miscellaneous files that come with the @@ -1742,11 +1745,13 @@ output.elasticsearch: # the default for the logs path is a logs subdirectory inside the home path. #path.logs: ${path.home}/logs -#================================ Keystore ========================================== +# ================================== Keystore ================================== + # Location of the Keystore containing the keys and their sensitive values. #keystore.path: "${path.config}/beats.keystore" -#============================== Dashboards ===================================== +# ================================= Dashboards ================================= + # These settings control loading the sample dashboards to the Kibana index. Loading # the dashboards are disabled by default and can be enabled either by setting the # options here, or by using the `-setup` CLI flag or the `setup` command. @@ -1790,8 +1795,7 @@ output.elasticsearch: # Maximum number of retries before exiting with an error, 0 for unlimited retrying. #setup.dashboards.retry.maximum: 0 - -#============================== Template ===================================== +# ================================== Template ================================== # A template is used to set the mapping in Elasticsearch # By default template loading is enabled and the template is loaded. @@ -1845,7 +1849,7 @@ setup.template.settings: #_source: #enabled: false -#============================== Setup ILM ===================================== +# ====================== Index Lifecycle Management (ILM) ====================== # Configure index lifecycle management (ILM). These settings create a write # alias and add additional settings to the index template. When ILM is enabled, @@ -1859,13 +1863,13 @@ setup.template.settings: # Set the prefix used in the index lifecycle write alias name. The default alias # name is 'filebeat-%{[agent.version]}'. -#setup.ilm.rollover_alias: "filebeat" +#setup.ilm.rollover_alias: 'filebeat' # Set the rollover index pattern. The default is "%{now/d}-000001". #setup.ilm.pattern: "{now/d}-000001" # Set the lifecycle policy name. The default policy name is -# 'filebeat'. +# 'beatname'. #setup.ilm.policy_name: "mypolicy" # The path to a JSON file that contains a lifecycle policy configuration. Used @@ -1880,7 +1884,7 @@ setup.template.settings: # Overwrite the lifecycle policy at startup. The default is false. #setup.ilm.overwrite: false -#============================== Kibana ===================================== +# =================================== Kibana =================================== # Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API. # This requires a Kibana endpoint configuration. @@ -1935,9 +1939,8 @@ setup.kibana: # Configure curve types for ECDHE-based cipher suites #ssl.curve_types: [] +# ================================== Logging =================================== - -#================================ Logging ====================================== # There are four options for the log output: file, stderr, syslog, eventlog # The file output is the default. @@ -2004,8 +2007,7 @@ logging.files: # Set to true to log messages in JSON format. #logging.json: false - -#============================== X-Pack Monitoring =============================== +# ============================= X-Pack Monitoring ============================== # Filebeat can export internal metrics to a central Elasticsearch monitoring # cluster. This requires xpack monitoring to be enabled in Elasticsearch. The # reporting is disabled by default. @@ -2147,7 +2149,8 @@ logging.files: # and `monitoring.elasticsearch.password` settings. The format is `:`. #monitoring.cloud.auth: -#================================ HTTP Endpoint ====================================== +# =============================== HTTP Endpoint ================================ + # Each beat can expose internal metrics through a HTTP endpoint. For security # reasons the endpoint is disabled by default. This feature is currently experimental. # Stats can be access through http://localhost:5066/stats . For pretty JSON output @@ -2171,12 +2174,13 @@ logging.files: # `http.user`. #http.named_pipe.security_descriptor: -#============================= Process Security ================================ +# ============================== Process Security ============================== # Enable or disable seccomp system call filtering on Linux. Default is enabled. #seccomp.enabled: true -#================================= Migration ================================== +# ================================= Migration ================================== # This allows to enable 6.7 migration aliases #migration.6_to_7.enabled: false + diff --git a/filebeat/filebeat.yml b/filebeat/filebeat.yml index 581e1a43f23..51a0d40224e 100644 --- a/filebeat/filebeat.yml +++ b/filebeat/filebeat.yml @@ -10,7 +10,7 @@ # For more available modules and options, please see the filebeat.reference.yml sample # configuration file. -#=========================== Filebeat inputs ============================= +# ============================== Filebeat inputs =============================== filebeat.inputs: @@ -62,8 +62,7 @@ filebeat.inputs: # Note: After is the equivalent to previous and before is the equivalent to to next in Logstash #multiline.match: after - -#============================= Filebeat modules =============================== +# ============================== Filebeat modules ============================== filebeat.config.modules: # Glob pattern for configuration loading @@ -75,14 +74,15 @@ filebeat.config.modules: # Period on which files under path should be checked for changes #reload.period: 10s -#==================== Elasticsearch template setting ========================== +# ======================= Elasticsearch template setting ======================= setup.template.settings: index.number_of_shards: 1 #index.codec: best_compression #_source.enabled: false -#================================ General ===================================== + +# ================================== General =================================== # The name of the shipper that publishes the network data. It can be used to group # all the transactions sent by a single shipper in the web interface. @@ -97,8 +97,7 @@ setup.template.settings: #fields: # env: staging - -#============================== Dashboards ===================================== +# ================================= Dashboards ================================= # These settings control loading the sample dashboards to the Kibana index. Loading # the dashboards is disabled by default and can be enabled either by setting the # options here or by using the `setup` command. @@ -110,7 +109,7 @@ setup.template.settings: # website. #setup.dashboards.url: -#============================== Kibana ===================================== +# =================================== Kibana =================================== # Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API. # This requires a Kibana endpoint configuration. @@ -127,7 +126,7 @@ setup.kibana: # the Default Space will be used. #space.id: -#============================= Elastic Cloud ================================== +# =============================== Elastic Cloud ================================ # These settings simplify using Filebeat with the Elastic Cloud (https://cloud.elastic.co/). @@ -140,11 +139,11 @@ setup.kibana: # `output.elasticsearch.password` settings. The format is `:`. #cloud.auth: -#================================ Outputs ===================================== +# ================================== Outputs =================================== # Configure what output to use when sending the data collected by the beat. -#-------------------------- Elasticsearch output ------------------------------ +# ---------------------------- Elasticsearch Output ---------------------------- output.elasticsearch: # Array of hosts to connect to. hosts: ["localhost:9200"] @@ -157,7 +156,7 @@ output.elasticsearch: #username: "elastic" #password: "changeme" -#----------------------------- Logstash output -------------------------------- +# ------------------------------ Logstash Output ------------------------------- #output.logstash: # The Logstash hosts #hosts: ["localhost:5044"] @@ -172,7 +171,7 @@ output.elasticsearch: # Client Certificate Key #ssl.key: "/etc/pki/client/cert.key" -#================================ Processors ===================================== +# ================================= Processors ================================= # Configure processors to enhance or manipulate events generated by the beat. @@ -182,7 +181,8 @@ processors: - add_docker_metadata: ~ - add_kubernetes_metadata: ~ -#================================ Logging ===================================== + +# ================================== Logging =================================== # Sets log level. The default log level is info. # Available log levels are: error, warning, info, debug @@ -193,8 +193,8 @@ processors: # "publish", "service". #logging.selectors: ["*"] -#============================== X-Pack Monitoring =============================== -# filebeat can export internal metrics to a central Elasticsearch monitoring +# ============================= X-Pack Monitoring ============================== +# Filebeat can export internal metrics to a central Elasticsearch monitoring # cluster. This requires xpack monitoring to be enabled in Elasticsearch. The # reporting is disabled by default. @@ -215,7 +215,8 @@ processors: # uncomment the following line. #monitoring.elasticsearch: -#================================= Migration ================================== +# ================================= Migration ================================== # This allows to enable 6.7 migration aliases #migration.6_to_7.enabled: true + diff --git a/filebeat/scripts/mage/config.go b/filebeat/scripts/mage/config.go index 7585c62b634..0893fbc0743 100644 --- a/filebeat/scripts/mage/config.go +++ b/filebeat/scripts/mage/config.go @@ -18,38 +18,25 @@ package mage import ( + "github.com/magefile/mage/mg" + devtools "github.com/elastic/beats/v7/dev-tools/mage" ) -const modulesConfigYml = "build/config.modules.yml" +const modulesConfigYml = "build/config.modules.yml.tmpl" func configFileParams(moduleDirs ...string) devtools.ConfigFileParams { collectModuleConfig := func() error { return devtools.GenerateModuleReferenceConfig(modulesConfigYml, moduleDirs...) } + mg.Deps(collectModuleConfig) - return devtools.ConfigFileParams{ - ShortParts: []string{ - devtools.OSSBeatDir("_meta/common.p1.yml"), - devtools.OSSBeatDir("_meta/common.p2.yml"), - devtools.LibbeatDir("_meta/config.yml.tmpl"), - }, - ReferenceDeps: []interface{}{collectModuleConfig}, - ReferenceParts: []string{ - devtools.OSSBeatDir("_meta/common.reference.p1.yml"), - modulesConfigYml, - devtools.OSSBeatDir("_meta/common.reference.inputs.yml"), - devtools.OSSBeatDir("_meta/common.reference.p2.yml"), - devtools.LibbeatDir("_meta/config.reference.yml.tmpl"), - }, - DockerParts: []string{ - devtools.OSSBeatDir("_meta/beat.docker.yml"), - devtools.LibbeatDir("_meta/config.docker.yml"), - }, - ExtraVars: map[string]interface{}{ - "UseKubernetesMetadataProcessor": true, - }, + p := devtools.DefaultConfigFileParams() + p.Templates = append(p.Templates, devtools.OSSBeatDir("_meta/config/*.tmpl"), modulesConfigYml) + p.ExtraVars = map[string]interface{}{ + "UseKubernetesMetadataProcessor": true, } + return p } // OSSConfigFileParams returns the default ConfigFileParams for generating @@ -62,13 +49,6 @@ func OSSConfigFileParams(moduleDirs ...string) devtools.ConfigFileParams { // filebeat*.yml files. func XPackConfigFileParams() devtools.ConfigFileParams { args := configFileParams(devtools.OSSBeatDir("module"), "module") - args.ReferenceParts = []string{ - devtools.OSSBeatDir("_meta/common.reference.p1.yml"), - modulesConfigYml, - devtools.OSSBeatDir("_meta/common.reference.inputs.yml"), - "_meta/common.reference.inputs.yml", // Added only to X-Pack. - devtools.OSSBeatDir("_meta/common.reference.p2.yml"), - devtools.LibbeatDir("_meta/config.reference.yml.tmpl"), - } + args.Templates = append(args.Templates, "_meta/config/*.tmpl") return args } diff --git a/generator/_templates/beat/{beat}/_meta/beat.docker.yml b/generator/_templates/beat/{beat}/_meta/config/beat.docker.yml.tmpl similarity index 100% rename from generator/_templates/beat/{beat}/_meta/beat.docker.yml rename to generator/_templates/beat/{beat}/_meta/config/beat.docker.yml.tmpl diff --git a/generator/_templates/beat/{beat}/_meta/beat.reference.yml b/generator/_templates/beat/{beat}/_meta/config/beat.reference.yml.tmpl similarity index 100% rename from generator/_templates/beat/{beat}/_meta/beat.reference.yml rename to generator/_templates/beat/{beat}/_meta/config/beat.reference.yml.tmpl diff --git a/generator/_templates/beat/{beat}/_meta/beat.yml b/generator/_templates/beat/{beat}/_meta/config/beat.yml.tmpl similarity index 100% rename from generator/_templates/beat/{beat}/_meta/beat.yml rename to generator/_templates/beat/{beat}/_meta/config/beat.yml.tmpl diff --git a/generator/_templates/beat/{beat}/magefile.go b/generator/_templates/beat/{beat}/magefile.go index d924f3c7946..28638df0f0a 100644 --- a/generator/_templates/beat/{beat}/magefile.go +++ b/generator/_templates/beat/{beat}/magefile.go @@ -56,7 +56,9 @@ func Fields() error { // Config generates both the short/reference/docker configs. func Config() error { - return devtools.Config(devtools.AllConfigTypes, devtools.ConfigFileParams{}, ".") + p := devtools.DefaultConfigFileParams() + p.Templates = append(p.Templates, "_meta/config/*.tmpl") + return devtools.Config(devtools.AllConfigTypes, p, ".") } // Clean cleans all generated files and build artifacts. diff --git a/generator/_templates/metricbeat/{beat}/_meta/docker.yml b/generator/_templates/metricbeat/{beat}/_meta/config/beat.docker.yml.tmpl similarity index 100% rename from generator/_templates/metricbeat/{beat}/_meta/docker.yml rename to generator/_templates/metricbeat/{beat}/_meta/config/beat.docker.yml.tmpl diff --git a/generator/_templates/metricbeat/{beat}/_meta/reference.yml b/generator/_templates/metricbeat/{beat}/_meta/config/beat.reference.yml.tmpl similarity index 100% rename from generator/_templates/metricbeat/{beat}/_meta/reference.yml rename to generator/_templates/metricbeat/{beat}/_meta/config/beat.reference.yml.tmpl diff --git a/generator/_templates/metricbeat/{beat}/_meta/short.yml b/generator/_templates/metricbeat/{beat}/_meta/config/beat.yml.tmpl similarity index 100% rename from generator/_templates/metricbeat/{beat}/_meta/short.yml rename to generator/_templates/metricbeat/{beat}/_meta/config/beat.yml.tmpl diff --git a/generator/_templates/metricbeat/{beat}/magefile.go b/generator/_templates/metricbeat/{beat}/magefile.go index 934276e633b..ba9f64fdefb 100644 --- a/generator/_templates/metricbeat/{beat}/magefile.go +++ b/generator/_templates/metricbeat/{beat}/magefile.go @@ -76,13 +76,9 @@ func Config() { } func configYML() error { - customDeps := devtools.ConfigFileParams{ - ShortParts: []string{"_meta/short.yml", devtools.LibbeatDir("_meta/config.yml.tmpl")}, - ReferenceParts: []string{"_meta/reference.yml", devtools.LibbeatDir("_meta/config.reference.yml.tmpl")}, - DockerParts: []string{"_meta/docker.yml", devtools.LibbeatDir("_meta/config.docker.yml")}, - ExtraVars: map[string]interface{}{"BeatName": devtools.BeatName}, - } - return devtools.Config(devtools.AllConfigTypes, customDeps, ".") + p := devtools.DefaultConfigFileParams() + p.Templates = append(p.Templates, "_meta/config/*.tmpl") + return devtools.Config(devtools.AllConfigTypes, p, ".") } // Clean cleans all generated files and build artifacts. diff --git a/heartbeat/_meta/beat.docker.yml b/heartbeat/_meta/config/beat.docker.yml.tmpl similarity index 99% rename from heartbeat/_meta/beat.docker.yml rename to heartbeat/_meta/config/beat.docker.yml.tmpl index f845c4a47a1..2d5c7b43afd 100644 --- a/heartbeat/_meta/beat.docker.yml +++ b/heartbeat/_meta/config/beat.docker.yml.tmpl @@ -21,4 +21,3 @@ heartbeat.monitors: hosts: - elasticsearch - kibana - diff --git a/heartbeat/_meta/beat.reference.yml b/heartbeat/_meta/config/beat.reference.yml.tmpl similarity index 100% rename from heartbeat/_meta/beat.reference.yml rename to heartbeat/_meta/config/beat.reference.yml.tmpl diff --git a/heartbeat/_meta/beat.yml b/heartbeat/_meta/config/beat.yml.tmpl similarity index 94% rename from heartbeat/_meta/beat.yml rename to heartbeat/_meta/config/beat.yml.tmpl index 5459f28f989..04c9b71f7a1 100644 --- a/heartbeat/_meta/beat.yml +++ b/heartbeat/_meta/config/beat.yml.tmpl @@ -33,7 +33,7 @@ heartbeat.monitors: # Total test connection and data exchange timeout #timeout: 16s -#==================== Elasticsearch template setting ========================== +{{header "Elasticsearch template setting"}} setup.template.settings: index.number_of_shards: 1 diff --git a/heartbeat/heartbeat.reference.yml b/heartbeat/heartbeat.reference.yml index d3d6dfbd7a2..cd8addcc09a 100644 --- a/heartbeat/heartbeat.reference.yml +++ b/heartbeat/heartbeat.reference.yml @@ -269,7 +269,7 @@ heartbeat.scheduler: # Set the scheduler it's time zone #location: '' -#================================ General ====================================== +# ================================== General =================================== # The name of the shipper that publishes the network data. It can be used to group # all the transactions sent by a single shipper in the web interface. @@ -377,7 +377,7 @@ heartbeat.scheduler: # default is the number of logical CPUs available in the system. #max_procs: -#================================ Processors =================================== +# ================================= Processors ================================= # Processors are used to reduce the number of fields in the exported event or to # enhance the event with external metadata. This section defines a list of @@ -540,7 +540,7 @@ heartbeat.scheduler: # ignore_missing: false # fail_on_error: true -#============================= Elastic Cloud ================================== +# =============================== Elastic Cloud ================================ # These settings simplify using Heartbeat with the Elastic Cloud (https://cloud.elastic.co/). @@ -553,11 +553,11 @@ heartbeat.scheduler: # `output.elasticsearch.password` settings. The format is `:`. #cloud.auth: -#================================ Outputs ====================================== +# ================================== Outputs =================================== # Configure what output to use when sending the data collected by the beat. -#-------------------------- Elasticsearch output ------------------------------- +# ---------------------------- Elasticsearch Output ---------------------------- output.elasticsearch: # Boolean flag to enable or disable the output module. #enabled: true @@ -698,7 +698,7 @@ output.elasticsearch: # Kerberos realm. #kerberos.realm: ELASTIC -#----------------------------- Logstash output --------------------------------- +# ------------------------------ Logstash Output ------------------------------- #output.logstash: # Boolean flag to enable or disable the output module. #enabled: true @@ -812,7 +812,7 @@ output.elasticsearch: # timing out. The default is 30s. #timeout: 30s -#------------------------------- Kafka output ---------------------------------- +# -------------------------------- Kafka Output -------------------------------- #output.kafka: # Boolean flag to enable or disable the output module. #enabled: true @@ -991,7 +991,7 @@ output.elasticsearch: # Kerberos realm. #kerberos.realm: ELASTIC -#------------------------------- Redis output ---------------------------------- +# -------------------------------- Redis Output -------------------------------- #output.redis: # Boolean flag to enable or disable the output module. #enabled: true @@ -1109,7 +1109,7 @@ output.elasticsearch: # never, once, and freely. Default is never. #ssl.renegotiation: never -#------------------------------- File output ----------------------------------- +# -------------------------------- File Output --------------------------------- #output.file: # Boolean flag to enable or disable the output module. #enabled: true @@ -1143,7 +1143,7 @@ output.elasticsearch: # Permissions to use for file creation. The default is 0600. #permissions: 0600 -#----------------------------- Console output --------------------------------- +# ------------------------------- Console Output ------------------------------- #output.console: # Boolean flag to enable or disable the output module. #enabled: true @@ -1156,7 +1156,7 @@ output.elasticsearch: # Configure escaping HTML symbols in strings. #escape_html: false -#================================= Paths ====================================== +# =================================== Paths ==================================== # The home path for the Heartbeat installation. This is the default base path # for all other path settings and for miscellaneous files that come with the @@ -1182,11 +1182,13 @@ output.elasticsearch: # the default for the logs path is a logs subdirectory inside the home path. #path.logs: ${path.home}/logs -#================================ Keystore ========================================== +# ================================== Keystore ================================== + # Location of the Keystore containing the keys and their sensitive values. #keystore.path: "${path.config}/beats.keystore" -#============================== Dashboards ===================================== +# ================================= Dashboards ================================= + # These settings control loading the sample dashboards to the Kibana index. Loading # the dashboards are disabled by default and can be enabled either by setting the # options here, or by using the `-setup` CLI flag or the `setup` command. @@ -1230,8 +1232,7 @@ output.elasticsearch: # Maximum number of retries before exiting with an error, 0 for unlimited retrying. #setup.dashboards.retry.maximum: 0 - -#============================== Template ===================================== +# ================================== Template ================================== # A template is used to set the mapping in Elasticsearch # By default template loading is enabled and the template is loaded. @@ -1285,7 +1286,7 @@ setup.template.settings: #_source: #enabled: false -#============================== Setup ILM ===================================== +# ====================== Index Lifecycle Management (ILM) ====================== # Configure index lifecycle management (ILM). These settings create a write # alias and add additional settings to the index template. When ILM is enabled, @@ -1299,13 +1300,13 @@ setup.template.settings: # Set the prefix used in the index lifecycle write alias name. The default alias # name is 'heartbeat-%{[agent.version]}'. -#setup.ilm.rollover_alias: "heartbeat" +#setup.ilm.rollover_alias: 'heartbeat' # Set the rollover index pattern. The default is "%{now/d}-000001". #setup.ilm.pattern: "{now/d}-000001" # Set the lifecycle policy name. The default policy name is -# 'heartbeat'. +# 'beatname'. #setup.ilm.policy_name: "mypolicy" # The path to a JSON file that contains a lifecycle policy configuration. Used @@ -1320,7 +1321,7 @@ setup.template.settings: # Overwrite the lifecycle policy at startup. The default is false. #setup.ilm.overwrite: false -#============================== Kibana ===================================== +# =================================== Kibana =================================== # Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API. # This requires a Kibana endpoint configuration. @@ -1375,9 +1376,8 @@ setup.kibana: # Configure curve types for ECDHE-based cipher suites #ssl.curve_types: [] +# ================================== Logging =================================== - -#================================ Logging ====================================== # There are four options for the log output: file, stderr, syslog, eventlog # The file output is the default. @@ -1444,8 +1444,7 @@ logging.files: # Set to true to log messages in JSON format. #logging.json: false - -#============================== X-Pack Monitoring =============================== +# ============================= X-Pack Monitoring ============================== # Heartbeat can export internal metrics to a central Elasticsearch monitoring # cluster. This requires xpack monitoring to be enabled in Elasticsearch. The # reporting is disabled by default. @@ -1587,7 +1586,8 @@ logging.files: # and `monitoring.elasticsearch.password` settings. The format is `:`. #monitoring.cloud.auth: -#================================ HTTP Endpoint ====================================== +# =============================== HTTP Endpoint ================================ + # Each beat can expose internal metrics through a HTTP endpoint. For security # reasons the endpoint is disabled by default. This feature is currently experimental. # Stats can be access through http://localhost:5066/stats . For pretty JSON output @@ -1611,12 +1611,13 @@ logging.files: # `http.user`. #http.named_pipe.security_descriptor: -#============================= Process Security ================================ +# ============================== Process Security ============================== # Enable or disable seccomp system call filtering on Linux. Default is enabled. #seccomp.enabled: true -#================================= Migration ================================== +# ================================= Migration ================================== # This allows to enable 6.7 migration aliases #migration.6_to_7.enabled: false + diff --git a/heartbeat/heartbeat.yml b/heartbeat/heartbeat.yml index aa3e1283f70..425329e7c9a 100644 --- a/heartbeat/heartbeat.yml +++ b/heartbeat/heartbeat.yml @@ -33,14 +33,14 @@ heartbeat.monitors: # Total test connection and data exchange timeout #timeout: 16s -#==================== Elasticsearch template setting ========================== +# ======================= Elasticsearch template setting ======================= setup.template.settings: index.number_of_shards: 1 index.codec: best_compression #_source.enabled: false -#================================ General ===================================== +# ================================== General =================================== # The name of the shipper that publishes the network data. It can be used to group # all the transactions sent by a single shipper in the web interface. @@ -56,7 +56,7 @@ setup.template.settings: # env: staging -#============================== Kibana ===================================== +# =================================== Kibana =================================== # Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API. # This requires a Kibana endpoint configuration. @@ -73,7 +73,7 @@ setup.kibana: # the Default Space will be used. #space.id: -#============================= Elastic Cloud ================================== +# =============================== Elastic Cloud ================================ # These settings simplify using Heartbeat with the Elastic Cloud (https://cloud.elastic.co/). @@ -86,11 +86,11 @@ setup.kibana: # `output.elasticsearch.password` settings. The format is `:`. #cloud.auth: -#================================ Outputs ===================================== +# ================================== Outputs =================================== # Configure what output to use when sending the data collected by the beat. -#-------------------------- Elasticsearch output ------------------------------ +# ---------------------------- Elasticsearch Output ---------------------------- output.elasticsearch: # Array of hosts to connect to. hosts: ["localhost:9200"] @@ -103,7 +103,7 @@ output.elasticsearch: #username: "elastic" #password: "changeme" -#----------------------------- Logstash output -------------------------------- +# ------------------------------ Logstash Output ------------------------------- #output.logstash: # The Logstash hosts #hosts: ["localhost:5044"] @@ -118,7 +118,7 @@ output.elasticsearch: # Client Certificate Key #ssl.key: "/etc/pki/client/cert.key" -#================================ Processors ===================================== +# ================================= Processors ================================= processors: - add_observer_metadata: @@ -129,7 +129,8 @@ processors: # Lat, Lon " #location: "37.926868, -78.024902" -#================================ Logging ===================================== + +# ================================== Logging =================================== # Sets log level. The default log level is info. # Available log levels are: error, warning, info, debug @@ -140,8 +141,8 @@ processors: # "publish", "service". #logging.selectors: ["*"] -#============================== X-Pack Monitoring =============================== -# heartbeat can export internal metrics to a central Elasticsearch monitoring +# ============================= X-Pack Monitoring ============================== +# Heartbeat can export internal metrics to a central Elasticsearch monitoring # cluster. This requires xpack monitoring to be enabled in Elasticsearch. The # reporting is disabled by default. @@ -162,7 +163,8 @@ processors: # uncomment the following line. #monitoring.elasticsearch: -#================================= Migration ================================== +# ================================= Migration ================================== # This allows to enable 6.7 migration aliases #migration.6_to_7.enabled: true + diff --git a/heartbeat/scripts/mage/config.go b/heartbeat/scripts/mage/config.go index 5128c7d6672..c1cc2e3f7ac 100644 --- a/heartbeat/scripts/mage/config.go +++ b/heartbeat/scripts/mage/config.go @@ -22,24 +22,13 @@ import ( ) // ConfigFileParams returns the default ConfigFileParams for generating -// packetbeat*.yml files. +// heartbeat*.yml files. func ConfigFileParams() devtools.ConfigFileParams { - return devtools.ConfigFileParams{ - ShortParts: []string{ - devtools.OSSBeatDir("_meta/beat.yml"), - devtools.LibbeatDir("_meta/config.yml.tmpl"), - }, - ReferenceParts: []string{ - devtools.OSSBeatDir("_meta/beat.reference.yml"), - devtools.LibbeatDir("_meta/config.reference.yml.tmpl"), - }, - DockerParts: []string{ - devtools.OSSBeatDir("_meta/beat.docker.yml"), - devtools.LibbeatDir("_meta/config.docker.yml"), - }, - ExtraVars: map[string]interface{}{ - "UseObserverProcessor": true, - "ExcludeDashboards": true, - }, + p := devtools.DefaultConfigFileParams() + p.Templates = append(p.Templates, devtools.OSSBeatDir("_meta/config/*.tmpl")) + p.ExtraVars = map[string]interface{}{ + "UseObserverProcessor": true, + "ExcludeDashboards": true, } + return p } diff --git a/journalbeat/_meta/beat.docker.yml b/journalbeat/_meta/config/beat.docker.yml.tmpl similarity index 97% rename from journalbeat/_meta/beat.docker.yml rename to journalbeat/_meta/config/beat.docker.yml.tmpl index b7d83bd0111..20ae6ee130d 100644 --- a/journalbeat/_meta/beat.docker.yml +++ b/journalbeat/_meta/config/beat.docker.yml.tmpl @@ -1,4 +1,3 @@ journalbeat.inputs: - paths: [] seek: cursor - diff --git a/journalbeat/_meta/beat.reference.yml b/journalbeat/_meta/config/beat.reference.yml.tmpl similarity index 89% rename from journalbeat/_meta/beat.reference.yml rename to journalbeat/_meta/config/beat.reference.yml.tmpl index c50755936d7..3d4bc90b6f1 100644 --- a/journalbeat/_meta/beat.reference.yml +++ b/journalbeat/_meta/config/beat.reference.yml.tmpl @@ -10,7 +10,7 @@ # For more available modules and options, please see the journalbeat.reference.yml sample # configuration file. -#=========================== Journalbeat inputs ============================= +{{header "Journalbeat inputs"}} journalbeat.inputs: # Paths that should be crawled and fetched. Possible values files and directories. @@ -44,13 +44,13 @@ journalbeat.inputs: # env: staging -#========================= Journalbeat global options ============================ +{{header "Journalbeat global options"}} #journalbeat: # Name of the registry file. If a relative path is used, it is considered relative to the # data path. #registry_file: registry -#==================== Elasticsearch template setting ========================== +{{header "Elasticsearch template setting"}} setup.template.settings: index.number_of_shards: 1 #index.codec: best_compression diff --git a/journalbeat/_meta/beat.yml b/journalbeat/_meta/config/beat.yml.tmpl similarity index 87% rename from journalbeat/_meta/beat.yml rename to journalbeat/_meta/config/beat.yml.tmpl index 2bd2c91ce91..9410e82a925 100644 --- a/journalbeat/_meta/beat.yml +++ b/journalbeat/_meta/config/beat.yml.tmpl @@ -10,7 +10,7 @@ # For more available modules and options, please see the journalbeat.reference.yml sample # configuration file. -#=========================== Journalbeat inputs ============================= +{{header "Journalbeat inputs"}} journalbeat.inputs: # Paths that should be crawled and fetched. Possible values files and directories. @@ -39,13 +39,13 @@ journalbeat.inputs: # env: staging -#========================= Journalbeat global options ============================ +{{header "Journalbeat global options"}} #journalbeat: # Name of the registry file. If a relative path is used, it is considered relative to the # data path. #registry_file: registry -#==================== Elasticsearch template setting ========================== +{{header "Elasticsearch template setting"}} setup.template.settings: index.number_of_shards: 1 #index.codec: best_compression diff --git a/journalbeat/journalbeat.reference.yml b/journalbeat/journalbeat.reference.yml index 4bb34807f85..2bdf764d03b 100644 --- a/journalbeat/journalbeat.reference.yml +++ b/journalbeat/journalbeat.reference.yml @@ -10,7 +10,7 @@ # For more available modules and options, please see the journalbeat.reference.yml sample # configuration file. -#=========================== Journalbeat inputs ============================= +# ============================= Journalbeat inputs ============================= journalbeat.inputs: # Paths that should be crawled and fetched. Possible values files and directories. @@ -44,19 +44,19 @@ journalbeat.inputs: # env: staging -#========================= Journalbeat global options ============================ +# ========================= Journalbeat global options ========================= #journalbeat: # Name of the registry file. If a relative path is used, it is considered relative to the # data path. #registry_file: registry -#==================== Elasticsearch template setting ========================== +# ======================= Elasticsearch template setting ======================= setup.template.settings: index.number_of_shards: 1 #index.codec: best_compression #_source.enabled: false -#================================ General ====================================== +# ================================== General =================================== # The name of the shipper that publishes the network data. It can be used to group # all the transactions sent by a single shipper in the web interface. @@ -164,7 +164,7 @@ setup.template.settings: # default is the number of logical CPUs available in the system. #max_procs: -#================================ Processors =================================== +# ================================= Processors ================================= # Processors are used to reduce the number of fields in the exported event or to # enhance the event with external metadata. This section defines a list of @@ -327,7 +327,7 @@ setup.template.settings: # ignore_missing: false # fail_on_error: true -#============================= Elastic Cloud ================================== +# =============================== Elastic Cloud ================================ # These settings simplify using Journalbeat with the Elastic Cloud (https://cloud.elastic.co/). @@ -340,11 +340,11 @@ setup.template.settings: # `output.elasticsearch.password` settings. The format is `:`. #cloud.auth: -#================================ Outputs ====================================== +# ================================== Outputs =================================== # Configure what output to use when sending the data collected by the beat. -#-------------------------- Elasticsearch output ------------------------------- +# ---------------------------- Elasticsearch Output ---------------------------- output.elasticsearch: # Boolean flag to enable or disable the output module. #enabled: true @@ -485,7 +485,7 @@ output.elasticsearch: # Kerberos realm. #kerberos.realm: ELASTIC -#----------------------------- Logstash output --------------------------------- +# ------------------------------ Logstash Output ------------------------------- #output.logstash: # Boolean flag to enable or disable the output module. #enabled: true @@ -599,7 +599,7 @@ output.elasticsearch: # timing out. The default is 30s. #timeout: 30s -#------------------------------- Kafka output ---------------------------------- +# -------------------------------- Kafka Output -------------------------------- #output.kafka: # Boolean flag to enable or disable the output module. #enabled: true @@ -778,7 +778,7 @@ output.elasticsearch: # Kerberos realm. #kerberos.realm: ELASTIC -#------------------------------- Redis output ---------------------------------- +# -------------------------------- Redis Output -------------------------------- #output.redis: # Boolean flag to enable or disable the output module. #enabled: true @@ -896,7 +896,7 @@ output.elasticsearch: # never, once, and freely. Default is never. #ssl.renegotiation: never -#------------------------------- File output ----------------------------------- +# -------------------------------- File Output --------------------------------- #output.file: # Boolean flag to enable or disable the output module. #enabled: true @@ -930,7 +930,7 @@ output.elasticsearch: # Permissions to use for file creation. The default is 0600. #permissions: 0600 -#----------------------------- Console output --------------------------------- +# ------------------------------- Console Output ------------------------------- #output.console: # Boolean flag to enable or disable the output module. #enabled: true @@ -943,7 +943,7 @@ output.elasticsearch: # Configure escaping HTML symbols in strings. #escape_html: false -#================================= Paths ====================================== +# =================================== Paths ==================================== # The home path for the Journalbeat installation. This is the default base path # for all other path settings and for miscellaneous files that come with the @@ -969,11 +969,13 @@ output.elasticsearch: # the default for the logs path is a logs subdirectory inside the home path. #path.logs: ${path.home}/logs -#================================ Keystore ========================================== +# ================================== Keystore ================================== + # Location of the Keystore containing the keys and their sensitive values. #keystore.path: "${path.config}/beats.keystore" -#============================== Dashboards ===================================== +# ================================= Dashboards ================================= + # These settings control loading the sample dashboards to the Kibana index. Loading # the dashboards are disabled by default and can be enabled either by setting the # options here, or by using the `-setup` CLI flag or the `setup` command. @@ -1017,8 +1019,7 @@ output.elasticsearch: # Maximum number of retries before exiting with an error, 0 for unlimited retrying. #setup.dashboards.retry.maximum: 0 - -#============================== Template ===================================== +# ================================== Template ================================== # A template is used to set the mapping in Elasticsearch # By default template loading is enabled and the template is loaded. @@ -1072,7 +1073,7 @@ setup.template.settings: #_source: #enabled: false -#============================== Setup ILM ===================================== +# ====================== Index Lifecycle Management (ILM) ====================== # Configure index lifecycle management (ILM). These settings create a write # alias and add additional settings to the index template. When ILM is enabled, @@ -1086,13 +1087,13 @@ setup.template.settings: # Set the prefix used in the index lifecycle write alias name. The default alias # name is 'journalbeat-%{[agent.version]}'. -#setup.ilm.rollover_alias: "journalbeat" +#setup.ilm.rollover_alias: 'journalbeat' # Set the rollover index pattern. The default is "%{now/d}-000001". #setup.ilm.pattern: "{now/d}-000001" # Set the lifecycle policy name. The default policy name is -# 'journalbeat'. +# 'beatname'. #setup.ilm.policy_name: "mypolicy" # The path to a JSON file that contains a lifecycle policy configuration. Used @@ -1107,7 +1108,7 @@ setup.template.settings: # Overwrite the lifecycle policy at startup. The default is false. #setup.ilm.overwrite: false -#============================== Kibana ===================================== +# =================================== Kibana =================================== # Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API. # This requires a Kibana endpoint configuration. @@ -1162,9 +1163,8 @@ setup.kibana: # Configure curve types for ECDHE-based cipher suites #ssl.curve_types: [] +# ================================== Logging =================================== - -#================================ Logging ====================================== # There are four options for the log output: file, stderr, syslog, eventlog # The file output is the default. @@ -1231,8 +1231,7 @@ logging.files: # Set to true to log messages in JSON format. #logging.json: false - -#============================== X-Pack Monitoring =============================== +# ============================= X-Pack Monitoring ============================== # Journalbeat can export internal metrics to a central Elasticsearch monitoring # cluster. This requires xpack monitoring to be enabled in Elasticsearch. The # reporting is disabled by default. @@ -1374,7 +1373,8 @@ logging.files: # and `monitoring.elasticsearch.password` settings. The format is `:`. #monitoring.cloud.auth: -#================================ HTTP Endpoint ====================================== +# =============================== HTTP Endpoint ================================ + # Each beat can expose internal metrics through a HTTP endpoint. For security # reasons the endpoint is disabled by default. This feature is currently experimental. # Stats can be access through http://localhost:5066/stats . For pretty JSON output @@ -1398,12 +1398,13 @@ logging.files: # `http.user`. #http.named_pipe.security_descriptor: -#============================= Process Security ================================ +# ============================== Process Security ============================== # Enable or disable seccomp system call filtering on Linux. Default is enabled. #seccomp.enabled: true -#================================= Migration ================================== +# ================================= Migration ================================== # This allows to enable 6.7 migration aliases #migration.6_to_7.enabled: false + diff --git a/journalbeat/journalbeat.yml b/journalbeat/journalbeat.yml index 9767cd72a0f..2dfa8a07f5f 100644 --- a/journalbeat/journalbeat.yml +++ b/journalbeat/journalbeat.yml @@ -10,7 +10,7 @@ # For more available modules and options, please see the journalbeat.reference.yml sample # configuration file. -#=========================== Journalbeat inputs ============================= +# ============================= Journalbeat inputs ============================= journalbeat.inputs: # Paths that should be crawled and fetched. Possible values files and directories. @@ -39,19 +39,19 @@ journalbeat.inputs: # env: staging -#========================= Journalbeat global options ============================ +# ========================= Journalbeat global options ========================= #journalbeat: # Name of the registry file. If a relative path is used, it is considered relative to the # data path. #registry_file: registry -#==================== Elasticsearch template setting ========================== +# ======================= Elasticsearch template setting ======================= setup.template.settings: index.number_of_shards: 1 #index.codec: best_compression #_source.enabled: false -#================================ General ===================================== +# ================================== General =================================== # The name of the shipper that publishes the network data. It can be used to group # all the transactions sent by a single shipper in the web interface. @@ -66,8 +66,7 @@ setup.template.settings: #fields: # env: staging - -#============================== Dashboards ===================================== +# ================================= Dashboards ================================= # These settings control loading the sample dashboards to the Kibana index. Loading # the dashboards is disabled by default and can be enabled either by setting the # options here or by using the `setup` command. @@ -79,7 +78,7 @@ setup.template.settings: # website. #setup.dashboards.url: -#============================== Kibana ===================================== +# =================================== Kibana =================================== # Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API. # This requires a Kibana endpoint configuration. @@ -96,7 +95,7 @@ setup.kibana: # the Default Space will be used. #space.id: -#============================= Elastic Cloud ================================== +# =============================== Elastic Cloud ================================ # These settings simplify using Journalbeat with the Elastic Cloud (https://cloud.elastic.co/). @@ -109,11 +108,11 @@ setup.kibana: # `output.elasticsearch.password` settings. The format is `:`. #cloud.auth: -#================================ Outputs ===================================== +# ================================== Outputs =================================== # Configure what output to use when sending the data collected by the beat. -#-------------------------- Elasticsearch output ------------------------------ +# ---------------------------- Elasticsearch Output ---------------------------- output.elasticsearch: # Array of hosts to connect to. hosts: ["localhost:9200"] @@ -126,7 +125,7 @@ output.elasticsearch: #username: "elastic" #password: "changeme" -#----------------------------- Logstash output -------------------------------- +# ------------------------------ Logstash Output ------------------------------- #output.logstash: # The Logstash hosts #hosts: ["localhost:5044"] @@ -141,7 +140,7 @@ output.elasticsearch: # Client Certificate Key #ssl.key: "/etc/pki/client/cert.key" -#================================ Processors ===================================== +# ================================= Processors ================================= # Configure processors to enhance or manipulate events generated by the beat. @@ -150,7 +149,8 @@ processors: - add_cloud_metadata: ~ - add_docker_metadata: ~ -#================================ Logging ===================================== + +# ================================== Logging =================================== # Sets log level. The default log level is info. # Available log levels are: error, warning, info, debug @@ -161,8 +161,8 @@ processors: # "publish", "service". #logging.selectors: ["*"] -#============================== X-Pack Monitoring =============================== -# journalbeat can export internal metrics to a central Elasticsearch monitoring +# ============================= X-Pack Monitoring ============================== +# Journalbeat can export internal metrics to a central Elasticsearch monitoring # cluster. This requires xpack monitoring to be enabled in Elasticsearch. The # reporting is disabled by default. @@ -183,7 +183,8 @@ processors: # uncomment the following line. #monitoring.elasticsearch: -#================================= Migration ================================== +# ================================= Migration ================================== # This allows to enable 6.7 migration aliases #migration.6_to_7.enabled: true + diff --git a/journalbeat/magefile.go b/journalbeat/magefile.go index 58fdf91dfbe..f4ef3c694aa 100644 --- a/journalbeat/magefile.go +++ b/journalbeat/magefile.go @@ -231,5 +231,7 @@ func selectImage(platform string) (string, error) { // Config generates both the short/reference/docker configs. func Config() error { - return devtools.Config(devtools.AllConfigTypes, devtools.ConfigFileParams{}, ".") + p := devtools.DefaultConfigFileParams() + p.Templates = append(p.Templates, devtools.OSSBeatDir("_meta/config/*.tmpl")) + return devtools.Config(devtools.AllConfigTypes, p, ".") } diff --git a/libbeat/_meta/config.reference.yml.tmpl b/libbeat/_meta/config.reference.yml.tmpl deleted file mode 100644 index be7913679bf..00000000000 --- a/libbeat/_meta/config.reference.yml.tmpl +++ /dev/null @@ -1,1352 +0,0 @@ - -#================================ General ====================================== - -# The name of the shipper that publishes the network data. It can be used to group -# all the transactions sent by a single shipper in the web interface. -# If this options is not defined, the hostname is used. -#name: - -# The tags of the shipper are included in their own field with each -# transaction published. Tags make it easy to group servers by different -# logical properties. -#tags: ["service-X", "web-tier"] - -# Optional fields that you can specify to add additional information to the -# output. Fields can be scalar values, arrays, dictionaries, or any nested -# combination of these. -#fields: -# env: staging - -# If this option is set to true, the custom fields are stored as top-level -# fields in the output document instead of being grouped under a fields -# sub-dictionary. Default is false. -#fields_under_root: false - -# Internal queue configuration for buffering events to be published. -#queue: - # Queue type by name (default 'mem') - # The memory queue will present all available events (up to the outputs - # bulk_max_size) to the output, the moment the output is ready to server - # another batch of events. - #mem: - # Max number of events the queue can buffer. - #events: 4096 - - # Hints the minimum number of events stored in the queue, - # before providing a batch of events to the outputs. - # The default value is set to 2048. - # A value of 0 ensures events are immediately available - # to be sent to the outputs. - #flush.min_events: 2048 - - # Maximum duration after which events are available to the outputs, - # if the number of events stored in the queue is < `flush.min_events`. - #flush.timeout: 1s - - # The spool queue will store events in a local spool file, before - # forwarding the events to the outputs. - # - # Beta: spooling to disk is currently a beta feature. Use with care. - # - # The spool file is a circular buffer, which blocks once the file/buffer is full. - # Events are put into a write buffer and flushed once the write buffer - # is full or the flush_timeout is triggered. - # Once ACKed by the output, events are removed immediately from the queue, - # making space for new events to be persisted. - #spool: - # The file namespace configures the file path and the file creation settings. - # Once the file exists, the `size`, `page_size` and `prealloc` settings - # will have no more effect. - #file: - # Location of spool file. The default value is ${path.data}/spool.dat. - #path: "${path.data}/spool.dat" - - # Configure file permissions if file is created. The default value is 0600. - #permissions: 0600 - - # File size hint. The spool blocks, once this limit is reached. The default value is 100 MiB. - #size: 100MiB - - # The files page size. A file is split into multiple pages of the same size. The default value is 4KiB. - #page_size: 4KiB - - # If prealloc is set, the required space for the file is reserved using - # truncate. The default value is true. - #prealloc: true - - # Spool writer settings - # Events are serialized into a write buffer. The write buffer is flushed if: - # - The buffer limit has been reached. - # - The configured limit of buffered events is reached. - # - The flush timeout is triggered. - #write: - # Sets the write buffer size. - #buffer_size: 1MiB - - # Maximum duration after which events are flushed if the write buffer - # is not full yet. The default value is 1s. - #flush.timeout: 1s - - # Number of maximum buffered events. The write buffer is flushed once the - # limit is reached. - #flush.events: 16384 - - # Configure the on-disk event encoding. The encoding can be changed - # between restarts. - # Valid encodings are: json, ubjson, and cbor. - #codec: cbor - #read: - # Reader flush timeout, waiting for more events to become available, so - # to fill a complete batch as required by the outputs. - # If flush_timeout is 0, all available events are forwarded to the - # outputs immediately. - # The default value is 0s. - #flush.timeout: 0s - -# Sets the maximum number of CPUs that can be executing simultaneously. The -# default is the number of logical CPUs available in the system. -#max_procs: - -#================================ Processors =================================== - -# Processors are used to reduce the number of fields in the exported event or to -# enhance the event with external metadata. This section defines a list of -# processors that are applied one by one and the first one receives the initial -# event: -# -# event -> filter1 -> event1 -> filter2 ->event2 ... -# -# The supported processors are drop_fields, drop_event, include_fields, -# decode_json_fields, and add_cloud_metadata. -# -# For example, you can use the following processors to keep the fields that -# contain CPU load percentages, but remove the fields that contain CPU ticks -# values: -# -#processors: -# - include_fields: -# fields: ["cpu"] -# - drop_fields: -# fields: ["cpu.user", "cpu.system"] -# -# The following example drops the events that have the HTTP response code 200: -# -#processors: -# - drop_event: -# when: -# equals: -# http.code: 200 -# -# The following example renames the field a to b: -# -#processors: -# - rename: -# fields: -# - from: "a" -# to: "b" -# -# The following example tokenizes the string into fields: -# -#processors: -# - dissect: -# tokenizer: "%{key1} - %{key2}" -# field: "message" -# target_prefix: "dissect" -# -# The following example enriches each event with metadata from the cloud -# provider about the host machine. It works on EC2, GCE, DigitalOcean, -# Tencent Cloud, and Alibaba Cloud. -# -#processors: -# - add_cloud_metadata: ~ -# -# The following example enriches each event with the machine's local time zone -# offset from UTC. -# -#processors: -# - add_locale: -# format: offset -# -# The following example enriches each event with docker metadata, it matches -# given fields to an existing container id and adds info from that container: -# -#processors: -# - add_docker_metadata: -# host: "unix:///var/run/docker.sock" -# match_fields: ["system.process.cgroup.id"] -# match_pids: ["process.pid", "process.ppid"] -# match_source: true -# match_source_index: 4 -# match_short_id: false -# cleanup_timeout: 60 -# labels.dedot: false -# # To connect to Docker over TLS you must specify a client and CA certificate. -# #ssl: -# # certificate_authority: "/etc/pki/root/ca.pem" -# # certificate: "/etc/pki/client/cert.pem" -# # key: "/etc/pki/client/cert.key" -# -# The following example enriches each event with docker metadata, it matches -# container id from log path available in `source` field (by default it expects -# it to be /var/lib/docker/containers/*/*.log). -# -#processors: -# - add_docker_metadata: ~ -# -# The following example enriches each event with host metadata. -# -#processors: -# - add_host_metadata: ~ -# -# The following example enriches each event with process metadata using -# process IDs included in the event. -# -#processors: -# - add_process_metadata: -# match_pids: ["system.process.ppid"] -# target: system.process.parent -# -# The following example decodes fields containing JSON strings -# and replaces the strings with valid JSON objects. -# -#processors: -# - decode_json_fields: -# fields: ["field1", "field2", ...] -# process_array: false -# max_depth: 1 -# target: "" -# overwrite_keys: false -# -#processors: -# - decompress_gzip_field: -# from: "field1" -# to: "field2" -# ignore_missing: false -# fail_on_error: true -# -# The following example copies the value of message to message_copied -# -#processors: -# - copy_fields: -# fields: -# - from: message -# to: message_copied -# fail_on_error: true -# ignore_missing: false -# -# The following example truncates the value of message to 1024 bytes -# -#processors: -# - truncate_fields: -# fields: -# - message -# max_bytes: 1024 -# fail_on_error: false -# ignore_missing: true -# -# The following example preserves the raw message under event.original -# -#processors: -# - copy_fields: -# fields: -# - from: message -# to: event.original -# fail_on_error: false -# ignore_missing: true -# - truncate_fields: -# fields: -# - event.original -# max_bytes: 1024 -# fail_on_error: false -# ignore_missing: true -# -# The following example URL-decodes the value of field1 to field2 -# -#processors: -# - urldecode: -# fields: -# - from: "field1" -# to: "field2" -# ignore_missing: false -# fail_on_error: true - -#============================= Elastic Cloud ================================== - -# These settings simplify using {{.BeatName | title}} with the Elastic Cloud (https://cloud.elastic.co/). - -# The cloud.id setting overwrites the `output.elasticsearch.hosts` and -# `setup.kibana.host` options. -# You can find the `cloud.id` in the Elastic Cloud web UI. -#cloud.id: - -# The cloud.auth setting overwrites the `output.elasticsearch.username` and -# `output.elasticsearch.password` settings. The format is `:`. -#cloud.auth: - -#================================ Outputs ====================================== - -# Configure what output to use when sending the data collected by the beat. - -#-------------------------- Elasticsearch output ------------------------------- -output.elasticsearch: - # Boolean flag to enable or disable the output module. - #enabled: true - - # Array of hosts to connect to. - # Scheme and port can be left out and will be set to the default (http and 9200) - # In case you specify and additional path, the scheme is required: http://localhost:9200/path - # IPv6 addresses should always be defined as: https://[2001:db8::1]:9200 - hosts: ["localhost:9200"] - - # Set gzip compression level. - #compression_level: 0 - - # Configure escaping HTML symbols in strings. - #escape_html: false - - # Protocol - either `http` (default) or `https`. - #protocol: "https" - - # Authentication credentials - either API key or username/password. - #api_key: "id:api_key" - #username: "elastic" - #password: "changeme" - - # Dictionary of HTTP parameters to pass within the URL with index operations. - #parameters: - #param1: value1 - #param2: value2 - - # Number of workers per Elasticsearch host. - #worker: 1 - - # Optional index name. The default is "beat-index-prefix" plus date - # and generates [beat-index-prefix-]YYYY.MM.DD keys. - # In case you modify this pattern you must update setup.template.name and setup.template.pattern accordingly. - #index: "beat-index-prefix-%{[agent.version]}-%{+yyyy.MM.dd}" - - # Optional ingest node pipeline. By default no pipeline will be used. - #pipeline: "" - - # Optional HTTP path - #path: "/elasticsearch" - - # Custom HTTP headers to add to each request - #headers: - # X-My-Header: Contents of the header - - # Proxy server URL - #proxy_url: http://proxy:3128 - - # Whether to disable proxy settings for outgoing connections. If true, this - # takes precedence over both the proxy_url field and any environment settings - # (HTTP_PROXY, HTTPS_PROXY). The default is false. - #proxy_disable: false - - # The number of times a particular Elasticsearch index operation is attempted. If - # the indexing operation doesn't succeed after this many retries, the events are - # dropped. The default is 3. - #max_retries: 3 - - # The maximum number of events to bulk in a single Elasticsearch bulk API index request. - # The default is 50. - #bulk_max_size: 50 - - # The number of seconds to wait before trying to reconnect to Elasticsearch - # after a network error. After waiting backoff.init seconds, the Beat - # tries to reconnect. If the attempt fails, the backoff timer is increased - # exponentially up to backoff.max. After a successful connection, the backoff - # timer is reset. The default is 1s. - #backoff.init: 1s - - # The maximum number of seconds to wait before attempting to connect to - # Elasticsearch after a network error. The default is 60s. - #backoff.max: 60s - - # Configure HTTP request timeout before failing a request to Elasticsearch. - #timeout: 90 - - # Use SSL settings for HTTPS. - #ssl.enabled: true - - # Configure SSL verification mode. If `none` is configured, all server hosts - # and certificates will be accepted. In this mode, SSL-based connections are - # susceptible to man-in-the-middle attacks. Use only for testing. Default is - # `full`. - #ssl.verification_mode: full - - # List of supported/valid TLS versions. By default all TLS versions from 1.1 - # up to 1.3 are enabled. - #ssl.supported_protocols: [TLSv1.1, TLSv1.2, TLSv1.3] - - # List of root certificates for HTTPS server verifications - #ssl.certificate_authorities: ["/etc/pki/root/ca.pem"] - - # Certificate for SSL client authentication - #ssl.certificate: "/etc/pki/client/cert.pem" - - # Client certificate key - #ssl.key: "/etc/pki/client/cert.key" - - # Optional passphrase for decrypting the certificate key. - #ssl.key_passphrase: '' - - # Configure cipher suites to be used for SSL connections - #ssl.cipher_suites: [] - - # Configure curve types for ECDHE-based cipher suites - #ssl.curve_types: [] - - # Configure what types of renegotiation are supported. Valid options are - # never, once, and freely. Default is never. - #ssl.renegotiation: never - - # Configure a pin that can be used to do extra validation of the verified certificate chain, - # this allow you to ensure that a specific certificate is used to validate the chain of trust. - # - # The pin is a base64 encoded string of the SHA-256 fingerprint. - #ssl.ca_sha256: "" - - # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. - #kerberos.enabled: true - - # Authentication type to use with Kerberos. Available options: keytab, password. - #kerberos.auth_type: password - - # Path to the keytab file. It is used when auth_type is set to keytab. - #kerberos.keytab: /etc/elastic.keytab - - # Path to the Kerberos configuration. - #kerberos.config_path: /etc/krb5.conf - - # Name of the Kerberos user. - #kerberos.username: elastic - - # Password of the Kerberos user. It is used when auth_type is set to password. - #kerberos.password: changeme - - # Kerberos realm. - #kerberos.realm: ELASTIC -{{if not .ExcludeLogstash}} -#----------------------------- Logstash output --------------------------------- -#output.logstash: - # Boolean flag to enable or disable the output module. - #enabled: true - - # The Logstash hosts - #hosts: ["localhost:5044"] - - # Number of workers per Logstash host. - #worker: 1 - - # Set gzip compression level. - #compression_level: 3 - - # Configure escaping HTML symbols in strings. - #escape_html: false - - # Optional maximum time to live for a connection to Logstash, after which the - # connection will be re-established. A value of `0s` (the default) will - # disable this feature. - # - # Not yet supported for async connections (i.e. with the "pipelining" option set) - #ttl: 30s - - # Optionally load-balance events between Logstash hosts. Default is false. - #loadbalance: false - - # Number of batches to be sent asynchronously to Logstash while processing - # new batches. - #pipelining: 2 - - # If enabled only a subset of events in a batch of events is transferred per - # transaction. The number of events to be sent increases up to `bulk_max_size` - # if no error is encountered. - #slow_start: false - - # The number of seconds to wait before trying to reconnect to Logstash - # after a network error. After waiting backoff.init seconds, the Beat - # tries to reconnect. If the attempt fails, the backoff timer is increased - # exponentially up to backoff.max. After a successful connection, the backoff - # timer is reset. The default is 1s. - #backoff.init: 1s - - # The maximum number of seconds to wait before attempting to connect to - # Logstash after a network error. The default is 60s. - #backoff.max: 60s - - # Optional index name. The default index name is set to beat-index-prefix - # in all lowercase. - #index: 'beat-index-prefix' - - # SOCKS5 proxy server URL - #proxy_url: socks5://user:password@socks5-server:2233 - - # Resolve names locally when using a proxy server. Defaults to false. - #proxy_use_local_resolver: false - - # Enable SSL support. SSL is automatically enabled if any SSL setting is set. - #ssl.enabled: true - - # Configure SSL verification mode. If `none` is configured, all server hosts - # and certificates will be accepted. In this mode, SSL based connections are - # susceptible to man-in-the-middle attacks. Use only for testing. Default is - # `full`. - #ssl.verification_mode: full - - # List of supported/valid TLS versions. By default all TLS versions from 1.1 - # up to 1.3 are enabled. - #ssl.supported_protocols: [TLSv1.1, TLSv1.2, TLSv1.3] - - # Optional SSL configuration options. SSL is off by default. - # List of root certificates for HTTPS server verifications - #ssl.certificate_authorities: ["/etc/pki/root/ca.pem"] - - # Certificate for SSL client authentication - #ssl.certificate: "/etc/pki/client/cert.pem" - - # Client certificate key - #ssl.key: "/etc/pki/client/cert.key" - - # Optional passphrase for decrypting the Certificate Key. - #ssl.key_passphrase: '' - - # Configure cipher suites to be used for SSL connections - #ssl.cipher_suites: [] - - # Configure curve types for ECDHE-based cipher suites - #ssl.curve_types: [] - - # Configure what types of renegotiation are supported. Valid options are - # never, once, and freely. Default is never. - #ssl.renegotiation: never - - # Configure a pin that can be used to do extra validation of the verified certificate chain, - # this allow you to ensure that a specific certificate is used to validate the chain of trust. - # - # The pin is a base64 encoded string of the SHA-256 fingerprint. - #ssl.ca_sha256: "" - - # The number of times to retry publishing an event after a publishing failure. - # After the specified number of retries, the events are typically dropped. - # Some Beats, such as Filebeat and Winlogbeat, ignore the max_retries setting - # and retry until all events are published. Set max_retries to a value less - # than 0 to retry until all events are published. The default is 3. - #max_retries: 3 - - # The maximum number of events to bulk in a single Logstash request. The - # default is 2048. - #bulk_max_size: 2048 - - # The number of seconds to wait for responses from the Logstash server before - # timing out. The default is 30s. - #timeout: 30s -{{end}}{{if not .ExcludeKafka}} -#------------------------------- Kafka output ---------------------------------- -#output.kafka: - # Boolean flag to enable or disable the output module. - #enabled: true - - # The list of Kafka broker addresses from which to fetch the cluster metadata. - # The cluster metadata contain the actual Kafka brokers events are published - # to. - #hosts: ["localhost:9092"] - - # The Kafka topic used for produced events. The setting can be a format string - # using any event field. To set the topic from document type use `%{[type]}`. - #topic: beats - - # The Kafka event key setting. Use format string to create a unique event key. - # By default no event key will be generated. - #key: '' - - # The Kafka event partitioning strategy. Default hashing strategy is `hash` - # using the `output.kafka.key` setting or randomly distributes events if - # `output.kafka.key` is not configured. - #partition.hash: - # If enabled, events will only be published to partitions with reachable - # leaders. Default is false. - #reachable_only: false - - # Configure alternative event field names used to compute the hash value. - # If empty `output.kafka.key` setting will be used. - # Default value is empty list. - #hash: [] - - # Authentication details. Password is required if username is set. - #username: '' - #password: '' - - # Kafka version {{.BeatName | title}} is assumed to run against. Defaults to the "1.0.0". - #version: '1.0.0' - - # Configure JSON encoding - #codec.json: - # Pretty-print JSON event - #pretty: false - - # Configure escaping HTML symbols in strings. - #escape_html: false - - # Metadata update configuration. Metadata contains leader information - # used to decide which broker to use when publishing. - #metadata: - # Max metadata request retry attempts when cluster is in middle of leader - # election. Defaults to 3 retries. - #retry.max: 3 - - # Wait time between retries during leader elections. Default is 250ms. - #retry.backoff: 250ms - - # Refresh metadata interval. Defaults to every 10 minutes. - #refresh_frequency: 10m - - # Strategy for fetching the topics metadata from the broker. Default is false. - #full: false - - # The number of concurrent load-balanced Kafka output workers. - #worker: 1 - - # The number of times to retry publishing an event after a publishing failure. - # After the specified number of retries, events are typically dropped. - # Some Beats, such as Filebeat, ignore the max_retries setting and retry until - # all events are published. Set max_retries to a value less than 0 to retry - # until all events are published. The default is 3. - #max_retries: 3 - - # The maximum number of events to bulk in a single Kafka request. The default - # is 2048. - #bulk_max_size: 2048 - - # Duration to wait before sending bulk Kafka request. 0 is no delay. The default - # is 0. - #bulk_flush_frequency: 0s - - # The number of seconds to wait for responses from the Kafka brokers before - # timing out. The default is 30s. - #timeout: 30s - - # The maximum duration a broker will wait for number of required ACKs. The - # default is 10s. - #broker_timeout: 10s - - # The number of messages buffered for each Kafka broker. The default is 256. - #channel_buffer_size: 256 - - # The keep-alive period for an active network connection. If 0s, keep-alives - # are disabled. The default is 0 seconds. - #keep_alive: 0 - - # Sets the output compression codec. Must be one of none, snappy and gzip. The - # default is gzip. - #compression: gzip - - # Set the compression level. Currently only gzip provides a compression level - # between 0 and 9. The default value is chosen by the compression algorithm. - #compression_level: 4 - - # The maximum permitted size of JSON-encoded messages. Bigger messages will be - # dropped. The default value is 1000000 (bytes). This value should be equal to - # or less than the broker's message.max.bytes. - #max_message_bytes: 1000000 - - # The ACK reliability level required from broker. 0=no response, 1=wait for - # local commit, -1=wait for all replicas to commit. The default is 1. Note: - # If set to 0, no ACKs are returned by Kafka. Messages might be lost silently - # on error. - #required_acks: 1 - - # The configurable ClientID used for logging, debugging, and auditing - # purposes. The default is "beats". - #client_id: beats - - # Enable SSL support. SSL is automatically enabled if any SSL setting is set. - #ssl.enabled: true - - # Optional SSL configuration options. SSL is off by default. - # List of root certificates for HTTPS server verifications - #ssl.certificate_authorities: ["/etc/pki/root/ca.pem"] - - # Configure SSL verification mode. If `none` is configured, all server hosts - # and certificates will be accepted. In this mode, SSL based connections are - # susceptible to man-in-the-middle attacks. Use only for testing. Default is - # `full`. - #ssl.verification_mode: full - - # List of supported/valid TLS versions. By default all TLS versions from 1.1 - # up to 1.3 are enabled. - #ssl.supported_protocols: [TLSv1.1, TLSv1.2, TLSv1.3] - - # Certificate for SSL client authentication - #ssl.certificate: "/etc/pki/client/cert.pem" - - # Client Certificate Key - #ssl.key: "/etc/pki/client/cert.key" - - # Optional passphrase for decrypting the Certificate Key. - #ssl.key_passphrase: '' - - # Configure cipher suites to be used for SSL connections - #ssl.cipher_suites: [] - - # Configure curve types for ECDHE-based cipher suites - #ssl.curve_types: [] - - # Configure what types of renegotiation are supported. Valid options are - # never, once, and freely. Default is never. - #ssl.renegotiation: never - - # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. - #kerberos.enabled: true - - # Authentication type to use with Kerberos. Available options: keytab, password. - #kerberos.auth_type: password - - # Path to the keytab file. It is used when auth_type is set to keytab. - #kerberos.keytab: /etc/security/keytabs/kafka.keytab - - # Path to the Kerberos configuration. - #kerberos.config_path: /etc/krb5.conf - - # The service name. Service principal name is contructed from - # service_name/hostname@realm. - #kerberos.service_name: kafka - - # Name of the Kerberos user. - #kerberos.username: elastic - - # Password of the Kerberos user. It is used when auth_type is set to password. - #kerberos.password: changeme - - # Kerberos realm. - #kerberos.realm: ELASTIC -{{end}}{{if not .ExcludeRedis}} -#------------------------------- Redis output ---------------------------------- -#output.redis: - # Boolean flag to enable or disable the output module. - #enabled: true - - # Configure JSON encoding - #codec.json: - # Pretty print json event - #pretty: false - - # Configure escaping HTML symbols in strings. - #escape_html: false - - # The list of Redis servers to connect to. If load-balancing is enabled, the - # events are distributed to the servers in the list. If one server becomes - # unreachable, the events are distributed to the reachable servers only. - # The hosts setting supports redis and rediss urls with custom password like - # redis://:password@localhost:6379. - #hosts: ["localhost:6379"] - - # The name of the Redis list or channel the events are published to. The - # default is {{.BeatName}}. - #key: {{.BeatName}} - - # The password to authenticate to Redis with. The default is no authentication. - #password: - - # The Redis database number where the events are published. The default is 0. - #db: 0 - - # The Redis data type to use for publishing events. If the data type is list, - # the Redis RPUSH command is used. If the data type is channel, the Redis - # PUBLISH command is used. The default value is list. - #datatype: list - - # The number of workers to use for each host configured to publish events to - # Redis. Use this setting along with the loadbalance option. For example, if - # you have 2 hosts and 3 workers, in total 6 workers are started (3 for each - # host). - #worker: 1 - - # If set to true and multiple hosts or workers are configured, the output - # plugin load balances published events onto all Redis hosts. If set to false, - # the output plugin sends all events to only one host (determined at random) - # and will switch to another host if the currently selected one becomes - # unreachable. The default value is true. - #loadbalance: true - - # The Redis connection timeout in seconds. The default is 5 seconds. - #timeout: 5s - - # The number of times to retry publishing an event after a publishing failure. - # After the specified number of retries, the events are typically dropped. - # Some Beats, such as Filebeat, ignore the max_retries setting and retry until - # all events are published. Set max_retries to a value less than 0 to retry - # until all events are published. The default is 3. - #max_retries: 3 - - # The number of seconds to wait before trying to reconnect to Redis - # after a network error. After waiting backoff.init seconds, the Beat - # tries to reconnect. If the attempt fails, the backoff timer is increased - # exponentially up to backoff.max. After a successful connection, the backoff - # timer is reset. The default is 1s. - #backoff.init: 1s - - # The maximum number of seconds to wait before attempting to connect to - # Redis after a network error. The default is 60s. - #backoff.max: 60s - - # The maximum number of events to bulk in a single Redis request or pipeline. - # The default is 2048. - #bulk_max_size: 2048 - - # The URL of the SOCKS5 proxy to use when connecting to the Redis servers. The - # value must be a URL with a scheme of socks5://. - #proxy_url: - - # This option determines whether Redis hostnames are resolved locally when - # using a proxy. The default value is false, which means that name resolution - # occurs on the proxy server. - #proxy_use_local_resolver: false - - # Enable SSL support. SSL is automatically enabled, if any SSL setting is set. - #ssl.enabled: true - - # Configure SSL verification mode. If `none` is configured, all server hosts - # and certificates will be accepted. In this mode, SSL based connections are - # susceptible to man-in-the-middle attacks. Use only for testing. Default is - # `full`. - #ssl.verification_mode: full - - # List of supported/valid TLS versions. By default all TLS versions from 1.1 - # up to 1.3 are enabled. - #ssl.supported_protocols: [TLSv1.1, TLSv1.2, TLSv1.3] - - # Optional SSL configuration options. SSL is off by default. - # List of root certificates for HTTPS server verifications - #ssl.certificate_authorities: ["/etc/pki/root/ca.pem"] - - # Certificate for SSL client authentication - #ssl.certificate: "/etc/pki/client/cert.pem" - - # Client Certificate Key - #ssl.key: "/etc/pki/client/cert.key" - - # Optional passphrase for decrypting the Certificate Key. - #ssl.key_passphrase: '' - - # Configure cipher suites to be used for SSL connections - #ssl.cipher_suites: [] - - # Configure curve types for ECDHE based cipher suites - #ssl.curve_types: [] - - # Configure what types of renegotiation are supported. Valid options are - # never, once, and freely. Default is never. - #ssl.renegotiation: never -{{end}}{{if not .ExcludeFileOutput}} -#------------------------------- File output ----------------------------------- -#output.file: - # Boolean flag to enable or disable the output module. - #enabled: true - - # Configure JSON encoding - #codec.json: - # Pretty-print JSON event - #pretty: false - - # Configure escaping HTML symbols in strings. - #escape_html: false - - # Path to the directory where to save the generated files. The option is - # mandatory. - #path: "/tmp/{{.BeatName}}" - - # Name of the generated files. The default is `{{.BeatName}}` and it generates - # files: `{{.BeatName}}`, `{{.BeatName}}.1`, `{{.BeatName}}.2`, etc. - #filename: {{.BeatName}} - - # Maximum size in kilobytes of each file. When this size is reached, and on - # every {{.BeatName | title}} restart, the files are rotated. The default value is 10240 - # kB. - #rotate_every_kb: 10000 - - # Maximum number of files under path. When this number of files is reached, - # the oldest file is deleted and the rest are shifted from last to first. The - # default is 7 files. - #number_of_files: 7 - - # Permissions to use for file creation. The default is 0600. - #permissions: 0600 -{{end}}{{if not .ExcludeConsole}} -#----------------------------- Console output --------------------------------- -#output.console: - # Boolean flag to enable or disable the output module. - #enabled: true - - # Configure JSON encoding - #codec.json: - # Pretty-print JSON event - #pretty: false - - # Configure escaping HTML symbols in strings. - #escape_html: false -{{end}} -#================================= Paths ====================================== - -# The home path for the {{.BeatName | title}} installation. This is the default base path -# for all other path settings and for miscellaneous files that come with the -# distribution (for example, the sample dashboards). -# If not set by a CLI flag or in the configuration file, the default for the -# home path is the location of the binary. -#path.home: - -# The configuration path for the {{.BeatName | title}} installation. This is the default -# base path for configuration files, including the main YAML configuration file -# and the Elasticsearch template file. If not set by a CLI flag or in the -# configuration file, the default for the configuration path is the home path. -#path.config: ${path.home} - -# The data path for the {{.BeatName | title}} installation. This is the default base path -# for all the files in which {{.BeatName | title}} needs to store its data. If not set by a -# CLI flag or in the configuration file, the default for the data path is a data -# subdirectory inside the home path. -#path.data: ${path.home}/data - -# The logs path for a {{.BeatName | title}} installation. This is the default location for -# the Beat's log files. If not set by a CLI flag or in the configuration file, -# the default for the logs path is a logs subdirectory inside the home path. -#path.logs: ${path.home}/logs - -#================================ Keystore ========================================== -# Location of the Keystore containing the keys and their sensitive values. -#keystore.path: "${path.config}/beats.keystore" - -#============================== Dashboards ===================================== -# These settings control loading the sample dashboards to the Kibana index. Loading -# the dashboards are disabled by default and can be enabled either by setting the -# options here, or by using the `-setup` CLI flag or the `setup` command. -#setup.dashboards.enabled: false - -# The directory from where to read the dashboards. The default is the `kibana` -# folder in the home path. -#setup.dashboards.directory: ${path.home}/kibana - -# The URL from where to download the dashboards archive. It is used instead of -# the directory if it has a value. -#setup.dashboards.url: - -# The file archive (zip file) from where to read the dashboards. It is used instead -# of the directory when it has a value. -#setup.dashboards.file: - -# In case the archive contains the dashboards from multiple Beats, this lets you -# select which one to load. You can load all the dashboards in the archive by -# setting this to the empty string. -#setup.dashboards.beat: {{.BeatName}} - -# The name of the Kibana index to use for setting the configuration. Default is ".kibana" -#setup.dashboards.kibana_index: .kibana - -# The Elasticsearch index name. This overwrites the index name defined in the -# dashboards and index pattern. Example: testbeat-* -#setup.dashboards.index: - -# Always use the Kibana API for loading the dashboards instead of autodetecting -# how to install the dashboards by first querying Elasticsearch. -#setup.dashboards.always_kibana: false - -# If true and Kibana is not reachable at the time when dashboards are loaded, -# it will retry to reconnect to Kibana instead of exiting with an error. -#setup.dashboards.retry.enabled: false - -# Duration interval between Kibana connection retries. -#setup.dashboards.retry.interval: 1s - -# Maximum number of retries before exiting with an error, 0 for unlimited retrying. -#setup.dashboards.retry.maximum: 0 - - -#============================== Template ===================================== - -# A template is used to set the mapping in Elasticsearch -# By default template loading is enabled and the template is loaded. -# These settings can be adjusted to load your own template or overwrite existing ones. - -# Set to false to disable template loading. -#setup.template.enabled: true - -# Template name. By default the template name is "beat-index-prefix-%{[agent.version]}" -# The template name and pattern has to be set in case the Elasticsearch index pattern is modified. -#setup.template.name: "beat-index-prefix-%{[agent.version]}" - -# Template pattern. By default the template pattern is "-%{[agent.version]}-*" to apply to the default index settings. -# The first part is the version of the beat and then -* is used to match all daily indices. -# The template name and pattern has to be set in case the Elasticsearch index pattern is modified. -#setup.template.pattern: "beat-index-prefix-%{[agent.version]}-*" - -# Path to fields.yml file to generate the template -#setup.template.fields: "${path.config}/fields.yml" - -# A list of fields to be added to the template and Kibana index pattern. Also -# specify setup.template.overwrite: true to overwrite the existing template. -#setup.template.append_fields: -#- name: field_name -# type: field_type - -# Enable JSON template loading. If this is enabled, the fields.yml is ignored. -#setup.template.json.enabled: false - -# Path to the JSON template file -#setup.template.json.path: "${path.config}/template.json" - -# Name under which the template is stored in Elasticsearch -#setup.template.json.name: "" - -# Overwrite existing template -#setup.template.overwrite: false - -# Elasticsearch template settings -setup.template.settings: - - # A dictionary of settings to place into the settings.index dictionary - # of the Elasticsearch template. For more details, please check - # https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html - #index: - #number_of_shards: 1 - #codec: best_compression - - # A dictionary of settings for the _source field. For more details, please check - # https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-source-field.html - #_source: - #enabled: false - -#============================== Setup ILM ===================================== - -# Configure index lifecycle management (ILM). These settings create a write -# alias and add additional settings to the index template. When ILM is enabled, -# output.elasticsearch.index is ignored, and the write alias is used to set the -# index name. - -# Enable ILM support. Valid values are true, false, and auto. When set to auto -# (the default), the Beat uses index lifecycle management when it connects to a -# cluster that supports ILM; otherwise, it creates daily indices. -#setup.ilm.enabled: auto - -# Set the prefix used in the index lifecycle write alias name. The default alias -# name is 'beatname-%{[agent.version]}'. -#setup.ilm.rollover_alias: "beat-index-prefix" - -# Set the rollover index pattern. The default is "%{now/d}-000001". -#setup.ilm.pattern: "{now/d}-000001" - -# Set the lifecycle policy name. The default policy name is -# 'beatname'. -#setup.ilm.policy_name: "mypolicy" - -# The path to a JSON file that contains a lifecycle policy configuration. Used -# to load your own lifecycle policy. -#setup.ilm.policy_file: - -# Disable the check for an existing lifecycle policy. The default is true. If -# you disable this check, set setup.ilm.overwrite: true so the lifecycle policy -# can be installed. -#setup.ilm.check_exists: true - -# Overwrite the lifecycle policy at startup. The default is false. -#setup.ilm.overwrite: false - -#============================== Kibana ===================================== - -# Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API. -# This requires a Kibana endpoint configuration. -setup.kibana: - - # Kibana Host - # Scheme and port can be left out and will be set to the default (http and 5601) - # In case you specify and additional path, the scheme is required: http://localhost:5601/path - # IPv6 addresses should always be defined as: https://[2001:db8::1]:5601 - #host: "localhost:5601" - - # Optional protocol and basic auth credentials. - #protocol: "https" - #username: "elastic" - #password: "changeme" - - # Optional HTTP path - #path: "" - - # Optional Kibana space ID. - #space.id: "" - - # Use SSL settings for HTTPS. Default is true. - #ssl.enabled: true - - # Configure SSL verification mode. If `none` is configured, all server hosts - # and certificates will be accepted. In this mode, SSL based connections are - # susceptible to man-in-the-middle attacks. Use only for testing. Default is - # `full`. - #ssl.verification_mode: full - - # List of supported/valid TLS versions. By default all TLS versions from 1.1 - # up to 1.3 are enabled. - #ssl.supported_protocols: [TLSv1.1, TLSv1.2, TLSv1.3] - - # SSL configuration. The default is off. - # List of root certificates for HTTPS server verifications - #ssl.certificate_authorities: ["/etc/pki/root/ca.pem"] - - # Certificate for SSL client authentication - #ssl.certificate: "/etc/pki/client/cert.pem" - - # Client certificate key - #ssl.key: "/etc/pki/client/cert.key" - - # Optional passphrase for decrypting the certificate key. - #ssl.key_passphrase: '' - - # Configure cipher suites to be used for SSL connections - #ssl.cipher_suites: [] - - # Configure curve types for ECDHE-based cipher suites - #ssl.curve_types: [] - - - -#================================ Logging ====================================== -# There are four options for the log output: file, stderr, syslog, eventlog -# The file output is the default. - -# Sets log level. The default log level is info. -# Available log levels are: error, warning, info, debug -#logging.level: info - -# Enable debug output for selected components. To enable all selectors use ["*"] -# Other available selectors are "beat", "publish", "service" -# Multiple selectors can be chained. -#logging.selectors: [ ] - -# Send all logging output to stderr. The default is false. -#logging.to_stderr: false - -# Send all logging output to syslog. The default is false. -#logging.to_syslog: false - -# Send all logging output to Windows Event Logs. The default is false. -#logging.to_eventlog: false - -# If enabled, {{.BeatName | title}} periodically logs its internal metrics that have changed -# in the last period. For each metric that changed, the delta from the value at -# the beginning of the period is logged. Also, the total values for -# all non-zero internal metrics are logged on shutdown. The default is true. -#logging.metrics.enabled: true - -# The period after which to log the internal metrics. The default is 30s. -#logging.metrics.period: 30s - -# Logging to rotating files. Set logging.to_files to false to disable logging to -# files. -logging.to_files: true -logging.files: - # Configure the path where the logs are written. The default is the logs directory - # under the home path (the binary location). - #path: /var/log/{{.BeatName}} - - # The name of the files where the logs are written to. - #name: {{.BeatName}} - - # Configure log file size limit. If limit is reached, log file will be - # automatically rotated - #rotateeverybytes: 10485760 # = 10MB - - # Number of rotated log files to keep. Oldest files will be deleted first. - #keepfiles: 7 - - # The permissions mask to apply when rotating log files. The default value is 0600. - # Must be a valid Unix-style file permissions mask expressed in octal notation. - #permissions: 0600 - - # Enable log file rotation on time intervals in addition to size-based rotation. - # Intervals must be at least 1s. Values of 1m, 1h, 24h, 7*24h, 30*24h, and 365*24h - # are boundary-aligned with minutes, hours, days, weeks, months, and years as - # reported by the local system clock. All other intervals are calculated from the - # Unix epoch. Defaults to disabled. - #interval: 0 - - # Rotate existing logs on startup rather than appending to the existing - # file. Defaults to true. - # rotateonstartup: true - -# Set to true to log messages in JSON format. -#logging.json: false - - -#============================== X-Pack Monitoring =============================== -# {{.BeatName | title}} can export internal metrics to a central Elasticsearch monitoring -# cluster. This requires xpack monitoring to be enabled in Elasticsearch. The -# reporting is disabled by default. - -# Set to true to enable the monitoring reporter. -#monitoring.enabled: false - -# Sets the UUID of the Elasticsearch cluster under which monitoring data for this -# {{.BeatName | title}} instance will appear in the Stack Monitoring UI. If output.elasticsearch -# is enabled, the UUID is derived from the Elasticsearch cluster referenced by output.elasticsearch. -#monitoring.cluster_uuid: - -# Uncomment to send the metrics to Elasticsearch. Most settings from the -# Elasticsearch output are accepted here as well. -# Note that the settings should point to your Elasticsearch *monitoring* cluster. -# Any setting that is not set is automatically inherited from the Elasticsearch -# output configuration, so if you have the Elasticsearch output configured such -# that it is pointing to your Elasticsearch monitoring cluster, you can simply -# uncomment the following line. -#monitoring.elasticsearch: - - # Array of hosts to connect to. - # Scheme and port can be left out and will be set to the default (http and 9200) - # In case you specify and additional path, the scheme is required: http://localhost:9200/path - # IPv6 addresses should always be defined as: https://[2001:db8::1]:9200 - #hosts: ["localhost:9200"] - - # Set gzip compression level. - #compression_level: 0 - - # Protocol - either `http` (default) or `https`. - #protocol: "https" - - # Authentication credentials - either API key or username/password. - #api_key: "id:api_key" - #username: "beats_system" - #password: "changeme" - - # Dictionary of HTTP parameters to pass within the URL with index operations. - #parameters: - #param1: value1 - #param2: value2 - - # Custom HTTP headers to add to each request - #headers: - # X-My-Header: Contents of the header - - # Proxy server url - #proxy_url: http://proxy:3128 - - # The number of times a particular Elasticsearch index operation is attempted. If - # the indexing operation doesn't succeed after this many retries, the events are - # dropped. The default is 3. - #max_retries: 3 - - # The maximum number of events to bulk in a single Elasticsearch bulk API index request. - # The default is 50. - #bulk_max_size: 50 - - # The number of seconds to wait before trying to reconnect to Elasticsearch - # after a network error. After waiting backoff.init seconds, the Beat - # tries to reconnect. If the attempt fails, the backoff timer is increased - # exponentially up to backoff.max. After a successful connection, the backoff - # timer is reset. The default is 1s. - #backoff.init: 1s - - # The maximum number of seconds to wait before attempting to connect to - # Elasticsearch after a network error. The default is 60s. - #backoff.max: 60s - - # Configure HTTP request timeout before failing an request to Elasticsearch. - #timeout: 90 - - # Use SSL settings for HTTPS. - #ssl.enabled: true - - # Configure SSL verification mode. If `none` is configured, all server hosts - # and certificates will be accepted. In this mode, SSL based connections are - # susceptible to man-in-the-middle attacks. Use only for testing. Default is - # `full`. - #ssl.verification_mode: full - - # List of supported/valid TLS versions. By default all TLS versions from 1.1 - # up to 1.3 are enabled. - #ssl.supported_protocols: [TLSv1.1, TLSv1.2, TLSv1.3] - - # SSL configuration. The default is off. - # List of root certificates for HTTPS server verifications - #ssl.certificate_authorities: ["/etc/pki/root/ca.pem"] - - # Certificate for SSL client authentication - #ssl.certificate: "/etc/pki/client/cert.pem" - - # Client certificate key - #ssl.key: "/etc/pki/client/cert.key" - - # Optional passphrase for decrypting the certificate key. - #ssl.key_passphrase: '' - - # Configure cipher suites to be used for SSL connections - #ssl.cipher_suites: [] - - # Configure curve types for ECDHE-based cipher suites - #ssl.curve_types: [] - - # Configure what types of renegotiation are supported. Valid options are - # never, once, and freely. Default is never. - #ssl.renegotiation: never - - # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. - #kerberos.enabled: true - - # Authentication type to use with Kerberos. Available options: keytab, password. - #kerberos.auth_type: password - - # Path to the keytab file. It is used when auth_type is set to keytab. - #kerberos.keytab: /etc/elastic.keytab - - # Path to the Kerberos configuration. - #kerberos.config_path: /etc/krb5.conf - - # Name of the Kerberos user. - #kerberos.username: elastic - - # Password of the Kerberos user. It is used when auth_type is set to password. - #kerberos.password: changeme - - # Kerberos realm. - #kerberos.realm: ELASTIC - - #metrics.period: 10s - #state.period: 1m - -# The `monitoring.cloud.id` setting overwrites the `monitoring.elasticsearch.hosts` -# setting. You can find the value for this setting in the Elastic Cloud web UI. -#monitoring.cloud.id: - -# The `monitoring.cloud.auth` setting overwrites the `monitoring.elasticsearch.username` -# and `monitoring.elasticsearch.password` settings. The format is `:`. -#monitoring.cloud.auth: - -#================================ HTTP Endpoint ====================================== -# Each beat can expose internal metrics through a HTTP endpoint. For security -# reasons the endpoint is disabled by default. This feature is currently experimental. -# Stats can be access through http://localhost:5066/stats . For pretty JSON output -# append ?pretty to the URL. - -# Defines if the HTTP endpoint is enabled. -#http.enabled: false - -# The HTTP endpoint will bind to this hostname, IP address, unix socket or named pipe. -# When using IP addresses, it is recommended to only use localhost. -#http.host: localhost - -# Port on which the HTTP endpoint will bind. Default is 5066. -#http.port: 5066 - -# Define which user should be owning the named pipe. -#http.named_pipe.user: - -# Define which the permissions that should be applied to the named pipe, use the Security -# Descriptor Definition Language (SDDL) to define the permission. This option cannot be used with -# `http.user`. -#http.named_pipe.security_descriptor: - -#============================= Process Security ================================ - -# Enable or disable seccomp system call filtering on Linux. Default is enabled. -#seccomp.enabled: true - -#================================= Migration ================================== - -# This allows to enable 6.7 migration aliases -#migration.6_to_7.enabled: false diff --git a/libbeat/_meta/config.docker.yml b/libbeat/_meta/config/default.docker.yml.tmpl similarity index 83% rename from libbeat/_meta/config.docker.yml rename to libbeat/_meta/config/default.docker.yml.tmpl index 6ce79dc1c42..720aafc117d 100644 --- a/libbeat/_meta/config.docker.yml +++ b/libbeat/_meta/config/default.docker.yml.tmpl @@ -1,3 +1,4 @@ +{{block "beat.docker.yml.tmpl" .}}{{end}} processors: - add_cloud_metadata: ~ - add_docker_metadata: ~ diff --git a/libbeat/_meta/config/default.reference.yml.tmpl b/libbeat/_meta/config/default.reference.yml.tmpl new file mode 100644 index 00000000000..c7c75d51cf4 --- /dev/null +++ b/libbeat/_meta/config/default.reference.yml.tmpl @@ -0,0 +1,22 @@ +{{block "beat.reference.yml.tmpl" . }}{{end}} +{{template "general.reference.yml.tmpl" .}} +{{template "processors.reference.yml.tmpl" .}} +{{template "elastic-cloud.yml.tmpl" .}} +{{template "outputs.yml.tmpl" .}} +{{template "output-elasticsearch.reference.yml.tmpl" .}} +{{template "output-logstash.reference.yml.tmpl" .}} +{{if not .ExcludeKafka}}{{template "output-kafka.reference.yml.tmpl" .}}{{end}} +{{if not .ExcludeRedis}}{{template "output-redis.reference.yml.tmpl" .}}{{end}} +{{if not .ExcludeFileOutput}}{{template "output-file.reference.yml.tmpl" .}}{{end}} +{{if not .ExcludeConsole}}{{template "output-console.reference.yml.tmpl" .}}{{end}} +{{template "paths.reference.yml.tmpl" .}} +{{template "keystore.reference.yml.tmpl" .}} +{{template "setup.dashboards.reference.yml.tmpl" .}} +{{template "setup.template.reference.yml.tmpl" .}} +{{template "setup.ilm.reference.yml.tmpl" .}} +{{template "setup.kibana.reference.yml.tmpl" .}} +{{template "logging.reference.yml.tmpl" .}} +{{template "monitoring.reference.yml.tmpl" .}} +{{template "http.reference.yml.tmpl" .}} +{{template "seccomp.reference.yml.tmpl" .}} +{{template "migration.yml.tmpl" .}} diff --git a/libbeat/_meta/config/default.short.yml.tmpl b/libbeat/_meta/config/default.short.yml.tmpl new file mode 100644 index 00000000000..c18d2abd8a5 --- /dev/null +++ b/libbeat/_meta/config/default.short.yml.tmpl @@ -0,0 +1,12 @@ +{{block "beat.yml.tmpl" .}}{{end}} +{{template "general.yml.tmpl" .}} +{{if not .ExcludeDashboards}}{{template "setup.dashboards.yml.tmpl" .}}{{end}} +{{template "setup.kibana.yml.tmpl" .}} +{{template "elastic-cloud.yml.tmpl" .}} +{{template "outputs.yml.tmpl" .}} +{{template "output-elasticsearch.yml.tmpl" .}} +{{template "output-logstash.yml.tmpl" .}} +{{template "processors.yml.tmpl" .}} +{{template "logging.yml.tmpl" .}} +{{template "monitoring.yml.tmpl" .}} +{{template "migration.yml.tmpl" .}} diff --git a/libbeat/_meta/config/elastic-cloud.yml.tmpl b/libbeat/_meta/config/elastic-cloud.yml.tmpl new file mode 100644 index 00000000000..f736f6ff659 --- /dev/null +++ b/libbeat/_meta/config/elastic-cloud.yml.tmpl @@ -0,0 +1,12 @@ +{{header "Elastic Cloud"}} + +# These settings simplify using {{ .BeatName | title }} with the Elastic Cloud (https://cloud.elastic.co/). + +# The cloud.id setting overwrites the `output.elasticsearch.hosts` and +# `setup.kibana.host` options. +# You can find the `cloud.id` in the Elastic Cloud web UI. +#cloud.id: + +# The cloud.auth setting overwrites the `output.elasticsearch.username` and +# `output.elasticsearch.password` settings. The format is `:`. +#cloud.auth: diff --git a/libbeat/_meta/config/general.reference.yml.tmpl b/libbeat/_meta/config/general.reference.yml.tmpl new file mode 100644 index 00000000000..8500d01c39e --- /dev/null +++ b/libbeat/_meta/config/general.reference.yml.tmpl @@ -0,0 +1,107 @@ +{{header "General"}} + +# The name of the shipper that publishes the network data. It can be used to group +# all the transactions sent by a single shipper in the web interface. +# If this options is not defined, the hostname is used. +#name: + +# The tags of the shipper are included in their own field with each +# transaction published. Tags make it easy to group servers by different +# logical properties. +#tags: ["service-X", "web-tier"] + +# Optional fields that you can specify to add additional information to the +# output. Fields can be scalar values, arrays, dictionaries, or any nested +# combination of these. +#fields: +# env: staging + +# If this option is set to true, the custom fields are stored as top-level +# fields in the output document instead of being grouped under a fields +# sub-dictionary. Default is false. +#fields_under_root: false + +# Internal queue configuration for buffering events to be published. +#queue: + # Queue type by name (default 'mem') + # The memory queue will present all available events (up to the outputs + # bulk_max_size) to the output, the moment the output is ready to server + # another batch of events. + #mem: + # Max number of events the queue can buffer. + #events: 4096 + + # Hints the minimum number of events stored in the queue, + # before providing a batch of events to the outputs. + # The default value is set to 2048. + # A value of 0 ensures events are immediately available + # to be sent to the outputs. + #flush.min_events: 2048 + + # Maximum duration after which events are available to the outputs, + # if the number of events stored in the queue is < `flush.min_events`. + #flush.timeout: 1s + + # The spool queue will store events in a local spool file, before + # forwarding the events to the outputs. + # + # Beta: spooling to disk is currently a beta feature. Use with care. + # + # The spool file is a circular buffer, which blocks once the file/buffer is full. + # Events are put into a write buffer and flushed once the write buffer + # is full or the flush_timeout is triggered. + # Once ACKed by the output, events are removed immediately from the queue, + # making space for new events to be persisted. + #spool: + # The file namespace configures the file path and the file creation settings. + # Once the file exists, the `size`, `page_size` and `prealloc` settings + # will have no more effect. + #file: + # Location of spool file. The default value is ${path.data}/spool.dat. + #path: "${path.data}/spool.dat" + + # Configure file permissions if file is created. The default value is 0600. + #permissions: 0600 + + # File size hint. The spool blocks, once this limit is reached. The default value is 100 MiB. + #size: 100MiB + + # The files page size. A file is split into multiple pages of the same size. The default value is 4KiB. + #page_size: 4KiB + + # If prealloc is set, the required space for the file is reserved using + # truncate. The default value is true. + #prealloc: true + + # Spool writer settings + # Events are serialized into a write buffer. The write buffer is flushed if: + # - The buffer limit has been reached. + # - The configured limit of buffered events is reached. + # - The flush timeout is triggered. + #write: + # Sets the write buffer size. + #buffer_size: 1MiB + + # Maximum duration after which events are flushed if the write buffer + # is not full yet. The default value is 1s. + #flush.timeout: 1s + + # Number of maximum buffered events. The write buffer is flushed once the + # limit is reached. + #flush.events: 16384 + + # Configure the on-disk event encoding. The encoding can be changed + # between restarts. + # Valid encodings are: json, ubjson, and cbor. + #codec: cbor + #read: + # Reader flush timeout, waiting for more events to become available, so + # to fill a complete batch as required by the outputs. + # If flush_timeout is 0, all available events are forwarded to the + # outputs immediately. + # The default value is 0s. + #flush.timeout: 0s + +# Sets the maximum number of CPUs that can be executing simultaneously. The +# default is the number of logical CPUs available in the system. +#max_procs: diff --git a/libbeat/_meta/config/general.yml.tmpl b/libbeat/_meta/config/general.yml.tmpl new file mode 100644 index 00000000000..1c85044c1b9 --- /dev/null +++ b/libbeat/_meta/config/general.yml.tmpl @@ -0,0 +1,14 @@ +{{header "General"}} + +# The name of the shipper that publishes the network data. It can be used to group +# all the transactions sent by a single shipper in the web interface. +#name: + +# The tags of the shipper are included in their own field with each +# transaction published. +#tags: ["service-X", "web-tier"] + +# Optional fields that you can specify to add additional information to the +# output. +#fields: +# env: staging diff --git a/libbeat/_meta/config/http.reference.yml.tmpl b/libbeat/_meta/config/http.reference.yml.tmpl new file mode 100644 index 00000000000..19a9f5fcd50 --- /dev/null +++ b/libbeat/_meta/config/http.reference.yml.tmpl @@ -0,0 +1,24 @@ +{{header "HTTP Endpoint"}} + +# Each beat can expose internal metrics through a HTTP endpoint. For security +# reasons the endpoint is disabled by default. This feature is currently experimental. +# Stats can be access through http://localhost:5066/stats . For pretty JSON output +# append ?pretty to the URL. + +# Defines if the HTTP endpoint is enabled. +#http.enabled: false + +# The HTTP endpoint will bind to this hostname, IP address, unix socket or named pipe. +# When using IP addresses, it is recommended to only use localhost. +#http.host: localhost + +# Port on which the HTTP endpoint will bind. Default is 5066. +#http.port: 5066 + +# Define which user should be owning the named pipe. +#http.named_pipe.user: + +# Define which the permissions that should be applied to the named pipe, use the Security +# Descriptor Definition Language (SDDL) to define the permission. This option cannot be used with +# `http.user`. +#http.named_pipe.security_descriptor: diff --git a/libbeat/_meta/config/keystore.reference.yml.tmpl b/libbeat/_meta/config/keystore.reference.yml.tmpl new file mode 100644 index 00000000000..2c4de9757a1 --- /dev/null +++ b/libbeat/_meta/config/keystore.reference.yml.tmpl @@ -0,0 +1,4 @@ +{{header "Keystore"}} + +# Location of the Keystore containing the keys and their sensitive values. +#keystore.path: "${path.config}/beats.keystore" diff --git a/libbeat/_meta/config/logging.reference.yml.tmpl b/libbeat/_meta/config/logging.reference.yml.tmpl new file mode 100644 index 00000000000..0c3000dc060 --- /dev/null +++ b/libbeat/_meta/config/logging.reference.yml.tmpl @@ -0,0 +1,67 @@ +{{header "Logging"}} + +# There are four options for the log output: file, stderr, syslog, eventlog +# The file output is the default. + +# Sets log level. The default log level is info. +# Available log levels are: error, warning, info, debug +#logging.level: info + +# Enable debug output for selected components. To enable all selectors use ["*"] +# Other available selectors are "beat", "publish", "service" +# Multiple selectors can be chained. +#logging.selectors: [ ] + +# Send all logging output to stderr. The default is false. +#logging.to_stderr: false + +# Send all logging output to syslog. The default is false. +#logging.to_syslog: false + +# Send all logging output to Windows Event Logs. The default is false. +#logging.to_eventlog: false + +# If enabled, {{.BeatName | title}} periodically logs its internal metrics that have changed +# in the last period. For each metric that changed, the delta from the value at +# the beginning of the period is logged. Also, the total values for +# all non-zero internal metrics are logged on shutdown. The default is true. +#logging.metrics.enabled: true + +# The period after which to log the internal metrics. The default is 30s. +#logging.metrics.period: 30s + +# Logging to rotating files. Set logging.to_files to false to disable logging to +# files. +logging.to_files: true +logging.files: + # Configure the path where the logs are written. The default is the logs directory + # under the home path (the binary location). + #path: /var/log/{{.BeatName}} + + # The name of the files where the logs are written to. + #name: {{.BeatName}} + + # Configure log file size limit. If limit is reached, log file will be + # automatically rotated + #rotateeverybytes: 10485760 # = 10MB + + # Number of rotated log files to keep. Oldest files will be deleted first. + #keepfiles: 7 + + # The permissions mask to apply when rotating log files. The default value is 0600. + # Must be a valid Unix-style file permissions mask expressed in octal notation. + #permissions: 0600 + + # Enable log file rotation on time intervals in addition to size-based rotation. + # Intervals must be at least 1s. Values of 1m, 1h, 24h, 7*24h, 30*24h, and 365*24h + # are boundary-aligned with minutes, hours, days, weeks, months, and years as + # reported by the local system clock. All other intervals are calculated from the + # Unix epoch. Defaults to disabled. + #interval: 0 + + # Rotate existing logs on startup rather than appending to the existing + # file. Defaults to true. + # rotateonstartup: true + +# Set to true to log messages in JSON format. +#logging.json: false diff --git a/libbeat/_meta/config/logging.yml.tmpl b/libbeat/_meta/config/logging.yml.tmpl new file mode 100644 index 00000000000..a639acc76ab --- /dev/null +++ b/libbeat/_meta/config/logging.yml.tmpl @@ -0,0 +1,10 @@ +{{header "Logging"}} + +# Sets log level. The default log level is info. +# Available log levels are: error, warning, info, debug +#logging.level: debug + +# At debug level, you can selectively enable logging only for some components. +# To enable all selectors use ["*"]. Examples of other selectors are "beat", +# "publish", "service". +#logging.selectors: ["*"] diff --git a/libbeat/_meta/config/migration.yml.tmpl b/libbeat/_meta/config/migration.yml.tmpl new file mode 100644 index 00000000000..8abeab86be4 --- /dev/null +++ b/libbeat/_meta/config/migration.yml.tmpl @@ -0,0 +1,4 @@ +{{header "Migration"}} + +# This allows to enable 6.7 migration aliases +#migration.6_to_7.enabled: {{not .Reference}} diff --git a/libbeat/_meta/config/monitoring.reference.yml.tmpl b/libbeat/_meta/config/monitoring.reference.yml.tmpl new file mode 100644 index 00000000000..187b92678eb --- /dev/null +++ b/libbeat/_meta/config/monitoring.reference.yml.tmpl @@ -0,0 +1,141 @@ +{{header "X-Pack Monitoring"}} +# {{.BeatName | title}} can export internal metrics to a central Elasticsearch monitoring +# cluster. This requires xpack monitoring to be enabled in Elasticsearch. The +# reporting is disabled by default. + +# Set to true to enable the monitoring reporter. +#monitoring.enabled: false + +# Sets the UUID of the Elasticsearch cluster under which monitoring data for this +# {{.BeatName | title}} instance will appear in the Stack Monitoring UI. If output.elasticsearch +# is enabled, the UUID is derived from the Elasticsearch cluster referenced by output.elasticsearch. +#monitoring.cluster_uuid: + +# Uncomment to send the metrics to Elasticsearch. Most settings from the +# Elasticsearch output are accepted here as well. +# Note that the settings should point to your Elasticsearch *monitoring* cluster. +# Any setting that is not set is automatically inherited from the Elasticsearch +# output configuration, so if you have the Elasticsearch output configured such +# that it is pointing to your Elasticsearch monitoring cluster, you can simply +# uncomment the following line. +#monitoring.elasticsearch: + + # Array of hosts to connect to. + # Scheme and port can be left out and will be set to the default (http and 9200) + # In case you specify and additional path, the scheme is required: http://localhost:9200/path + # IPv6 addresses should always be defined as: https://[2001:db8::1]:9200 + #hosts: ["localhost:9200"] + + # Set gzip compression level. + #compression_level: 0 + + # Protocol - either `http` (default) or `https`. + #protocol: "https" + + # Authentication credentials - either API key or username/password. + #api_key: "id:api_key" + #username: "beats_system" + #password: "changeme" + + # Dictionary of HTTP parameters to pass within the URL with index operations. + #parameters: + #param1: value1 + #param2: value2 + + # Custom HTTP headers to add to each request + #headers: + # X-My-Header: Contents of the header + + # Proxy server url + #proxy_url: http://proxy:3128 + + # The number of times a particular Elasticsearch index operation is attempted. If + # the indexing operation doesn't succeed after this many retries, the events are + # dropped. The default is 3. + #max_retries: 3 + + # The maximum number of events to bulk in a single Elasticsearch bulk API index request. + # The default is 50. + #bulk_max_size: 50 + + # The number of seconds to wait before trying to reconnect to Elasticsearch + # after a network error. After waiting backoff.init seconds, the Beat + # tries to reconnect. If the attempt fails, the backoff timer is increased + # exponentially up to backoff.max. After a successful connection, the backoff + # timer is reset. The default is 1s. + #backoff.init: 1s + + # The maximum number of seconds to wait before attempting to connect to + # Elasticsearch after a network error. The default is 60s. + #backoff.max: 60s + + # Configure HTTP request timeout before failing an request to Elasticsearch. + #timeout: 90 + + # Use SSL settings for HTTPS. + #ssl.enabled: true + + # Configure SSL verification mode. If `none` is configured, all server hosts + # and certificates will be accepted. In this mode, SSL based connections are + # susceptible to man-in-the-middle attacks. Use only for testing. Default is + # `full`. + #ssl.verification_mode: full + + # List of supported/valid TLS versions. By default all TLS versions from 1.1 + # up to 1.3 are enabled. + #ssl.supported_protocols: [TLSv1.1, TLSv1.2, TLSv1.3] + + # SSL configuration. The default is off. + # List of root certificates for HTTPS server verifications + #ssl.certificate_authorities: ["/etc/pki/root/ca.pem"] + + # Certificate for SSL client authentication + #ssl.certificate: "/etc/pki/client/cert.pem" + + # Client certificate key + #ssl.key: "/etc/pki/client/cert.key" + + # Optional passphrase for decrypting the certificate key. + #ssl.key_passphrase: '' + + # Configure cipher suites to be used for SSL connections + #ssl.cipher_suites: [] + + # Configure curve types for ECDHE-based cipher suites + #ssl.curve_types: [] + + # Configure what types of renegotiation are supported. Valid options are + # never, once, and freely. Default is never. + #ssl.renegotiation: never + + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + + # Authentication type to use with Kerberos. Available options: keytab, password. + #kerberos.auth_type: password + + # Path to the keytab file. It is used when auth_type is set to keytab. + #kerberos.keytab: /etc/elastic.keytab + + # Path to the Kerberos configuration. + #kerberos.config_path: /etc/krb5.conf + + # Name of the Kerberos user. + #kerberos.username: elastic + + # Password of the Kerberos user. It is used when auth_type is set to password. + #kerberos.password: changeme + + # Kerberos realm. + #kerberos.realm: ELASTIC + + #metrics.period: 10s + #state.period: 1m + +# The `monitoring.cloud.id` setting overwrites the `monitoring.elasticsearch.hosts` +# setting. You can find the value for this setting in the Elastic Cloud web UI. +#monitoring.cloud.id: + +# The `monitoring.cloud.auth` setting overwrites the `monitoring.elasticsearch.username` +# and `monitoring.elasticsearch.password` settings. The format is `:`. +#monitoring.cloud.auth: diff --git a/libbeat/_meta/config/monitoring.yml.tmpl b/libbeat/_meta/config/monitoring.yml.tmpl new file mode 100644 index 00000000000..6253cb167d5 --- /dev/null +++ b/libbeat/_meta/config/monitoring.yml.tmpl @@ -0,0 +1,21 @@ +{{header "X-Pack Monitoring"}} +# {{.BeatName | title }} can export internal metrics to a central Elasticsearch monitoring +# cluster. This requires xpack monitoring to be enabled in Elasticsearch. The +# reporting is disabled by default. + +# Set to true to enable the monitoring reporter. +#monitoring.enabled: false + +# Sets the UUID of the Elasticsearch cluster under which monitoring data for this +# {{ .BeatName | title }} instance will appear in the Stack Monitoring UI. If output.elasticsearch +# is enabled, the UUID is derived from the Elasticsearch cluster referenced by output.elasticsearch. +#monitoring.cluster_uuid: + +# Uncomment to send the metrics to Elasticsearch. Most settings from the +# Elasticsearch output are accepted here as well. +# Note that the settings should point to your Elasticsearch *monitoring* cluster. +# Any setting that is not set is automatically inherited from the Elasticsearch +# output configuration, so if you have the Elasticsearch output configured such +# that it is pointing to your Elasticsearch monitoring cluster, you can simply +# uncomment the following line. +#monitoring.elasticsearch: diff --git a/libbeat/_meta/config/output-console.reference.yml.tmpl b/libbeat/_meta/config/output-console.reference.yml.tmpl new file mode 100644 index 00000000000..163e9382807 --- /dev/null +++ b/libbeat/_meta/config/output-console.reference.yml.tmpl @@ -0,0 +1,12 @@ +{{subheader "Console Output"}} +#output.console: + # Boolean flag to enable or disable the output module. + #enabled: true + + # Configure JSON encoding + #codec.json: + # Pretty-print JSON event + #pretty: false + + # Configure escaping HTML symbols in strings. + #escape_html: false diff --git a/libbeat/_meta/config/output-elasticsearch.reference.yml.tmpl b/libbeat/_meta/config/output-elasticsearch.reference.yml.tmpl new file mode 100644 index 00000000000..5de92febf3c --- /dev/null +++ b/libbeat/_meta/config/output-elasticsearch.reference.yml.tmpl @@ -0,0 +1,140 @@ +{{subheader "Elasticsearch Output"}} +output.elasticsearch: + # Boolean flag to enable or disable the output module. + #enabled: true + + # Array of hosts to connect to. + # Scheme and port can be left out and will be set to the default (http and 9200) + # In case you specify and additional path, the scheme is required: http://localhost:9200/path + # IPv6 addresses should always be defined as: https://[2001:db8::1]:9200 + hosts: ["localhost:9200"] + + # Set gzip compression level. + #compression_level: 0 + + # Configure escaping HTML symbols in strings. + #escape_html: false + + # Protocol - either `http` (default) or `https`. + #protocol: "https" + + # Authentication credentials - either API key or username/password. + #api_key: "id:api_key" + #username: "elastic" + #password: "changeme" + + # Dictionary of HTTP parameters to pass within the URL with index operations. + #parameters: + #param1: value1 + #param2: value2 + + # Number of workers per Elasticsearch host. + #worker: 1 + + # Optional index name. The default is "{{.BeatIndexPrefix}}" plus date + # and generates [{{.BeatIndexPrefix}}-]YYYY.MM.DD keys. + # In case you modify this pattern you must update setup.template.name and setup.template.pattern accordingly. + #index: "{{.BeatIndexPrefix}}-%{[agent.version]}-%{+yyyy.MM.dd}" + + # Optional ingest node pipeline. By default no pipeline will be used. + #pipeline: "" + + # Optional HTTP path + #path: "/elasticsearch" + + # Custom HTTP headers to add to each request + #headers: + # X-My-Header: Contents of the header + + # Proxy server URL + #proxy_url: http://proxy:3128 + + # Whether to disable proxy settings for outgoing connections. If true, this + # takes precedence over both the proxy_url field and any environment settings + # (HTTP_PROXY, HTTPS_PROXY). The default is false. + #proxy_disable: false + + # The number of times a particular Elasticsearch index operation is attempted. If + # the indexing operation doesn't succeed after this many retries, the events are + # dropped. The default is 3. + #max_retries: 3 + + # The maximum number of events to bulk in a single Elasticsearch bulk API index request. + # The default is 50. + #bulk_max_size: 50 + + # The number of seconds to wait before trying to reconnect to Elasticsearch + # after a network error. After waiting backoff.init seconds, the Beat + # tries to reconnect. If the attempt fails, the backoff timer is increased + # exponentially up to backoff.max. After a successful connection, the backoff + # timer is reset. The default is 1s. + #backoff.init: 1s + + # The maximum number of seconds to wait before attempting to connect to + # Elasticsearch after a network error. The default is 60s. + #backoff.max: 60s + + # Configure HTTP request timeout before failing a request to Elasticsearch. + #timeout: 90 + + # Use SSL settings for HTTPS. + #ssl.enabled: true + + # Configure SSL verification mode. If `none` is configured, all server hosts + # and certificates will be accepted. In this mode, SSL-based connections are + # susceptible to man-in-the-middle attacks. Use only for testing. Default is + # `full`. + #ssl.verification_mode: full + + # List of supported/valid TLS versions. By default all TLS versions from 1.1 + # up to 1.3 are enabled. + #ssl.supported_protocols: [TLSv1.1, TLSv1.2, TLSv1.3] + + # List of root certificates for HTTPS server verifications + #ssl.certificate_authorities: ["/etc/pki/root/ca.pem"] + + # Certificate for SSL client authentication + #ssl.certificate: "/etc/pki/client/cert.pem" + + # Client certificate key + #ssl.key: "/etc/pki/client/cert.key" + + # Optional passphrase for decrypting the certificate key. + #ssl.key_passphrase: '' + + # Configure cipher suites to be used for SSL connections + #ssl.cipher_suites: [] + + # Configure curve types for ECDHE-based cipher suites + #ssl.curve_types: [] + + # Configure what types of renegotiation are supported. Valid options are + # never, once, and freely. Default is never. + #ssl.renegotiation: never + + # Configure a pin that can be used to do extra validation of the verified certificate chain, + # this allow you to ensure that a specific certificate is used to validate the chain of trust. + # + # The pin is a base64 encoded string of the SHA-256 fingerprint. + #ssl.ca_sha256: "" + + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + + # Authentication type to use with Kerberos. Available options: keytab, password. + #kerberos.auth_type: password + + # Path to the keytab file. It is used when auth_type is set to keytab. + #kerberos.keytab: /etc/elastic.keytab + + # Path to the Kerberos configuration. + #kerberos.config_path: /etc/krb5.conf + + # Name of the Kerberos user. + #kerberos.username: elastic + + # Password of the Kerberos user. It is used when auth_type is set to password. + #kerberos.password: changeme + + # Kerberos realm. + #kerberos.realm: ELASTIC diff --git a/libbeat/_meta/config/output-elasticsearch.yml.tmpl b/libbeat/_meta/config/output-elasticsearch.yml.tmpl new file mode 100644 index 00000000000..7c1287e01a1 --- /dev/null +++ b/libbeat/_meta/config/output-elasticsearch.yml.tmpl @@ -0,0 +1,12 @@ +{{subheader "Elasticsearch Output"}} +output.elasticsearch: + # Array of hosts to connect to. + hosts: ["localhost:9200"] + + # Protocol - either `http` (default) or `https`. + #protocol: "https" + + # Authentication credentials - either API key or username/password. + #api_key: "id:api_key" + #username: "elastic" + #password: "changeme" diff --git a/libbeat/_meta/config/output-file.reference.yml.tmpl b/libbeat/_meta/config/output-file.reference.yml.tmpl new file mode 100644 index 00000000000..2c383444107 --- /dev/null +++ b/libbeat/_meta/config/output-file.reference.yml.tmpl @@ -0,0 +1,33 @@ +{{subheader "File Output"}} +#output.file: + # Boolean flag to enable or disable the output module. + #enabled: true + + # Configure JSON encoding + #codec.json: + # Pretty-print JSON event + #pretty: false + + # Configure escaping HTML symbols in strings. + #escape_html: false + + # Path to the directory where to save the generated files. The option is + # mandatory. + #path: "/tmp/{{.BeatName}}" + + # Name of the generated files. The default is `{{.BeatName}}` and it generates + # files: `{{.BeatName}}`, `{{.BeatName}}.1`, `{{.BeatName}}.2`, etc. + #filename: {{.BeatName}} + + # Maximum size in kilobytes of each file. When this size is reached, and on + # every {{.BeatName | title}} restart, the files are rotated. The default value is 10240 + # kB. + #rotate_every_kb: 10000 + + # Maximum number of files under path. When this number of files is reached, + # the oldest file is deleted and the rest are shifted from last to first. The + # default is 7 files. + #number_of_files: 7 + + # Permissions to use for file creation. The default is 0600. + #permissions: 0600 diff --git a/libbeat/_meta/config/output-kafka.reference.yml.tmpl b/libbeat/_meta/config/output-kafka.reference.yml.tmpl new file mode 100644 index 00000000000..50efb87fbb1 --- /dev/null +++ b/libbeat/_meta/config/output-kafka.reference.yml.tmpl @@ -0,0 +1,178 @@ +{{subheader "Kafka Output"}} +#output.kafka: + # Boolean flag to enable or disable the output module. + #enabled: true + + # The list of Kafka broker addresses from which to fetch the cluster metadata. + # The cluster metadata contain the actual Kafka brokers events are published + # to. + #hosts: ["localhost:9092"] + + # The Kafka topic used for produced events. The setting can be a format string + # using any event field. To set the topic from document type use `%{[type]}`. + #topic: beats + + # The Kafka event key setting. Use format string to create a unique event key. + # By default no event key will be generated. + #key: '' + + # The Kafka event partitioning strategy. Default hashing strategy is `hash` + # using the `output.kafka.key` setting or randomly distributes events if + # `output.kafka.key` is not configured. + #partition.hash: + # If enabled, events will only be published to partitions with reachable + # leaders. Default is false. + #reachable_only: false + + # Configure alternative event field names used to compute the hash value. + # If empty `output.kafka.key` setting will be used. + # Default value is empty list. + #hash: [] + + # Authentication details. Password is required if username is set. + #username: '' + #password: '' + + # Kafka version {{.BeatName | title}} is assumed to run against. Defaults to the "1.0.0". + #version: '1.0.0' + + # Configure JSON encoding + #codec.json: + # Pretty-print JSON event + #pretty: false + + # Configure escaping HTML symbols in strings. + #escape_html: false + + # Metadata update configuration. Metadata contains leader information + # used to decide which broker to use when publishing. + #metadata: + # Max metadata request retry attempts when cluster is in middle of leader + # election. Defaults to 3 retries. + #retry.max: 3 + + # Wait time between retries during leader elections. Default is 250ms. + #retry.backoff: 250ms + + # Refresh metadata interval. Defaults to every 10 minutes. + #refresh_frequency: 10m + + # Strategy for fetching the topics metadata from the broker. Default is false. + #full: false + + # The number of concurrent load-balanced Kafka output workers. + #worker: 1 + + # The number of times to retry publishing an event after a publishing failure. + # After the specified number of retries, events are typically dropped. + # Some Beats, such as Filebeat, ignore the max_retries setting and retry until + # all events are published. Set max_retries to a value less than 0 to retry + # until all events are published. The default is 3. + #max_retries: 3 + + # The maximum number of events to bulk in a single Kafka request. The default + # is 2048. + #bulk_max_size: 2048 + + # Duration to wait before sending bulk Kafka request. 0 is no delay. The default + # is 0. + #bulk_flush_frequency: 0s + + # The number of seconds to wait for responses from the Kafka brokers before + # timing out. The default is 30s. + #timeout: 30s + + # The maximum duration a broker will wait for number of required ACKs. The + # default is 10s. + #broker_timeout: 10s + + # The number of messages buffered for each Kafka broker. The default is 256. + #channel_buffer_size: 256 + + # The keep-alive period for an active network connection. If 0s, keep-alives + # are disabled. The default is 0 seconds. + #keep_alive: 0 + + # Sets the output compression codec. Must be one of none, snappy and gzip. The + # default is gzip. + #compression: gzip + + # Set the compression level. Currently only gzip provides a compression level + # between 0 and 9. The default value is chosen by the compression algorithm. + #compression_level: 4 + + # The maximum permitted size of JSON-encoded messages. Bigger messages will be + # dropped. The default value is 1000000 (bytes). This value should be equal to + # or less than the broker's message.max.bytes. + #max_message_bytes: 1000000 + + # The ACK reliability level required from broker. 0=no response, 1=wait for + # local commit, -1=wait for all replicas to commit. The default is 1. Note: + # If set to 0, no ACKs are returned by Kafka. Messages might be lost silently + # on error. + #required_acks: 1 + + # The configurable ClientID used for logging, debugging, and auditing + # purposes. The default is "beats". + #client_id: beats + + # Enable SSL support. SSL is automatically enabled if any SSL setting is set. + #ssl.enabled: true + + # Optional SSL configuration options. SSL is off by default. + # List of root certificates for HTTPS server verifications + #ssl.certificate_authorities: ["/etc/pki/root/ca.pem"] + + # Configure SSL verification mode. If `none` is configured, all server hosts + # and certificates will be accepted. In this mode, SSL based connections are + # susceptible to man-in-the-middle attacks. Use only for testing. Default is + # `full`. + #ssl.verification_mode: full + + # List of supported/valid TLS versions. By default all TLS versions from 1.1 + # up to 1.3 are enabled. + #ssl.supported_protocols: [TLSv1.1, TLSv1.2, TLSv1.3] + + # Certificate for SSL client authentication + #ssl.certificate: "/etc/pki/client/cert.pem" + + # Client Certificate Key + #ssl.key: "/etc/pki/client/cert.key" + + # Optional passphrase for decrypting the Certificate Key. + #ssl.key_passphrase: '' + + # Configure cipher suites to be used for SSL connections + #ssl.cipher_suites: [] + + # Configure curve types for ECDHE-based cipher suites + #ssl.curve_types: [] + + # Configure what types of renegotiation are supported. Valid options are + # never, once, and freely. Default is never. + #ssl.renegotiation: never + + # Enable Kerberos support. Kerberos is automatically enabled if any Kerberos setting is set. + #kerberos.enabled: true + + # Authentication type to use with Kerberos. Available options: keytab, password. + #kerberos.auth_type: password + + # Path to the keytab file. It is used when auth_type is set to keytab. + #kerberos.keytab: /etc/security/keytabs/kafka.keytab + + # Path to the Kerberos configuration. + #kerberos.config_path: /etc/krb5.conf + + # The service name. Service principal name is contructed from + # service_name/hostname@realm. + #kerberos.service_name: kafka + + # Name of the Kerberos user. + #kerberos.username: elastic + + # Password of the Kerberos user. It is used when auth_type is set to password. + #kerberos.password: changeme + + # Kerberos realm. + #kerberos.realm: ELASTIC diff --git a/libbeat/_meta/config/output-logstash.reference.yml.tmpl b/libbeat/_meta/config/output-logstash.reference.yml.tmpl new file mode 100644 index 00000000000..da182d0496e --- /dev/null +++ b/libbeat/_meta/config/output-logstash.reference.yml.tmpl @@ -0,0 +1,113 @@ +{{subheader "Logstash Output"}} +#output.logstash: + # Boolean flag to enable or disable the output module. + #enabled: true + + # The Logstash hosts + #hosts: ["localhost:5044"] + + # Number of workers per Logstash host. + #worker: 1 + + # Set gzip compression level. + #compression_level: 3 + + # Configure escaping HTML symbols in strings. + #escape_html: false + + # Optional maximum time to live for a connection to Logstash, after which the + # connection will be re-established. A value of `0s` (the default) will + # disable this feature. + # + # Not yet supported for async connections (i.e. with the "pipelining" option set) + #ttl: 30s + + # Optionally load-balance events between Logstash hosts. Default is false. + #loadbalance: false + + # Number of batches to be sent asynchronously to Logstash while processing + # new batches. + #pipelining: 2 + + # If enabled only a subset of events in a batch of events is transferred per + # transaction. The number of events to be sent increases up to `bulk_max_size` + # if no error is encountered. + #slow_start: false + + # The number of seconds to wait before trying to reconnect to Logstash + # after a network error. After waiting backoff.init seconds, the Beat + # tries to reconnect. If the attempt fails, the backoff timer is increased + # exponentially up to backoff.max. After a successful connection, the backoff + # timer is reset. The default is 1s. + #backoff.init: 1s + + # The maximum number of seconds to wait before attempting to connect to + # Logstash after a network error. The default is 60s. + #backoff.max: 60s + + # Optional index name. The default index name is set to {{.BeatIndexPrefix}} + # in all lowercase. + #index: '{{.BeatIndexPrefix}}' + + # SOCKS5 proxy server URL + #proxy_url: socks5://user:password@socks5-server:2233 + + # Resolve names locally when using a proxy server. Defaults to false. + #proxy_use_local_resolver: false + + # Enable SSL support. SSL is automatically enabled if any SSL setting is set. + #ssl.enabled: true + + # Configure SSL verification mode. If `none` is configured, all server hosts + # and certificates will be accepted. In this mode, SSL based connections are + # susceptible to man-in-the-middle attacks. Use only for testing. Default is + # `full`. + #ssl.verification_mode: full + + # List of supported/valid TLS versions. By default all TLS versions from 1.1 + # up to 1.3 are enabled. + #ssl.supported_protocols: [TLSv1.1, TLSv1.2, TLSv1.3] + + # Optional SSL configuration options. SSL is off by default. + # List of root certificates for HTTPS server verifications + #ssl.certificate_authorities: ["/etc/pki/root/ca.pem"] + + # Certificate for SSL client authentication + #ssl.certificate: "/etc/pki/client/cert.pem" + + # Client certificate key + #ssl.key: "/etc/pki/client/cert.key" + + # Optional passphrase for decrypting the Certificate Key. + #ssl.key_passphrase: '' + + # Configure cipher suites to be used for SSL connections + #ssl.cipher_suites: [] + + # Configure curve types for ECDHE-based cipher suites + #ssl.curve_types: [] + + # Configure what types of renegotiation are supported. Valid options are + # never, once, and freely. Default is never. + #ssl.renegotiation: never + + # Configure a pin that can be used to do extra validation of the verified certificate chain, + # this allow you to ensure that a specific certificate is used to validate the chain of trust. + # + # The pin is a base64 encoded string of the SHA-256 fingerprint. + #ssl.ca_sha256: "" + + # The number of times to retry publishing an event after a publishing failure. + # After the specified number of retries, the events are typically dropped. + # Some Beats, such as Filebeat and Winlogbeat, ignore the max_retries setting + # and retry until all events are published. Set max_retries to a value less + # than 0 to retry until all events are published. The default is 3. + #max_retries: 3 + + # The maximum number of events to bulk in a single Logstash request. The + # default is 2048. + #bulk_max_size: 2048 + + # The number of seconds to wait for responses from the Logstash server before + # timing out. The default is 30s. + #timeout: 30s diff --git a/libbeat/_meta/config/output-logstash.yml.tmpl b/libbeat/_meta/config/output-logstash.yml.tmpl new file mode 100644 index 00000000000..a937e0de99e --- /dev/null +++ b/libbeat/_meta/config/output-logstash.yml.tmpl @@ -0,0 +1,14 @@ +{{subheader "Logstash Output"}} +#output.logstash: + # The Logstash hosts + #hosts: ["localhost:5044"] + + # Optional SSL. By default is off. + # List of root certificates for HTTPS server verifications + #ssl.certificate_authorities: ["/etc/pki/root/ca.pem"] + + # Certificate for SSL client authentication + #ssl.certificate: "/etc/pki/client/cert.pem" + + # Client Certificate Key + #ssl.key: "/etc/pki/client/cert.key" diff --git a/libbeat/_meta/config/output-redis.reference.yml.tmpl b/libbeat/_meta/config/output-redis.reference.yml.tmpl new file mode 100644 index 00000000000..3b8fa47f292 --- /dev/null +++ b/libbeat/_meta/config/output-redis.reference.yml.tmpl @@ -0,0 +1,117 @@ +{{subheader "Redis Output"}} +#output.redis: + # Boolean flag to enable or disable the output module. + #enabled: true + + # Configure JSON encoding + #codec.json: + # Pretty print json event + #pretty: false + + # Configure escaping HTML symbols in strings. + #escape_html: false + + # The list of Redis servers to connect to. If load-balancing is enabled, the + # events are distributed to the servers in the list. If one server becomes + # unreachable, the events are distributed to the reachable servers only. + # The hosts setting supports redis and rediss urls with custom password like + # redis://:password@localhost:6379. + #hosts: ["localhost:6379"] + + # The name of the Redis list or channel the events are published to. The + # default is {{.BeatName}}. + #key: {{.BeatName}} + + # The password to authenticate to Redis with. The default is no authentication. + #password: + + # The Redis database number where the events are published. The default is 0. + #db: 0 + + # The Redis data type to use for publishing events. If the data type is list, + # the Redis RPUSH command is used. If the data type is channel, the Redis + # PUBLISH command is used. The default value is list. + #datatype: list + + # The number of workers to use for each host configured to publish events to + # Redis. Use this setting along with the loadbalance option. For example, if + # you have 2 hosts and 3 workers, in total 6 workers are started (3 for each + # host). + #worker: 1 + + # If set to true and multiple hosts or workers are configured, the output + # plugin load balances published events onto all Redis hosts. If set to false, + # the output plugin sends all events to only one host (determined at random) + # and will switch to another host if the currently selected one becomes + # unreachable. The default value is true. + #loadbalance: true + + # The Redis connection timeout in seconds. The default is 5 seconds. + #timeout: 5s + + # The number of times to retry publishing an event after a publishing failure. + # After the specified number of retries, the events are typically dropped. + # Some Beats, such as Filebeat, ignore the max_retries setting and retry until + # all events are published. Set max_retries to a value less than 0 to retry + # until all events are published. The default is 3. + #max_retries: 3 + + # The number of seconds to wait before trying to reconnect to Redis + # after a network error. After waiting backoff.init seconds, the Beat + # tries to reconnect. If the attempt fails, the backoff timer is increased + # exponentially up to backoff.max. After a successful connection, the backoff + # timer is reset. The default is 1s. + #backoff.init: 1s + + # The maximum number of seconds to wait before attempting to connect to + # Redis after a network error. The default is 60s. + #backoff.max: 60s + + # The maximum number of events to bulk in a single Redis request or pipeline. + # The default is 2048. + #bulk_max_size: 2048 + + # The URL of the SOCKS5 proxy to use when connecting to the Redis servers. The + # value must be a URL with a scheme of socks5://. + #proxy_url: + + # This option determines whether Redis hostnames are resolved locally when + # using a proxy. The default value is false, which means that name resolution + # occurs on the proxy server. + #proxy_use_local_resolver: false + + # Enable SSL support. SSL is automatically enabled, if any SSL setting is set. + #ssl.enabled: true + + # Configure SSL verification mode. If `none` is configured, all server hosts + # and certificates will be accepted. In this mode, SSL based connections are + # susceptible to man-in-the-middle attacks. Use only for testing. Default is + # `full`. + #ssl.verification_mode: full + + # List of supported/valid TLS versions. By default all TLS versions from 1.1 + # up to 1.3 are enabled. + #ssl.supported_protocols: [TLSv1.1, TLSv1.2, TLSv1.3] + + # Optional SSL configuration options. SSL is off by default. + # List of root certificates for HTTPS server verifications + #ssl.certificate_authorities: ["/etc/pki/root/ca.pem"] + + # Certificate for SSL client authentication + #ssl.certificate: "/etc/pki/client/cert.pem" + + # Client Certificate Key + #ssl.key: "/etc/pki/client/cert.key" + + # Optional passphrase for decrypting the Certificate Key. + #ssl.key_passphrase: '' + + # Configure cipher suites to be used for SSL connections + #ssl.cipher_suites: [] + + # Configure curve types for ECDHE based cipher suites + #ssl.curve_types: [] + + # Configure what types of renegotiation are supported. Valid options are + # never, once, and freely. Default is never. + #ssl.renegotiation: never diff --git a/libbeat/_meta/config/outputs.yml.tmpl b/libbeat/_meta/config/outputs.yml.tmpl new file mode 100644 index 00000000000..662c88cfbcf --- /dev/null +++ b/libbeat/_meta/config/outputs.yml.tmpl @@ -0,0 +1,3 @@ +{{header "Outputs"}} + +# Configure what output to use when sending the data collected by the beat. diff --git a/libbeat/_meta/config/paths.reference.yml.tmpl b/libbeat/_meta/config/paths.reference.yml.tmpl new file mode 100644 index 00000000000..7907777f5e1 --- /dev/null +++ b/libbeat/_meta/config/paths.reference.yml.tmpl @@ -0,0 +1,25 @@ +{{header "Paths"}} + +# The home path for the {{.BeatName | title}} installation. This is the default base path +# for all other path settings and for miscellaneous files that come with the +# distribution (for example, the sample dashboards). +# If not set by a CLI flag or in the configuration file, the default for the +# home path is the location of the binary. +#path.home: + +# The configuration path for the {{.BeatName | title}} installation. This is the default +# base path for configuration files, including the main YAML configuration file +# and the Elasticsearch template file. If not set by a CLI flag or in the +# configuration file, the default for the configuration path is the home path. +#path.config: ${path.home} + +# The data path for the {{.BeatName | title}} installation. This is the default base path +# for all the files in which {{.BeatName | title}} needs to store its data. If not set by a +# CLI flag or in the configuration file, the default for the data path is a data +# subdirectory inside the home path. +#path.data: ${path.home}/data + +# The logs path for a {{.BeatName | title}} installation. This is the default location for +# the Beat's log files. If not set by a CLI flag or in the configuration file, +# the default for the logs path is a logs subdirectory inside the home path. +#path.logs: ${path.home}/logs diff --git a/libbeat/_meta/config/processors.reference.yml.tmpl b/libbeat/_meta/config/processors.reference.yml.tmpl new file mode 100644 index 00000000000..c7c081b49bb --- /dev/null +++ b/libbeat/_meta/config/processors.reference.yml.tmpl @@ -0,0 +1,162 @@ +{{header "Processors"}} + +# Processors are used to reduce the number of fields in the exported event or to +# enhance the event with external metadata. This section defines a list of +# processors that are applied one by one and the first one receives the initial +# event: +# +# event -> filter1 -> event1 -> filter2 ->event2 ... +# +# The supported processors are drop_fields, drop_event, include_fields, +# decode_json_fields, and add_cloud_metadata. +# +# For example, you can use the following processors to keep the fields that +# contain CPU load percentages, but remove the fields that contain CPU ticks +# values: +# +#processors: +# - include_fields: +# fields: ["cpu"] +# - drop_fields: +# fields: ["cpu.user", "cpu.system"] +# +# The following example drops the events that have the HTTP response code 200: +# +#processors: +# - drop_event: +# when: +# equals: +# http.code: 200 +# +# The following example renames the field a to b: +# +#processors: +# - rename: +# fields: +# - from: "a" +# to: "b" +# +# The following example tokenizes the string into fields: +# +#processors: +# - dissect: +# tokenizer: "%{key1} - %{key2}" +# field: "message" +# target_prefix: "dissect" +# +# The following example enriches each event with metadata from the cloud +# provider about the host machine. It works on EC2, GCE, DigitalOcean, +# Tencent Cloud, and Alibaba Cloud. +# +#processors: +# - add_cloud_metadata: ~ +# +# The following example enriches each event with the machine's local time zone +# offset from UTC. +# +#processors: +# - add_locale: +# format: offset +# +# The following example enriches each event with docker metadata, it matches +# given fields to an existing container id and adds info from that container: +# +#processors: +# - add_docker_metadata: +# host: "unix:///var/run/docker.sock" +# match_fields: ["system.process.cgroup.id"] +# match_pids: ["process.pid", "process.ppid"] +# match_source: true +# match_source_index: 4 +# match_short_id: false +# cleanup_timeout: 60 +# labels.dedot: false +# # To connect to Docker over TLS you must specify a client and CA certificate. +# #ssl: +# # certificate_authority: "/etc/pki/root/ca.pem" +# # certificate: "/etc/pki/client/cert.pem" +# # key: "/etc/pki/client/cert.key" +# +# The following example enriches each event with docker metadata, it matches +# container id from log path available in `source` field (by default it expects +# it to be /var/lib/docker/containers/*/*.log). +# +#processors: +# - add_docker_metadata: ~ +# +# The following example enriches each event with host metadata. +# +#processors: +# - add_host_metadata: ~ +# +# The following example enriches each event with process metadata using +# process IDs included in the event. +# +#processors: +# - add_process_metadata: +# match_pids: ["system.process.ppid"] +# target: system.process.parent +# +# The following example decodes fields containing JSON strings +# and replaces the strings with valid JSON objects. +# +#processors: +# - decode_json_fields: +# fields: ["field1", "field2", ...] +# process_array: false +# max_depth: 1 +# target: "" +# overwrite_keys: false +# +#processors: +# - decompress_gzip_field: +# from: "field1" +# to: "field2" +# ignore_missing: false +# fail_on_error: true +# +# The following example copies the value of message to message_copied +# +#processors: +# - copy_fields: +# fields: +# - from: message +# to: message_copied +# fail_on_error: true +# ignore_missing: false +# +# The following example truncates the value of message to 1024 bytes +# +#processors: +# - truncate_fields: +# fields: +# - message +# max_bytes: 1024 +# fail_on_error: false +# ignore_missing: true +# +# The following example preserves the raw message under event.original +# +#processors: +# - copy_fields: +# fields: +# - from: message +# to: event.original +# fail_on_error: false +# ignore_missing: true +# - truncate_fields: +# fields: +# - event.original +# max_bytes: 1024 +# fail_on_error: false +# ignore_missing: true +# +# The following example URL-decodes the value of field1 to field2 +# +#processors: +# - urldecode: +# fields: +# - from: "field1" +# to: "field2" +# ignore_missing: false +# fail_on_error: true diff --git a/libbeat/_meta/config/processors.yml.tmpl b/libbeat/_meta/config/processors.yml.tmpl new file mode 100644 index 00000000000..b935f85dbac --- /dev/null +++ b/libbeat/_meta/config/processors.yml.tmpl @@ -0,0 +1,24 @@ +{{header "Processors"}} +{{if not .UseObserverProcessor}} +# Configure processors to enhance or manipulate events generated by the beat. + +processors: + - add_host_metadata: ~ + - add_cloud_metadata: ~ +{{- if .UseDockerMetadataProcessor }} + - add_docker_metadata: ~{{ end }} + +{{- if .UseKubernetesMetadataProcessor }} + - add_kubernetes_metadata: ~ +{{- else -}} +{{ end }} +{{else}} +processors: + - add_observer_metadata: + # Optional, but recommended geo settings for the location {{ .BeatName | title }} is running in + #geo: + # Token describing this location + #name: us-east-1a + # Lat, Lon " + #location: "37.926868, -78.024902" +{{end}} diff --git a/libbeat/_meta/config/seccomp.reference.yml.tmpl b/libbeat/_meta/config/seccomp.reference.yml.tmpl new file mode 100644 index 00000000000..54edc3326e3 --- /dev/null +++ b/libbeat/_meta/config/seccomp.reference.yml.tmpl @@ -0,0 +1,4 @@ +{{header "Process Security"}} + +# Enable or disable seccomp system call filtering on Linux. Default is enabled. +#seccomp.enabled: true diff --git a/libbeat/_meta/config/setup.dashboards.reference.yml.tmpl b/libbeat/_meta/config/setup.dashboards.reference.yml.tmpl new file mode 100644 index 00000000000..1d3b0798007 --- /dev/null +++ b/libbeat/_meta/config/setup.dashboards.reference.yml.tmpl @@ -0,0 +1,44 @@ +{{header "Dashboards"}} + +# These settings control loading the sample dashboards to the Kibana index. Loading +# the dashboards are disabled by default and can be enabled either by setting the +# options here, or by using the `-setup` CLI flag or the `setup` command. +#setup.dashboards.enabled: false + +# The directory from where to read the dashboards. The default is the `kibana` +# folder in the home path. +#setup.dashboards.directory: ${path.home}/kibana + +# The URL from where to download the dashboards archive. It is used instead of +# the directory if it has a value. +#setup.dashboards.url: + +# The file archive (zip file) from where to read the dashboards. It is used instead +# of the directory when it has a value. +#setup.dashboards.file: + +# In case the archive contains the dashboards from multiple Beats, this lets you +# select which one to load. You can load all the dashboards in the archive by +# setting this to the empty string. +#setup.dashboards.beat: {{.BeatName}} + +# The name of the Kibana index to use for setting the configuration. Default is ".kibana" +#setup.dashboards.kibana_index: .kibana + +# The Elasticsearch index name. This overwrites the index name defined in the +# dashboards and index pattern. Example: testbeat-* +#setup.dashboards.index: + +# Always use the Kibana API for loading the dashboards instead of autodetecting +# how to install the dashboards by first querying Elasticsearch. +#setup.dashboards.always_kibana: false + +# If true and Kibana is not reachable at the time when dashboards are loaded, +# it will retry to reconnect to Kibana instead of exiting with an error. +#setup.dashboards.retry.enabled: false + +# Duration interval between Kibana connection retries. +#setup.dashboards.retry.interval: 1s + +# Maximum number of retries before exiting with an error, 0 for unlimited retrying. +#setup.dashboards.retry.maximum: 0 diff --git a/libbeat/_meta/config/setup.dashboards.yml.tmpl b/libbeat/_meta/config/setup.dashboards.yml.tmpl new file mode 100644 index 00000000000..227b742a86e --- /dev/null +++ b/libbeat/_meta/config/setup.dashboards.yml.tmpl @@ -0,0 +1,11 @@ +{{header "Dashboards"}} +# These settings control loading the sample dashboards to the Kibana index. Loading +# the dashboards is disabled by default and can be enabled either by setting the +# options here or by using the `setup` command. +#setup.dashboards.enabled: false + +# The URL from where to download the dashboards archive. By default this URL +# has a value which is computed based on the Beat name and version. For released +# versions, this URL points to the dashboard archive on the artifacts.elastic.co +# website. +#setup.dashboards.url: diff --git a/libbeat/_meta/config/setup.ilm.reference.yml.tmpl b/libbeat/_meta/config/setup.ilm.reference.yml.tmpl new file mode 100644 index 00000000000..1416da0cc8d --- /dev/null +++ b/libbeat/_meta/config/setup.ilm.reference.yml.tmpl @@ -0,0 +1,34 @@ +{{header "Index Lifecycle Management (ILM)"}} + +# Configure index lifecycle management (ILM). These settings create a write +# alias and add additional settings to the index template. When ILM is enabled, +# output.elasticsearch.index is ignored, and the write alias is used to set the +# index name. + +# Enable ILM support. Valid values are true, false, and auto. When set to auto +# (the default), the Beat uses index lifecycle management when it connects to a +# cluster that supports ILM; otherwise, it creates daily indices. +#setup.ilm.enabled: auto + +# Set the prefix used in the index lifecycle write alias name. The default alias +# name is '{{.BeatName}}-%{[agent.version]}'. +#setup.ilm.rollover_alias: '{{.BeatIndexPrefix}}' + +# Set the rollover index pattern. The default is "%{now/d}-000001". +#setup.ilm.pattern: "{now/d}-000001" + +# Set the lifecycle policy name. The default policy name is +# 'beatname'. +#setup.ilm.policy_name: "mypolicy" + +# The path to a JSON file that contains a lifecycle policy configuration. Used +# to load your own lifecycle policy. +#setup.ilm.policy_file: + +# Disable the check for an existing lifecycle policy. The default is true. If +# you disable this check, set setup.ilm.overwrite: true so the lifecycle policy +# can be installed. +#setup.ilm.check_exists: true + +# Overwrite the lifecycle policy at startup. The default is false. +#setup.ilm.overwrite: false diff --git a/libbeat/_meta/config/setup.kibana.reference.yml.tmpl b/libbeat/_meta/config/setup.kibana.reference.yml.tmpl new file mode 100644 index 00000000000..603b3da4196 --- /dev/null +++ b/libbeat/_meta/config/setup.kibana.reference.yml.tmpl @@ -0,0 +1,54 @@ +{{header "Kibana"}} + +# Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API. +# This requires a Kibana endpoint configuration. +setup.kibana: + + # Kibana Host + # Scheme and port can be left out and will be set to the default (http and 5601) + # In case you specify and additional path, the scheme is required: http://localhost:5601/path + # IPv6 addresses should always be defined as: https://[2001:db8::1]:5601 + #host: "localhost:5601" + + # Optional protocol and basic auth credentials. + #protocol: "https" + #username: "elastic" + #password: "changeme" + + # Optional HTTP path + #path: "" + + # Optional Kibana space ID. + #space.id: "" + + # Use SSL settings for HTTPS. Default is true. + #ssl.enabled: true + + # Configure SSL verification mode. If `none` is configured, all server hosts + # and certificates will be accepted. In this mode, SSL based connections are + # susceptible to man-in-the-middle attacks. Use only for testing. Default is + # `full`. + #ssl.verification_mode: full + + # List of supported/valid TLS versions. By default all TLS versions from 1.1 + # up to 1.3 are enabled. + #ssl.supported_protocols: [TLSv1.1, TLSv1.2, TLSv1.3] + + # SSL configuration. The default is off. + # List of root certificates for HTTPS server verifications + #ssl.certificate_authorities: ["/etc/pki/root/ca.pem"] + + # Certificate for SSL client authentication + #ssl.certificate: "/etc/pki/client/cert.pem" + + # Client certificate key + #ssl.key: "/etc/pki/client/cert.key" + + # Optional passphrase for decrypting the certificate key. + #ssl.key_passphrase: '' + + # Configure cipher suites to be used for SSL connections + #ssl.cipher_suites: [] + + # Configure curve types for ECDHE-based cipher suites + #ssl.curve_types: [] diff --git a/libbeat/_meta/config/setup.kibana.yml.tmpl b/libbeat/_meta/config/setup.kibana.yml.tmpl new file mode 100644 index 00000000000..6954fb814c7 --- /dev/null +++ b/libbeat/_meta/config/setup.kibana.yml.tmpl @@ -0,0 +1,16 @@ +{{header "Kibana"}} + +# Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API. +# This requires a Kibana endpoint configuration. +setup.kibana: + + # Kibana Host + # Scheme and port can be left out and will be set to the default (http and 5601) + # In case you specify and additional path, the scheme is required: http://localhost:5601/path + # IPv6 addresses should always be defined as: https://[2001:db8::1]:5601 + #host: "localhost:5601" + + # Kibana Space ID + # ID of the Kibana Space into which the dashboards should be loaded. By default, + # the Default Space will be used. + #space.id: diff --git a/libbeat/_meta/config/setup.template.reference.yml.tmpl b/libbeat/_meta/config/setup.template.reference.yml.tmpl new file mode 100644 index 00000000000..48d23d9d0c9 --- /dev/null +++ b/libbeat/_meta/config/setup.template.reference.yml.tmpl @@ -0,0 +1,53 @@ +{{header "Template"}} + +# A template is used to set the mapping in Elasticsearch +# By default template loading is enabled and the template is loaded. +# These settings can be adjusted to load your own template or overwrite existing ones. + +# Set to false to disable template loading. +#setup.template.enabled: true + +# Template name. By default the template name is "{{.BeatIndexPrefix}}-%{[agent.version]}" +# The template name and pattern has to be set in case the Elasticsearch index pattern is modified. +#setup.template.name: "{{.BeatIndexPrefix}}-%{[agent.version]}" + +# Template pattern. By default the template pattern is "-%{[agent.version]}-*" to apply to the default index settings. +# The first part is the version of the beat and then -* is used to match all daily indices. +# The template name and pattern has to be set in case the Elasticsearch index pattern is modified. +#setup.template.pattern: "{{.BeatIndexPrefix}}-%{[agent.version]}-*" + +# Path to fields.yml file to generate the template +#setup.template.fields: "${path.config}/fields.yml" + +# A list of fields to be added to the template and Kibana index pattern. Also +# specify setup.template.overwrite: true to overwrite the existing template. +#setup.template.append_fields: +#- name: field_name +# type: field_type + +# Enable JSON template loading. If this is enabled, the fields.yml is ignored. +#setup.template.json.enabled: false + +# Path to the JSON template file +#setup.template.json.path: "${path.config}/template.json" + +# Name under which the template is stored in Elasticsearch +#setup.template.json.name: "" + +# Overwrite existing template +#setup.template.overwrite: false + +# Elasticsearch template settings +setup.template.settings: + + # A dictionary of settings to place into the settings.index dictionary + # of the Elasticsearch template. For more details, please check + # https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html + #index: + #number_of_shards: 1 + #codec: best_compression + + # A dictionary of settings for the _source field. For more details, please check + # https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-source-field.html + #_source: + #enabled: false diff --git a/libbeat/magefile.go b/libbeat/magefile.go index e5a22799b6d..67e84e63be3 100644 --- a/libbeat/magefile.go +++ b/libbeat/magefile.go @@ -49,5 +49,5 @@ func Fields() error { // Config generates example and reference configuration for libbeat. func Config() error { - return devtools.Config(devtools.ShortConfigType|devtools.ReferenceConfigType, devtools.ConfigFileParams{}, ".") + return devtools.Config(devtools.ShortConfigType|devtools.ReferenceConfigType, devtools.DefaultConfigFileParams(), ".") } diff --git a/metricbeat/_meta/beat.docker.yml b/metricbeat/_meta/config/beat.docker.yml.tmpl similarity index 98% rename from metricbeat/_meta/beat.docker.yml rename to metricbeat/_meta/config/beat.docker.yml.tmpl index 16b19a866dd..10c25e6a5cc 100644 --- a/metricbeat/_meta/beat.docker.yml +++ b/metricbeat/_meta/config/beat.docker.yml.tmpl @@ -1,4 +1,3 @@ metricbeat.config.modules: path: ${path.config}/modules.d/*.yml reload.enabled: false - diff --git a/metricbeat/_meta/config/beat.reference.yml.tmpl b/metricbeat/_meta/config/beat.reference.yml.tmpl new file mode 100644 index 00000000000..7d00457d5c6 --- /dev/null +++ b/metricbeat/_meta/config/beat.reference.yml.tmpl @@ -0,0 +1,2 @@ +{{template "header.reference.yml.tmpl" .}} +{{template "config.modules.yml.tmpl" .}} diff --git a/metricbeat/_meta/config/beat.yml.tmpl b/metricbeat/_meta/config/beat.yml.tmpl new file mode 100644 index 00000000000..f34d7c55515 --- /dev/null +++ b/metricbeat/_meta/config/beat.yml.tmpl @@ -0,0 +1,3 @@ +{{template "header.yml.tmpl" .}} +{{template "metricbeat.config.modules.yml.tmpl" .}} +{{template "setup.template.yml.tmpl" .}} diff --git a/metricbeat/_meta/common.reference.yml b/metricbeat/_meta/config/header.reference.yml.tmpl similarity index 100% rename from metricbeat/_meta/common.reference.yml rename to metricbeat/_meta/config/header.reference.yml.tmpl diff --git a/metricbeat/_meta/common.yml b/metricbeat/_meta/config/header.yml.tmpl similarity index 100% rename from metricbeat/_meta/common.yml rename to metricbeat/_meta/config/header.yml.tmpl diff --git a/metricbeat/_meta/config/metricbeat.config.modules.yml.tmpl b/metricbeat/_meta/config/metricbeat.config.modules.yml.tmpl new file mode 100644 index 00000000000..e616bc21b63 --- /dev/null +++ b/metricbeat/_meta/config/metricbeat.config.modules.yml.tmpl @@ -0,0 +1,11 @@ +{{header "Modules configuration"}} + +metricbeat.config.modules: + # Glob pattern for configuration loading + path: ${path.config}/modules.d/*.yml + + # Set to true to enable config reloading + reload.enabled: false + + # Period on which files under path should be checked for changes + #reload.period: 10s diff --git a/metricbeat/_meta/config/setup.template.yml.tmpl b/metricbeat/_meta/config/setup.template.yml.tmpl new file mode 100644 index 00000000000..2b8d28f2a30 --- /dev/null +++ b/metricbeat/_meta/config/setup.template.yml.tmpl @@ -0,0 +1,6 @@ +{{header "Elasticsearch template setting"}} + +setup.template.settings: + index.number_of_shards: 1 + index.codec: best_compression + #_source.enabled: false diff --git a/metricbeat/_meta/setup.yml b/metricbeat/_meta/setup.yml deleted file mode 100644 index 337067373f1..00000000000 --- a/metricbeat/_meta/setup.yml +++ /dev/null @@ -1,19 +0,0 @@ - -#========================== Modules configuration ============================ - -metricbeat.config.modules: - # Glob pattern for configuration loading - path: ${path.config}/modules.d/*.yml - - # Set to true to enable config reloading - reload.enabled: false - - # Period on which files under path should be checked for changes - #reload.period: 10s - -#==================== Elasticsearch template setting ========================== - -setup.template.settings: - index.number_of_shards: 1 - index.codec: best_compression - #_source.enabled: false diff --git a/metricbeat/metricbeat.reference.yml b/metricbeat/metricbeat.reference.yml index 1f9d6558620..ee6266559f0 100644 --- a/metricbeat/metricbeat.reference.yml +++ b/metricbeat/metricbeat.reference.yml @@ -53,6 +53,7 @@ metricbeat.max_start_delay: 10s #timeseries.enabled: false + #========================== Modules configuration ============================= metricbeat.modules: @@ -871,7 +872,8 @@ metricbeat.modules: -#================================ General ====================================== + +# ================================== General =================================== # The name of the shipper that publishes the network data. It can be used to group # all the transactions sent by a single shipper in the web interface. @@ -979,7 +981,7 @@ metricbeat.modules: # default is the number of logical CPUs available in the system. #max_procs: -#================================ Processors =================================== +# ================================= Processors ================================= # Processors are used to reduce the number of fields in the exported event or to # enhance the event with external metadata. This section defines a list of @@ -1142,7 +1144,7 @@ metricbeat.modules: # ignore_missing: false # fail_on_error: true -#============================= Elastic Cloud ================================== +# =============================== Elastic Cloud ================================ # These settings simplify using Metricbeat with the Elastic Cloud (https://cloud.elastic.co/). @@ -1155,11 +1157,11 @@ metricbeat.modules: # `output.elasticsearch.password` settings. The format is `:`. #cloud.auth: -#================================ Outputs ====================================== +# ================================== Outputs =================================== # Configure what output to use when sending the data collected by the beat. -#-------------------------- Elasticsearch output ------------------------------- +# ---------------------------- Elasticsearch Output ---------------------------- output.elasticsearch: # Boolean flag to enable or disable the output module. #enabled: true @@ -1300,7 +1302,7 @@ output.elasticsearch: # Kerberos realm. #kerberos.realm: ELASTIC -#----------------------------- Logstash output --------------------------------- +# ------------------------------ Logstash Output ------------------------------- #output.logstash: # Boolean flag to enable or disable the output module. #enabled: true @@ -1414,7 +1416,7 @@ output.elasticsearch: # timing out. The default is 30s. #timeout: 30s -#------------------------------- Kafka output ---------------------------------- +# -------------------------------- Kafka Output -------------------------------- #output.kafka: # Boolean flag to enable or disable the output module. #enabled: true @@ -1593,7 +1595,7 @@ output.elasticsearch: # Kerberos realm. #kerberos.realm: ELASTIC -#------------------------------- Redis output ---------------------------------- +# -------------------------------- Redis Output -------------------------------- #output.redis: # Boolean flag to enable or disable the output module. #enabled: true @@ -1711,7 +1713,7 @@ output.elasticsearch: # never, once, and freely. Default is never. #ssl.renegotiation: never -#------------------------------- File output ----------------------------------- +# -------------------------------- File Output --------------------------------- #output.file: # Boolean flag to enable or disable the output module. #enabled: true @@ -1745,7 +1747,7 @@ output.elasticsearch: # Permissions to use for file creation. The default is 0600. #permissions: 0600 -#----------------------------- Console output --------------------------------- +# ------------------------------- Console Output ------------------------------- #output.console: # Boolean flag to enable or disable the output module. #enabled: true @@ -1758,7 +1760,7 @@ output.elasticsearch: # Configure escaping HTML symbols in strings. #escape_html: false -#================================= Paths ====================================== +# =================================== Paths ==================================== # The home path for the Metricbeat installation. This is the default base path # for all other path settings and for miscellaneous files that come with the @@ -1784,11 +1786,13 @@ output.elasticsearch: # the default for the logs path is a logs subdirectory inside the home path. #path.logs: ${path.home}/logs -#================================ Keystore ========================================== +# ================================== Keystore ================================== + # Location of the Keystore containing the keys and their sensitive values. #keystore.path: "${path.config}/beats.keystore" -#============================== Dashboards ===================================== +# ================================= Dashboards ================================= + # These settings control loading the sample dashboards to the Kibana index. Loading # the dashboards are disabled by default and can be enabled either by setting the # options here, or by using the `-setup` CLI flag or the `setup` command. @@ -1832,8 +1836,7 @@ output.elasticsearch: # Maximum number of retries before exiting with an error, 0 for unlimited retrying. #setup.dashboards.retry.maximum: 0 - -#============================== Template ===================================== +# ================================== Template ================================== # A template is used to set the mapping in Elasticsearch # By default template loading is enabled and the template is loaded. @@ -1887,7 +1890,7 @@ setup.template.settings: #_source: #enabled: false -#============================== Setup ILM ===================================== +# ====================== Index Lifecycle Management (ILM) ====================== # Configure index lifecycle management (ILM). These settings create a write # alias and add additional settings to the index template. When ILM is enabled, @@ -1901,13 +1904,13 @@ setup.template.settings: # Set the prefix used in the index lifecycle write alias name. The default alias # name is 'metricbeat-%{[agent.version]}'. -#setup.ilm.rollover_alias: "metricbeat" +#setup.ilm.rollover_alias: 'metricbeat' # Set the rollover index pattern. The default is "%{now/d}-000001". #setup.ilm.pattern: "{now/d}-000001" # Set the lifecycle policy name. The default policy name is -# 'metricbeat'. +# 'beatname'. #setup.ilm.policy_name: "mypolicy" # The path to a JSON file that contains a lifecycle policy configuration. Used @@ -1922,7 +1925,7 @@ setup.template.settings: # Overwrite the lifecycle policy at startup. The default is false. #setup.ilm.overwrite: false -#============================== Kibana ===================================== +# =================================== Kibana =================================== # Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API. # This requires a Kibana endpoint configuration. @@ -1977,9 +1980,8 @@ setup.kibana: # Configure curve types for ECDHE-based cipher suites #ssl.curve_types: [] +# ================================== Logging =================================== - -#================================ Logging ====================================== # There are four options for the log output: file, stderr, syslog, eventlog # The file output is the default. @@ -2046,8 +2048,7 @@ logging.files: # Set to true to log messages in JSON format. #logging.json: false - -#============================== X-Pack Monitoring =============================== +# ============================= X-Pack Monitoring ============================== # Metricbeat can export internal metrics to a central Elasticsearch monitoring # cluster. This requires xpack monitoring to be enabled in Elasticsearch. The # reporting is disabled by default. @@ -2189,7 +2190,8 @@ logging.files: # and `monitoring.elasticsearch.password` settings. The format is `:`. #monitoring.cloud.auth: -#================================ HTTP Endpoint ====================================== +# =============================== HTTP Endpoint ================================ + # Each beat can expose internal metrics through a HTTP endpoint. For security # reasons the endpoint is disabled by default. This feature is currently experimental. # Stats can be access through http://localhost:5066/stats . For pretty JSON output @@ -2213,12 +2215,13 @@ logging.files: # `http.user`. #http.named_pipe.security_descriptor: -#============================= Process Security ================================ +# ============================== Process Security ============================== # Enable or disable seccomp system call filtering on Linux. Default is enabled. #seccomp.enabled: true -#================================= Migration ================================== +# ================================= Migration ================================== # This allows to enable 6.7 migration aliases #migration.6_to_7.enabled: false + diff --git a/metricbeat/metricbeat.yml b/metricbeat/metricbeat.yml index 5bd19f3030c..96975ee6027 100644 --- a/metricbeat/metricbeat.yml +++ b/metricbeat/metricbeat.yml @@ -7,7 +7,7 @@ # You can find the full configuration reference here: # https://www.elastic.co/guide/en/beats/metricbeat/index.html -#========================== Modules configuration ============================ +# =========================== Modules configuration ============================ metricbeat.config.modules: # Glob pattern for configuration loading @@ -19,14 +19,15 @@ metricbeat.config.modules: # Period on which files under path should be checked for changes #reload.period: 10s -#==================== Elasticsearch template setting ========================== +# ======================= Elasticsearch template setting ======================= setup.template.settings: index.number_of_shards: 1 index.codec: best_compression #_source.enabled: false -#================================ General ===================================== + +# ================================== General =================================== # The name of the shipper that publishes the network data. It can be used to group # all the transactions sent by a single shipper in the web interface. @@ -41,8 +42,7 @@ setup.template.settings: #fields: # env: staging - -#============================== Dashboards ===================================== +# ================================= Dashboards ================================= # These settings control loading the sample dashboards to the Kibana index. Loading # the dashboards is disabled by default and can be enabled either by setting the # options here or by using the `setup` command. @@ -54,7 +54,7 @@ setup.template.settings: # website. #setup.dashboards.url: -#============================== Kibana ===================================== +# =================================== Kibana =================================== # Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API. # This requires a Kibana endpoint configuration. @@ -71,7 +71,7 @@ setup.kibana: # the Default Space will be used. #space.id: -#============================= Elastic Cloud ================================== +# =============================== Elastic Cloud ================================ # These settings simplify using Metricbeat with the Elastic Cloud (https://cloud.elastic.co/). @@ -84,11 +84,11 @@ setup.kibana: # `output.elasticsearch.password` settings. The format is `:`. #cloud.auth: -#================================ Outputs ===================================== +# ================================== Outputs =================================== # Configure what output to use when sending the data collected by the beat. -#-------------------------- Elasticsearch output ------------------------------ +# ---------------------------- Elasticsearch Output ---------------------------- output.elasticsearch: # Array of hosts to connect to. hosts: ["localhost:9200"] @@ -101,7 +101,7 @@ output.elasticsearch: #username: "elastic" #password: "changeme" -#----------------------------- Logstash output -------------------------------- +# ------------------------------ Logstash Output ------------------------------- #output.logstash: # The Logstash hosts #hosts: ["localhost:5044"] @@ -116,7 +116,7 @@ output.elasticsearch: # Client Certificate Key #ssl.key: "/etc/pki/client/cert.key" -#================================ Processors ===================================== +# ================================= Processors ================================= # Configure processors to enhance or manipulate events generated by the beat. @@ -126,7 +126,8 @@ processors: - add_docker_metadata: ~ - add_kubernetes_metadata: ~ -#================================ Logging ===================================== + +# ================================== Logging =================================== # Sets log level. The default log level is info. # Available log levels are: error, warning, info, debug @@ -137,8 +138,8 @@ processors: # "publish", "service". #logging.selectors: ["*"] -#============================== X-Pack Monitoring =============================== -# metricbeat can export internal metrics to a central Elasticsearch monitoring +# ============================= X-Pack Monitoring ============================== +# Metricbeat can export internal metrics to a central Elasticsearch monitoring # cluster. This requires xpack monitoring to be enabled in Elasticsearch. The # reporting is disabled by default. @@ -159,7 +160,8 @@ processors: # uncomment the following line. #monitoring.elasticsearch: -#================================= Migration ================================== +# ================================= Migration ================================== # This allows to enable 6.7 migration aliases #migration.6_to_7.enabled: true + diff --git a/metricbeat/scripts/mage/config.go b/metricbeat/scripts/mage/config.go index ffa30d27bad..e49e223753f 100644 --- a/metricbeat/scripts/mage/config.go +++ b/metricbeat/scripts/mage/config.go @@ -18,36 +18,25 @@ package mage import ( + "github.com/magefile/mage/mg" + devtools "github.com/elastic/beats/v7/dev-tools/mage" ) -const modulesConfigYml = "build/config.modules.yml" +const modulesConfigYml = "build/config.modules.yml.tmpl" func configFileParams(moduleDirs ...string) devtools.ConfigFileParams { collectModuleConfig := func() error { return devtools.GenerateModuleReferenceConfig(modulesConfigYml, moduleDirs...) } + mg.Deps(collectModuleConfig) - return devtools.ConfigFileParams{ - ShortParts: []string{ - devtools.OSSBeatDir("_meta/common.yml"), - devtools.OSSBeatDir("_meta/setup.yml"), - devtools.LibbeatDir("_meta/config.yml.tmpl"), - }, - ReferenceDeps: []interface{}{collectModuleConfig}, - ReferenceParts: []string{ - devtools.OSSBeatDir("_meta/common.reference.yml"), - modulesConfigYml, - devtools.LibbeatDir("_meta/config.reference.yml.tmpl"), - }, - DockerParts: []string{ - devtools.OSSBeatDir("_meta/beat.docker.yml"), - devtools.LibbeatDir("_meta/config.docker.yml"), - }, - ExtraVars: map[string]interface{}{ - "UseKubernetesMetadataProcessor": true, - }, + p := devtools.DefaultConfigFileParams() + p.Templates = append(p.Templates, devtools.OSSBeatDir("_meta/config/*.tmpl"), modulesConfigYml) + p.ExtraVars = map[string]interface{}{ + "UseKubernetesMetadataProcessor": true, } + return p } // OSSConfigFileParams returns the default ConfigFileParams for generating @@ -59,11 +48,5 @@ func OSSConfigFileParams(moduleDirs ...string) devtools.ConfigFileParams { // XPackConfigFileParams returns the default ConfigFileParams for generating // metricbeat*.yml files. func XPackConfigFileParams() devtools.ConfigFileParams { - args := configFileParams(devtools.OSSBeatDir("module"), "module") - args.ReferenceParts = []string{ - devtools.OSSBeatDir("_meta/common.reference.yml"), - modulesConfigYml, - devtools.LibbeatDir("_meta/config.reference.yml.tmpl"), - } - return args + return configFileParams(devtools.OSSBeatDir("module"), "module") } diff --git a/packetbeat/_meta/beat.docker.yml b/packetbeat/_meta/config/beat.docker.yml.tmpl similarity index 99% rename from packetbeat/_meta/beat.docker.yml rename to packetbeat/_meta/config/beat.docker.yml.tmpl index 90c4c24e665..f4f0db1f7e6 100644 --- a/packetbeat/_meta/beat.docker.yml +++ b/packetbeat/_meta/config/beat.docker.yml.tmpl @@ -36,4 +36,3 @@ packetbeat.protocols.cassandra: packetbeat.protocols.tls: ports: [443, 993, 995, 5223, 8443, 8883, 9243] - diff --git a/packetbeat/_meta/beat.reference.yml b/packetbeat/_meta/config/beat.reference.yml.tmpl similarity index 98% rename from packetbeat/_meta/beat.reference.yml rename to packetbeat/_meta/config/beat.reference.yml.tmpl index 6ddd057b4c9..1a3aab315d7 100644 --- a/packetbeat/_meta/beat.reference.yml +++ b/packetbeat/_meta/config/beat.reference.yml.tmpl @@ -7,7 +7,7 @@ # You can find the full configuration reference here: # https://www.elastic.co/guide/en/beats/packetbeat/index.html -#============================== Network device ================================ +{{header "Network device"}} # Select the network interface to sniff the data. You can use the "any" # keyword to sniff on all connected interfaces. @@ -47,7 +47,7 @@ packetbeat.interfaces.device: {{ call .device .GOOS }} # can stay enabled even after beat is shut down. #packetbeat.interfaces.auto_promisc_mode: true -#================================== Flows ===================================== +{{header "Flows"}} packetbeat.flows: # Enable Network flows. Default: true @@ -63,7 +63,7 @@ packetbeat.flows: # Set to true to publish fields with null values in events. #keep_null: false -#========================== Transaction protocols ============================= +{{header "Transaction protocols"}} packetbeat.protocols: - type: icmp @@ -531,7 +531,7 @@ packetbeat.protocols: # Set to true to publish fields with null values in events. #keep_null: false -#=========================== Monitored processes ============================== +{{header "Monitored processes"}} # Packetbeat can enrich events with information about the process associated # the socket that sent or received the packet if Packetbeat is monitoring diff --git a/packetbeat/_meta/beat.yml b/packetbeat/_meta/config/beat.yml.tmpl similarity index 91% rename from packetbeat/_meta/beat.yml rename to packetbeat/_meta/config/beat.yml.tmpl index 8c8037a5e7c..fb221cba3c9 100644 --- a/packetbeat/_meta/beat.yml +++ b/packetbeat/_meta/config/beat.yml.tmpl @@ -7,13 +7,13 @@ # You can find the full configuration reference here: # https://www.elastic.co/guide/en/beats/packetbeat/index.html -#============================== Network device ================================ +{{header "Network device"}} # Select the network interface to sniff the data. On Linux, you can use the # "any" keyword to sniff on all connected interfaces. packetbeat.interfaces.device: {{ call .device .GOOS }} -#================================== Flows ===================================== +{{header "Flows"}} # Set `enabled: false` or comment out all options to disable flows reporting. packetbeat.flows: @@ -24,7 +24,7 @@ packetbeat.flows: # Configure reporting period. If set to -1, only killed flows will be reported period: 10s -#========================== Transaction protocols ============================= +{{header "Transaction protocols"}} packetbeat.protocols: - type: icmp @@ -101,7 +101,7 @@ packetbeat.protocols: - 8883 # Secure MQTT - 9243 # Elasticsearch -#==================== Elasticsearch template setting ========================== +{{header "Elasticsearch template setting"}} setup.template.settings: index.number_of_shards: 1 diff --git a/packetbeat/packetbeat.reference.yml b/packetbeat/packetbeat.reference.yml index 3132c03bb9d..8e66830c5cf 100644 --- a/packetbeat/packetbeat.reference.yml +++ b/packetbeat/packetbeat.reference.yml @@ -7,7 +7,7 @@ # You can find the full configuration reference here: # https://www.elastic.co/guide/en/beats/packetbeat/index.html -#============================== Network device ================================ +# =============================== Network device =============================== # Select the network interface to sniff the data. You can use the "any" # keyword to sniff on all connected interfaces. @@ -47,7 +47,7 @@ packetbeat.interfaces.device: any # can stay enabled even after beat is shut down. #packetbeat.interfaces.auto_promisc_mode: true -#================================== Flows ===================================== +# =================================== Flows ==================================== packetbeat.flows: # Enable Network flows. Default: true @@ -63,7 +63,7 @@ packetbeat.flows: # Set to true to publish fields with null values in events. #keep_null: false -#========================== Transaction protocols ============================= +# =========================== Transaction protocols ============================ packetbeat.protocols: - type: icmp @@ -531,7 +531,7 @@ packetbeat.protocols: # Set to true to publish fields with null values in events. #keep_null: false -#=========================== Monitored processes ============================== +# ============================ Monitored processes ============================= # Packetbeat can enrich events with information about the process associated # the socket that sent or received the packet if Packetbeat is monitoring @@ -545,7 +545,7 @@ packetbeat.procs.enabled: false # false. packetbeat.ignore_outgoing: false -#================================ General ====================================== +# ================================== General =================================== # The name of the shipper that publishes the network data. It can be used to group # all the transactions sent by a single shipper in the web interface. @@ -653,7 +653,7 @@ packetbeat.ignore_outgoing: false # default is the number of logical CPUs available in the system. #max_procs: -#================================ Processors =================================== +# ================================= Processors ================================= # Processors are used to reduce the number of fields in the exported event or to # enhance the event with external metadata. This section defines a list of @@ -816,7 +816,7 @@ packetbeat.ignore_outgoing: false # ignore_missing: false # fail_on_error: true -#============================= Elastic Cloud ================================== +# =============================== Elastic Cloud ================================ # These settings simplify using Packetbeat with the Elastic Cloud (https://cloud.elastic.co/). @@ -829,11 +829,11 @@ packetbeat.ignore_outgoing: false # `output.elasticsearch.password` settings. The format is `:`. #cloud.auth: -#================================ Outputs ====================================== +# ================================== Outputs =================================== # Configure what output to use when sending the data collected by the beat. -#-------------------------- Elasticsearch output ------------------------------- +# ---------------------------- Elasticsearch Output ---------------------------- output.elasticsearch: # Boolean flag to enable or disable the output module. #enabled: true @@ -974,7 +974,7 @@ output.elasticsearch: # Kerberos realm. #kerberos.realm: ELASTIC -#----------------------------- Logstash output --------------------------------- +# ------------------------------ Logstash Output ------------------------------- #output.logstash: # Boolean flag to enable or disable the output module. #enabled: true @@ -1088,7 +1088,7 @@ output.elasticsearch: # timing out. The default is 30s. #timeout: 30s -#------------------------------- Kafka output ---------------------------------- +# -------------------------------- Kafka Output -------------------------------- #output.kafka: # Boolean flag to enable or disable the output module. #enabled: true @@ -1267,7 +1267,7 @@ output.elasticsearch: # Kerberos realm. #kerberos.realm: ELASTIC -#------------------------------- Redis output ---------------------------------- +# -------------------------------- Redis Output -------------------------------- #output.redis: # Boolean flag to enable or disable the output module. #enabled: true @@ -1385,7 +1385,7 @@ output.elasticsearch: # never, once, and freely. Default is never. #ssl.renegotiation: never -#------------------------------- File output ----------------------------------- +# -------------------------------- File Output --------------------------------- #output.file: # Boolean flag to enable or disable the output module. #enabled: true @@ -1419,7 +1419,7 @@ output.elasticsearch: # Permissions to use for file creation. The default is 0600. #permissions: 0600 -#----------------------------- Console output --------------------------------- +# ------------------------------- Console Output ------------------------------- #output.console: # Boolean flag to enable or disable the output module. #enabled: true @@ -1432,7 +1432,7 @@ output.elasticsearch: # Configure escaping HTML symbols in strings. #escape_html: false -#================================= Paths ====================================== +# =================================== Paths ==================================== # The home path for the Packetbeat installation. This is the default base path # for all other path settings and for miscellaneous files that come with the @@ -1458,11 +1458,13 @@ output.elasticsearch: # the default for the logs path is a logs subdirectory inside the home path. #path.logs: ${path.home}/logs -#================================ Keystore ========================================== +# ================================== Keystore ================================== + # Location of the Keystore containing the keys and their sensitive values. #keystore.path: "${path.config}/beats.keystore" -#============================== Dashboards ===================================== +# ================================= Dashboards ================================= + # These settings control loading the sample dashboards to the Kibana index. Loading # the dashboards are disabled by default and can be enabled either by setting the # options here, or by using the `-setup` CLI flag or the `setup` command. @@ -1506,8 +1508,7 @@ output.elasticsearch: # Maximum number of retries before exiting with an error, 0 for unlimited retrying. #setup.dashboards.retry.maximum: 0 - -#============================== Template ===================================== +# ================================== Template ================================== # A template is used to set the mapping in Elasticsearch # By default template loading is enabled and the template is loaded. @@ -1561,7 +1562,7 @@ setup.template.settings: #_source: #enabled: false -#============================== Setup ILM ===================================== +# ====================== Index Lifecycle Management (ILM) ====================== # Configure index lifecycle management (ILM). These settings create a write # alias and add additional settings to the index template. When ILM is enabled, @@ -1575,13 +1576,13 @@ setup.template.settings: # Set the prefix used in the index lifecycle write alias name. The default alias # name is 'packetbeat-%{[agent.version]}'. -#setup.ilm.rollover_alias: "packetbeat" +#setup.ilm.rollover_alias: 'packetbeat' # Set the rollover index pattern. The default is "%{now/d}-000001". #setup.ilm.pattern: "{now/d}-000001" # Set the lifecycle policy name. The default policy name is -# 'packetbeat'. +# 'beatname'. #setup.ilm.policy_name: "mypolicy" # The path to a JSON file that contains a lifecycle policy configuration. Used @@ -1596,7 +1597,7 @@ setup.template.settings: # Overwrite the lifecycle policy at startup. The default is false. #setup.ilm.overwrite: false -#============================== Kibana ===================================== +# =================================== Kibana =================================== # Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API. # This requires a Kibana endpoint configuration. @@ -1651,9 +1652,8 @@ setup.kibana: # Configure curve types for ECDHE-based cipher suites #ssl.curve_types: [] +# ================================== Logging =================================== - -#================================ Logging ====================================== # There are four options for the log output: file, stderr, syslog, eventlog # The file output is the default. @@ -1720,8 +1720,7 @@ logging.files: # Set to true to log messages in JSON format. #logging.json: false - -#============================== X-Pack Monitoring =============================== +# ============================= X-Pack Monitoring ============================== # Packetbeat can export internal metrics to a central Elasticsearch monitoring # cluster. This requires xpack monitoring to be enabled in Elasticsearch. The # reporting is disabled by default. @@ -1863,7 +1862,8 @@ logging.files: # and `monitoring.elasticsearch.password` settings. The format is `:`. #monitoring.cloud.auth: -#================================ HTTP Endpoint ====================================== +# =============================== HTTP Endpoint ================================ + # Each beat can expose internal metrics through a HTTP endpoint. For security # reasons the endpoint is disabled by default. This feature is currently experimental. # Stats can be access through http://localhost:5066/stats . For pretty JSON output @@ -1887,12 +1887,13 @@ logging.files: # `http.user`. #http.named_pipe.security_descriptor: -#============================= Process Security ================================ +# ============================== Process Security ============================== # Enable or disable seccomp system call filtering on Linux. Default is enabled. #seccomp.enabled: true -#================================= Migration ================================== +# ================================= Migration ================================== # This allows to enable 6.7 migration aliases #migration.6_to_7.enabled: false + diff --git a/packetbeat/packetbeat.yml b/packetbeat/packetbeat.yml index 9d06f982f61..66e1bc85991 100644 --- a/packetbeat/packetbeat.yml +++ b/packetbeat/packetbeat.yml @@ -7,13 +7,13 @@ # You can find the full configuration reference here: # https://www.elastic.co/guide/en/beats/packetbeat/index.html -#============================== Network device ================================ +# =============================== Network device =============================== # Select the network interface to sniff the data. On Linux, you can use the # "any" keyword to sniff on all connected interfaces. packetbeat.interfaces.device: any -#================================== Flows ===================================== +# =================================== Flows ==================================== # Set `enabled: false` or comment out all options to disable flows reporting. packetbeat.flows: @@ -24,7 +24,7 @@ packetbeat.flows: # Configure reporting period. If set to -1, only killed flows will be reported period: 10s -#========================== Transaction protocols ============================= +# =========================== Transaction protocols ============================ packetbeat.protocols: - type: icmp @@ -101,14 +101,14 @@ packetbeat.protocols: - 8883 # Secure MQTT - 9243 # Elasticsearch -#==================== Elasticsearch template setting ========================== +# ======================= Elasticsearch template setting ======================= setup.template.settings: index.number_of_shards: 1 #index.codec: best_compression #_source.enabled: false -#================================ General ===================================== +# ================================== General =================================== # The name of the shipper that publishes the network data. It can be used to group # all the transactions sent by a single shipper in the web interface. @@ -123,8 +123,7 @@ setup.template.settings: #fields: # env: staging - -#============================== Dashboards ===================================== +# ================================= Dashboards ================================= # These settings control loading the sample dashboards to the Kibana index. Loading # the dashboards is disabled by default and can be enabled either by setting the # options here or by using the `setup` command. @@ -136,7 +135,7 @@ setup.template.settings: # website. #setup.dashboards.url: -#============================== Kibana ===================================== +# =================================== Kibana =================================== # Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API. # This requires a Kibana endpoint configuration. @@ -153,7 +152,7 @@ setup.kibana: # the Default Space will be used. #space.id: -#============================= Elastic Cloud ================================== +# =============================== Elastic Cloud ================================ # These settings simplify using Packetbeat with the Elastic Cloud (https://cloud.elastic.co/). @@ -166,11 +165,11 @@ setup.kibana: # `output.elasticsearch.password` settings. The format is `:`. #cloud.auth: -#================================ Outputs ===================================== +# ================================== Outputs =================================== # Configure what output to use when sending the data collected by the beat. -#-------------------------- Elasticsearch output ------------------------------ +# ---------------------------- Elasticsearch Output ---------------------------- output.elasticsearch: # Array of hosts to connect to. hosts: ["localhost:9200"] @@ -183,7 +182,7 @@ output.elasticsearch: #username: "elastic" #password: "changeme" -#----------------------------- Logstash output -------------------------------- +# ------------------------------ Logstash Output ------------------------------- #output.logstash: # The Logstash hosts #hosts: ["localhost:5044"] @@ -198,7 +197,7 @@ output.elasticsearch: # Client Certificate Key #ssl.key: "/etc/pki/client/cert.key" -#================================ Processors ===================================== +# ================================= Processors ================================= # Configure processors to enhance or manipulate events generated by the beat. @@ -207,7 +206,8 @@ processors: - add_cloud_metadata: ~ - add_docker_metadata: ~ -#================================ Logging ===================================== + +# ================================== Logging =================================== # Sets log level. The default log level is info. # Available log levels are: error, warning, info, debug @@ -218,8 +218,8 @@ processors: # "publish", "service". #logging.selectors: ["*"] -#============================== X-Pack Monitoring =============================== -# packetbeat can export internal metrics to a central Elasticsearch monitoring +# ============================= X-Pack Monitoring ============================== +# Packetbeat can export internal metrics to a central Elasticsearch monitoring # cluster. This requires xpack monitoring to be enabled in Elasticsearch. The # reporting is disabled by default. @@ -240,7 +240,8 @@ processors: # uncomment the following line. #monitoring.elasticsearch: -#================================= Migration ================================== +# ================================= Migration ================================== # This allows to enable 6.7 migration aliases #migration.6_to_7.enabled: true + diff --git a/packetbeat/scripts/mage/config.go b/packetbeat/scripts/mage/config.go index fac457cd7ef..a143cda22e7 100644 --- a/packetbeat/scripts/mage/config.go +++ b/packetbeat/scripts/mage/config.go @@ -43,24 +43,10 @@ func device(goos string) string { // ConfigFileParams returns the default ConfigFileParams for generating // packetbeat*.yml files. func ConfigFileParams() devtools.ConfigFileParams { - return devtools.ConfigFileParams{ - ShortParts: []string{ - devtools.OSSBeatDir("_meta/beat.yml"), - configTemplateGlob, - devtools.LibbeatDir("_meta/config.yml.tmpl"), - }, - ReferenceParts: []string{ - devtools.OSSBeatDir("_meta/beat.reference.yml"), - configTemplateGlob, - devtools.LibbeatDir("_meta/config.reference.yml.tmpl"), - }, - DockerParts: []string{ - devtools.OSSBeatDir("_meta/beat.docker.yml"), - configTemplateGlob, - devtools.LibbeatDir("_meta/config.docker.yml"), - }, - ExtraVars: map[string]interface{}{ - "device": device, - }, + p := devtools.DefaultConfigFileParams() + p.Templates = append(p.Templates, devtools.OSSBeatDir("_meta/config/*.tmpl")) + p.ExtraVars = map[string]interface{}{ + "device": device, } + return p } diff --git a/winlogbeat/_meta/beat.reference.yml b/winlogbeat/_meta/beat.reference.yml deleted file mode 100644 index c3f14bd412c..00000000000 --- a/winlogbeat/_meta/beat.reference.yml +++ /dev/null @@ -1,34 +0,0 @@ -########################## Winlogbeat Configuration ########################### - -# This file is a full configuration example documenting all non-deprecated -# options in comments. For a shorter configuration example, that contains only -# the most common options, please see winlogbeat.yml in the same directory. -# -# You can find the full configuration reference here: -# https://www.elastic.co/guide/en/beats/winlogbeat/index.html - -#======================= Winlogbeat specific options ========================== - -# The registry file is where Winlogbeat persists its state so that the beat -# can resume after shutdown or an outage. The default is .winlogbeat.yml -# in the directory in which it was started. -#winlogbeat.registry_file: .winlogbeat.yml - -# The maximum amount of time Winlogbeat should wait for events to finish -# publishing when shutting down. -#winlogbeat.shutdown_timeout: 0s - -# event_logs specifies a list of event logs to monitor as well as any -# accompanying options. The YAML data type of event_logs is a list of -# dictionaries. -# -# The supported keys are name (required), tags, fields, fields_under_root, -# forwarded, ignore_older, level, no_more_events, event_id, provider, and -# include_xml. Please visit the documentation for the complete details of each -# option. -# https://go.es.io/WinlogbeatConfig -winlogbeat.event_logs: - - name: Application - ignore_older: 72h - - name: Security - - name: System diff --git a/winlogbeat/_meta/config/beat.reference.yml.tmpl b/winlogbeat/_meta/config/beat.reference.yml.tmpl new file mode 100644 index 00000000000..e84dd482b55 --- /dev/null +++ b/winlogbeat/_meta/config/beat.reference.yml.tmpl @@ -0,0 +1,2 @@ +{{template "header.yml.tmpl" .}} +{{template "winlogbeat.event_logs.yml.tmpl" .}} diff --git a/winlogbeat/_meta/config/beat.yml.tmpl b/winlogbeat/_meta/config/beat.yml.tmpl new file mode 100644 index 00000000000..39cc766b2b3 --- /dev/null +++ b/winlogbeat/_meta/config/beat.yml.tmpl @@ -0,0 +1,3 @@ +{{template "header.yml.tmpl" .}} +{{template "winlogbeat.event_logs.yml.tmpl" .}} +{{template "setup.template.yml.tmpl" .}} diff --git a/winlogbeat/_meta/common.yml.tmpl b/winlogbeat/_meta/config/header.yml.tmpl similarity index 66% rename from winlogbeat/_meta/common.yml.tmpl rename to winlogbeat/_meta/config/header.yml.tmpl index 63aa30fa0b5..7ace0063a18 100644 --- a/winlogbeat/_meta/common.yml.tmpl +++ b/winlogbeat/_meta/config/header.yml.tmpl @@ -1,4 +1,3 @@ -{{define "header" -}} ###################### Winlogbeat Configuration Example ######################## # This file is an example configuration file highlighting only the most common @@ -8,7 +7,7 @@ # You can find the full configuration reference here: # https://www.elastic.co/guide/en/beats/winlogbeat/index.html -#======================= Winlogbeat specific options =========================== +{{header "Winlogbeat specific options"}} {{if .Reference -}} # The registry file is where Winlogbeat persists its state so that the beat can @@ -24,19 +23,4 @@ # The supported keys are name (required), tags, fields, fields_under_root, # forwarded, ignore_older, level, event_id, provider, and include_xml. Please # visit the documentation for the complete details of each option. -# https://go.es.io/WinlogbeatConfig{{end -}} - -{{define "elasticsearch_settings" -}} -#==================== Elasticsearch template settings ========================== - -setup.template.settings: - index.number_of_shards: 1 - #index.codec: best_compression - #_source.enabled: false -{{end -}} -{{define "processors.yml.tmpl"}} -processors: - - add_host_metadata: - when.not.contains.tags: forwarded - - add_cloud_metadata: ~ -{{end -}} +# https://go.es.io/WinlogbeatConfig diff --git a/winlogbeat/_meta/config/processors.yml.tmpl b/winlogbeat/_meta/config/processors.yml.tmpl new file mode 100644 index 00000000000..4642130fe8b --- /dev/null +++ b/winlogbeat/_meta/config/processors.yml.tmpl @@ -0,0 +1,5 @@ +{{header "Processors"}} +processors: + - add_host_metadata: + when.not.contains.tags: forwarded + - add_cloud_metadata: ~ diff --git a/winlogbeat/_meta/config/setup.template.yml.tmpl b/winlogbeat/_meta/config/setup.template.yml.tmpl new file mode 100644 index 00000000000..4eb23e9da92 --- /dev/null +++ b/winlogbeat/_meta/config/setup.template.yml.tmpl @@ -0,0 +1,6 @@ +{{header "Elasticsearch template settings"}} + +setup.template.settings: + index.number_of_shards: 1 + #index.codec: best_compression + #_source.enabled: false diff --git a/winlogbeat/_meta/beat.yml.tmpl b/winlogbeat/_meta/config/winlogbeat.event_logs.yml.tmpl similarity index 61% rename from winlogbeat/_meta/beat.yml.tmpl rename to winlogbeat/_meta/config/winlogbeat.event_logs.yml.tmpl index 093c6f69c04..64a01bb8352 100644 --- a/winlogbeat/_meta/beat.yml.tmpl +++ b/winlogbeat/_meta/config/winlogbeat.event_logs.yml.tmpl @@ -1,4 +1,3 @@ -{{ template "header" . }} winlogbeat.event_logs: - name: Application ignore_older: 72h @@ -9,4 +8,3 @@ winlogbeat.event_logs: - name: ForwardedEvents tags: [forwarded] -{{if not .Reference}}{{ template "elasticsearch_settings" . }}{{end}} diff --git a/winlogbeat/scripts/mage/config.go b/winlogbeat/scripts/mage/config.go index 70cc8cb43a6..15240127193 100644 --- a/winlogbeat/scripts/mage/config.go +++ b/winlogbeat/scripts/mage/config.go @@ -28,34 +28,11 @@ func config() error { } func configFileParams() devtools.ConfigFileParams { - beatDir := devtools.OSSBeatDir - switch SelectLogic { - case devtools.OSSProject: - beatDir = devtools.OSSBeatDir - case devtools.XPackProject: - beatDir = devtools.XPackBeatDir - default: - panic(devtools.ErrUnknownProjectType) - } - - return devtools.ConfigFileParams{ - ShortParts: []string{ - devtools.OSSBeatDir("_meta/common.yml.tmpl"), - beatDir("_meta/beat.yml.tmpl"), - devtools.LibbeatDir("_meta/config.yml.tmpl"), - }, - ReferenceParts: []string{ - devtools.OSSBeatDir("_meta/common.yml.tmpl"), - beatDir("_meta/beat.yml.tmpl"), - devtools.LibbeatDir("_meta/config.reference.yml.tmpl"), - }, - DockerParts: []string{ - devtools.OSSBeatDir("_meta/beat.docker.yml"), - devtools.LibbeatDir("_meta/config.docker.yml"), - }, - ExtraVars: map[string]interface{}{ - "GOOS": "windows", - "UseProcessorsTemplate": true, - }, + conf := devtools.DefaultConfigFileParams() + conf.ExtraVars = map[string]interface{}{"GOOS": "windows"} + conf.Templates = append(conf.Templates, devtools.OSSBeatDir("_meta/config/*.tmpl")) + if devtools.XPackProject == SelectLogic { + conf.Templates = append(conf.Templates, devtools.XPackBeatDir("_meta/config/*.tmpl")) } + return conf } diff --git a/winlogbeat/winlogbeat.reference.yml b/winlogbeat/winlogbeat.reference.yml index 9dca185c13a..aee40b39bf9 100644 --- a/winlogbeat/winlogbeat.reference.yml +++ b/winlogbeat/winlogbeat.reference.yml @@ -7,7 +7,7 @@ # You can find the full configuration reference here: # https://www.elastic.co/guide/en/beats/winlogbeat/index.html -#======================= Winlogbeat specific options =========================== +# ======================== Winlogbeat specific options ========================= # The registry file is where Winlogbeat persists its state so that the beat can # resume after shutdown or an outage. The default is .winlogbeat.yml in the @@ -22,6 +22,7 @@ # forwarded, ignore_older, level, event_id, provider, and include_xml. Please # visit the documentation for the complete details of each option. # https://go.es.io/WinlogbeatConfig + winlogbeat.event_logs: - name: Application ignore_older: 72h @@ -34,7 +35,7 @@ winlogbeat.event_logs: tags: [forwarded] -#================================ General ====================================== +# ================================== General =================================== # The name of the shipper that publishes the network data. It can be used to group # all the transactions sent by a single shipper in the web interface. @@ -142,7 +143,7 @@ winlogbeat.event_logs: # default is the number of logical CPUs available in the system. #max_procs: -#================================ Processors =================================== +# ================================= Processors ================================= # Processors are used to reduce the number of fields in the exported event or to # enhance the event with external metadata. This section defines a list of @@ -305,7 +306,7 @@ winlogbeat.event_logs: # ignore_missing: false # fail_on_error: true -#============================= Elastic Cloud ================================== +# =============================== Elastic Cloud ================================ # These settings simplify using Winlogbeat with the Elastic Cloud (https://cloud.elastic.co/). @@ -318,11 +319,11 @@ winlogbeat.event_logs: # `output.elasticsearch.password` settings. The format is `:`. #cloud.auth: -#================================ Outputs ====================================== +# ================================== Outputs =================================== # Configure what output to use when sending the data collected by the beat. -#-------------------------- Elasticsearch output ------------------------------- +# ---------------------------- Elasticsearch Output ---------------------------- output.elasticsearch: # Boolean flag to enable or disable the output module. #enabled: true @@ -463,7 +464,7 @@ output.elasticsearch: # Kerberos realm. #kerberos.realm: ELASTIC -#----------------------------- Logstash output --------------------------------- +# ------------------------------ Logstash Output ------------------------------- #output.logstash: # Boolean flag to enable or disable the output module. #enabled: true @@ -577,7 +578,7 @@ output.elasticsearch: # timing out. The default is 30s. #timeout: 30s -#------------------------------- Kafka output ---------------------------------- +# -------------------------------- Kafka Output -------------------------------- #output.kafka: # Boolean flag to enable or disable the output module. #enabled: true @@ -756,7 +757,7 @@ output.elasticsearch: # Kerberos realm. #kerberos.realm: ELASTIC -#------------------------------- Redis output ---------------------------------- +# -------------------------------- Redis Output -------------------------------- #output.redis: # Boolean flag to enable or disable the output module. #enabled: true @@ -874,7 +875,7 @@ output.elasticsearch: # never, once, and freely. Default is never. #ssl.renegotiation: never -#------------------------------- File output ----------------------------------- +# -------------------------------- File Output --------------------------------- #output.file: # Boolean flag to enable or disable the output module. #enabled: true @@ -908,7 +909,7 @@ output.elasticsearch: # Permissions to use for file creation. The default is 0600. #permissions: 0600 -#----------------------------- Console output --------------------------------- +# ------------------------------- Console Output ------------------------------- #output.console: # Boolean flag to enable or disable the output module. #enabled: true @@ -921,7 +922,7 @@ output.elasticsearch: # Configure escaping HTML symbols in strings. #escape_html: false -#================================= Paths ====================================== +# =================================== Paths ==================================== # The home path for the Winlogbeat installation. This is the default base path # for all other path settings and for miscellaneous files that come with the @@ -947,11 +948,13 @@ output.elasticsearch: # the default for the logs path is a logs subdirectory inside the home path. #path.logs: ${path.home}/logs -#================================ Keystore ========================================== +# ================================== Keystore ================================== + # Location of the Keystore containing the keys and their sensitive values. #keystore.path: "${path.config}/beats.keystore" -#============================== Dashboards ===================================== +# ================================= Dashboards ================================= + # These settings control loading the sample dashboards to the Kibana index. Loading # the dashboards are disabled by default and can be enabled either by setting the # options here, or by using the `-setup` CLI flag or the `setup` command. @@ -995,8 +998,7 @@ output.elasticsearch: # Maximum number of retries before exiting with an error, 0 for unlimited retrying. #setup.dashboards.retry.maximum: 0 - -#============================== Template ===================================== +# ================================== Template ================================== # A template is used to set the mapping in Elasticsearch # By default template loading is enabled and the template is loaded. @@ -1050,7 +1052,7 @@ setup.template.settings: #_source: #enabled: false -#============================== Setup ILM ===================================== +# ====================== Index Lifecycle Management (ILM) ====================== # Configure index lifecycle management (ILM). These settings create a write # alias and add additional settings to the index template. When ILM is enabled, @@ -1064,13 +1066,13 @@ setup.template.settings: # Set the prefix used in the index lifecycle write alias name. The default alias # name is 'winlogbeat-%{[agent.version]}'. -#setup.ilm.rollover_alias: "winlogbeat" +#setup.ilm.rollover_alias: 'winlogbeat' # Set the rollover index pattern. The default is "%{now/d}-000001". #setup.ilm.pattern: "{now/d}-000001" # Set the lifecycle policy name. The default policy name is -# 'winlogbeat'. +# 'beatname'. #setup.ilm.policy_name: "mypolicy" # The path to a JSON file that contains a lifecycle policy configuration. Used @@ -1085,7 +1087,7 @@ setup.template.settings: # Overwrite the lifecycle policy at startup. The default is false. #setup.ilm.overwrite: false -#============================== Kibana ===================================== +# =================================== Kibana =================================== # Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API. # This requires a Kibana endpoint configuration. @@ -1140,9 +1142,8 @@ setup.kibana: # Configure curve types for ECDHE-based cipher suites #ssl.curve_types: [] +# ================================== Logging =================================== - -#================================ Logging ====================================== # There are four options for the log output: file, stderr, syslog, eventlog # The file output is the default. @@ -1209,8 +1210,7 @@ logging.files: # Set to true to log messages in JSON format. #logging.json: false - -#============================== X-Pack Monitoring =============================== +# ============================= X-Pack Monitoring ============================== # Winlogbeat can export internal metrics to a central Elasticsearch monitoring # cluster. This requires xpack monitoring to be enabled in Elasticsearch. The # reporting is disabled by default. @@ -1352,7 +1352,8 @@ logging.files: # and `monitoring.elasticsearch.password` settings. The format is `:`. #monitoring.cloud.auth: -#================================ HTTP Endpoint ====================================== +# =============================== HTTP Endpoint ================================ + # Each beat can expose internal metrics through a HTTP endpoint. For security # reasons the endpoint is disabled by default. This feature is currently experimental. # Stats can be access through http://localhost:5066/stats . For pretty JSON output @@ -1376,12 +1377,13 @@ logging.files: # `http.user`. #http.named_pipe.security_descriptor: -#============================= Process Security ================================ +# ============================== Process Security ============================== # Enable or disable seccomp system call filtering on Linux. Default is enabled. #seccomp.enabled: true -#================================= Migration ================================== +# ================================= Migration ================================== # This allows to enable 6.7 migration aliases #migration.6_to_7.enabled: false + diff --git a/winlogbeat/winlogbeat.yml b/winlogbeat/winlogbeat.yml index d816327de99..d1308e2a2ed 100644 --- a/winlogbeat/winlogbeat.yml +++ b/winlogbeat/winlogbeat.yml @@ -7,7 +7,7 @@ # You can find the full configuration reference here: # https://www.elastic.co/guide/en/beats/winlogbeat/index.html -#======================= Winlogbeat specific options =========================== +# ======================== Winlogbeat specific options ========================= # event_logs specifies a list of event logs to monitor as well as any # accompanying options. The YAML data type of event_logs is a list of @@ -17,6 +17,7 @@ # forwarded, ignore_older, level, event_id, provider, and include_xml. Please # visit the documentation for the complete details of each option. # https://go.es.io/WinlogbeatConfig + winlogbeat.event_logs: - name: Application ignore_older: 72h @@ -27,7 +28,8 @@ winlogbeat.event_logs: - name: ForwardedEvents tags: [forwarded] -#==================== Elasticsearch template settings ========================== + +# ====================== Elasticsearch template settings ======================= setup.template.settings: index.number_of_shards: 1 @@ -35,7 +37,7 @@ setup.template.settings: #_source.enabled: false -#================================ General ===================================== +# ================================== General =================================== # The name of the shipper that publishes the network data. It can be used to group # all the transactions sent by a single shipper in the web interface. @@ -50,8 +52,7 @@ setup.template.settings: #fields: # env: staging - -#============================== Dashboards ===================================== +# ================================= Dashboards ================================= # These settings control loading the sample dashboards to the Kibana index. Loading # the dashboards is disabled by default and can be enabled either by setting the # options here or by using the `setup` command. @@ -63,7 +64,7 @@ setup.template.settings: # website. #setup.dashboards.url: -#============================== Kibana ===================================== +# =================================== Kibana =================================== # Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API. # This requires a Kibana endpoint configuration. @@ -80,7 +81,7 @@ setup.kibana: # the Default Space will be used. #space.id: -#============================= Elastic Cloud ================================== +# =============================== Elastic Cloud ================================ # These settings simplify using Winlogbeat with the Elastic Cloud (https://cloud.elastic.co/). @@ -93,11 +94,11 @@ setup.kibana: # `output.elasticsearch.password` settings. The format is `:`. #cloud.auth: -#================================ Outputs ===================================== +# ================================== Outputs =================================== # Configure what output to use when sending the data collected by the beat. -#-------------------------- Elasticsearch output ------------------------------ +# ---------------------------- Elasticsearch Output ---------------------------- output.elasticsearch: # Array of hosts to connect to. hosts: ["localhost:9200"] @@ -110,7 +111,7 @@ output.elasticsearch: #username: "elastic" #password: "changeme" -#----------------------------- Logstash output -------------------------------- +# ------------------------------ Logstash Output ------------------------------- #output.logstash: # The Logstash hosts #hosts: ["localhost:5044"] @@ -125,14 +126,13 @@ output.elasticsearch: # Client Certificate Key #ssl.key: "/etc/pki/client/cert.key" -#================================ Processors ===================================== - +# ================================= Processors ================================= processors: - add_host_metadata: when.not.contains.tags: forwarded - add_cloud_metadata: ~ -#================================ Logging ===================================== +# ================================== Logging =================================== # Sets log level. The default log level is info. # Available log levels are: error, warning, info, debug @@ -143,8 +143,8 @@ processors: # "publish", "service". #logging.selectors: ["*"] -#============================== X-Pack Monitoring =============================== -# winlogbeat can export internal metrics to a central Elasticsearch monitoring +# ============================= X-Pack Monitoring ============================== +# Winlogbeat can export internal metrics to a central Elasticsearch monitoring # cluster. This requires xpack monitoring to be enabled in Elasticsearch. The # reporting is disabled by default. @@ -165,7 +165,8 @@ processors: # uncomment the following line. #monitoring.elasticsearch: -#================================= Migration ================================== +# ================================= Migration ================================== # This allows to enable 6.7 migration aliases #migration.6_to_7.enabled: true + diff --git a/x-pack/auditbeat/auditbeat.docker.yml b/x-pack/auditbeat/auditbeat.docker.yml index a012bbb6aad..19c9bd1b477 100644 --- a/x-pack/auditbeat/auditbeat.docker.yml +++ b/x-pack/auditbeat/auditbeat.docker.yml @@ -12,6 +12,7 @@ auditbeat.modules: - /sbin - /usr/sbin - /etc + processors: - add_cloud_metadata: ~ - add_docker_metadata: ~ diff --git a/x-pack/auditbeat/auditbeat.reference.yml b/x-pack/auditbeat/auditbeat.reference.yml index 4514be47ddd..2e6f8e955fb 100644 --- a/x-pack/auditbeat/auditbeat.reference.yml +++ b/x-pack/auditbeat/auditbeat.reference.yml @@ -7,7 +7,7 @@ # You can find the full configuration reference here: # https://www.elastic.co/guide/en/beats/auditbeat/index.html -#============================ Config Reloading ================================ +# ============================== Config Reloading ============================== # Config reloading allows to dynamically load modules. Each file which is # monitored must contain one or multiple modules as a list. @@ -26,7 +26,7 @@ auditbeat.config.modules: # disable startup delay. auditbeat.max_start_delay: 10s -#========================== Modules configuration ============================= +# =========================== Modules configuration ============================ auditbeat.modules: # The auditd module collects events from the audit framework in the Linux @@ -174,7 +174,8 @@ auditbeat.modules: login.wtmp_file_pattern: /var/log/wtmp* login.btmp_file_pattern: /var/log/btmp* -#================================ General ====================================== + +# ================================== General =================================== # The name of the shipper that publishes the network data. It can be used to group # all the transactions sent by a single shipper in the web interface. @@ -282,7 +283,7 @@ auditbeat.modules: # default is the number of logical CPUs available in the system. #max_procs: -#================================ Processors =================================== +# ================================= Processors ================================= # Processors are used to reduce the number of fields in the exported event or to # enhance the event with external metadata. This section defines a list of @@ -445,7 +446,7 @@ auditbeat.modules: # ignore_missing: false # fail_on_error: true -#============================= Elastic Cloud ================================== +# =============================== Elastic Cloud ================================ # These settings simplify using Auditbeat with the Elastic Cloud (https://cloud.elastic.co/). @@ -458,11 +459,11 @@ auditbeat.modules: # `output.elasticsearch.password` settings. The format is `:`. #cloud.auth: -#================================ Outputs ====================================== +# ================================== Outputs =================================== # Configure what output to use when sending the data collected by the beat. -#-------------------------- Elasticsearch output ------------------------------- +# ---------------------------- Elasticsearch Output ---------------------------- output.elasticsearch: # Boolean flag to enable or disable the output module. #enabled: true @@ -603,7 +604,7 @@ output.elasticsearch: # Kerberos realm. #kerberos.realm: ELASTIC -#----------------------------- Logstash output --------------------------------- +# ------------------------------ Logstash Output ------------------------------- #output.logstash: # Boolean flag to enable or disable the output module. #enabled: true @@ -717,7 +718,7 @@ output.elasticsearch: # timing out. The default is 30s. #timeout: 30s -#------------------------------- Kafka output ---------------------------------- +# -------------------------------- Kafka Output -------------------------------- #output.kafka: # Boolean flag to enable or disable the output module. #enabled: true @@ -896,7 +897,7 @@ output.elasticsearch: # Kerberos realm. #kerberos.realm: ELASTIC -#------------------------------- Redis output ---------------------------------- +# -------------------------------- Redis Output -------------------------------- #output.redis: # Boolean flag to enable or disable the output module. #enabled: true @@ -1014,7 +1015,7 @@ output.elasticsearch: # never, once, and freely. Default is never. #ssl.renegotiation: never -#------------------------------- File output ----------------------------------- +# -------------------------------- File Output --------------------------------- #output.file: # Boolean flag to enable or disable the output module. #enabled: true @@ -1048,7 +1049,7 @@ output.elasticsearch: # Permissions to use for file creation. The default is 0600. #permissions: 0600 -#----------------------------- Console output --------------------------------- +# ------------------------------- Console Output ------------------------------- #output.console: # Boolean flag to enable or disable the output module. #enabled: true @@ -1061,7 +1062,7 @@ output.elasticsearch: # Configure escaping HTML symbols in strings. #escape_html: false -#================================= Paths ====================================== +# =================================== Paths ==================================== # The home path for the Auditbeat installation. This is the default base path # for all other path settings and for miscellaneous files that come with the @@ -1087,11 +1088,13 @@ output.elasticsearch: # the default for the logs path is a logs subdirectory inside the home path. #path.logs: ${path.home}/logs -#================================ Keystore ========================================== +# ================================== Keystore ================================== + # Location of the Keystore containing the keys and their sensitive values. #keystore.path: "${path.config}/beats.keystore" -#============================== Dashboards ===================================== +# ================================= Dashboards ================================= + # These settings control loading the sample dashboards to the Kibana index. Loading # the dashboards are disabled by default and can be enabled either by setting the # options here, or by using the `-setup` CLI flag or the `setup` command. @@ -1135,8 +1138,7 @@ output.elasticsearch: # Maximum number of retries before exiting with an error, 0 for unlimited retrying. #setup.dashboards.retry.maximum: 0 - -#============================== Template ===================================== +# ================================== Template ================================== # A template is used to set the mapping in Elasticsearch # By default template loading is enabled and the template is loaded. @@ -1190,7 +1192,7 @@ setup.template.settings: #_source: #enabled: false -#============================== Setup ILM ===================================== +# ====================== Index Lifecycle Management (ILM) ====================== # Configure index lifecycle management (ILM). These settings create a write # alias and add additional settings to the index template. When ILM is enabled, @@ -1204,13 +1206,13 @@ setup.template.settings: # Set the prefix used in the index lifecycle write alias name. The default alias # name is 'auditbeat-%{[agent.version]}'. -#setup.ilm.rollover_alias: "auditbeat" +#setup.ilm.rollover_alias: 'auditbeat' # Set the rollover index pattern. The default is "%{now/d}-000001". #setup.ilm.pattern: "{now/d}-000001" # Set the lifecycle policy name. The default policy name is -# 'auditbeat'. +# 'beatname'. #setup.ilm.policy_name: "mypolicy" # The path to a JSON file that contains a lifecycle policy configuration. Used @@ -1225,7 +1227,7 @@ setup.template.settings: # Overwrite the lifecycle policy at startup. The default is false. #setup.ilm.overwrite: false -#============================== Kibana ===================================== +# =================================== Kibana =================================== # Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API. # This requires a Kibana endpoint configuration. @@ -1280,9 +1282,8 @@ setup.kibana: # Configure curve types for ECDHE-based cipher suites #ssl.curve_types: [] +# ================================== Logging =================================== - -#================================ Logging ====================================== # There are four options for the log output: file, stderr, syslog, eventlog # The file output is the default. @@ -1349,8 +1350,7 @@ logging.files: # Set to true to log messages in JSON format. #logging.json: false - -#============================== X-Pack Monitoring =============================== +# ============================= X-Pack Monitoring ============================== # Auditbeat can export internal metrics to a central Elasticsearch monitoring # cluster. This requires xpack monitoring to be enabled in Elasticsearch. The # reporting is disabled by default. @@ -1492,7 +1492,8 @@ logging.files: # and `monitoring.elasticsearch.password` settings. The format is `:`. #monitoring.cloud.auth: -#================================ HTTP Endpoint ====================================== +# =============================== HTTP Endpoint ================================ + # Each beat can expose internal metrics through a HTTP endpoint. For security # reasons the endpoint is disabled by default. This feature is currently experimental. # Stats can be access through http://localhost:5066/stats . For pretty JSON output @@ -1516,12 +1517,13 @@ logging.files: # `http.user`. #http.named_pipe.security_descriptor: -#============================= Process Security ================================ +# ============================== Process Security ============================== # Enable or disable seccomp system call filtering on Linux. Default is enabled. #seccomp.enabled: true -#================================= Migration ================================== +# ================================= Migration ================================== # This allows to enable 6.7 migration aliases #migration.6_to_7.enabled: false + diff --git a/x-pack/auditbeat/auditbeat.yml b/x-pack/auditbeat/auditbeat.yml index f174d7a793e..408e52004e6 100644 --- a/x-pack/auditbeat/auditbeat.yml +++ b/x-pack/auditbeat/auditbeat.yml @@ -7,7 +7,7 @@ # You can find the full configuration reference here: # https://www.elastic.co/guide/en/beats/auditbeat/index.html -#========================== Modules configuration ============================= +# =========================== Modules configuration ============================ auditbeat.modules: - module: auditd @@ -75,13 +75,14 @@ auditbeat.modules: login.wtmp_file_pattern: /var/log/wtmp* login.btmp_file_pattern: /var/log/btmp* -#==================== Elasticsearch template setting ========================== +# ======================= Elasticsearch template setting ======================= setup.template.settings: index.number_of_shards: 1 #index.codec: best_compression #_source.enabled: false -#================================ General ===================================== + +# ================================== General =================================== # The name of the shipper that publishes the network data. It can be used to group # all the transactions sent by a single shipper in the web interface. @@ -96,8 +97,7 @@ setup.template.settings: #fields: # env: staging - -#============================== Dashboards ===================================== +# ================================= Dashboards ================================= # These settings control loading the sample dashboards to the Kibana index. Loading # the dashboards is disabled by default and can be enabled either by setting the # options here or by using the `setup` command. @@ -109,7 +109,7 @@ setup.template.settings: # website. #setup.dashboards.url: -#============================== Kibana ===================================== +# =================================== Kibana =================================== # Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API. # This requires a Kibana endpoint configuration. @@ -126,7 +126,7 @@ setup.kibana: # the Default Space will be used. #space.id: -#============================= Elastic Cloud ================================== +# =============================== Elastic Cloud ================================ # These settings simplify using Auditbeat with the Elastic Cloud (https://cloud.elastic.co/). @@ -139,11 +139,11 @@ setup.kibana: # `output.elasticsearch.password` settings. The format is `:`. #cloud.auth: -#================================ Outputs ===================================== +# ================================== Outputs =================================== # Configure what output to use when sending the data collected by the beat. -#-------------------------- Elasticsearch output ------------------------------ +# ---------------------------- Elasticsearch Output ---------------------------- output.elasticsearch: # Array of hosts to connect to. hosts: ["localhost:9200"] @@ -156,7 +156,7 @@ output.elasticsearch: #username: "elastic" #password: "changeme" -#----------------------------- Logstash output -------------------------------- +# ------------------------------ Logstash Output ------------------------------- #output.logstash: # The Logstash hosts #hosts: ["localhost:5044"] @@ -171,7 +171,7 @@ output.elasticsearch: # Client Certificate Key #ssl.key: "/etc/pki/client/cert.key" -#================================ Processors ===================================== +# ================================= Processors ================================= # Configure processors to enhance or manipulate events generated by the beat. @@ -180,7 +180,8 @@ processors: - add_cloud_metadata: ~ - add_docker_metadata: ~ -#================================ Logging ===================================== + +# ================================== Logging =================================== # Sets log level. The default log level is info. # Available log levels are: error, warning, info, debug @@ -191,8 +192,8 @@ processors: # "publish", "service". #logging.selectors: ["*"] -#============================== X-Pack Monitoring =============================== -# auditbeat can export internal metrics to a central Elasticsearch monitoring +# ============================= X-Pack Monitoring ============================== +# Auditbeat can export internal metrics to a central Elasticsearch monitoring # cluster. This requires xpack monitoring to be enabled in Elasticsearch. The # reporting is disabled by default. @@ -213,7 +214,8 @@ processors: # uncomment the following line. #monitoring.elasticsearch: -#================================= Migration ================================== +# ================================= Migration ================================== # This allows to enable 6.7 migration aliases #migration.6_to_7.enabled: true + diff --git a/x-pack/elastic-agent/_meta/common.p1.yml b/x-pack/elastic-agent/_meta/config/common.p1.yml.tmpl similarity index 100% rename from x-pack/elastic-agent/_meta/common.p1.yml rename to x-pack/elastic-agent/_meta/config/common.p1.yml.tmpl diff --git a/x-pack/elastic-agent/_meta/common.p2.yml b/x-pack/elastic-agent/_meta/config/common.p2.yml.tmpl similarity index 100% rename from x-pack/elastic-agent/_meta/common.p2.yml rename to x-pack/elastic-agent/_meta/config/common.p2.yml.tmpl diff --git a/x-pack/elastic-agent/_meta/common.reference.p1.yml b/x-pack/elastic-agent/_meta/config/common.reference.p1.yml.tmpl similarity index 100% rename from x-pack/elastic-agent/_meta/common.reference.p1.yml rename to x-pack/elastic-agent/_meta/config/common.reference.p1.yml.tmpl diff --git a/x-pack/elastic-agent/_meta/common.reference.p2.yml b/x-pack/elastic-agent/_meta/config/common.reference.p2.yml.tmpl similarity index 100% rename from x-pack/elastic-agent/_meta/common.reference.p2.yml rename to x-pack/elastic-agent/_meta/config/common.reference.p2.yml.tmpl diff --git a/x-pack/elastic-agent/_meta/elastic-agent.docker.yml b/x-pack/elastic-agent/_meta/config/elastic-agent.docker.yml.tmpl similarity index 100% rename from x-pack/elastic-agent/_meta/elastic-agent.docker.yml rename to x-pack/elastic-agent/_meta/config/elastic-agent.docker.yml.tmpl diff --git a/x-pack/elastic-agent/_meta/config/elastic-agent.reference.yml.tmpl b/x-pack/elastic-agent/_meta/config/elastic-agent.reference.yml.tmpl new file mode 100644 index 00000000000..d3d5225f0ec --- /dev/null +++ b/x-pack/elastic-agent/_meta/config/elastic-agent.reference.yml.tmpl @@ -0,0 +1,2 @@ +{{template "common.reference.p1.yml.tmpl" .}} +{{template "common.reference.p2.yml.tmpl" .}} diff --git a/x-pack/elastic-agent/_meta/config/elastic-agent.yml.tmpl b/x-pack/elastic-agent/_meta/config/elastic-agent.yml.tmpl new file mode 100644 index 00000000000..01634504728 --- /dev/null +++ b/x-pack/elastic-agent/_meta/config/elastic-agent.yml.tmpl @@ -0,0 +1,2 @@ +{{template "common.p1.yml.tmpl" .}} +{{template "common.p2.yml.tmpl" .}} diff --git a/x-pack/elastic-agent/elastic-agent.reference.yml b/x-pack/elastic-agent/elastic-agent.reference.yml index 547053af6b2..ae06f02c816 100644 --- a/x-pack/elastic-agent/elastic-agent.reference.yml +++ b/x-pack/elastic-agent/elastic-agent.reference.yml @@ -3,6 +3,7 @@ # This file is an example configuration file highlighting only the most common # options. The elastic-agent.reference.yml file from the same directory contains all the # supported options with more comments. You can use it as a reference. + ###################################### # Fleet configuration ###################################### @@ -128,3 +129,4 @@ datasources: # Sets log level. The default log level is info. # Available log levels are: error, warning, info, debug #logging.level: trace + diff --git a/x-pack/elastic-agent/elastic-agent.yml b/x-pack/elastic-agent/elastic-agent.yml index 547053af6b2..ae06f02c816 100644 --- a/x-pack/elastic-agent/elastic-agent.yml +++ b/x-pack/elastic-agent/elastic-agent.yml @@ -3,6 +3,7 @@ # This file is an example configuration file highlighting only the most common # options. The elastic-agent.reference.yml file from the same directory contains all the # supported options with more comments. You can use it as a reference. + ###################################### # Fleet configuration ###################################### @@ -128,3 +129,4 @@ datasources: # Sets log level. The default log level is info. # Available log levels are: error, warning, info, debug #logging.level: trace + diff --git a/x-pack/elastic-agent/magefile.go b/x-pack/elastic-agent/magefile.go index 8168890998e..1816dd3b205 100644 --- a/x-pack/elastic-agent/magefile.go +++ b/x-pack/elastic-agent/magefile.go @@ -383,19 +383,12 @@ func configYML() error { // ConfigFileParams returns the parameters for generating OSS config. func ConfigFileParams() devtools.ConfigFileParams { - return devtools.ConfigFileParams{ - ShortParts: []string{ - devtools.XPackBeatDir("_meta/common.p1.yml"), - devtools.XPackBeatDir("_meta/common.p2.yml"), - }, - ReferenceParts: []string{ - devtools.XPackBeatDir("_meta/common.reference.p1.yml"), - devtools.XPackBeatDir("_meta/common.reference.p2.yml"), - }, - DockerParts: []string{ - devtools.XPackBeatDir("_meta/elastic-agent.docker.yml"), - }, - } + p := devtools.DefaultConfigFileParams() + p.Templates = append(p.Templates, "_meta/config/*.tmpl") + p.Short.Template = "_meta/config/elastic-agent.yml.tmpl" + p.Reference.Template = "_meta/config/elastic-agent.reference.yml.tmpl" + p.Docker.Template = "_meta/config/elastic-agent.docker.yml.tmpl" + return p } // fieldDocs generates docs/fields.asciidoc containing all fields diff --git a/x-pack/filebeat/_meta/config/beat.reference.yml.tmpl b/x-pack/filebeat/_meta/config/beat.reference.yml.tmpl new file mode 100644 index 00000000000..d90bd36c7b9 --- /dev/null +++ b/x-pack/filebeat/_meta/config/beat.reference.yml.tmpl @@ -0,0 +1,6 @@ +{{template "header.reference.yml.tmpl" .}} +{{template "config.modules.yml.tmpl" .}} +{{template "filebeat.inputs.reference.yml.tmpl" .}} +{{template "filebeat.inputs.reference.xpack.yml.tmpl" .}} +{{template "filebeat.autodiscover.reference.yml.tmpl" .}} +{{template "filebeat.global.reference.yml.tmpl" .}} diff --git a/x-pack/filebeat/_meta/common.reference.inputs.yml b/x-pack/filebeat/_meta/config/filebeat.inputs.reference.xpack.yml.tmpl similarity index 100% rename from x-pack/filebeat/_meta/common.reference.inputs.yml rename to x-pack/filebeat/_meta/config/filebeat.inputs.reference.xpack.yml.tmpl diff --git a/x-pack/filebeat/filebeat.reference.yml b/x-pack/filebeat/filebeat.reference.yml index e9503f55863..056b574abe5 100644 --- a/x-pack/filebeat/filebeat.reference.yml +++ b/x-pack/filebeat/filebeat.reference.yml @@ -1047,6 +1047,7 @@ filebeat.modules: #var.paths: + #=========================== Filebeat inputs ============================= # List of inputs to fetch data. @@ -1428,6 +1429,7 @@ filebeat.inputs: # Configure stream to filter to a specific stream: stdout, stderr or all (default) #stream: all + #------------------------------ NetFlow input -------------------------------- # Experimental: Config options for the Netflow/IPFIX collector over UDP input #- type: netflow @@ -1503,7 +1505,8 @@ filebeat.inputs: # The duration (in seconds) that the received messages are hidden from subsequent # retrieve requests after being retrieved by a ReceiveMessage request. #visibility_timeout: 300 -#========================== Filebeat autodiscover ============================== + +# =========================== Filebeat autodiscover ============================ # Autodiscover allows you to detect changes in the system and spawn new modules # or inputs as they happen. @@ -1520,7 +1523,7 @@ filebeat.inputs: # paths: # - /var/lib/docker/containers/${data.docker.container.id}/*.log -#========================= Filebeat global options ============================ +# ========================== Filebeat global options =========================== # Registry data path. If a relative path is used, it is considered relative to the # data path. @@ -1567,7 +1570,8 @@ filebeat.inputs: #reload.enabled: true #reload.period: 10s -#================================ General ====================================== + +# ================================== General =================================== # The name of the shipper that publishes the network data. It can be used to group # all the transactions sent by a single shipper in the web interface. @@ -1675,7 +1679,7 @@ filebeat.inputs: # default is the number of logical CPUs available in the system. #max_procs: -#================================ Processors =================================== +# ================================= Processors ================================= # Processors are used to reduce the number of fields in the exported event or to # enhance the event with external metadata. This section defines a list of @@ -1838,7 +1842,7 @@ filebeat.inputs: # ignore_missing: false # fail_on_error: true -#============================= Elastic Cloud ================================== +# =============================== Elastic Cloud ================================ # These settings simplify using Filebeat with the Elastic Cloud (https://cloud.elastic.co/). @@ -1851,11 +1855,11 @@ filebeat.inputs: # `output.elasticsearch.password` settings. The format is `:`. #cloud.auth: -#================================ Outputs ====================================== +# ================================== Outputs =================================== # Configure what output to use when sending the data collected by the beat. -#-------------------------- Elasticsearch output ------------------------------- +# ---------------------------- Elasticsearch Output ---------------------------- output.elasticsearch: # Boolean flag to enable or disable the output module. #enabled: true @@ -1996,7 +2000,7 @@ output.elasticsearch: # Kerberos realm. #kerberos.realm: ELASTIC -#----------------------------- Logstash output --------------------------------- +# ------------------------------ Logstash Output ------------------------------- #output.logstash: # Boolean flag to enable or disable the output module. #enabled: true @@ -2110,7 +2114,7 @@ output.elasticsearch: # timing out. The default is 30s. #timeout: 30s -#------------------------------- Kafka output ---------------------------------- +# -------------------------------- Kafka Output -------------------------------- #output.kafka: # Boolean flag to enable or disable the output module. #enabled: true @@ -2289,7 +2293,7 @@ output.elasticsearch: # Kerberos realm. #kerberos.realm: ELASTIC -#------------------------------- Redis output ---------------------------------- +# -------------------------------- Redis Output -------------------------------- #output.redis: # Boolean flag to enable or disable the output module. #enabled: true @@ -2407,7 +2411,7 @@ output.elasticsearch: # never, once, and freely. Default is never. #ssl.renegotiation: never -#------------------------------- File output ----------------------------------- +# -------------------------------- File Output --------------------------------- #output.file: # Boolean flag to enable or disable the output module. #enabled: true @@ -2441,7 +2445,7 @@ output.elasticsearch: # Permissions to use for file creation. The default is 0600. #permissions: 0600 -#----------------------------- Console output --------------------------------- +# ------------------------------- Console Output ------------------------------- #output.console: # Boolean flag to enable or disable the output module. #enabled: true @@ -2454,7 +2458,7 @@ output.elasticsearch: # Configure escaping HTML symbols in strings. #escape_html: false -#================================= Paths ====================================== +# =================================== Paths ==================================== # The home path for the Filebeat installation. This is the default base path # for all other path settings and for miscellaneous files that come with the @@ -2480,11 +2484,13 @@ output.elasticsearch: # the default for the logs path is a logs subdirectory inside the home path. #path.logs: ${path.home}/logs -#================================ Keystore ========================================== +# ================================== Keystore ================================== + # Location of the Keystore containing the keys and their sensitive values. #keystore.path: "${path.config}/beats.keystore" -#============================== Dashboards ===================================== +# ================================= Dashboards ================================= + # These settings control loading the sample dashboards to the Kibana index. Loading # the dashboards are disabled by default and can be enabled either by setting the # options here, or by using the `-setup` CLI flag or the `setup` command. @@ -2528,8 +2534,7 @@ output.elasticsearch: # Maximum number of retries before exiting with an error, 0 for unlimited retrying. #setup.dashboards.retry.maximum: 0 - -#============================== Template ===================================== +# ================================== Template ================================== # A template is used to set the mapping in Elasticsearch # By default template loading is enabled and the template is loaded. @@ -2583,7 +2588,7 @@ setup.template.settings: #_source: #enabled: false -#============================== Setup ILM ===================================== +# ====================== Index Lifecycle Management (ILM) ====================== # Configure index lifecycle management (ILM). These settings create a write # alias and add additional settings to the index template. When ILM is enabled, @@ -2597,13 +2602,13 @@ setup.template.settings: # Set the prefix used in the index lifecycle write alias name. The default alias # name is 'filebeat-%{[agent.version]}'. -#setup.ilm.rollover_alias: "filebeat" +#setup.ilm.rollover_alias: 'filebeat' # Set the rollover index pattern. The default is "%{now/d}-000001". #setup.ilm.pattern: "{now/d}-000001" # Set the lifecycle policy name. The default policy name is -# 'filebeat'. +# 'beatname'. #setup.ilm.policy_name: "mypolicy" # The path to a JSON file that contains a lifecycle policy configuration. Used @@ -2618,7 +2623,7 @@ setup.template.settings: # Overwrite the lifecycle policy at startup. The default is false. #setup.ilm.overwrite: false -#============================== Kibana ===================================== +# =================================== Kibana =================================== # Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API. # This requires a Kibana endpoint configuration. @@ -2673,9 +2678,8 @@ setup.kibana: # Configure curve types for ECDHE-based cipher suites #ssl.curve_types: [] +# ================================== Logging =================================== - -#================================ Logging ====================================== # There are four options for the log output: file, stderr, syslog, eventlog # The file output is the default. @@ -2742,8 +2746,7 @@ logging.files: # Set to true to log messages in JSON format. #logging.json: false - -#============================== X-Pack Monitoring =============================== +# ============================= X-Pack Monitoring ============================== # Filebeat can export internal metrics to a central Elasticsearch monitoring # cluster. This requires xpack monitoring to be enabled in Elasticsearch. The # reporting is disabled by default. @@ -2885,7 +2888,8 @@ logging.files: # and `monitoring.elasticsearch.password` settings. The format is `:`. #monitoring.cloud.auth: -#================================ HTTP Endpoint ====================================== +# =============================== HTTP Endpoint ================================ + # Each beat can expose internal metrics through a HTTP endpoint. For security # reasons the endpoint is disabled by default. This feature is currently experimental. # Stats can be access through http://localhost:5066/stats . For pretty JSON output @@ -2909,12 +2913,13 @@ logging.files: # `http.user`. #http.named_pipe.security_descriptor: -#============================= Process Security ================================ +# ============================== Process Security ============================== # Enable or disable seccomp system call filtering on Linux. Default is enabled. #seccomp.enabled: true -#================================= Migration ================================== +# ================================= Migration ================================== # This allows to enable 6.7 migration aliases #migration.6_to_7.enabled: false + diff --git a/x-pack/filebeat/filebeat.yml b/x-pack/filebeat/filebeat.yml index 581e1a43f23..51a0d40224e 100644 --- a/x-pack/filebeat/filebeat.yml +++ b/x-pack/filebeat/filebeat.yml @@ -10,7 +10,7 @@ # For more available modules and options, please see the filebeat.reference.yml sample # configuration file. -#=========================== Filebeat inputs ============================= +# ============================== Filebeat inputs =============================== filebeat.inputs: @@ -62,8 +62,7 @@ filebeat.inputs: # Note: After is the equivalent to previous and before is the equivalent to to next in Logstash #multiline.match: after - -#============================= Filebeat modules =============================== +# ============================== Filebeat modules ============================== filebeat.config.modules: # Glob pattern for configuration loading @@ -75,14 +74,15 @@ filebeat.config.modules: # Period on which files under path should be checked for changes #reload.period: 10s -#==================== Elasticsearch template setting ========================== +# ======================= Elasticsearch template setting ======================= setup.template.settings: index.number_of_shards: 1 #index.codec: best_compression #_source.enabled: false -#================================ General ===================================== + +# ================================== General =================================== # The name of the shipper that publishes the network data. It can be used to group # all the transactions sent by a single shipper in the web interface. @@ -97,8 +97,7 @@ setup.template.settings: #fields: # env: staging - -#============================== Dashboards ===================================== +# ================================= Dashboards ================================= # These settings control loading the sample dashboards to the Kibana index. Loading # the dashboards is disabled by default and can be enabled either by setting the # options here or by using the `setup` command. @@ -110,7 +109,7 @@ setup.template.settings: # website. #setup.dashboards.url: -#============================== Kibana ===================================== +# =================================== Kibana =================================== # Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API. # This requires a Kibana endpoint configuration. @@ -127,7 +126,7 @@ setup.kibana: # the Default Space will be used. #space.id: -#============================= Elastic Cloud ================================== +# =============================== Elastic Cloud ================================ # These settings simplify using Filebeat with the Elastic Cloud (https://cloud.elastic.co/). @@ -140,11 +139,11 @@ setup.kibana: # `output.elasticsearch.password` settings. The format is `:`. #cloud.auth: -#================================ Outputs ===================================== +# ================================== Outputs =================================== # Configure what output to use when sending the data collected by the beat. -#-------------------------- Elasticsearch output ------------------------------ +# ---------------------------- Elasticsearch Output ---------------------------- output.elasticsearch: # Array of hosts to connect to. hosts: ["localhost:9200"] @@ -157,7 +156,7 @@ output.elasticsearch: #username: "elastic" #password: "changeme" -#----------------------------- Logstash output -------------------------------- +# ------------------------------ Logstash Output ------------------------------- #output.logstash: # The Logstash hosts #hosts: ["localhost:5044"] @@ -172,7 +171,7 @@ output.elasticsearch: # Client Certificate Key #ssl.key: "/etc/pki/client/cert.key" -#================================ Processors ===================================== +# ================================= Processors ================================= # Configure processors to enhance or manipulate events generated by the beat. @@ -182,7 +181,8 @@ processors: - add_docker_metadata: ~ - add_kubernetes_metadata: ~ -#================================ Logging ===================================== + +# ================================== Logging =================================== # Sets log level. The default log level is info. # Available log levels are: error, warning, info, debug @@ -193,8 +193,8 @@ processors: # "publish", "service". #logging.selectors: ["*"] -#============================== X-Pack Monitoring =============================== -# filebeat can export internal metrics to a central Elasticsearch monitoring +# ============================= X-Pack Monitoring ============================== +# Filebeat can export internal metrics to a central Elasticsearch monitoring # cluster. This requires xpack monitoring to be enabled in Elasticsearch. The # reporting is disabled by default. @@ -215,7 +215,8 @@ processors: # uncomment the following line. #monitoring.elasticsearch: -#================================= Migration ================================== +# ================================= Migration ================================== # This allows to enable 6.7 migration aliases #migration.6_to_7.enabled: true + diff --git a/x-pack/functionbeat/_meta/beat.reference.yml b/x-pack/functionbeat/_meta/config/beat.reference.yml.tmpl similarity index 99% rename from x-pack/functionbeat/_meta/beat.reference.yml rename to x-pack/functionbeat/_meta/config/beat.reference.yml.tmpl index dc0b0832bd9..c306fb0ac2a 100644 --- a/x-pack/functionbeat/_meta/beat.reference.yml +++ b/x-pack/functionbeat/_meta/config/beat.reference.yml.tmpl @@ -6,8 +6,8 @@ # # You can find the full configuration reference here: # https://www.elastic.co/guide/en/beats/functionbeat/index.html -# -#============================ Provider =============================== + +{{header "Provider"}} # Configure functions to run on AWS Lambda, currently we assume that the credentials # are present in the environment to correctly create the function when using the CLI. # diff --git a/x-pack/functionbeat/_meta/beat.yml b/x-pack/functionbeat/_meta/config/beat.yml.tmpl similarity index 99% rename from x-pack/functionbeat/_meta/beat.yml rename to x-pack/functionbeat/_meta/config/beat.yml.tmpl index ae1c7ee97dc..533d33dc599 100644 --- a/x-pack/functionbeat/_meta/beat.yml +++ b/x-pack/functionbeat/_meta/config/beat.yml.tmpl @@ -8,7 +8,7 @@ # https://www.elastic.co/guide/en/beats/functionbeat/index.html # -#============================ Provider =============================== +{{header "Provider"}} # Configure functions to run on AWS Lambda, currently we assume that the credentials # are present in the environment to correctly create the function when using the CLI. # @@ -337,7 +337,7 @@ functionbeat.provider.gcp.functions: # Define custom processors for this function. #processors: # - dissect: - # tokenizer: "%{key1} %{key2}" + # tokenizer: "%{key1} %{key2}" #==================== Elasticsearch template setting ========================== setup.template.settings: diff --git a/x-pack/functionbeat/functionbeat.reference.yml b/x-pack/functionbeat/functionbeat.reference.yml index c3ab460ffe3..7815f35077b 100644 --- a/x-pack/functionbeat/functionbeat.reference.yml +++ b/x-pack/functionbeat/functionbeat.reference.yml @@ -6,8 +6,8 @@ # # You can find the full configuration reference here: # https://www.elastic.co/guide/en/beats/functionbeat/index.html -# -#============================ Provider =============================== + +# ================================== Provider ================================== # Configure functions to run on AWS Lambda, currently we assume that the credentials # are present in the environment to correctly create the function when using the CLI. # @@ -384,7 +384,7 @@ functionbeat.provider.gcp.functions: # - dissect: # tokenizer: "%{key1} %{key2}" -#================================ General ====================================== +# ================================== General =================================== # The name of the shipper that publishes the network data. It can be used to group # all the transactions sent by a single shipper in the web interface. @@ -492,7 +492,7 @@ functionbeat.provider.gcp.functions: # default is the number of logical CPUs available in the system. #max_procs: -#================================ Processors =================================== +# ================================= Processors ================================= # Processors are used to reduce the number of fields in the exported event or to # enhance the event with external metadata. This section defines a list of @@ -655,7 +655,7 @@ functionbeat.provider.gcp.functions: # ignore_missing: false # fail_on_error: true -#============================= Elastic Cloud ================================== +# =============================== Elastic Cloud ================================ # These settings simplify using Functionbeat with the Elastic Cloud (https://cloud.elastic.co/). @@ -668,11 +668,11 @@ functionbeat.provider.gcp.functions: # `output.elasticsearch.password` settings. The format is `:`. #cloud.auth: -#================================ Outputs ====================================== +# ================================== Outputs =================================== # Configure what output to use when sending the data collected by the beat. -#-------------------------- Elasticsearch output ------------------------------- +# ---------------------------- Elasticsearch Output ---------------------------- output.elasticsearch: # Boolean flag to enable or disable the output module. #enabled: true @@ -813,7 +813,7 @@ output.elasticsearch: # Kerberos realm. #kerberos.realm: ELASTIC -#----------------------------- Logstash output --------------------------------- +# ------------------------------ Logstash Output ------------------------------- #output.logstash: # Boolean flag to enable or disable the output module. #enabled: true @@ -927,7 +927,11 @@ output.elasticsearch: # timing out. The default is 30s. #timeout: 30s -#================================= Paths ====================================== + + + + +# =================================== Paths ==================================== # The home path for the Functionbeat installation. This is the default base path # for all other path settings and for miscellaneous files that come with the @@ -953,11 +957,13 @@ output.elasticsearch: # the default for the logs path is a logs subdirectory inside the home path. #path.logs: ${path.home}/logs -#================================ Keystore ========================================== +# ================================== Keystore ================================== + # Location of the Keystore containing the keys and their sensitive values. #keystore.path: "${path.config}/beats.keystore" -#============================== Dashboards ===================================== +# ================================= Dashboards ================================= + # These settings control loading the sample dashboards to the Kibana index. Loading # the dashboards are disabled by default and can be enabled either by setting the # options here, or by using the `-setup` CLI flag or the `setup` command. @@ -1001,8 +1007,7 @@ output.elasticsearch: # Maximum number of retries before exiting with an error, 0 for unlimited retrying. #setup.dashboards.retry.maximum: 0 - -#============================== Template ===================================== +# ================================== Template ================================== # A template is used to set the mapping in Elasticsearch # By default template loading is enabled and the template is loaded. @@ -1056,7 +1061,7 @@ setup.template.settings: #_source: #enabled: false -#============================== Setup ILM ===================================== +# ====================== Index Lifecycle Management (ILM) ====================== # Configure index lifecycle management (ILM). These settings create a write # alias and add additional settings to the index template. When ILM is enabled, @@ -1070,13 +1075,13 @@ setup.template.settings: # Set the prefix used in the index lifecycle write alias name. The default alias # name is 'functionbeat-%{[agent.version]}'. -#setup.ilm.rollover_alias: "functionbeat" +#setup.ilm.rollover_alias: 'functionbeat' # Set the rollover index pattern. The default is "%{now/d}-000001". #setup.ilm.pattern: "{now/d}-000001" # Set the lifecycle policy name. The default policy name is -# 'functionbeat'. +# 'beatname'. #setup.ilm.policy_name: "mypolicy" # The path to a JSON file that contains a lifecycle policy configuration. Used @@ -1091,7 +1096,7 @@ setup.template.settings: # Overwrite the lifecycle policy at startup. The default is false. #setup.ilm.overwrite: false -#============================== Kibana ===================================== +# =================================== Kibana =================================== # Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API. # This requires a Kibana endpoint configuration. @@ -1146,9 +1151,8 @@ setup.kibana: # Configure curve types for ECDHE-based cipher suites #ssl.curve_types: [] +# ================================== Logging =================================== - -#================================ Logging ====================================== # There are four options for the log output: file, stderr, syslog, eventlog # The file output is the default. @@ -1215,8 +1219,7 @@ logging.files: # Set to true to log messages in JSON format. #logging.json: false - -#============================== X-Pack Monitoring =============================== +# ============================= X-Pack Monitoring ============================== # Functionbeat can export internal metrics to a central Elasticsearch monitoring # cluster. This requires xpack monitoring to be enabled in Elasticsearch. The # reporting is disabled by default. @@ -1358,7 +1361,8 @@ logging.files: # and `monitoring.elasticsearch.password` settings. The format is `:`. #monitoring.cloud.auth: -#================================ HTTP Endpoint ====================================== +# =============================== HTTP Endpoint ================================ + # Each beat can expose internal metrics through a HTTP endpoint. For security # reasons the endpoint is disabled by default. This feature is currently experimental. # Stats can be access through http://localhost:5066/stats . For pretty JSON output @@ -1382,12 +1386,13 @@ logging.files: # `http.user`. #http.named_pipe.security_descriptor: -#============================= Process Security ================================ +# ============================== Process Security ============================== # Enable or disable seccomp system call filtering on Linux. Default is enabled. #seccomp.enabled: true -#================================= Migration ================================== +# ================================= Migration ================================== # This allows to enable 6.7 migration aliases #migration.6_to_7.enabled: false + diff --git a/x-pack/functionbeat/functionbeat.yml b/x-pack/functionbeat/functionbeat.yml index f26658b15e6..bb8322840c8 100644 --- a/x-pack/functionbeat/functionbeat.yml +++ b/x-pack/functionbeat/functionbeat.yml @@ -8,7 +8,7 @@ # https://www.elastic.co/guide/en/beats/functionbeat/index.html # -#============================ Provider =============================== +# ================================== Provider ================================== # Configure functions to run on AWS Lambda, currently we assume that the credentials # are present in the environment to correctly create the function when using the CLI. # @@ -337,7 +337,7 @@ functionbeat.provider.gcp.functions: # Define custom processors for this function. #processors: # - dissect: - # tokenizer: "%{key1} %{key2}" + # tokenizer: "%{key1} %{key2}" #==================== Elasticsearch template setting ========================== setup.template.settings: @@ -345,7 +345,7 @@ setup.template.settings: #index.codec: best_compression #_source.enabled: false -#================================ General ===================================== +# ================================== General =================================== # The name of the shipper that publishes the network data. It can be used to group # all the transactions sent by a single shipper in the web interface. @@ -360,8 +360,7 @@ setup.template.settings: #fields: # env: staging - -#============================== Dashboards ===================================== +# ================================= Dashboards ================================= # These settings control loading the sample dashboards to the Kibana index. Loading # the dashboards is disabled by default and can be enabled either by setting the # options here or by using the `setup` command. @@ -373,7 +372,7 @@ setup.template.settings: # website. #setup.dashboards.url: -#============================== Kibana ===================================== +# =================================== Kibana =================================== # Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API. # This requires a Kibana endpoint configuration. @@ -390,7 +389,7 @@ setup.kibana: # the Default Space will be used. #space.id: -#============================= Elastic Cloud ================================== +# =============================== Elastic Cloud ================================ # These settings simplify using Functionbeat with the Elastic Cloud (https://cloud.elastic.co/). @@ -403,11 +402,11 @@ setup.kibana: # `output.elasticsearch.password` settings. The format is `:`. #cloud.auth: -#================================ Outputs ===================================== +# ================================== Outputs =================================== # Configure what output to use when sending the data collected by the beat. -#-------------------------- Elasticsearch output ------------------------------ +# ---------------------------- Elasticsearch Output ---------------------------- output.elasticsearch: # Array of hosts to connect to. hosts: ["localhost:9200"] @@ -420,7 +419,7 @@ output.elasticsearch: #username: "elastic" #password: "changeme" -#----------------------------- Logstash output -------------------------------- +# ------------------------------ Logstash Output ------------------------------- #output.logstash: # The Logstash hosts #hosts: ["localhost:5044"] @@ -435,7 +434,7 @@ output.elasticsearch: # Client Certificate Key #ssl.key: "/etc/pki/client/cert.key" -#================================ Processors ===================================== +# ================================= Processors ================================= # Configure processors to enhance or manipulate events generated by the beat. @@ -443,7 +442,8 @@ processors: - add_host_metadata: ~ - add_cloud_metadata: ~ -#================================ Logging ===================================== + +# ================================== Logging =================================== # Sets log level. The default log level is info. # Available log levels are: error, warning, info, debug @@ -454,8 +454,8 @@ processors: # "publish", "service". #logging.selectors: ["*"] -#============================== X-Pack Monitoring =============================== -# functionbeat can export internal metrics to a central Elasticsearch monitoring +# ============================= X-Pack Monitoring ============================== +# Functionbeat can export internal metrics to a central Elasticsearch monitoring # cluster. This requires xpack monitoring to be enabled in Elasticsearch. The # reporting is disabled by default. @@ -476,7 +476,8 @@ processors: # uncomment the following line. #monitoring.elasticsearch: -#================================= Migration ================================== +# ================================= Migration ================================== # This allows to enable 6.7 migration aliases #migration.6_to_7.enabled: true + diff --git a/x-pack/functionbeat/scripts/mage/config.go b/x-pack/functionbeat/scripts/mage/config.go index 576fc765b12..7fb4395e2bc 100644 --- a/x-pack/functionbeat/scripts/mage/config.go +++ b/x-pack/functionbeat/scripts/mage/config.go @@ -10,21 +10,14 @@ import ( // XPackConfigFileParams returns the configuration of sample and reference configuration data. func XPackConfigFileParams() devtools.ConfigFileParams { - return devtools.ConfigFileParams{ - ShortParts: []string{ - devtools.OSSBeatDir("_meta/beat.yml"), - devtools.LibbeatDir("_meta/config.yml.tmpl"), - }, - ReferenceParts: []string{ - devtools.OSSBeatDir("_meta/beat.reference.yml"), - devtools.LibbeatDir("_meta/config.reference.yml.tmpl"), - }, - ExtraVars: map[string]interface{}{ - "ExcludeConsole": true, - "ExcludeFileOutput": true, - "ExcludeKafka": true, - "ExcludeRedis": true, - "UseDockerMetadataProcessor": false, - }, + p := devtools.DefaultConfigFileParams() + p.Templates = append(p.Templates, "_meta/config/*.tmpl") + p.ExtraVars = map[string]interface{}{ + "ExcludeConsole": true, + "ExcludeFileOutput": true, + "ExcludeKafka": true, + "ExcludeRedis": true, + "UseDockerMetadataProcessor": false, } + return p } diff --git a/x-pack/metricbeat/metricbeat.reference.yml b/x-pack/metricbeat/metricbeat.reference.yml index c02e6a43471..6ed77efc639 100644 --- a/x-pack/metricbeat/metricbeat.reference.yml +++ b/x-pack/metricbeat/metricbeat.reference.yml @@ -53,6 +53,7 @@ metricbeat.max_start_delay: 10s #timeseries.enabled: false + #========================== Modules configuration ============================= metricbeat.modules: @@ -1296,7 +1297,8 @@ metricbeat.modules: -#================================ General ====================================== + +# ================================== General =================================== # The name of the shipper that publishes the network data. It can be used to group # all the transactions sent by a single shipper in the web interface. @@ -1404,7 +1406,7 @@ metricbeat.modules: # default is the number of logical CPUs available in the system. #max_procs: -#================================ Processors =================================== +# ================================= Processors ================================= # Processors are used to reduce the number of fields in the exported event or to # enhance the event with external metadata. This section defines a list of @@ -1567,7 +1569,7 @@ metricbeat.modules: # ignore_missing: false # fail_on_error: true -#============================= Elastic Cloud ================================== +# =============================== Elastic Cloud ================================ # These settings simplify using Metricbeat with the Elastic Cloud (https://cloud.elastic.co/). @@ -1580,11 +1582,11 @@ metricbeat.modules: # `output.elasticsearch.password` settings. The format is `:`. #cloud.auth: -#================================ Outputs ====================================== +# ================================== Outputs =================================== # Configure what output to use when sending the data collected by the beat. -#-------------------------- Elasticsearch output ------------------------------- +# ---------------------------- Elasticsearch Output ---------------------------- output.elasticsearch: # Boolean flag to enable or disable the output module. #enabled: true @@ -1725,7 +1727,7 @@ output.elasticsearch: # Kerberos realm. #kerberos.realm: ELASTIC -#----------------------------- Logstash output --------------------------------- +# ------------------------------ Logstash Output ------------------------------- #output.logstash: # Boolean flag to enable or disable the output module. #enabled: true @@ -1839,7 +1841,7 @@ output.elasticsearch: # timing out. The default is 30s. #timeout: 30s -#------------------------------- Kafka output ---------------------------------- +# -------------------------------- Kafka Output -------------------------------- #output.kafka: # Boolean flag to enable or disable the output module. #enabled: true @@ -2018,7 +2020,7 @@ output.elasticsearch: # Kerberos realm. #kerberos.realm: ELASTIC -#------------------------------- Redis output ---------------------------------- +# -------------------------------- Redis Output -------------------------------- #output.redis: # Boolean flag to enable or disable the output module. #enabled: true @@ -2136,7 +2138,7 @@ output.elasticsearch: # never, once, and freely. Default is never. #ssl.renegotiation: never -#------------------------------- File output ----------------------------------- +# -------------------------------- File Output --------------------------------- #output.file: # Boolean flag to enable or disable the output module. #enabled: true @@ -2170,7 +2172,7 @@ output.elasticsearch: # Permissions to use for file creation. The default is 0600. #permissions: 0600 -#----------------------------- Console output --------------------------------- +# ------------------------------- Console Output ------------------------------- #output.console: # Boolean flag to enable or disable the output module. #enabled: true @@ -2183,7 +2185,7 @@ output.elasticsearch: # Configure escaping HTML symbols in strings. #escape_html: false -#================================= Paths ====================================== +# =================================== Paths ==================================== # The home path for the Metricbeat installation. This is the default base path # for all other path settings and for miscellaneous files that come with the @@ -2209,11 +2211,13 @@ output.elasticsearch: # the default for the logs path is a logs subdirectory inside the home path. #path.logs: ${path.home}/logs -#================================ Keystore ========================================== +# ================================== Keystore ================================== + # Location of the Keystore containing the keys and their sensitive values. #keystore.path: "${path.config}/beats.keystore" -#============================== Dashboards ===================================== +# ================================= Dashboards ================================= + # These settings control loading the sample dashboards to the Kibana index. Loading # the dashboards are disabled by default and can be enabled either by setting the # options here, or by using the `-setup` CLI flag or the `setup` command. @@ -2257,8 +2261,7 @@ output.elasticsearch: # Maximum number of retries before exiting with an error, 0 for unlimited retrying. #setup.dashboards.retry.maximum: 0 - -#============================== Template ===================================== +# ================================== Template ================================== # A template is used to set the mapping in Elasticsearch # By default template loading is enabled and the template is loaded. @@ -2312,7 +2315,7 @@ setup.template.settings: #_source: #enabled: false -#============================== Setup ILM ===================================== +# ====================== Index Lifecycle Management (ILM) ====================== # Configure index lifecycle management (ILM). These settings create a write # alias and add additional settings to the index template. When ILM is enabled, @@ -2326,13 +2329,13 @@ setup.template.settings: # Set the prefix used in the index lifecycle write alias name. The default alias # name is 'metricbeat-%{[agent.version]}'. -#setup.ilm.rollover_alias: "metricbeat" +#setup.ilm.rollover_alias: 'metricbeat' # Set the rollover index pattern. The default is "%{now/d}-000001". #setup.ilm.pattern: "{now/d}-000001" # Set the lifecycle policy name. The default policy name is -# 'metricbeat'. +# 'beatname'. #setup.ilm.policy_name: "mypolicy" # The path to a JSON file that contains a lifecycle policy configuration. Used @@ -2347,7 +2350,7 @@ setup.template.settings: # Overwrite the lifecycle policy at startup. The default is false. #setup.ilm.overwrite: false -#============================== Kibana ===================================== +# =================================== Kibana =================================== # Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API. # This requires a Kibana endpoint configuration. @@ -2402,9 +2405,8 @@ setup.kibana: # Configure curve types for ECDHE-based cipher suites #ssl.curve_types: [] +# ================================== Logging =================================== - -#================================ Logging ====================================== # There are four options for the log output: file, stderr, syslog, eventlog # The file output is the default. @@ -2471,8 +2473,7 @@ logging.files: # Set to true to log messages in JSON format. #logging.json: false - -#============================== X-Pack Monitoring =============================== +# ============================= X-Pack Monitoring ============================== # Metricbeat can export internal metrics to a central Elasticsearch monitoring # cluster. This requires xpack monitoring to be enabled in Elasticsearch. The # reporting is disabled by default. @@ -2614,7 +2615,8 @@ logging.files: # and `monitoring.elasticsearch.password` settings. The format is `:`. #monitoring.cloud.auth: -#================================ HTTP Endpoint ====================================== +# =============================== HTTP Endpoint ================================ + # Each beat can expose internal metrics through a HTTP endpoint. For security # reasons the endpoint is disabled by default. This feature is currently experimental. # Stats can be access through http://localhost:5066/stats . For pretty JSON output @@ -2638,12 +2640,13 @@ logging.files: # `http.user`. #http.named_pipe.security_descriptor: -#============================= Process Security ================================ +# ============================== Process Security ============================== # Enable or disable seccomp system call filtering on Linux. Default is enabled. #seccomp.enabled: true -#================================= Migration ================================== +# ================================= Migration ================================== # This allows to enable 6.7 migration aliases #migration.6_to_7.enabled: false + diff --git a/x-pack/metricbeat/metricbeat.yml b/x-pack/metricbeat/metricbeat.yml index 5bd19f3030c..96975ee6027 100644 --- a/x-pack/metricbeat/metricbeat.yml +++ b/x-pack/metricbeat/metricbeat.yml @@ -7,7 +7,7 @@ # You can find the full configuration reference here: # https://www.elastic.co/guide/en/beats/metricbeat/index.html -#========================== Modules configuration ============================ +# =========================== Modules configuration ============================ metricbeat.config.modules: # Glob pattern for configuration loading @@ -19,14 +19,15 @@ metricbeat.config.modules: # Period on which files under path should be checked for changes #reload.period: 10s -#==================== Elasticsearch template setting ========================== +# ======================= Elasticsearch template setting ======================= setup.template.settings: index.number_of_shards: 1 index.codec: best_compression #_source.enabled: false -#================================ General ===================================== + +# ================================== General =================================== # The name of the shipper that publishes the network data. It can be used to group # all the transactions sent by a single shipper in the web interface. @@ -41,8 +42,7 @@ setup.template.settings: #fields: # env: staging - -#============================== Dashboards ===================================== +# ================================= Dashboards ================================= # These settings control loading the sample dashboards to the Kibana index. Loading # the dashboards is disabled by default and can be enabled either by setting the # options here or by using the `setup` command. @@ -54,7 +54,7 @@ setup.template.settings: # website. #setup.dashboards.url: -#============================== Kibana ===================================== +# =================================== Kibana =================================== # Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API. # This requires a Kibana endpoint configuration. @@ -71,7 +71,7 @@ setup.kibana: # the Default Space will be used. #space.id: -#============================= Elastic Cloud ================================== +# =============================== Elastic Cloud ================================ # These settings simplify using Metricbeat with the Elastic Cloud (https://cloud.elastic.co/). @@ -84,11 +84,11 @@ setup.kibana: # `output.elasticsearch.password` settings. The format is `:`. #cloud.auth: -#================================ Outputs ===================================== +# ================================== Outputs =================================== # Configure what output to use when sending the data collected by the beat. -#-------------------------- Elasticsearch output ------------------------------ +# ---------------------------- Elasticsearch Output ---------------------------- output.elasticsearch: # Array of hosts to connect to. hosts: ["localhost:9200"] @@ -101,7 +101,7 @@ output.elasticsearch: #username: "elastic" #password: "changeme" -#----------------------------- Logstash output -------------------------------- +# ------------------------------ Logstash Output ------------------------------- #output.logstash: # The Logstash hosts #hosts: ["localhost:5044"] @@ -116,7 +116,7 @@ output.elasticsearch: # Client Certificate Key #ssl.key: "/etc/pki/client/cert.key" -#================================ Processors ===================================== +# ================================= Processors ================================= # Configure processors to enhance or manipulate events generated by the beat. @@ -126,7 +126,8 @@ processors: - add_docker_metadata: ~ - add_kubernetes_metadata: ~ -#================================ Logging ===================================== + +# ================================== Logging =================================== # Sets log level. The default log level is info. # Available log levels are: error, warning, info, debug @@ -137,8 +138,8 @@ processors: # "publish", "service". #logging.selectors: ["*"] -#============================== X-Pack Monitoring =============================== -# metricbeat can export internal metrics to a central Elasticsearch monitoring +# ============================= X-Pack Monitoring ============================== +# Metricbeat can export internal metrics to a central Elasticsearch monitoring # cluster. This requires xpack monitoring to be enabled in Elasticsearch. The # reporting is disabled by default. @@ -159,7 +160,8 @@ processors: # uncomment the following line. #monitoring.elasticsearch: -#================================= Migration ================================== +# ================================= Migration ================================== # This allows to enable 6.7 migration aliases #migration.6_to_7.enabled: true + diff --git a/x-pack/winlogbeat/_meta/beat.yml.tmpl b/x-pack/winlogbeat/_meta/config/winlogbeat.event_logs.yml.tmpl similarity index 90% rename from x-pack/winlogbeat/_meta/beat.yml.tmpl rename to x-pack/winlogbeat/_meta/config/winlogbeat.event_logs.yml.tmpl index 1ea8cdcc879..6c29d94f6db 100644 --- a/x-pack/winlogbeat/_meta/beat.yml.tmpl +++ b/x-pack/winlogbeat/_meta/config/winlogbeat.event_logs.yml.tmpl @@ -1,4 +1,3 @@ -{{ template "header" . }} winlogbeat.event_logs: - name: Application ignore_older: 72h @@ -32,5 +31,3 @@ winlogbeat.event_logs: lang: javascript id: sysmon file: ${path.home}/module/sysmon/config/winlogbeat-sysmon.js - -{{if not .Reference}}{{ template "elasticsearch_settings" . }}{{end}} diff --git a/x-pack/winlogbeat/winlogbeat.reference.yml b/x-pack/winlogbeat/winlogbeat.reference.yml index a84f5a41676..8db00a73f5a 100644 --- a/x-pack/winlogbeat/winlogbeat.reference.yml +++ b/x-pack/winlogbeat/winlogbeat.reference.yml @@ -7,7 +7,7 @@ # You can find the full configuration reference here: # https://www.elastic.co/guide/en/beats/winlogbeat/index.html -#======================= Winlogbeat specific options =========================== +# ======================== Winlogbeat specific options ========================= # The registry file is where Winlogbeat persists its state so that the beat can # resume after shutdown or an outage. The default is .winlogbeat.yml in the @@ -22,6 +22,7 @@ # forwarded, ignore_older, level, event_id, provider, and include_xml. Please # visit the documentation for the complete details of each option. # https://go.es.io/WinlogbeatConfig + winlogbeat.event_logs: - name: Application ignore_older: 72h @@ -57,8 +58,7 @@ winlogbeat.event_logs: file: ${path.home}/module/sysmon/config/winlogbeat-sysmon.js - -#================================ General ====================================== +# ================================== General =================================== # The name of the shipper that publishes the network data. It can be used to group # all the transactions sent by a single shipper in the web interface. @@ -166,7 +166,7 @@ winlogbeat.event_logs: # default is the number of logical CPUs available in the system. #max_procs: -#================================ Processors =================================== +# ================================= Processors ================================= # Processors are used to reduce the number of fields in the exported event or to # enhance the event with external metadata. This section defines a list of @@ -329,7 +329,7 @@ winlogbeat.event_logs: # ignore_missing: false # fail_on_error: true -#============================= Elastic Cloud ================================== +# =============================== Elastic Cloud ================================ # These settings simplify using Winlogbeat with the Elastic Cloud (https://cloud.elastic.co/). @@ -342,11 +342,11 @@ winlogbeat.event_logs: # `output.elasticsearch.password` settings. The format is `:`. #cloud.auth: -#================================ Outputs ====================================== +# ================================== Outputs =================================== # Configure what output to use when sending the data collected by the beat. -#-------------------------- Elasticsearch output ------------------------------- +# ---------------------------- Elasticsearch Output ---------------------------- output.elasticsearch: # Boolean flag to enable or disable the output module. #enabled: true @@ -487,7 +487,7 @@ output.elasticsearch: # Kerberos realm. #kerberos.realm: ELASTIC -#----------------------------- Logstash output --------------------------------- +# ------------------------------ Logstash Output ------------------------------- #output.logstash: # Boolean flag to enable or disable the output module. #enabled: true @@ -601,7 +601,7 @@ output.elasticsearch: # timing out. The default is 30s. #timeout: 30s -#------------------------------- Kafka output ---------------------------------- +# -------------------------------- Kafka Output -------------------------------- #output.kafka: # Boolean flag to enable or disable the output module. #enabled: true @@ -780,7 +780,7 @@ output.elasticsearch: # Kerberos realm. #kerberos.realm: ELASTIC -#------------------------------- Redis output ---------------------------------- +# -------------------------------- Redis Output -------------------------------- #output.redis: # Boolean flag to enable or disable the output module. #enabled: true @@ -898,7 +898,7 @@ output.elasticsearch: # never, once, and freely. Default is never. #ssl.renegotiation: never -#------------------------------- File output ----------------------------------- +# -------------------------------- File Output --------------------------------- #output.file: # Boolean flag to enable or disable the output module. #enabled: true @@ -932,7 +932,7 @@ output.elasticsearch: # Permissions to use for file creation. The default is 0600. #permissions: 0600 -#----------------------------- Console output --------------------------------- +# ------------------------------- Console Output ------------------------------- #output.console: # Boolean flag to enable or disable the output module. #enabled: true @@ -945,7 +945,7 @@ output.elasticsearch: # Configure escaping HTML symbols in strings. #escape_html: false -#================================= Paths ====================================== +# =================================== Paths ==================================== # The home path for the Winlogbeat installation. This is the default base path # for all other path settings and for miscellaneous files that come with the @@ -971,11 +971,13 @@ output.elasticsearch: # the default for the logs path is a logs subdirectory inside the home path. #path.logs: ${path.home}/logs -#================================ Keystore ========================================== +# ================================== Keystore ================================== + # Location of the Keystore containing the keys and their sensitive values. #keystore.path: "${path.config}/beats.keystore" -#============================== Dashboards ===================================== +# ================================= Dashboards ================================= + # These settings control loading the sample dashboards to the Kibana index. Loading # the dashboards are disabled by default and can be enabled either by setting the # options here, or by using the `-setup` CLI flag or the `setup` command. @@ -1019,8 +1021,7 @@ output.elasticsearch: # Maximum number of retries before exiting with an error, 0 for unlimited retrying. #setup.dashboards.retry.maximum: 0 - -#============================== Template ===================================== +# ================================== Template ================================== # A template is used to set the mapping in Elasticsearch # By default template loading is enabled and the template is loaded. @@ -1074,7 +1075,7 @@ setup.template.settings: #_source: #enabled: false -#============================== Setup ILM ===================================== +# ====================== Index Lifecycle Management (ILM) ====================== # Configure index lifecycle management (ILM). These settings create a write # alias and add additional settings to the index template. When ILM is enabled, @@ -1088,13 +1089,13 @@ setup.template.settings: # Set the prefix used in the index lifecycle write alias name. The default alias # name is 'winlogbeat-%{[agent.version]}'. -#setup.ilm.rollover_alias: "winlogbeat" +#setup.ilm.rollover_alias: 'winlogbeat' # Set the rollover index pattern. The default is "%{now/d}-000001". #setup.ilm.pattern: "{now/d}-000001" # Set the lifecycle policy name. The default policy name is -# 'winlogbeat'. +# 'beatname'. #setup.ilm.policy_name: "mypolicy" # The path to a JSON file that contains a lifecycle policy configuration. Used @@ -1109,7 +1110,7 @@ setup.template.settings: # Overwrite the lifecycle policy at startup. The default is false. #setup.ilm.overwrite: false -#============================== Kibana ===================================== +# =================================== Kibana =================================== # Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API. # This requires a Kibana endpoint configuration. @@ -1164,9 +1165,8 @@ setup.kibana: # Configure curve types for ECDHE-based cipher suites #ssl.curve_types: [] +# ================================== Logging =================================== - -#================================ Logging ====================================== # There are four options for the log output: file, stderr, syslog, eventlog # The file output is the default. @@ -1233,8 +1233,7 @@ logging.files: # Set to true to log messages in JSON format. #logging.json: false - -#============================== X-Pack Monitoring =============================== +# ============================= X-Pack Monitoring ============================== # Winlogbeat can export internal metrics to a central Elasticsearch monitoring # cluster. This requires xpack monitoring to be enabled in Elasticsearch. The # reporting is disabled by default. @@ -1376,7 +1375,8 @@ logging.files: # and `monitoring.elasticsearch.password` settings. The format is `:`. #monitoring.cloud.auth: -#================================ HTTP Endpoint ====================================== +# =============================== HTTP Endpoint ================================ + # Each beat can expose internal metrics through a HTTP endpoint. For security # reasons the endpoint is disabled by default. This feature is currently experimental. # Stats can be access through http://localhost:5066/stats . For pretty JSON output @@ -1400,12 +1400,13 @@ logging.files: # `http.user`. #http.named_pipe.security_descriptor: -#============================= Process Security ================================ +# ============================== Process Security ============================== # Enable or disable seccomp system call filtering on Linux. Default is enabled. #seccomp.enabled: true -#================================= Migration ================================== +# ================================= Migration ================================== # This allows to enable 6.7 migration aliases #migration.6_to_7.enabled: false + diff --git a/x-pack/winlogbeat/winlogbeat.yml b/x-pack/winlogbeat/winlogbeat.yml index e718fb91d41..bb852a289db 100644 --- a/x-pack/winlogbeat/winlogbeat.yml +++ b/x-pack/winlogbeat/winlogbeat.yml @@ -7,7 +7,7 @@ # You can find the full configuration reference here: # https://www.elastic.co/guide/en/beats/winlogbeat/index.html -#======================= Winlogbeat specific options =========================== +# ======================== Winlogbeat specific options ========================= # event_logs specifies a list of event logs to monitor as well as any # accompanying options. The YAML data type of event_logs is a list of @@ -17,6 +17,7 @@ # forwarded, ignore_older, level, event_id, provider, and include_xml. Please # visit the documentation for the complete details of each option. # https://go.es.io/WinlogbeatConfig + winlogbeat.event_logs: - name: Application ignore_older: 72h @@ -51,7 +52,7 @@ winlogbeat.event_logs: id: sysmon file: ${path.home}/module/sysmon/config/winlogbeat-sysmon.js -#==================== Elasticsearch template settings ========================== +# ====================== Elasticsearch template settings ======================= setup.template.settings: index.number_of_shards: 1 @@ -59,7 +60,7 @@ setup.template.settings: #_source.enabled: false -#================================ General ===================================== +# ================================== General =================================== # The name of the shipper that publishes the network data. It can be used to group # all the transactions sent by a single shipper in the web interface. @@ -74,8 +75,7 @@ setup.template.settings: #fields: # env: staging - -#============================== Dashboards ===================================== +# ================================= Dashboards ================================= # These settings control loading the sample dashboards to the Kibana index. Loading # the dashboards is disabled by default and can be enabled either by setting the # options here or by using the `setup` command. @@ -87,7 +87,7 @@ setup.template.settings: # website. #setup.dashboards.url: -#============================== Kibana ===================================== +# =================================== Kibana =================================== # Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API. # This requires a Kibana endpoint configuration. @@ -104,7 +104,7 @@ setup.kibana: # the Default Space will be used. #space.id: -#============================= Elastic Cloud ================================== +# =============================== Elastic Cloud ================================ # These settings simplify using Winlogbeat with the Elastic Cloud (https://cloud.elastic.co/). @@ -117,11 +117,11 @@ setup.kibana: # `output.elasticsearch.password` settings. The format is `:`. #cloud.auth: -#================================ Outputs ===================================== +# ================================== Outputs =================================== # Configure what output to use when sending the data collected by the beat. -#-------------------------- Elasticsearch output ------------------------------ +# ---------------------------- Elasticsearch Output ---------------------------- output.elasticsearch: # Array of hosts to connect to. hosts: ["localhost:9200"] @@ -134,7 +134,7 @@ output.elasticsearch: #username: "elastic" #password: "changeme" -#----------------------------- Logstash output -------------------------------- +# ------------------------------ Logstash Output ------------------------------- #output.logstash: # The Logstash hosts #hosts: ["localhost:5044"] @@ -149,14 +149,13 @@ output.elasticsearch: # Client Certificate Key #ssl.key: "/etc/pki/client/cert.key" -#================================ Processors ===================================== - +# ================================= Processors ================================= processors: - add_host_metadata: when.not.contains.tags: forwarded - add_cloud_metadata: ~ -#================================ Logging ===================================== +# ================================== Logging =================================== # Sets log level. The default log level is info. # Available log levels are: error, warning, info, debug @@ -167,8 +166,8 @@ processors: # "publish", "service". #logging.selectors: ["*"] -#============================== X-Pack Monitoring =============================== -# winlogbeat can export internal metrics to a central Elasticsearch monitoring +# ============================= X-Pack Monitoring ============================== +# Winlogbeat can export internal metrics to a central Elasticsearch monitoring # cluster. This requires xpack monitoring to be enabled in Elasticsearch. The # reporting is disabled by default. @@ -189,7 +188,8 @@ processors: # uncomment the following line. #monitoring.elasticsearch: -#================================= Migration ================================== +# ================================= Migration ================================== # This allows to enable 6.7 migration aliases #migration.6_to_7.enabled: true + From 68fc3c42df9dcd56bce15d5f52af280a7a95e816 Mon Sep 17 00:00:00 2001 From: Steffen Siering Date: Mon, 4 May 2020 22:16:18 +0200 Subject: [PATCH 105/116] Regression: Fix assigning to nil map in DeepMerge (#18143) --- CHANGELOG.next.asciidoc | 1 + libbeat/common/mapstr.go | 16 +++++++++++----- libbeat/common/mapstr_test.go | 5 +++++ 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 2e7c57ca634..9f59c707ab2 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -81,6 +81,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Fix goroutine leak and Elasticsearch output file descriptor leak when output reloading is in use. {issue}10491[10491] {pull}17381[17381] - Fix `setup.dashboards.index` setting not working. {pull}17749[17749] - Fix Elasticsearch license endpoint URL referenced in error message. {issue}17880[17880] {pull}18030[18030] +- Fix panic when assigning a key to a `nil` value in an event. {pull}18143[18143] *Auditbeat* diff --git a/libbeat/common/mapstr.go b/libbeat/common/mapstr.go index 8cf589b0f04..d70f05dd349 100644 --- a/libbeat/common/mapstr.go +++ b/libbeat/common/mapstr.go @@ -96,20 +96,26 @@ func (m MapStr) deepUpdateMap(d MapStr, overwrite bool) { } func deepUpdateValue(old interface{}, val MapStr, overwrite bool) interface{} { - if old == nil { - return val - } - switch sub := old.(type) { case MapStr: + if sub == nil { + return val + } + sub.deepUpdateMap(val, overwrite) return sub case map[string]interface{}: + if sub == nil { + return val + } + tmp := MapStr(sub) tmp.deepUpdateMap(val, overwrite) return tmp default: - // This should never happen + // We reach the default branch if old is no map or if old == nil. + // In either case we return `val`, such that the old value is completely + // replaced when merging. return val } } diff --git a/libbeat/common/mapstr_test.go b/libbeat/common/mapstr_test.go index de633ff162e..e1ffb8f5ee4 100644 --- a/libbeat/common/mapstr_test.go +++ b/libbeat/common/mapstr_test.go @@ -87,6 +87,11 @@ func TestMapStrDeepUpdate(t *testing.T) { MapStr{"a.b": 1}, MapStr{"a": 1, "a.b": 1}, }, + { + MapStr{"a": (MapStr)(nil)}, + MapStr{"a": MapStr{"b": 1}}, + MapStr{"a": MapStr{"b": 1}}, + }, } for i, test := range tests { From 9e7321ac2bf9dc362a6416ac4aac6f6f1c4aa247 Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Mon, 4 May 2020 16:55:04 -0400 Subject: [PATCH 106/116] Fix metricbeat generator test to use mage test (#18086) * Use mage unit-test. * Should be make. * Update mage test. * Add PackageKibanaDashboardsFromBuildDir. --- generator/_templates/metricbeat/{beat}/magefile.go | 1 + generator/common/Makefile | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/generator/_templates/metricbeat/{beat}/magefile.go b/generator/_templates/metricbeat/{beat}/magefile.go index ba9f64fdefb..8805fc36a32 100644 --- a/generator/_templates/metricbeat/{beat}/magefile.go +++ b/generator/_templates/metricbeat/{beat}/magefile.go @@ -46,6 +46,7 @@ func Package() { defer func() { fmt.Println("package ran for", time.Since(start)) }() devtools.UseCommunityBeatPackaging() + devtools.PackageKibanaDashboardsFromBuildDir() mg.Deps(Update) mg.Deps(build.CrossBuild, build.CrossBuildGoDaemon) diff --git a/generator/common/Makefile b/generator/common/Makefile index 927da092a22..8d82a444e84 100644 --- a/generator/common/Makefile +++ b/generator/common/Makefile @@ -16,7 +16,7 @@ test: prepare-test git config user.name "beats-jenkins" || exit 1 ; \ $(MAKE) check CHECK_HEADERS_DISABLED=y || exit 1 ; \ $(MAKE) || exit 1 ; \ - $(MAKE) unit + mage test .PHONY: test-package test-package: test From 1ceb3cbd9423ca6935b9ce0a7e899c16a87fbe80 Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Mon, 4 May 2020 17:21:18 -0700 Subject: [PATCH 107/116] Give precedence to monitoring reporter hosts over output hosts (#17991) * Give precedence to monitoring reporter hosts over output hosts * Add CHANGELOG entry * Removing debugging statements * No delete * Helper function * Make new config if nil * Formatting code --- CHANGELOG.next.asciidoc | 1 + libbeat/monitoring/report/report.go | 58 +++++++++++++++++- libbeat/monitoring/report/report_test.go | 78 ++++++++++++++++++++++++ 3 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 libbeat/monitoring/report/report_test.go diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 9f59c707ab2..b3eaf0a08dc 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -82,6 +82,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Fix `setup.dashboards.index` setting not working. {pull}17749[17749] - Fix Elasticsearch license endpoint URL referenced in error message. {issue}17880[17880] {pull}18030[18030] - Fix panic when assigning a key to a `nil` value in an event. {pull}18143[18143] +- Gives monitoring reporter hosts, if configured, total precedence over corresponding output hosts. {issue}17937[17937] {pull}17991[17991] *Auditbeat* diff --git a/libbeat/monitoring/report/report.go b/libbeat/monitoring/report/report.go index e6812515af9..0f79af4e874 100644 --- a/libbeat/monitoring/report/report.go +++ b/libbeat/monitoring/report/report.go @@ -21,6 +21,8 @@ import ( "errors" "fmt" + errw "github.com/pkg/errors" + "github.com/elastic/beats/v7/libbeat/beat" "github.com/elastic/beats/v7/libbeat/common" ) @@ -59,6 +61,10 @@ type Reporter interface { type ReporterFactory func(beat.Info, Settings, *common.Config) (Reporter, error) +type hostsCfg struct { + Hosts []string `config:"hosts"` +} + var ( defaultConfig = config{} @@ -111,9 +117,7 @@ func getReporterConfig( // merge reporter config with output config if both are present if outCfg := outputs.Config(); outputs.Name() == name && outCfg != nil { // require monitoring to not configure any hosts if output is configured: - hosts := struct { - Hosts []string `config:"hosts"` - }{} + hosts := hostsCfg{} rc.Unpack(&hosts) if settings.Format == FormatXPackMonitoringBulk && len(hosts.Hosts) > 0 { @@ -127,6 +131,13 @@ func getReporterConfig( if err != nil { return "", nil, err } + + // Make sure hosts from reporter configuration get precedence over hosts + // from output configuration + if err := mergeHosts(merged, outCfg, rc); err != nil { + return "", nil, err + } + rc = merged } @@ -155,3 +166,44 @@ func collectSubObject(cfg *common.Config) *common.Config { } return out } + +func mergeHosts(merged, outCfg, reporterCfg *common.Config) error { + if merged == nil { + merged = common.NewConfig() + } + + outputHosts := hostsCfg{} + if outCfg != nil { + if err := outCfg.Unpack(&outputHosts); err != nil { + return errw.Wrap(err, "unable to parse hosts from output config") + } + } + + reporterHosts := hostsCfg{} + if reporterCfg != nil { + if err := reporterCfg.Unpack(&reporterHosts); err != nil { + return errw.Wrap(err, "unable to parse hosts from reporter config") + } + } + + if len(outputHosts.Hosts) == 0 && len(reporterHosts.Hosts) == 0 { + return nil + } + + // Give precedence to reporter hosts over output hosts + var newHostsCfg *common.Config + var err error + if len(reporterHosts.Hosts) > 0 { + newHostsCfg, err = common.NewConfigFrom(reporterHosts.Hosts) + } else { + newHostsCfg, err = common.NewConfigFrom(outputHosts.Hosts) + } + if err != nil { + return errw.Wrap(err, "unable to make config from new hosts") + } + + if err := merged.SetChild("hosts", -1, newHostsCfg); err != nil { + return errw.Wrap(err, "unable to set new hosts into merged config") + } + return nil +} diff --git a/libbeat/monitoring/report/report_test.go b/libbeat/monitoring/report/report_test.go new file mode 100644 index 00000000000..45b0dadc83f --- /dev/null +++ b/libbeat/monitoring/report/report_test.go @@ -0,0 +1,78 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package report + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/elastic/beats/v7/libbeat/common" +) + +func TestMergeHosts(t *testing.T) { + tests := map[string]struct { + outCfg *common.Config + reporterCfg *common.Config + expectedCfg *common.Config + }{ + "no_hosts": { + expectedCfg: newConfigWithHosts(), + }, + "only_reporter_hosts": { + reporterCfg: newConfigWithHosts("r1", "r2"), + expectedCfg: newConfigWithHosts("r1", "r2"), + }, + "only_output_hosts": { + outCfg: newConfigWithHosts("o1", "o2"), + expectedCfg: newConfigWithHosts("o1", "o2"), + }, + "equal_hosts": { + outCfg: newConfigWithHosts("o1", "o2"), + reporterCfg: newConfigWithHosts("r1", "r2"), + expectedCfg: newConfigWithHosts("r1", "r2"), + }, + "more_output_hosts": { + outCfg: newConfigWithHosts("o1", "o2"), + reporterCfg: newConfigWithHosts("r1"), + expectedCfg: newConfigWithHosts("r1"), + }, + "more_reporter_hosts": { + outCfg: newConfigWithHosts("o1"), + reporterCfg: newConfigWithHosts("r1", "r2"), + expectedCfg: newConfigWithHosts("r1", "r2"), + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + mergedCfg := common.MustNewConfigFrom(map[string]interface{}{}) + err := mergeHosts(mergedCfg, test.outCfg, test.reporterCfg) + require.NoError(t, err) + + require.Equal(t, test.expectedCfg, mergedCfg) + }) + } +} + +func newConfigWithHosts(hosts ...string) *common.Config { + if len(hosts) == 0 { + return common.MustNewConfigFrom(map[string][]string{}) + } + return common.MustNewConfigFrom(map[string][]string{"hosts": hosts}) +} From 9f379b217dea28cca46ab46b725e724bef423762 Mon Sep 17 00:00:00 2001 From: kaiyan-sheng Date: Mon, 4 May 2020 21:08:15 -0600 Subject: [PATCH 108/116] [Metricbeat] Add debug log for cloudwatch metricset (#18074) * add debug log for cloudwatch metricset --- x-pack/metricbeat/module/aws/aws.go | 2 ++ .../module/aws/cloudwatch/cloudwatch.go | 19 +++++++++++++++++-- .../module/aws/cloudwatch/cloudwatch_test.go | 3 +++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/x-pack/metricbeat/module/aws/aws.go b/x-pack/metricbeat/module/aws/aws.go index dcf047da87d..5476f0dae5f 100644 --- a/x-pack/metricbeat/module/aws/aws.go +++ b/x-pack/metricbeat/module/aws/aws.go @@ -103,6 +103,7 @@ func NewMetricSet(base mb.BaseMetricSet) (*MetricSet, error) { // collecting the first one. if output.AccountAliases != nil { metricSet.AccountName = output.AccountAliases[0] + base.Logger().Debug("AWS Credentials belong to account name: ", metricSet.AccountName) } } @@ -115,6 +116,7 @@ func NewMetricSet(base mb.BaseMetricSet) (*MetricSet, error) { base.Logger().Warn("failed to get caller identity, please check permission setting: ", err) } else { metricSet.AccountID = *outputIdentity.Account + base.Logger().Debug("AWS Credentials belong to account ID: ", metricSet.AccountID) } // Construct MetricSet with a full regions list diff --git a/x-pack/metricbeat/module/aws/cloudwatch/cloudwatch.go b/x-pack/metricbeat/module/aws/cloudwatch/cloudwatch.go index 05c9b21cd3a..5674a45eccd 100644 --- a/x-pack/metricbeat/module/aws/cloudwatch/cloudwatch.go +++ b/x-pack/metricbeat/module/aws/cloudwatch/cloudwatch.go @@ -18,6 +18,7 @@ import ( "github.com/pkg/errors" "github.com/elastic/beats/v7/libbeat/common" + "github.com/elastic/beats/v7/libbeat/logp" "github.com/elastic/beats/v7/metricbeat/mb" awscommon "github.com/elastic/beats/v7/x-pack/libbeat/common/aws" "github.com/elastic/beats/v7/x-pack/metricbeat/module/aws" @@ -51,6 +52,7 @@ func init() { // interface methods except for Fetch. type MetricSet struct { *aws.MetricSet + logger *logp.Logger CloudwatchConfigs []Config `config:"metrics" validate:"nonzero,required"` } @@ -113,6 +115,7 @@ func New(base mb.BaseMetricSet) (mb.MetricSet, error) { return &MetricSet{ MetricSet: metricSet, + logger: logp.NewLogger(metricsetName), CloudwatchConfigs: config.CloudwatchMetrics, }, nil } @@ -132,10 +135,13 @@ func (m *MetricSet) Fetch(report mb.ReporterV2) error { // Get listMetricDetailTotal and namespaceDetailTotal from configuration listMetricDetailTotal, namespaceDetailTotal := m.readCloudwatchConfig() + m.logger.Debugf("listMetricDetailTotal = %s", listMetricDetailTotal) + m.logger.Debugf("namespaceDetailTotal = %s", namespaceDetailTotal) // Create events based on listMetricDetailTotal from configuration if len(listMetricDetailTotal.metricsWithStats) != 0 { for _, regionName := range m.MetricSet.RegionsList { + m.logger.Debugf("Collecting metrics from AWS region %s", regionName) awsConfig := m.MetricSet.AwsConfig.Copy() awsConfig.Region = regionName @@ -150,6 +156,8 @@ func (m *MetricSet) Fetch(report mb.ReporterV2) error { return errors.Wrap(err, "createEvents failed for region "+regionName) } + m.logger.Debugf("Collected metrics of metrics = %d", len(eventsWithIdentifier)) + err = reportEvents(eventsWithIdentifier, report) if err != nil { return errors.Wrap(err, "reportEvents failed") @@ -158,6 +166,7 @@ func (m *MetricSet) Fetch(report mb.ReporterV2) error { } for _, regionName := range m.MetricSet.RegionsList { + m.logger.Debugf("Collecting metrics from AWS region %s", regionName) awsConfig := m.MetricSet.AwsConfig.Copy() awsConfig.Region = regionName @@ -169,9 +178,11 @@ func (m *MetricSet) Fetch(report mb.ReporterV2) error { // Create events based on namespaceDetailTotal from configuration for namespace, namespaceDetails := range namespaceDetailTotal { + m.logger.Debugf("Collected metrics from namespace %s", namespace) + listMetricsOutput, err := aws.GetListMetricsOutput(namespace, regionName, svcCloudwatch) if err != nil { - m.Logger().Info(err.Error()) + m.logger.Info(err.Error()) continue } @@ -189,6 +200,8 @@ func (m *MetricSet) Fetch(report mb.ReporterV2) error { return errors.Wrap(err, "createEvents failed for region "+regionName) } + m.logger.Debugf("Collected number of metrics = %d", len(eventsWithIdentifier)) + err = reportEvents(eventsWithIdentifier, report) if err != nil { return errors.Wrap(err, "reportEvents failed") @@ -434,12 +447,14 @@ func (m *MetricSet) createEvents(svcCloudwatch cloudwatchiface.ClientAPI, svcRes // Construct metricDataQueries metricDataQueries := createMetricDataQueries(listMetricWithStatsTotal, m.Period) + m.logger.Debugf("Number of MetricDataQueries = %d", len(metricDataQueries)) if len(metricDataQueries) == 0 { return events, nil } // Use metricDataQueries to make GetMetricData API calls metricDataResults, err := aws.GetMetricDataResults(metricDataQueries, svcCloudwatch, startTime, endTime) + m.logger.Debugf("Number of metricDataResults = %d", len(metricDataResults)) if err != nil { return events, errors.Wrap(err, "GetMetricDataResults failed") } @@ -482,7 +497,7 @@ func (m *MetricSet) createEvents(svcCloudwatch cloudwatchiface.ClientAPI, svcRes resourceTagMap, err := aws.GetResourcesTags(svcResourceAPI, []string{resourceType}) if err != nil { // If GetResourcesTags failed, continue report event just without tags. - m.Logger().Info(errors.Wrap(err, "getResourcesTags failed, skipping region "+regionName)) + m.logger.Info(errors.Wrap(err, "getResourcesTags failed, skipping region "+regionName)) } if len(tagsFilter) != 0 && len(resourceTagMap) == 0 { diff --git a/x-pack/metricbeat/module/aws/cloudwatch/cloudwatch_test.go b/x-pack/metricbeat/module/aws/cloudwatch/cloudwatch_test.go index 92016c1cbc9..0b8cc468c06 100644 --- a/x-pack/metricbeat/module/aws/cloudwatch/cloudwatch_test.go +++ b/x-pack/metricbeat/module/aws/cloudwatch/cloudwatch_test.go @@ -11,6 +11,7 @@ import ( "testing" "time" + "github.com/elastic/beats/v7/libbeat/logp" "github.com/elastic/beats/v7/metricbeat/mb" awssdk "github.com/aws/aws-sdk-go-v2/aws" @@ -1232,6 +1233,7 @@ func TestCreateEventsWithIdentifier(t *testing.T) { m := MetricSet{} m.CloudwatchConfigs = []Config{{Statistic: []string{"Average"}}} m.MetricSet = &aws.MetricSet{Period: 5} + m.logger = logp.NewLogger("test") mockTaggingSvc := &MockResourceGroupsTaggingClient{} mockCloudwatchSvc := &MockCloudWatchClient{} @@ -1272,6 +1274,7 @@ func TestCreateEventsWithoutIdentifier(t *testing.T) { m := MetricSet{} m.CloudwatchConfigs = []Config{{Statistic: []string{"Average"}}} m.MetricSet = &aws.MetricSet{Period: 5, AccountID: accountID} + m.logger = logp.NewLogger("test") mockTaggingSvc := &MockResourceGroupsTaggingClient{} mockCloudwatchSvc := &MockCloudWatchClientWithoutDim{} From c87c8e73407809b07d78de0f217f97bef24e5acf Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Tue, 5 May 2020 01:52:10 -0700 Subject: [PATCH 109/116] Collect bulk indexing stats for Elasticsearch metricsets (#17992) * Collecting new index_stats bulk metrics * Collecting new indices_stats bulk metrics * Collecting new node_stats bulk metrics * Adding CHANGELOG entry * Request bulk stats * Request bulk metrics only if supported * Fixing code and tests * Fixing code so only service URI path is replaced * Updating unit tests --- CHANGELOG.next.asciidoc | 1 + .../module/elasticsearch/elasticsearch.go | 13 ++++ .../module/elasticsearch/index/data_xpack.go | 9 +++ .../module/elasticsearch/index/index.go | 50 ++++++++++++- .../module/elasticsearch/index/index_test.go | 70 +++++++++++++++++++ .../elasticsearch/index_summary/data_xpack.go | 1 + .../elasticsearch/node_stats/data_xpack.go | 1 + 7 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 metricbeat/module/elasticsearch/index/index_test.go diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index b3eaf0a08dc..7e5c18ceb3a 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -373,6 +373,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Add aggregation aligner as a config parameter for googlecloud stackdriver metricset. {issue}17141[[17141] {pull}17719[17719] - Move the perfmon metricset to GA. {issue}16608[16608] {pull}17879[17879] - Add static mapping for metricsets under aws module. {pull}17614[17614] {pull}17650[17650] +- Collect new `bulk` indexing metrics from Elasticsearch when `xpack.enabled:true` is set. {issue} {pull}17992[17992] *Packetbeat* diff --git a/metricbeat/module/elasticsearch/elasticsearch.go b/metricbeat/module/elasticsearch/elasticsearch.go index c2264f9d6a8..46825ee0084 100644 --- a/metricbeat/module/elasticsearch/elasticsearch.go +++ b/metricbeat/module/elasticsearch/elasticsearch.go @@ -28,6 +28,8 @@ import ( "github.com/pkg/errors" "github.com/elastic/beats/v7/libbeat/common" + s "github.com/elastic/beats/v7/libbeat/common/schema" + c "github.com/elastic/beats/v7/libbeat/common/schema/mapstriface" "github.com/elastic/beats/v7/libbeat/logp" "github.com/elastic/beats/v7/metricbeat/helper" "github.com/elastic/beats/v7/metricbeat/helper/elastic" @@ -63,6 +65,9 @@ var CCRStatsAPIAvailableVersion = common.MustNewVersion("6.5.0") // EnrichStatsAPIAvailableVersion is the version of Elasticsearch since when the Enrich stats API is available. var EnrichStatsAPIAvailableVersion = common.MustNewVersion("7.5.0") +// BulkStatsAvailableVersion is the version since when bulk indexing stats are available +var BulkStatsAvailableVersion = common.MustNewVersion("7.8.0") + // Global clusterIdCache. Assumption is that the same node id never can belong to a different cluster id. var clusterIDCache = map[string]string{} @@ -107,6 +112,14 @@ type licenseWrapper struct { License License `json:"license"` } +var BulkStatsDict = c.Dict("bulk", s.Schema{ + "total_operations": c.Int("total_operations"), + "total_time_in_millis": c.Int("total_time_in_millis"), + "total_size_in_bytes": c.Int("total_size_in_bytes"), + "avg_time_in_millis": c.Int("avg_time_in_millis"), + "avg_size_in_bytes": c.Int("avg_size_in_bytes"), +}, c.DictOptional) + // GetClusterID fetches cluster id for given nodeID. func GetClusterID(http *helper.HTTP, uri string, nodeID string) (string, error) { // Check if cluster id already cached. If yes, return it. diff --git a/metricbeat/module/elasticsearch/index/data_xpack.go b/metricbeat/module/elasticsearch/index/data_xpack.go index 35e9119fdf7..6c73b4ee2e1 100644 --- a/metricbeat/module/elasticsearch/index/data_xpack.go +++ b/metricbeat/module/elasticsearch/index/data_xpack.go @@ -65,6 +65,7 @@ type indexStats struct { IndexTimeInMillis int `json:"index_time_in_millis"` ThrottleTimeInMillis int `json:"throttle_time_in_millis"` } `json:"indexing"` + Bulk bulkStats `json:"bulk"` Merges struct { TotalSizeInBytes int `json:"total_size_in_bytes"` } `json:"merges"` @@ -120,6 +121,14 @@ type shardStats struct { Relocating int `json:"relocating"` } +type bulkStats struct { + TotalOperations int `json:"total_operations"` + TotalTimeInMillis int `json:"total_time_in_millis"` + TotalSizeInBytes int `json:"total_size_in_bytes"` + AvgTimeInMillis int `json:"throttle_time_in_millis"` + AvgSizeInBytes int `json:"avg_size_in_bytes"` +} + func eventsMappingXPack(r mb.ReporterV2, m *MetricSet, info elasticsearch.Info, content []byte) error { clusterStateMetrics := []string{"metadata", "routing_table"} clusterState, err := elasticsearch.GetClusterState(m.HTTP, m.HTTP.GetURI(), clusterStateMetrics) diff --git a/metricbeat/module/elasticsearch/index/index.go b/metricbeat/module/elasticsearch/index/index.go index cd2dc3ffca0..3454b0d6554 100644 --- a/metricbeat/module/elasticsearch/index/index.go +++ b/metricbeat/module/elasticsearch/index/index.go @@ -18,8 +18,12 @@ package index import ( + "net/url" + "strings" + "github.com/pkg/errors" + "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/beats/v7/metricbeat/module/elasticsearch" ) @@ -67,14 +71,22 @@ func (m *MetricSet) Fetch(r mb.ReporterV2) error { return nil } - content, err := m.HTTP.FetchContent() + info, err := elasticsearch.GetInfo(m.HTTP, m.HostData().SanitizedURI) if err != nil { + return errors.Wrap(err, "failed to get info from Elasticsearch") + } + + if err := m.updateServicePath(*info.Version.Number); err != nil { + if m.XPack { + m.Logger().Error(err) + return nil + } return err } - info, err := elasticsearch.GetInfo(m.HTTP, m.HostData().SanitizedURI) + content, err := m.HTTP.FetchContent() if err != nil { - return errors.Wrap(err, "failed to get info from Elasticsearch") + return err } if m.XPack { @@ -92,3 +104,35 @@ func (m *MetricSet) Fetch(r mb.ReporterV2) error { return nil } + +func (m *MetricSet) updateServicePath(esVersion common.Version) error { + p, err := getServicePath(esVersion) + if err != nil { + return err + } + + m.SetServiceURI(p) + return nil + +} + +func getServicePath(esVersion common.Version) (string, error) { + currPath := statsPath + if esVersion.LessThan(elasticsearch.BulkStatsAvailableVersion) { + // Can't request bulk stats so don't change service URI + return currPath, nil + } + + u, err := url.Parse(currPath) + if err != nil { + return "", err + } + + if strings.HasSuffix(u.Path, ",bulk") { + // Bulk stats already being requested so don't change service URI + return currPath, nil + } + + u.Path += ",bulk" + return u.String(), nil +} diff --git a/metricbeat/module/elasticsearch/index/index_test.go b/metricbeat/module/elasticsearch/index/index_test.go new file mode 100644 index 00000000000..8c4106e9944 --- /dev/null +++ b/metricbeat/module/elasticsearch/index/index_test.go @@ -0,0 +1,70 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package index + +import ( + "strings" + "testing" + "testing/quick" + + "github.com/elastic/beats/v7/libbeat/common" + + "github.com/stretchr/testify/require" +) + +func TestGetServiceURI(t *testing.T) { + tests := map[string]struct { + esVersion *common.Version + expectedPath string + }{ + "bulk_stats_unavailable": { + esVersion: common.MustNewVersion("7.7.0"), + expectedPath: statsPath, + }, + "bulk_stats_available": { + esVersion: common.MustNewVersion("7.8.0"), + expectedPath: strings.Replace(statsPath, statsMetrics, statsMetrics+",bulk", 1), + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + newURI, err := getServicePath(*test.esVersion) + require.NoError(t, err) + require.Equal(t, test.expectedPath, newURI) + }) + } +} + +func TestGetServiceURIMultipleCalls(t *testing.T) { + err := quick.Check(func(r uint) bool { + numCalls := 2 + (r % 10) // between 2 and 11 + + var uri string + var err error + for i := uint(0); i < numCalls; i++ { + uri, err = getServicePath(*common.MustNewVersion("7.8.0")) + if err != nil { + return false + } + } + + return err == nil && uri == strings.Replace(statsPath, statsMetrics, statsMetrics+",bulk", 1) + }, nil) + require.NoError(t, err) +} diff --git a/metricbeat/module/elasticsearch/index_summary/data_xpack.go b/metricbeat/module/elasticsearch/index_summary/data_xpack.go index d1e00ea64b8..4e35744133d 100644 --- a/metricbeat/module/elasticsearch/index_summary/data_xpack.go +++ b/metricbeat/module/elasticsearch/index_summary/data_xpack.go @@ -51,6 +51,7 @@ var ( "is_throttled": c.Bool("is_throttled"), "throttle_time_in_millis": c.Int("throttle_time_in_millis"), }), + "bulk": elasticsearch.BulkStatsDict, "search": c.Dict("search", s.Schema{ "query_total": c.Int("query_total"), "query_time_in_millis": c.Int("query_time_in_millis"), diff --git a/metricbeat/module/elasticsearch/node_stats/data_xpack.go b/metricbeat/module/elasticsearch/node_stats/data_xpack.go index f7f612b11ee..53340103176 100644 --- a/metricbeat/module/elasticsearch/node_stats/data_xpack.go +++ b/metricbeat/module/elasticsearch/node_stats/data_xpack.go @@ -49,6 +49,7 @@ var ( "index_time_in_millis": c.Int("index_time_in_millis"), "throttle_time_in_millis": c.Int("throttle_time_in_millis"), }), + "bulk": elasticsearch.BulkStatsDict, "search": c.Dict("search", s.Schema{ "query_total": c.Int("query_total"), "query_time_in_millis": c.Int("query_time_in_millis"), From 25a813e9dfbf5cba74a39dfba0d27bc955f7484d Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Tue, 5 May 2020 02:10:41 -0700 Subject: [PATCH 110/116] Deep merge event fields and metadata maps (#17958) * Deep merge event fields and metadata maps * Add CHANGELOG entry * Use loop to remove keys; extract into function * Relocating CHANGELOG entry * Rewording and moving to bugfix section per review feedback * Adapting other CHANGELOG entry * Fix comparison in test --- CHANGELOG.next.asciidoc | 5 + libbeat/common/jsontransform/jsonhelper.go | 27 ++-- .../common/jsontransform/jsonhelper_test.go | 136 ++++++++++++++++++ 3 files changed, 159 insertions(+), 9 deletions(-) create mode 100644 libbeat/common/jsontransform/jsonhelper_test.go diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 7e5c18ceb3a..34646273450 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -83,6 +83,8 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Fix Elasticsearch license endpoint URL referenced in error message. {issue}17880[17880] {pull}18030[18030] - Fix panic when assigning a key to a `nil` value in an event. {pull}18143[18143] - Gives monitoring reporter hosts, if configured, total precedence over corresponding output hosts. {issue}17937[17937] {pull}17991[17991] +- Arbitrary fields and metadata maps are now deep merged into event. {pull}17958[17958] +- Change `decode_json_fields` processor, to merge parsed json objects with existing objects in the event instead of fully replacing them. {pull}17958[17958] *Auditbeat* @@ -215,6 +217,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Add support for fixed length extraction in `dissect` processor. {pull}17191[17191] - Set `agent.name` to the hostname by default. {issue}16377[16377] {pull}18000[18000] - Add config example of how to skip the `add_host_metadata` processor when forwarding logs. {issue}13920[13920] {pull}18153[18153] +- When using the `decode_json_fields` processor, decoded fields are now deep-merged into existing event. {pull}17958[17958] *Auditbeat* @@ -294,6 +297,8 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Added an input option `publisher_pipeline.disable_host` to disable `host.name` from being added to events by default. {pull}18159[18159] - Improve ECS categorization field mappings in system module. {issue}16031[16031] {pull}18065[18065] +- When using the `json.*` setting available on some inputs, decoded fields are now deep-merged into existing event. {pull}17958[17958] +- Change the `json.*` input settings implementation to merge parsed json objects with existing objects in the event instead of fully replacing them. {pull}17958[17958] *Heartbeat* diff --git a/libbeat/common/jsontransform/jsonhelper.go b/libbeat/common/jsontransform/jsonhelper.go index 1490bcff170..164e1e9e1f4 100644 --- a/libbeat/common/jsontransform/jsonhelper.go +++ b/libbeat/common/jsontransform/jsonhelper.go @@ -29,11 +29,12 @@ import ( // WriteJSONKeys writes the json keys to the given event based on the overwriteKeys option and the addErrKey func WriteJSONKeys(event *beat.Event, keys map[string]interface{}, overwriteKeys bool, addErrKey bool) { if !overwriteKeys { - for k, v := range keys { - if _, exists := event.Fields[k]; !exists && k != "@timestamp" && k != "@metadata" { - event.Fields[k] = v - } - } + // @timestamp and @metadata fields are root-level fields. We remove them so they + // don't become part of event.Fields. + removeKeys(keys, "@timestamp", "@metadata") + + // Then, perform deep update without overwriting + event.Fields.DeepUpdateNoOverwrite(keys) return } @@ -64,7 +65,7 @@ func WriteJSONKeys(event *beat.Event, keys map[string]interface{}, overwriteKeys } case map[string]interface{}: - event.Meta.Update(common.MapStr(m)) + event.Meta.DeepUpdate(common.MapStr(m)) default: event.SetErrorWithOption(createJSONError("failed to update @metadata"), addErrKey) @@ -83,13 +84,21 @@ func WriteJSONKeys(event *beat.Event, keys map[string]interface{}, overwriteKeys continue } event.Fields[k] = vstr - - default: - event.Fields[k] = v } } + + // We have accounted for @timestamp, @metadata, type above. So let's remove these keys and + // deep update the event with the rest of the keys. + removeKeys(keys, "@timestamp", "@metadata", "type") + event.Fields.DeepUpdate(keys) } func createJSONError(message string) common.MapStr { return common.MapStr{"message": message, "type": "json"} } + +func removeKeys(keys map[string]interface{}, names ...string) { + for _, name := range names { + delete(keys, name) + } +} diff --git a/libbeat/common/jsontransform/jsonhelper_test.go b/libbeat/common/jsontransform/jsonhelper_test.go new file mode 100644 index 00000000000..d7679579be1 --- /dev/null +++ b/libbeat/common/jsontransform/jsonhelper_test.go @@ -0,0 +1,136 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package jsontransform + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/elastic/beats/v7/libbeat/beat" + "github.com/elastic/beats/v7/libbeat/common" +) + +func TestWriteJSONKeys(t *testing.T) { + now := time.Now() + now = now.Round(time.Second) + + eventTimestamp := time.Date(2020, 01, 01, 01, 01, 00, 0, time.UTC) + eventMetadata := common.MapStr{ + "foo": "bar", + "baz": common.MapStr{ + "qux": 17, + }, + } + eventFields := common.MapStr{ + "top_a": 23, + "top_b": common.MapStr{ + "inner_c": "see", + "inner_d": "dee", + }, + } + + tests := map[string]struct { + keys map[string]interface{} + overwriteKeys bool + expectedMetadata common.MapStr + expectedTimestamp time.Time + expectedFields common.MapStr + }{ + "overwrite_true": { + overwriteKeys: true, + keys: map[string]interface{}{ + "@metadata": map[string]interface{}{ + "foo": "NEW_bar", + "baz": map[string]interface{}{ + "qux": "NEW_qux", + "durrr": "COMPLETELY_NEW", + }, + }, + "@timestamp": now.Format(time.RFC3339), + "top_b": map[string]interface{}{ + "inner_d": "NEW_dee", + "inner_e": "COMPLETELY_NEW_e", + }, + "top_c": "COMPLETELY_NEW_c", + }, + expectedMetadata: common.MapStr{ + "foo": "NEW_bar", + "baz": common.MapStr{ + "qux": "NEW_qux", + "durrr": "COMPLETELY_NEW", + }, + }, + expectedTimestamp: now, + expectedFields: common.MapStr{ + "top_a": 23, + "top_b": common.MapStr{ + "inner_c": "see", + "inner_d": "NEW_dee", + "inner_e": "COMPLETELY_NEW_e", + }, + "top_c": "COMPLETELY_NEW_c", + }, + }, + "overwrite_false": { + overwriteKeys: false, + keys: map[string]interface{}{ + "@metadata": map[string]interface{}{ + "foo": "NEW_bar", + "baz": map[string]interface{}{ + "qux": "NEW_qux", + "durrr": "COMPLETELY_NEW", + }, + }, + "@timestamp": now.Format(time.RFC3339), + "top_b": map[string]interface{}{ + "inner_d": "NEW_dee", + "inner_e": "COMPLETELY_NEW_e", + }, + "top_c": "COMPLETELY_NEW_c", + }, + expectedMetadata: eventMetadata.Clone(), + expectedTimestamp: eventTimestamp, + expectedFields: common.MapStr{ + "top_a": 23, + "top_b": common.MapStr{ + "inner_c": "see", + "inner_d": "dee", + "inner_e": "COMPLETELY_NEW_e", + }, + "top_c": "COMPLETELY_NEW_c", + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + event := &beat.Event{ + Timestamp: eventTimestamp, + Meta: eventMetadata.Clone(), + Fields: eventFields.Clone(), + } + + WriteJSONKeys(event, test.keys, test.overwriteKeys, false) + require.Equal(t, test.expectedMetadata, event.Meta) + require.Equal(t, test.expectedTimestamp.UnixNano(), event.Timestamp.UnixNano()) + require.Equal(t, test.expectedFields, event.Fields) + }) + } +} From 22c944d4d223ce388ab0193869d08bccfc791d3b Mon Sep 17 00:00:00 2001 From: Nicolas Ruflin Date: Tue, 5 May 2020 12:41:07 +0200 Subject: [PATCH 111/116] [Agent] Enable monitoring by default (#18204) This enables monitoring of the agent by default. This PR only changes it in the configuration file. If we decide to move forward with this default, it should also be changed in the code. The reason I think monitoring should be enabled out of the box as it will make debugging agents easier as all the data is directly avaiable in Elasticsearch and enhances the out of the box experience. --- .../_meta/config/common.p2.yml.tmpl | 16 ++++++++-------- x-pack/elastic-agent/elastic-agent.yml | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) 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 15582908fe7..0e7c950cdd5 100644 --- a/x-pack/elastic-agent/_meta/config/common.p2.yml.tmpl +++ b/x-pack/elastic-agent/_meta/config/common.p2.yml.tmpl @@ -23,6 +23,14 @@ 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 + # management: # # Mode of management, the Elastic Agent support two modes of operation: # # @@ -112,14 +120,6 @@ datasources: # # Default is false # exponential: false -# settings.monitoring: -# # enabled turns on monitoring of running processes -# enabled: false -# # enables log monitoring -# logs: false -# # enables metrics monitoring -# metrics: false - # Sets log level. The default log level is info. # Available log levels are: error, warning, info, debug #logging.level: trace diff --git a/x-pack/elastic-agent/elastic-agent.yml b/x-pack/elastic-agent/elastic-agent.yml index ae06f02c816..d28cce65ab5 100644 --- a/x-pack/elastic-agent/elastic-agent.yml +++ b/x-pack/elastic-agent/elastic-agent.yml @@ -29,6 +29,14 @@ 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 + # management: # # Mode of management, the Elastic Agent support two modes of operation: # # @@ -118,14 +126,6 @@ datasources: # # Default is false # exponential: false -# settings.monitoring: -# # enabled turns on monitoring of running processes -# enabled: false -# # enables log monitoring -# logs: false -# # enables metrics monitoring -# metrics: false - # Sets log level. The default log level is info. # Available log levels are: error, warning, info, debug #logging.level: trace From d7fbc77dd3c5012a9b72212fd9ebeb4897b69a7a Mon Sep 17 00:00:00 2001 From: Michal Pristas Date: Tue, 5 May 2020 12:44:36 +0200 Subject: [PATCH 112/116] [Elastic Agent] Fix default configuration after enroll (#18232) * fix enroll * changelog * changelog --- x-pack/elastic-agent/CHANGELOG.asciidoc | 1 + .../_meta/elastic-agent.fleet.yml | 36 +++++++++---------- .../agent/application/configuration_embed.go | 2 +- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/x-pack/elastic-agent/CHANGELOG.asciidoc b/x-pack/elastic-agent/CHANGELOG.asciidoc index d7877bfc81f..0fb1c64d273 100644 --- a/x-pack/elastic-agent/CHANGELOG.asciidoc +++ b/x-pack/elastic-agent/CHANGELOG.asciidoc @@ -30,6 +30,7 @@ - Use default output by default {pull}18091[18091] - Use /tmp for default monitoring endpoint location for libbeat {pull}18131[18131] - Fix panic and flaky tests for the Agent. {pull}18135[18135] +- Fix default configuration after enroll {pull}18232[18232] ==== New features diff --git a/x-pack/elastic-agent/_meta/elastic-agent.fleet.yml b/x-pack/elastic-agent/_meta/elastic-agent.fleet.yml index b8b3eb2ac99..30c0a68431b 100644 --- a/x-pack/elastic-agent/_meta/elastic-agent.fleet.yml +++ b/x-pack/elastic-agent/_meta/elastic-agent.fleet.yml @@ -1,27 +1,27 @@ -#================================ General ===================================== +# ================================ General ===================================== # Beats is configured under Fleet, you can define most settings # from the Kibana UI. You can update this file to configure the settings that # are not supported by Fleet. -# management: -# mode: "fleet" +management: + mode: "fleet" -# # Check in frequency configure the time between calls to fleet to retrieve the new configuration. -# # -# # Default is 30s -# #checkin_frequency: 30s + # Check in frequency configure the time between calls to fleet to retrieve the new configuration. + # + # Default is 30s + #checkin_frequency: 30s -# # Add variance between API calls to better distribute the calls. -# #jitter: 5s + # Add variance between API calls to better distribute the calls. + #jitter: 5s -# # The Elastic Agent does Exponential backoff when an error happen. -# # -# #backoff: -# # -# # Initial time to wait before retrying the call. -# # init: 1s -# # -# # Maximum time to wait before retrying the call. -# # max: 10s + # The Elastic Agent does Exponential backoff when an error happen. + # + #backoff: + # + # Initial time to wait before retrying the call. + # init: 1s + # + # Maximum time to wait before retrying the call. + # max: 10s # download: # # source of the artifacts, requires elastic like structure and naming of the binaries diff --git a/x-pack/elastic-agent/pkg/agent/application/configuration_embed.go b/x-pack/elastic-agent/pkg/agent/application/configuration_embed.go index 7ccc52a40a8..b6bc827cc82 100644 --- a/x-pack/elastic-agent/pkg/agent/application/configuration_embed.go +++ b/x-pack/elastic-agent/pkg/agent/application/configuration_embed.go @@ -15,7 +15,7 @@ var DefaultAgentFleetConfig []byte func init() { // Packed File // _meta/elastic-agent.fleet.yml - unpacked := packer.MustUnpack("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") + unpacked := packer.MustUnpack("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") raw, ok := unpacked["_meta/elastic-agent.fleet.yml"] if !ok { // ensure we have something loaded. From 561eea462b027d49104a7f040c79ba4872e94aa2 Mon Sep 17 00:00:00 2001 From: Mario Castro Date: Tue, 5 May 2020 12:57:35 +0200 Subject: [PATCH 113/116] [Metricbeat] Remove requirement of connect as sysdba in Oracle (#18182) --- CHANGELOG.next.asciidoc | 1 + metricbeat/docs/modules/oracle.asciidoc | 2 +- x-pack/metricbeat/metricbeat.reference.yml | 2 +- x-pack/metricbeat/module/oracle/_meta/config.yml | 2 +- x-pack/metricbeat/module/oracle/connection.go | 4 ---- x-pack/metricbeat/module/oracle/testing.go | 2 +- x-pack/metricbeat/modules.d/oracle.yml.disabled | 2 +- 7 files changed, 6 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 34646273450..2ea420a3afd 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -379,6 +379,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Move the perfmon metricset to GA. {issue}16608[16608] {pull}17879[17879] - Add static mapping for metricsets under aws module. {pull}17614[17614] {pull}17650[17650] - Collect new `bulk` indexing metrics from Elasticsearch when `xpack.enabled:true` is set. {issue} {pull}17992[17992] +- Remove requirement to connect as sysdba in Oracle module {issue}15846[15846] {pull}18182[18182] *Packetbeat* diff --git a/metricbeat/docs/modules/oracle.asciidoc b/metricbeat/docs/modules/oracle.asciidoc index d56abf8648b..2492adcaaaf 100644 --- a/metricbeat/docs/modules/oracle.asciidoc +++ b/metricbeat/docs/modules/oracle.asciidoc @@ -57,7 +57,7 @@ metricbeat.modules: metricsets: ["tablespace", "performance"] enabled: true period: 10s - hosts: ["oracle://user:pass@localhost:1521/ORCLPDB1.localdomain?sysdba=1"] + hosts: ["user:pass@0.0.0.0:1521/ORCLPDB1.localdomain"] # username: "" # password: "" diff --git a/x-pack/metricbeat/metricbeat.reference.yml b/x-pack/metricbeat/metricbeat.reference.yml index 6ed77efc639..d2293995c5a 100644 --- a/x-pack/metricbeat/metricbeat.reference.yml +++ b/x-pack/metricbeat/metricbeat.reference.yml @@ -993,7 +993,7 @@ metricbeat.modules: metricsets: ["tablespace", "performance"] enabled: true period: 10s - hosts: ["oracle://user:pass@localhost:1521/ORCLPDB1.localdomain?sysdba=1"] + hosts: ["user:pass@0.0.0.0:1521/ORCLPDB1.localdomain"] # username: "" # password: "" diff --git a/x-pack/metricbeat/module/oracle/_meta/config.yml b/x-pack/metricbeat/module/oracle/_meta/config.yml index b36667b5733..8208685e033 100644 --- a/x-pack/metricbeat/module/oracle/_meta/config.yml +++ b/x-pack/metricbeat/module/oracle/_meta/config.yml @@ -2,7 +2,7 @@ metricsets: ["tablespace", "performance"] enabled: true period: 10s - hosts: ["oracle://user:pass@localhost:1521/ORCLPDB1.localdomain?sysdba=1"] + hosts: ["user:pass@0.0.0.0:1521/ORCLPDB1.localdomain"] # username: "" # password: "" diff --git a/x-pack/metricbeat/module/oracle/connection.go b/x-pack/metricbeat/module/oracle/connection.go index f2fc870fb8d..8beb7c936ad 100644 --- a/x-pack/metricbeat/module/oracle/connection.go +++ b/x-pack/metricbeat/module/oracle/connection.go @@ -50,10 +50,6 @@ func NewConnection(c *ConnectionDetails) (*sql.DB, error) { params.Password = c.Password } - if params.IsSysDBA == false { - return nil, errors.New("a user with DBA permissions are required, check your connection details on field `hosts`") - } - db, err := sql.Open("godror", params.StringWithPassword()) if err != nil { return nil, errors.Wrap(err, "could not open database") diff --git a/x-pack/metricbeat/module/oracle/testing.go b/x-pack/metricbeat/module/oracle/testing.go index 5ffe9cd83f4..5a177f9d823 100644 --- a/x-pack/metricbeat/module/oracle/testing.go +++ b/x-pack/metricbeat/module/oracle/testing.go @@ -28,7 +28,7 @@ func GetOracleEnvServiceName() string { serviceName := os.Getenv("ORACLE_SERVICE_NAME") if len(serviceName) == 0 { - serviceName = "ORCLPDB1.localdomain" + serviceName = "ORCLCDB.localdomain" } return serviceName } diff --git a/x-pack/metricbeat/modules.d/oracle.yml.disabled b/x-pack/metricbeat/modules.d/oracle.yml.disabled index 219a27f0fe7..04beb96f9b7 100644 --- a/x-pack/metricbeat/modules.d/oracle.yml.disabled +++ b/x-pack/metricbeat/modules.d/oracle.yml.disabled @@ -5,7 +5,7 @@ metricsets: ["tablespace", "performance"] enabled: true period: 10s - hosts: ["oracle://user:pass@localhost:1521/ORCLPDB1.localdomain?sysdba=1"] + hosts: ["user:pass@0.0.0.0:1521/ORCLPDB1.localdomain"] # username: "" # password: "" From 19ea84d22c52db9d066065f49f717c6e07414578 Mon Sep 17 00:00:00 2001 From: Mario Castro Date: Tue, 5 May 2020 13:03:15 +0200 Subject: [PATCH 114/116] [Metricbeat] Update MSSQL module to use latest library version and fix release tags (#17862) --- CHANGELOG.next.asciidoc | 1 + NOTICE.txt | 11 +- go.mod | 2 +- go.sum | 7 +- metricbeat/docs/modules/mssql.asciidoc | 5 +- .../docs/modules/mssql/performance.asciidoc | 2 - .../modules/mssql/transaction_log.asciidoc | 2 - metricbeat/docs/modules_list.asciidoc | 4 +- .../denisenkom/go-mssqldb/README.md | 102 ++- .../go-mssqldb/accesstokenconnector.go | 51 ++ .../denisenkom/go-mssqldb/appveyor.yml | 6 +- .../github.com/denisenkom/go-mssqldb/buf.go | 24 +- .../denisenkom/go-mssqldb/bulkcopy.go | 117 +-- .../denisenkom/go-mssqldb/conn_str.go | 471 ++++++++++++ .../denisenkom/go-mssqldb/decimal.go | 131 ---- .../github.com/denisenkom/go-mssqldb/go.mod | 8 + .../github.com/denisenkom/go-mssqldb/go.sum | 5 + .../go-mssqldb/internal/decimal/decimal.go | 252 ++++++ .../{ => internal/querytext}/parser.go | 12 +- .../github.com/denisenkom/go-mssqldb/mssql.go | 90 +-- .../denisenkom/go-mssqldb/mssql_go110.go | 10 +- .../denisenkom/go-mssqldb/mssql_go110pre.go | 31 + .../denisenkom/go-mssqldb/mssql_go19.go | 27 +- .../github.com/denisenkom/go-mssqldb/net.go | 145 +++- .../github.com/denisenkom/go-mssqldb/ntlm.go | 178 ++++- .../github.com/denisenkom/go-mssqldb/tds.go | 726 +++++------------- .../github.com/denisenkom/go-mssqldb/token.go | 47 +- .../denisenkom/go-mssqldb/tvp_go19.go | 231 ++++++ .../github.com/denisenkom/go-mssqldb/types.go | 32 +- .../denisenkom/go-mssqldb/uniqueidentifier.go | 6 + .../golang-sql/civil/CONTRIBUTING.md | 73 ++ vendor/github.com/golang-sql/civil/LICENSE | 202 +++++ vendor/github.com/golang-sql/civil/README.md | 15 + .../golang-sql}/civil/civil.go | 0 vendor/modules.txt | 7 +- .../module/mssql/_meta/docs.asciidoc | 6 +- x-pack/metricbeat/module/mssql/fields.go | 2 +- .../module/mssql/performance/_meta/data.json | 37 +- .../module/mssql/performance/_meta/fields.yml | 2 +- .../performance/data_integration_test.go | 23 +- .../module/mssql/performance/performance.go | 71 +- .../performance_integration_test.go | 2 +- .../mssql/transaction_log/_meta/data.json | 21 +- .../mssql/transaction_log/_meta/fields.yml | 2 +- .../transaction_log/data_integration_test.go | 8 +- .../mssql/transaction_log/transaction_log.go | 7 +- 46 files changed, 2242 insertions(+), 972 deletions(-) create mode 100644 vendor/github.com/denisenkom/go-mssqldb/accesstokenconnector.go create mode 100644 vendor/github.com/denisenkom/go-mssqldb/conn_str.go delete mode 100644 vendor/github.com/denisenkom/go-mssqldb/decimal.go create mode 100644 vendor/github.com/denisenkom/go-mssqldb/go.mod create mode 100644 vendor/github.com/denisenkom/go-mssqldb/go.sum create mode 100644 vendor/github.com/denisenkom/go-mssqldb/internal/decimal/decimal.go rename vendor/github.com/denisenkom/go-mssqldb/{ => internal/querytext}/parser.go (89%) create mode 100644 vendor/github.com/denisenkom/go-mssqldb/mssql_go110pre.go create mode 100644 vendor/github.com/denisenkom/go-mssqldb/tvp_go19.go create mode 100644 vendor/github.com/golang-sql/civil/CONTRIBUTING.md create mode 100644 vendor/github.com/golang-sql/civil/LICENSE create mode 100644 vendor/github.com/golang-sql/civil/README.md rename vendor/{cloud.google.com/go => github.com/golang-sql}/civil/civil.go (100%) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 2ea420a3afd..003eaad9f40 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -380,6 +380,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Add static mapping for metricsets under aws module. {pull}17614[17614] {pull}17650[17650] - Collect new `bulk` indexing metrics from Elasticsearch when `xpack.enabled:true` is set. {issue} {pull}17992[17992] - Remove requirement to connect as sysdba in Oracle module {issue}15846[15846] {pull}18182[18182] +- Update MSSQL module to fix some SSPI authentication and add brackets to USE statements {pull}17862[17862]] *Packetbeat* diff --git a/NOTICE.txt b/NOTICE.txt index 57b366327b6..d86728cd811 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1158,7 +1158,7 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -------------------------------------------------------------------- Dependency: github.com/denisenkom/go-mssqldb -Revision: 4e0d7dc8888f +Revision: bbfc9a55622e License type (autodetected): BSD-3-Clause ./vendor/github.com/denisenkom/go-mssqldb/LICENSE.txt: -------------------------------------------------------------------- @@ -2829,6 +2829,15 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-------------------------------------------------------------------- +Dependency: github.com/golang-sql/civil +Revision: cb61b32ac6fe +License type (autodetected): Apache-2.0 +./vendor/github.com/golang-sql/civil/LICENSE: +-------------------------------------------------------------------- +Apache License 2.0 + + -------------------------------------------------------------------- Dependency: github.com/golang/groupcache Revision: 215e87163ea7 diff --git a/go.mod b/go.mod index 4cd1af71ef4..ff42f1ef92c 100644 --- a/go.mod +++ b/go.mod @@ -42,7 +42,7 @@ require ( github.com/coreos/go-systemd/v22 v22.0.0 github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea github.com/davecgh/go-xdr v0.0.0-20161123171359-e6a2ba005892 // indirect - github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f + github.com/denisenkom/go-mssqldb v0.0.0-20200206145737-bbfc9a55622e github.com/devigned/tab v0.1.2-0.20190607222403-0c15cf42f9a2 // indirect github.com/dgrijalva/jwt-go v3.2.1-0.20190620180102-5e25c22bd5d6+incompatible // indirect github.com/digitalocean/go-libvirt v0.0.0-20180301200012-6075ea3c39a1 diff --git a/go.sum b/go.sum index c064c9cf1b9..ac7a7a0b0db 100644 --- a/go.sum +++ b/go.sum @@ -183,8 +183,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-xdr v0.0.0-20161123171359-e6a2ba005892 h1:qg9VbHo1TlL0KDM0vYvBG9EY0X0Yku5WYIPoFWt8f6o= github.com/davecgh/go-xdr v0.0.0-20161123171359-e6a2ba005892/go.mod h1:CTDl0pzVzE5DEzZhPfvhY/9sPFMQIxaJ9VAMs9AagrE= -github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f h1:WH0w/R4Yoey+04HhFxqZ6VX6I0d7RMyw5aXQ9UTvQPs= -github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc= +github.com/denisenkom/go-mssqldb v0.0.0-20200206145737-bbfc9a55622e h1:LzwWXEScfcTu7vUZNlDDWDARoSGEtvlDKK2BYHowNeE= +github.com/denisenkom/go-mssqldb v0.0.0-20200206145737-bbfc9a55622e/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY= github.com/devigned/tab v0.1.2-0.20190607222403-0c15cf42f9a2 h1:6+hM8KeYKV0Z9EIINNqIEDyyIRAcNc2FW+/TUYNmWyw= github.com/devigned/tab v0.1.2-0.20190607222403-0c15cf42f9a2/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY= @@ -323,6 +323,8 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903 h1:LbsanbbD6LieFkXbj9YNNBupiGHJgFeLpO0j0Fza1h8= @@ -700,6 +702,7 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= diff --git a/metricbeat/docs/modules/mssql.asciidoc b/metricbeat/docs/modules/mssql.asciidoc index 15ad0f80113..a8a5aa2ba28 100644 --- a/metricbeat/docs/modules/mssql.asciidoc +++ b/metricbeat/docs/modules/mssql.asciidoc @@ -8,7 +8,7 @@ This file is generated! See scripts/mage/docs_collector.go beta[] -This is the https://www.microsoft.com/en-us/sql-server/sql-server-2017[Microsoft SQL 2017] Metricbeat module. It is still in beta and under active development to add new Metricsets and introduce enhancements. +This is the https://www.microsoft.com/en-us/sql-server/sql-server-2017[Microsoft SQL 2017] Metricbeat module. It is still under active development to add new Metricsets and introduce enhancements. [float] === Compatibility @@ -33,7 +33,7 @@ The following Metricsets are already included: [float] === Module-specific configuration notes -When configuring the `hosts` option, you can specify native user credentials +When configuring the `hosts` option, you can specify native user credentials as part of the host string with the following format: ---- @@ -57,6 +57,7 @@ metricbeat.modules: Store sensitive values like passwords in the <>. + [float] === Example configuration diff --git a/metricbeat/docs/modules/mssql/performance.asciidoc b/metricbeat/docs/modules/mssql/performance.asciidoc index 214ae0455fa..b21411b5a60 100644 --- a/metricbeat/docs/modules/mssql/performance.asciidoc +++ b/metricbeat/docs/modules/mssql/performance.asciidoc @@ -5,8 +5,6 @@ This file is generated! See scripts/mage/docs_collector.go [[metricbeat-metricset-mssql-performance]] === MSSQL performance metricset -beta[] - include::../../../../x-pack/metricbeat/module/mssql/performance/_meta/docs.asciidoc[] This is a default metricset. If the host module is unconfigured, this metricset is enabled by default. diff --git a/metricbeat/docs/modules/mssql/transaction_log.asciidoc b/metricbeat/docs/modules/mssql/transaction_log.asciidoc index cf0bb4f35eb..63bf00583c4 100644 --- a/metricbeat/docs/modules/mssql/transaction_log.asciidoc +++ b/metricbeat/docs/modules/mssql/transaction_log.asciidoc @@ -5,8 +5,6 @@ This file is generated! See scripts/mage/docs_collector.go [[metricbeat-metricset-mssql-transaction_log]] === MSSQL transaction_log metricset -beta[] - include::../../../../x-pack/metricbeat/module/mssql/transaction_log/_meta/docs.asciidoc[] This is a default metricset. If the host module is unconfigured, this metricset is enabled by default. diff --git a/metricbeat/docs/modules_list.asciidoc b/metricbeat/docs/modules_list.asciidoc index 2382eb508fc..73af89aca05 100644 --- a/metricbeat/docs/modules_list.asciidoc +++ b/metricbeat/docs/modules_list.asciidoc @@ -182,8 +182,8 @@ This file is generated! See scripts/mage/docs_collector.go |<> |<> |<> beta[] |image:./images/icon-yes.png[Prebuilt dashboards are available] | -.2+| .2+| |<> beta[] -|<> beta[] +.2+| .2+| |<> +|<> |<> |image:./images/icon-no.png[No prebuilt dashboards] | .1+| .1+| |<> |<> |image:./images/icon-yes.png[Prebuilt dashboards are available] | diff --git a/vendor/github.com/denisenkom/go-mssqldb/README.md b/vendor/github.com/denisenkom/go-mssqldb/README.md index ea06b900a8f..46e4eeabb5a 100644 --- a/vendor/github.com/denisenkom/go-mssqldb/README.md +++ b/vendor/github.com/denisenkom/go-mssqldb/README.md @@ -56,7 +56,7 @@ Other supported formats are listed below. * `hostNameInCertificate` - Specifies the Common Name (CN) in the server certificate. Default value is the server host. * `ServerSPN` - The kerberos SPN (Service Principal Name) for the server. Default is MSSQLSvc/host:port. * `Workstation ID` - The workstation name (default is the host name) -* `ApplicationIntent` - Can be given the value `ReadOnly` to initiate a read-only connection to an Availability Group listener. +* `ApplicationIntent` - Can be given the value `ReadOnly` to initiate a read-only connection to an Availability Group listener. The `database` must be specified when connecting with `Application Intent` set to `ReadOnly`. ### The connection string can be specified in one of three formats: @@ -106,6 +106,26 @@ Other supported formats are listed below. * `odbc:server=localhost;user id=sa;password={foo{bar}` // Literal `{`, password is "foo{bar" * `odbc:server=localhost;user id=sa;password={foo}}bar}` // Escaped `} with `}}`, password is "foo}bar" +### Azure Active Directory authentication - preview + +The configuration of functionality might change in the future. + +Azure Active Directory (AAD) access tokens are relatively short lived and need to be +valid when a new connection is made. Authentication is supported using a callback func that +provides a fresh and valid token using a connector: +``` golang +conn, err := mssql.NewAccessTokenConnector( + "Server=test.database.windows.net;Database=testdb", + tokenProvider) +if err != nil { + // handle errors in DSN +} +db := sql.OpenDB(conn) +``` +Where `tokenProvider` is a function that returns a fresh access token or an error. None of these statements +actually trigger the retrieval of a token, this happens when the first statment is issued and a connection +is created. + ## Executing Stored Procedures To run a stored procedure, set the query text to the procedure name: @@ -117,6 +137,66 @@ _, err := db.ExecContext(ctx, "sp_RunMe", ) ``` +## Reading Output Parameters from a Stored Procedure with Resultset + +To read output parameters from a stored procedure with resultset, make sure you read all the rows before reading the output parameters: +```go +sqltextcreate := ` +CREATE PROCEDURE spwithoutputandrows + @bitparam BIT OUTPUT +AS BEGIN + SET @bitparam = 1 + SELECT 'Row 1' +END +` +var bitout int64 +rows, err := db.QueryContext(ctx, "spwithoutputandrows", sql.Named("bitparam", sql.Out{Dest: &bitout})) +var strrow string +for rows.Next() { + err = rows.Scan(&strrow) +} +fmt.Printf("bitparam is %d", bitout) +``` + +## Caveat for local temporary tables + +Due to protocol limitations, temporary tables will only be allocated on the connection +as a result of executing a query with zero parameters. The following query +will, due to the use of a parameter, execute in its own session, +and `#mytemp` will be de-allocated right away: + +```go +conn, err := pool.Conn(ctx) +defer conn.Close() +_, err := conn.ExecContext(ctx, "select @p1 as x into #mytemp", 1) +// at this point #mytemp is already dropped again as the session of the ExecContext is over +``` + +To work around this, always explicitly create the local temporary +table in a query without any parameters. As a special case, the driver +will then be able to execute the query directly on the +connection-scoped session. The following example works: + +```go +conn, err := pool.Conn(ctx) + +// Set us up so that temp table is always cleaned up, since conn.Close() +// merely returns conn to pool, rather than actually closing the connection. +defer func() { + _, _ = conn.ExecContext(ctx, "drop table #mytemp") // always clean up + conn.Close() // merely returns conn to pool +}() + + +// Since we not pass any parameters below, the query will execute on the scope of +// the connection and succeed in creating the table. +_, err := conn.ExecContext(ctx, "create table #mytemp ( x int )") + +// #mytemp is now available even if you pass parameters +_, err := conn.ExecContext(ctx, "insert into #mytemp (x) values (@p1)", 1) + +``` + ## Return Status To get the procedure return status, pass into the parameters a @@ -127,6 +207,19 @@ _, err := db.ExecContext(ctx, "theproc", &rs) log.Printf("status=%d", rs) ``` +or + +``` +var rs mssql.ReturnStatus +_, err := db.QueryContext(ctx, "theproc", &rs) +for rows.Next() { + err = rows.Scan(&val) +} +log.Printf("status=%d", rs) +``` + +Limitation: ReturnStatus cannot be retrieved using `QueryRow`. + ## Parameters The `sqlserver` driver uses normal MS SQL Server syntax and expects parameters in @@ -147,9 +240,10 @@ are supported: * time.Time -> datetimeoffset or datetime (TDS version dependent) * mssql.DateTime1 -> datetime * mssql.DateTimeOffset -> datetimeoffset - * "cloud.google.com/go/civil".Date -> date - * "cloud.google.com/go/civil".DateTime -> datetime2 - * "cloud.google.com/go/civil".Time -> time + * "github.com/golang-sql/civil".Date -> date + * "github.com/golang-sql/civil".DateTime -> datetime2 + * "github.com/golang-sql/civil".Time -> time + * mssql.TVP -> Table Value Parameter (TDS version dependent) ## Important Notes diff --git a/vendor/github.com/denisenkom/go-mssqldb/accesstokenconnector.go b/vendor/github.com/denisenkom/go-mssqldb/accesstokenconnector.go new file mode 100644 index 00000000000..8dbe5099e49 --- /dev/null +++ b/vendor/github.com/denisenkom/go-mssqldb/accesstokenconnector.go @@ -0,0 +1,51 @@ +// +build go1.10 + +package mssql + +import ( + "context" + "database/sql/driver" + "errors" + "fmt" +) + +var _ driver.Connector = &accessTokenConnector{} + +// accessTokenConnector wraps Connector and injects a +// fresh access token when connecting to the database +type accessTokenConnector struct { + Connector + + accessTokenProvider func() (string, error) +} + +// NewAccessTokenConnector creates a new connector from a DSN and a token provider. +// The token provider func will be called when a new connection is requested and should return a valid access token. +// The returned connector may be used with sql.OpenDB. +func NewAccessTokenConnector(dsn string, tokenProvider func() (string, error)) (driver.Connector, error) { + if tokenProvider == nil { + return nil, errors.New("mssql: tokenProvider cannot be nil") + } + + conn, err := NewConnector(dsn) + if err != nil { + return nil, err + } + + c := &accessTokenConnector{ + Connector: *conn, + accessTokenProvider: tokenProvider, + } + return c, nil +} + +// Connect returns a new database connection +func (c *accessTokenConnector) Connect(ctx context.Context) (driver.Conn, error) { + var err error + c.Connector.params.fedAuthAccessToken, err = c.accessTokenProvider() + if err != nil { + return nil, fmt.Errorf("mssql: error retrieving access token: %+v", err) + } + + return c.Connector.Connect(ctx) +} diff --git a/vendor/github.com/denisenkom/go-mssqldb/appveyor.yml b/vendor/github.com/denisenkom/go-mssqldb/appveyor.yml index 2ae5456d5cb..c4d2bb060ee 100644 --- a/vendor/github.com/denisenkom/go-mssqldb/appveyor.yml +++ b/vendor/github.com/denisenkom/go-mssqldb/appveyor.yml @@ -10,7 +10,7 @@ environment: SQLUSER: sa SQLPASSWORD: Password12! DATABASE: test - GOVERSION: 110 + GOVERSION: 111 matrix: - GOVERSION: 18 SQLINSTANCE: SQL2016 @@ -18,6 +18,8 @@ environment: SQLINSTANCE: SQL2016 - GOVERSION: 110 SQLINSTANCE: SQL2016 + - GOVERSION: 111 + SQLINSTANCE: SQL2016 - SQLINSTANCE: SQL2014 - SQLINSTANCE: SQL2012SP1 - SQLINSTANCE: SQL2008R2SP2 @@ -27,7 +29,7 @@ install: - set PATH=%GOPATH%\bin;%GOROOT%\bin;%PATH% - go version - go env - - go get -u cloud.google.com/go/civil + - go get -u github.com/golang-sql/civil build_script: - go build diff --git a/vendor/github.com/denisenkom/go-mssqldb/buf.go b/vendor/github.com/denisenkom/go-mssqldb/buf.go index 927d75d1b78..ba39b40f173 100644 --- a/vendor/github.com/denisenkom/go-mssqldb/buf.go +++ b/vendor/github.com/denisenkom/go-mssqldb/buf.go @@ -221,23 +221,27 @@ func (r *tdsBuffer) uint16() uint16 { } func (r *tdsBuffer) BVarChar() string { - l := int(r.byte()) - return r.readUcs2(l) + return readBVarCharOrPanic(r) } -func (r *tdsBuffer) UsVarChar() string { - l := int(r.uint16()) - return r.readUcs2(l) +func readBVarCharOrPanic(r io.Reader) string { + s, err := readBVarChar(r) + if err != nil { + badStreamPanic(err) + } + return s } -func (r *tdsBuffer) readUcs2(numchars int) string { - b := make([]byte, numchars*2) - r.ReadFull(b) - res, err := ucs22str(b) +func readUsVarCharOrPanic(r io.Reader) string { + s, err := readUsVarChar(r) if err != nil { badStreamPanic(err) } - return res + return s +} + +func (r *tdsBuffer) UsVarChar() string { + return readUsVarCharOrPanic(r) } func (r *tdsBuffer) Read(buf []byte) (copied int, err error) { diff --git a/vendor/github.com/denisenkom/go-mssqldb/bulkcopy.go b/vendor/github.com/denisenkom/go-mssqldb/bulkcopy.go index 3b319af893f..1d5eacb3818 100644 --- a/vendor/github.com/denisenkom/go-mssqldb/bulkcopy.go +++ b/vendor/github.com/denisenkom/go-mssqldb/bulkcopy.go @@ -7,9 +7,10 @@ import ( "fmt" "math" "reflect" - "strconv" "strings" "time" + + "github.com/denisenkom/go-mssqldb/internal/decimal" ) type Bulk struct { @@ -42,6 +43,11 @@ type BulkOptions struct { type DataValue interface{} +const ( + sqlDateFormat = "2006-01-02" + sqlTimeFormat = "2006-01-02 15:04:05.999999999Z07:00" +) + func (cn *Conn) CreateBulk(table string, columns []string) (_ *Bulk) { b := Bulk{ctx: context.Background(), cn: cn, tablename: table, headerSent: false, columnsName: columns} b.Debug = false @@ -334,7 +340,7 @@ func (b *Bulk) makeParam(val DataValue, col columnStruct) (res param, err error) case int64: intvalue = val default: - err = fmt.Errorf("mssql: invalid type for int column") + err = fmt.Errorf("mssql: invalid type for int column: %T", val) return } @@ -361,7 +367,7 @@ func (b *Bulk) makeParam(val DataValue, col columnStruct) (res param, err error) case int64: floatvalue = float64(val) default: - err = fmt.Errorf("mssql: invalid type for float column: %s", val) + err = fmt.Errorf("mssql: invalid type for float column: %T %s", val, val) return } @@ -380,7 +386,7 @@ func (b *Bulk) makeParam(val DataValue, col columnStruct) (res param, err error) case []byte: res.buffer = val default: - err = fmt.Errorf("mssql: invalid type for nvarchar column: %s", val) + err = fmt.Errorf("mssql: invalid type for nvarchar column: %T %s", val, val) return } res.ti.Size = len(res.buffer) @@ -392,14 +398,14 @@ func (b *Bulk) makeParam(val DataValue, col columnStruct) (res param, err error) case []byte: res.buffer = val default: - err = fmt.Errorf("mssql: invalid type for varchar column: %s", val) + err = fmt.Errorf("mssql: invalid type for varchar column: %T %s", val, val) return } res.ti.Size = len(res.buffer) case typeBit, typeBitN: if reflect.TypeOf(val).Kind() != reflect.Bool { - err = fmt.Errorf("mssql: invalid type for bit column: %s", val) + err = fmt.Errorf("mssql: invalid type for bit column: %T %s", val, val) return } res.ti.TypeId = typeBitN @@ -413,18 +419,31 @@ func (b *Bulk) makeParam(val DataValue, col columnStruct) (res param, err error) case time.Time: res.buffer = encodeDateTime2(val, int(col.ti.Scale)) res.ti.Size = len(res.buffer) + case string: + var t time.Time + if t, err = time.Parse(sqlTimeFormat, val); err != nil { + return res, fmt.Errorf("bulk: unable to convert string to date: %v", err) + } + res.buffer = encodeDateTime2(t, int(col.ti.Scale)) + res.ti.Size = len(res.buffer) default: - err = fmt.Errorf("mssql: invalid type for datetime2 column: %s", val) + err = fmt.Errorf("mssql: invalid type for datetime2 column: %T %s", val, val) return } case typeDateTimeOffsetN: switch val := val.(type) { case time.Time: - res.buffer = encodeDateTimeOffset(val, int(res.ti.Scale)) + res.buffer = encodeDateTimeOffset(val, int(col.ti.Scale)) + res.ti.Size = len(res.buffer) + case string: + var t time.Time + if t, err = time.Parse(sqlTimeFormat, val); err != nil { + return res, fmt.Errorf("bulk: unable to convert string to date: %v", err) + } + res.buffer = encodeDateTimeOffset(t, int(col.ti.Scale)) res.ti.Size = len(res.buffer) - default: - err = fmt.Errorf("mssql: invalid type for datetimeoffset column: %s", val) + err = fmt.Errorf("mssql: invalid type for datetimeoffset column: %T %s", val, val) return } case typeDateN: @@ -432,69 +451,79 @@ func (b *Bulk) makeParam(val DataValue, col columnStruct) (res param, err error) case time.Time: res.buffer = encodeDate(val) res.ti.Size = len(res.buffer) + case string: + var t time.Time + if t, err = time.ParseInLocation(sqlDateFormat, val, time.UTC); err != nil { + return res, fmt.Errorf("bulk: unable to convert string to date: %v", err) + } + res.buffer = encodeDate(t) + res.ti.Size = len(res.buffer) default: - err = fmt.Errorf("mssql: invalid type for date column: %s", val) + err = fmt.Errorf("mssql: invalid type for date column: %T %s", val, val) return } case typeDateTime, typeDateTimeN, typeDateTim4: + var t time.Time switch val := val.(type) { case time.Time: - if col.ti.Size == 4 { - res.buffer = encodeDateTim4(val) - res.ti.Size = len(res.buffer) - } else if col.ti.Size == 8 { - res.buffer = encodeDateTime(val) - res.ti.Size = len(res.buffer) - } else { - err = fmt.Errorf("mssql: invalid size of column") + t = val + case string: + if t, err = time.Parse(sqlTimeFormat, val); err != nil { + return res, fmt.Errorf("bulk: unable to convert string to date: %v", err) } - default: - err = fmt.Errorf("mssql: invalid type for datetime column: %s", val) + err = fmt.Errorf("mssql: invalid type for datetime column: %T %s", val, val) + return + } + + if col.ti.Size == 4 { + res.buffer = encodeDateTim4(t) + res.ti.Size = len(res.buffer) + } else if col.ti.Size == 8 { + res.buffer = encodeDateTime(t) + res.ti.Size = len(res.buffer) + } else { + err = fmt.Errorf("mssql: invalid size of column %d", col.ti.Size) } // case typeMoney, typeMoney4, typeMoneyN: case typeDecimal, typeDecimalN, typeNumeric, typeNumericN: - var value float64 + prec := col.ti.Prec + scale := col.ti.Scale + var dec decimal.Decimal switch v := val.(type) { case int: - value = float64(v) + dec = decimal.Int64ToDecimalScale(int64(v), 0) case int8: - value = float64(v) + dec = decimal.Int64ToDecimalScale(int64(v), 0) case int16: - value = float64(v) + dec = decimal.Int64ToDecimalScale(int64(v), 0) case int32: - value = float64(v) + dec = decimal.Int64ToDecimalScale(int64(v), 0) case int64: - value = float64(v) + dec = decimal.Int64ToDecimalScale(int64(v), 0) case float32: - value = float64(v) + dec, err = decimal.Float64ToDecimalScale(float64(v), scale) case float64: - value = v + dec, err = decimal.Float64ToDecimalScale(float64(v), scale) case string: - if value, err = strconv.ParseFloat(v, 64); err != nil { - return res, fmt.Errorf("bulk: unable to convert string to float: %v", err) - } + dec, err = decimal.StringToDecimalScale(v, scale) default: - return res, fmt.Errorf("unknown value for decimal: %#v", v) + return res, fmt.Errorf("unknown value for decimal: %T %#v", v, v) } - perc := col.ti.Prec - scale := col.ti.Scale - var dec Decimal - dec, err = Float64ToDecimalScale(value, scale) if err != nil { return res, err } - dec.prec = perc + dec.SetPrec(prec) var length byte switch { - case perc <= 9: + case prec <= 9: length = 4 - case perc <= 19: + case prec <= 19: length = 8 - case perc <= 28: + case prec <= 28: length = 12 default: length = 16 @@ -504,7 +533,7 @@ func (b *Bulk) makeParam(val DataValue, col columnStruct) (res param, err error) // first byte length written by typeInfo.writer res.ti.Size = int(length) + 1 // second byte sign - if value < 0 { + if !dec.IsPositive() { buf[0] = 0 } else { buf[0] = 1 @@ -527,7 +556,7 @@ func (b *Bulk) makeParam(val DataValue, col columnStruct) (res param, err error) res.ti.Size = len(val) res.buffer = val default: - err = fmt.Errorf("mssql: invalid type for Binary column: %s", val) + err = fmt.Errorf("mssql: invalid type for Binary column: %T %s", val, val) return } case typeGuid: @@ -536,7 +565,7 @@ func (b *Bulk) makeParam(val DataValue, col columnStruct) (res param, err error) res.ti.Size = len(val) res.buffer = val default: - err = fmt.Errorf("mssql: invalid type for Guid column: %s", val) + err = fmt.Errorf("mssql: invalid type for Guid column: %T %s", val, val) return } diff --git a/vendor/github.com/denisenkom/go-mssqldb/conn_str.go b/vendor/github.com/denisenkom/go-mssqldb/conn_str.go new file mode 100644 index 00000000000..26ac50f38df --- /dev/null +++ b/vendor/github.com/denisenkom/go-mssqldb/conn_str.go @@ -0,0 +1,471 @@ +package mssql + +import ( + "fmt" + "net" + "net/url" + "os" + "strconv" + "strings" + "time" + "unicode" +) + +const defaultServerPort = 1433 + +type connectParams struct { + logFlags uint64 + port uint64 + host string + instance string + database string + user string + password string + dial_timeout time.Duration + conn_timeout time.Duration + keepAlive time.Duration + encrypt bool + disableEncryption bool + trustServerCertificate bool + certificate string + hostInCertificate string + hostInCertificateProvided bool + serverSPN string + workstation string + appname string + typeFlags uint8 + failOverPartner string + failOverPort uint64 + packetSize uint16 + fedAuthAccessToken string +} + +func parseConnectParams(dsn string) (connectParams, error) { + var p connectParams + + var params map[string]string + if strings.HasPrefix(dsn, "odbc:") { + parameters, err := splitConnectionStringOdbc(dsn[len("odbc:"):]) + if err != nil { + return p, err + } + params = parameters + } else if strings.HasPrefix(dsn, "sqlserver://") { + parameters, err := splitConnectionStringURL(dsn) + if err != nil { + return p, err + } + params = parameters + } else { + params = splitConnectionString(dsn) + } + + strlog, ok := params["log"] + if ok { + var err error + p.logFlags, err = strconv.ParseUint(strlog, 10, 64) + if err != nil { + return p, fmt.Errorf("Invalid log parameter '%s': %s", strlog, err.Error()) + } + } + server := params["server"] + parts := strings.SplitN(server, `\`, 2) + p.host = parts[0] + if p.host == "." || strings.ToUpper(p.host) == "(LOCAL)" || p.host == "" { + p.host = "localhost" + } + if len(parts) > 1 { + p.instance = parts[1] + } + p.database = params["database"] + p.user = params["user id"] + p.password = params["password"] + + p.port = 0 + strport, ok := params["port"] + if ok { + var err error + p.port, err = strconv.ParseUint(strport, 10, 16) + if err != nil { + f := "Invalid tcp port '%v': %v" + return p, fmt.Errorf(f, strport, err.Error()) + } + } + + // https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/configure-the-network-packet-size-server-configuration-option + // Default packet size remains at 4096 bytes + p.packetSize = 4096 + strpsize, ok := params["packet size"] + if ok { + var err error + psize, err := strconv.ParseUint(strpsize, 0, 16) + if err != nil { + f := "Invalid packet size '%v': %v" + return p, fmt.Errorf(f, strpsize, err.Error()) + } + + // Ensure packet size falls within the TDS protocol range of 512 to 32767 bytes + // NOTE: Encrypted connections have a maximum size of 16383 bytes. If you request + // a higher packet size, the server will respond with an ENVCHANGE request to + // alter the packet size to 16383 bytes. + p.packetSize = uint16(psize) + if p.packetSize < 512 { + p.packetSize = 512 + } else if p.packetSize > 32767 { + p.packetSize = 32767 + } + } + + // https://msdn.microsoft.com/en-us/library/dd341108.aspx + // + // Do not set a connection timeout. Use Context to manage such things. + // Default to zero, but still allow it to be set. + if strconntimeout, ok := params["connection timeout"]; ok { + timeout, err := strconv.ParseUint(strconntimeout, 10, 64) + if err != nil { + f := "Invalid connection timeout '%v': %v" + return p, fmt.Errorf(f, strconntimeout, err.Error()) + } + p.conn_timeout = time.Duration(timeout) * time.Second + } + p.dial_timeout = 15 * time.Second + if strdialtimeout, ok := params["dial timeout"]; ok { + timeout, err := strconv.ParseUint(strdialtimeout, 10, 64) + if err != nil { + f := "Invalid dial timeout '%v': %v" + return p, fmt.Errorf(f, strdialtimeout, err.Error()) + } + p.dial_timeout = time.Duration(timeout) * time.Second + } + + // default keep alive should be 30 seconds according to spec: + // https://msdn.microsoft.com/en-us/library/dd341108.aspx + p.keepAlive = 30 * time.Second + if keepAlive, ok := params["keepalive"]; ok { + timeout, err := strconv.ParseUint(keepAlive, 10, 64) + if err != nil { + f := "Invalid keepAlive value '%s': %s" + return p, fmt.Errorf(f, keepAlive, err.Error()) + } + p.keepAlive = time.Duration(timeout) * time.Second + } + encrypt, ok := params["encrypt"] + if ok { + if strings.EqualFold(encrypt, "DISABLE") { + p.disableEncryption = true + } else { + var err error + p.encrypt, err = strconv.ParseBool(encrypt) + if err != nil { + f := "Invalid encrypt '%s': %s" + return p, fmt.Errorf(f, encrypt, err.Error()) + } + } + } else { + p.trustServerCertificate = true + } + trust, ok := params["trustservercertificate"] + if ok { + var err error + p.trustServerCertificate, err = strconv.ParseBool(trust) + if err != nil { + f := "Invalid trust server certificate '%s': %s" + return p, fmt.Errorf(f, trust, err.Error()) + } + } + p.certificate = params["certificate"] + p.hostInCertificate, ok = params["hostnameincertificate"] + if ok { + p.hostInCertificateProvided = true + } else { + p.hostInCertificate = p.host + p.hostInCertificateProvided = false + } + + serverSPN, ok := params["serverspn"] + if ok { + p.serverSPN = serverSPN + } else { + p.serverSPN = generateSpn(p.host, resolveServerPort(p.port)) + } + + workstation, ok := params["workstation id"] + if ok { + p.workstation = workstation + } else { + workstation, err := os.Hostname() + if err == nil { + p.workstation = workstation + } + } + + appname, ok := params["app name"] + if !ok { + appname = "go-mssqldb" + } + p.appname = appname + + appintent, ok := params["applicationintent"] + if ok { + if appintent == "ReadOnly" { + if p.database == "" { + return p, fmt.Errorf("Database must be specified when ApplicationIntent is ReadOnly") + } + p.typeFlags |= fReadOnlyIntent + } + } + + failOverPartner, ok := params["failoverpartner"] + if ok { + p.failOverPartner = failOverPartner + } + + failOverPort, ok := params["failoverport"] + if ok { + var err error + p.failOverPort, err = strconv.ParseUint(failOverPort, 0, 16) + if err != nil { + f := "Invalid tcp port '%v': %v" + return p, fmt.Errorf(f, failOverPort, err.Error()) + } + } + + return p, nil +} + +func splitConnectionString(dsn string) (res map[string]string) { + res = map[string]string{} + parts := strings.Split(dsn, ";") + for _, part := range parts { + if len(part) == 0 { + continue + } + lst := strings.SplitN(part, "=", 2) + name := strings.TrimSpace(strings.ToLower(lst[0])) + if len(name) == 0 { + continue + } + var value string = "" + if len(lst) > 1 { + value = strings.TrimSpace(lst[1]) + } + res[name] = value + } + return res +} + +// Splits a URL of the form sqlserver://username:password@host/instance?param1=value¶m2=value +func splitConnectionStringURL(dsn string) (map[string]string, error) { + res := map[string]string{} + + u, err := url.Parse(dsn) + if err != nil { + return res, err + } + + if u.Scheme != "sqlserver" { + return res, fmt.Errorf("scheme %s is not recognized", u.Scheme) + } + + if u.User != nil { + res["user id"] = u.User.Username() + p, exists := u.User.Password() + if exists { + res["password"] = p + } + } + + host, port, err := net.SplitHostPort(u.Host) + if err != nil { + host = u.Host + } + + if len(u.Path) > 0 { + res["server"] = host + "\\" + u.Path[1:] + } else { + res["server"] = host + } + + if len(port) > 0 { + res["port"] = port + } + + query := u.Query() + for k, v := range query { + if len(v) > 1 { + return res, fmt.Errorf("key %s provided more than once", k) + } + res[strings.ToLower(k)] = v[0] + } + + return res, nil +} + +// Splits a URL in the ODBC format +func splitConnectionStringOdbc(dsn string) (map[string]string, error) { + res := map[string]string{} + + type parserState int + const ( + // Before the start of a key + parserStateBeforeKey parserState = iota + + // Inside a key + parserStateKey + + // Beginning of a value. May be bare or braced + parserStateBeginValue + + // Inside a bare value + parserStateBareValue + + // Inside a braced value + parserStateBracedValue + + // A closing brace inside a braced value. + // May be the end of the value or an escaped closing brace, depending on the next character + parserStateBracedValueClosingBrace + + // After a value. Next character should be a semicolon or whitespace. + parserStateEndValue + ) + + var state = parserStateBeforeKey + + var key string + var value string + + for i, c := range dsn { + switch state { + case parserStateBeforeKey: + switch { + case c == '=': + return res, fmt.Errorf("Unexpected character = at index %d. Expected start of key or semi-colon or whitespace.", i) + case !unicode.IsSpace(c) && c != ';': + state = parserStateKey + key += string(c) + } + + case parserStateKey: + switch c { + case '=': + key = normalizeOdbcKey(key) + state = parserStateBeginValue + + case ';': + // Key without value + key = normalizeOdbcKey(key) + res[key] = value + key = "" + value = "" + state = parserStateBeforeKey + + default: + key += string(c) + } + + case parserStateBeginValue: + switch { + case c == '{': + state = parserStateBracedValue + case c == ';': + // Empty value + res[key] = value + key = "" + state = parserStateBeforeKey + case unicode.IsSpace(c): + // Ignore whitespace + default: + state = parserStateBareValue + value += string(c) + } + + case parserStateBareValue: + if c == ';' { + res[key] = strings.TrimRightFunc(value, unicode.IsSpace) + key = "" + value = "" + state = parserStateBeforeKey + } else { + value += string(c) + } + + case parserStateBracedValue: + if c == '}' { + state = parserStateBracedValueClosingBrace + } else { + value += string(c) + } + + case parserStateBracedValueClosingBrace: + if c == '}' { + // Escaped closing brace + value += string(c) + state = parserStateBracedValue + continue + } + + // End of braced value + res[key] = value + key = "" + value = "" + + // This character is the first character past the end, + // so it needs to be parsed like the parserStateEndValue state. + state = parserStateEndValue + switch { + case c == ';': + state = parserStateBeforeKey + case unicode.IsSpace(c): + // Ignore whitespace + default: + return res, fmt.Errorf("Unexpected character %c at index %d. Expected semi-colon or whitespace.", c, i) + } + + case parserStateEndValue: + switch { + case c == ';': + state = parserStateBeforeKey + case unicode.IsSpace(c): + // Ignore whitespace + default: + return res, fmt.Errorf("Unexpected character %c at index %d. Expected semi-colon or whitespace.", c, i) + } + } + } + + switch state { + case parserStateBeforeKey: // Okay + case parserStateKey: // Unfinished key. Treat as key without value. + key = normalizeOdbcKey(key) + res[key] = value + case parserStateBeginValue: // Empty value + res[key] = value + case parserStateBareValue: + res[key] = strings.TrimRightFunc(value, unicode.IsSpace) + case parserStateBracedValue: + return res, fmt.Errorf("Unexpected end of braced value at index %d.", len(dsn)) + case parserStateBracedValueClosingBrace: // End of braced value + res[key] = value + case parserStateEndValue: // Okay + } + + return res, nil +} + +// Normalizes the given string as an ODBC-format key +func normalizeOdbcKey(s string) string { + return strings.ToLower(strings.TrimRightFunc(s, unicode.IsSpace)) +} + +func resolveServerPort(port uint64) uint64 { + if port == 0 { + return defaultServerPort + } + + return port +} + +func generateSpn(host string, port uint64) string { + return fmt.Sprintf("MSSQLSvc/%s:%d", host, port) +} diff --git a/vendor/github.com/denisenkom/go-mssqldb/decimal.go b/vendor/github.com/denisenkom/go-mssqldb/decimal.go deleted file mode 100644 index 372f64b4eb1..00000000000 --- a/vendor/github.com/denisenkom/go-mssqldb/decimal.go +++ /dev/null @@ -1,131 +0,0 @@ -package mssql - -import ( - "encoding/binary" - "errors" - "math" - "math/big" -) - -// http://msdn.microsoft.com/en-us/library/ee780893.aspx -type Decimal struct { - integer [4]uint32 - positive bool - prec uint8 - scale uint8 -} - -var scaletblflt64 [39]float64 - -func (d Decimal) ToFloat64() float64 { - val := float64(0) - for i := 3; i >= 0; i-- { - val *= 0x100000000 - val += float64(d.integer[i]) - } - if !d.positive { - val = -val - } - if d.scale != 0 { - val /= scaletblflt64[d.scale] - } - return val -} - -const autoScale = 100 - -func Float64ToDecimal(f float64) (Decimal, error) { - return Float64ToDecimalScale(f, autoScale) -} - -func Float64ToDecimalScale(f float64, scale uint8) (Decimal, error) { - var dec Decimal - if math.IsNaN(f) { - return dec, errors.New("NaN") - } - if math.IsInf(f, 0) { - return dec, errors.New("Infinity can't be converted to decimal") - } - dec.positive = f >= 0 - if !dec.positive { - f = math.Abs(f) - } - if f > 3.402823669209385e+38 { - return dec, errors.New("Float value is out of range") - } - dec.prec = 20 - var integer float64 - for dec.scale = 0; dec.scale <= scale; dec.scale++ { - integer = f * scaletblflt64[dec.scale] - _, frac := math.Modf(integer) - if frac == 0 && scale == autoScale { - break - } - } - for i := 0; i < 4; i++ { - mod := math.Mod(integer, 0x100000000) - integer -= mod - integer /= 0x100000000 - dec.integer[i] = uint32(mod) - } - return dec, nil -} - -func init() { - var acc float64 = 1 - for i := 0; i <= 38; i++ { - scaletblflt64[i] = acc - acc *= 10 - } -} - -func (d Decimal) BigInt() big.Int { - bytes := make([]byte, 16) - binary.BigEndian.PutUint32(bytes[0:4], d.integer[3]) - binary.BigEndian.PutUint32(bytes[4:8], d.integer[2]) - binary.BigEndian.PutUint32(bytes[8:12], d.integer[1]) - binary.BigEndian.PutUint32(bytes[12:16], d.integer[0]) - var x big.Int - x.SetBytes(bytes) - if !d.positive { - x.Neg(&x) - } - return x -} - -func (d Decimal) Bytes() []byte { - x := d.BigInt() - return scaleBytes(x.String(), d.scale) -} - -func (d Decimal) UnscaledBytes() []byte { - x := d.BigInt() - return x.Bytes() -} - -func scaleBytes(s string, scale uint8) []byte { - z := make([]byte, 0, len(s)+1) - if s[0] == '-' || s[0] == '+' { - z = append(z, byte(s[0])) - s = s[1:] - } - pos := len(s) - int(scale) - if pos <= 0 { - z = append(z, byte('0')) - } else if pos > 0 { - z = append(z, []byte(s[:pos])...) - } - if scale > 0 { - z = append(z, byte('.')) - for pos < 0 { - z = append(z, byte('0')) - pos++ - } - z = append(z, []byte(s[pos:])...) - } - return z -} - -func (d Decimal) String() string { - return string(d.Bytes()) -} diff --git a/vendor/github.com/denisenkom/go-mssqldb/go.mod b/vendor/github.com/denisenkom/go-mssqldb/go.mod new file mode 100644 index 00000000000..ebc02ab88f9 --- /dev/null +++ b/vendor/github.com/denisenkom/go-mssqldb/go.mod @@ -0,0 +1,8 @@ +module github.com/denisenkom/go-mssqldb + +go 1.11 + +require ( + github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe + golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c +) diff --git a/vendor/github.com/denisenkom/go-mssqldb/go.sum b/vendor/github.com/denisenkom/go-mssqldb/go.sum new file mode 100644 index 00000000000..1887801bbf4 --- /dev/null +++ b/vendor/github.com/denisenkom/go-mssqldb/go.sum @@ -0,0 +1,5 @@ +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/vendor/github.com/denisenkom/go-mssqldb/internal/decimal/decimal.go b/vendor/github.com/denisenkom/go-mssqldb/internal/decimal/decimal.go new file mode 100644 index 00000000000..7da0375d91d --- /dev/null +++ b/vendor/github.com/denisenkom/go-mssqldb/internal/decimal/decimal.go @@ -0,0 +1,252 @@ +package decimal + +import ( + "encoding/binary" + "errors" + "fmt" + "math" + "math/big" + "strings" +) + +// Decimal represents decimal type in the Microsoft Open Specifications: http://msdn.microsoft.com/en-us/library/ee780893.aspx +type Decimal struct { + integer [4]uint32 // Little-endian + positive bool + prec uint8 + scale uint8 +} + +var ( + scaletblflt64 [39]float64 + int10 big.Int + int1e5 big.Int +) + +func init() { + var acc float64 = 1 + for i := 0; i <= 38; i++ { + scaletblflt64[i] = acc + acc *= 10 + } + + int10.SetInt64(10) + int1e5.SetInt64(1e5) +} + +const autoScale = 100 + +// SetInteger sets the ind'th element in the integer array +func (d *Decimal) SetInteger(integer uint32, ind uint8) { + d.integer[ind] = integer +} + +// SetPositive sets the positive member +func (d *Decimal) SetPositive(positive bool) { + d.positive = positive +} + +// SetPrec sets the prec member +func (d *Decimal) SetPrec(prec uint8) { + d.prec = prec +} + +// SetScale sets the scale member +func (d *Decimal) SetScale(scale uint8) { + d.scale = scale +} + +// IsPositive returns true if the Decimal is positive +func (d *Decimal) IsPositive() bool { + return d.positive +} + +// ToFloat64 converts decimal to a float64 +func (d Decimal) ToFloat64() float64 { + val := float64(0) + for i := 3; i >= 0; i-- { + val *= 0x100000000 + val += float64(d.integer[i]) + } + if !d.positive { + val = -val + } + if d.scale != 0 { + val /= scaletblflt64[d.scale] + } + return val +} + +// BigInt converts decimal to a bigint +func (d Decimal) BigInt() big.Int { + bytes := make([]byte, 16) + binary.BigEndian.PutUint32(bytes[0:4], d.integer[3]) + binary.BigEndian.PutUint32(bytes[4:8], d.integer[2]) + binary.BigEndian.PutUint32(bytes[8:12], d.integer[1]) + binary.BigEndian.PutUint32(bytes[12:16], d.integer[0]) + var x big.Int + x.SetBytes(bytes) + if !d.positive { + x.Neg(&x) + } + return x +} + +// Bytes converts decimal to a scaled byte slice +func (d Decimal) Bytes() []byte { + x := d.BigInt() + return ScaleBytes(x.String(), d.scale) +} + +// UnscaledBytes converts decimal to a unscaled byte slice +func (d Decimal) UnscaledBytes() []byte { + x := d.BigInt() + return x.Bytes() +} + +// String converts decimal to a string +func (d Decimal) String() string { + return string(d.Bytes()) +} + +// Float64ToDecimal converts float64 to decimal +func Float64ToDecimal(f float64) (Decimal, error) { + return Float64ToDecimalScale(f, autoScale) +} + +// Float64ToDecimalScale converts float64 to decimal; user can specify the scale +func Float64ToDecimalScale(f float64, scale uint8) (Decimal, error) { + var dec Decimal + if math.IsNaN(f) { + return dec, errors.New("NaN") + } + if math.IsInf(f, 0) { + return dec, errors.New("Infinity can't be converted to decimal") + } + dec.positive = f >= 0 + if !dec.positive { + f = math.Abs(f) + } + if f > 3.402823669209385e+38 { + return dec, errors.New("Float value is out of range") + } + dec.prec = 20 + var integer float64 + for dec.scale = 0; dec.scale <= scale; dec.scale++ { + integer = f * scaletblflt64[dec.scale] + _, frac := math.Modf(integer) + if frac == 0 && scale == autoScale { + break + } + } + for i := 0; i < 4; i++ { + mod := math.Mod(integer, 0x100000000) + integer -= mod + integer /= 0x100000000 + dec.integer[i] = uint32(mod) + if mod-math.Trunc(mod) >= 0.5 { + dec.integer[i] = uint32(mod) + 1 + } + } + return dec, nil +} + +// Int64ToDecimalScale converts float64 to decimal; user can specify the scale +func Int64ToDecimalScale(v int64, scale uint8) Decimal { + positive := v >= 0 + if !positive { + if v == math.MinInt64 { + // Special case - can't negate + return Decimal{ + integer: [4]uint32{0, 0x80000000, 0, 0}, + positive: false, + prec: 20, + scale: 0, + } + } + v = -v + } + return Decimal{ + integer: [4]uint32{uint32(v), uint32(v >> 32), 0, 0}, + positive: positive, + prec: 20, + scale: scale, + } +} + +// StringToDecimalScale converts string to decimal +func StringToDecimalScale(v string, outScale uint8) (Decimal, error) { + var r big.Int + var unscaled string + var inScale int + + point := strings.LastIndexByte(v, '.') + if point == -1 { + inScale = 0 + unscaled = v + } else { + inScale = len(v) - point - 1 + unscaled = v[:point] + v[point+1:] + } + if inScale > math.MaxUint8 { + return Decimal{}, fmt.Errorf("can't parse %q as a decimal number: scale too large", v) + } + + _, ok := r.SetString(unscaled, 10) + if !ok { + return Decimal{}, fmt.Errorf("can't parse %q as a decimal number", v) + } + + if inScale > int(outScale) { + return Decimal{}, fmt.Errorf("can't parse %q as a decimal number: scale %d is larger than the scale %d of the target column", v, inScale, outScale) + } + for inScale < int(outScale) { + if int(outScale)-inScale >= 5 { + r.Mul(&r, &int1e5) + inScale += 5 + } else { + r.Mul(&r, &int10) + inScale++ + } + } + + bytes := r.Bytes() + if len(bytes) > 16 { + return Decimal{}, fmt.Errorf("can't parse %q as a decimal number: precision too large", v) + } + var out [4]uint32 + for i, b := range bytes { + pos := len(bytes) - i - 1 + out[pos/4] += uint32(b) << uint(pos%4*8) + } + return Decimal{ + integer: out, + positive: r.Sign() >= 0, + prec: 20, + scale: uint8(inScale), + }, nil +} + +// ScaleBytes converts a stringified decimal to a scaled byte slice +func ScaleBytes(s string, scale uint8) []byte { + z := make([]byte, 0, len(s)+1) + if s[0] == '-' || s[0] == '+' { + z = append(z, byte(s[0])) + s = s[1:] + } + pos := len(s) - int(scale) + if pos <= 0 { + z = append(z, byte('0')) + } else if pos > 0 { + z = append(z, []byte(s[:pos])...) + } + if scale > 0 { + z = append(z, byte('.')) + for pos < 0 { + z = append(z, byte('0')) + pos++ + } + z = append(z, []byte(s[pos:])...) + } + return z +} diff --git a/vendor/github.com/denisenkom/go-mssqldb/parser.go b/vendor/github.com/denisenkom/go-mssqldb/internal/querytext/parser.go similarity index 89% rename from vendor/github.com/denisenkom/go-mssqldb/parser.go rename to vendor/github.com/denisenkom/go-mssqldb/internal/querytext/parser.go index 8021ca603c9..14650e38944 100644 --- a/vendor/github.com/denisenkom/go-mssqldb/parser.go +++ b/vendor/github.com/denisenkom/go-mssqldb/internal/querytext/parser.go @@ -1,4 +1,8 @@ -package mssql +// Package querytext is the old query parser and parameter substitute process. +// Do not use on new code. +// +// This package is not subject to any API compatibility guarantee. +package querytext import ( "bytes" @@ -40,7 +44,11 @@ func (p *parser) write(ch rune) { type stateFunc func(*parser) stateFunc -func parseParams(query string) (string, int) { +// ParseParams rewrites the query from using "?" placeholders +// to using "@pN" parameter names that SQL Server will accept. +// +// This function and package is not subject to any API compatibility guarantee. +func ParseParams(query string) (string, int) { p := &parser{ r: bytes.NewReader([]byte(query)), namedParams: map[string]bool{}, diff --git a/vendor/github.com/denisenkom/go-mssqldb/mssql.go b/vendor/github.com/denisenkom/go-mssqldb/mssql.go index aa173b3ed51..a74bc7e3fc4 100644 --- a/vendor/github.com/denisenkom/go-mssqldb/mssql.go +++ b/vendor/github.com/denisenkom/go-mssqldb/mssql.go @@ -14,6 +14,8 @@ import ( "strings" "time" "unicode" + + "github.com/denisenkom/go-mssqldb/internal/querytext" ) // ReturnStatus may be used to return the return value from a proc. @@ -29,24 +31,19 @@ var driverInstanceNoProcess = &Driver{processQueryText: false} func init() { sql.Register("mssql", driverInstance) sql.Register("sqlserver", driverInstanceNoProcess) - createDialer = func(p *connectParams) dialer { - return tcpDialer{&net.Dialer{KeepAlive: p.keepAlive}} + createDialer = func(p *connectParams) Dialer { + return netDialer{&net.Dialer{KeepAlive: p.keepAlive}} } } -// Abstract the dialer for testing and for non-TCP based connections. -type dialer interface { - Dial(ctx context.Context, addr string) (net.Conn, error) -} - -var createDialer func(p *connectParams) dialer +var createDialer func(p *connectParams) Dialer -type tcpDialer struct { +type netDialer struct { nd *net.Dialer } -func (d tcpDialer) Dial(ctx context.Context, addr string) (net.Conn, error) { - return d.nd.DialContext(ctx, "tcp", addr) +func (d netDialer) DialContext(ctx context.Context, network string, addr string) (net.Conn, error) { + return d.nd.DialContext(ctx, network, addr) } type Driver struct { @@ -125,6 +122,21 @@ type Connector struct { // SessionInitSQL is optional. The session will be reset even if // SessionInitSQL is empty. SessionInitSQL string + + // Dialer sets a custom dialer for all network operations. + // If Dialer is not set, normal net dialers are used. + Dialer Dialer +} + +type Dialer interface { + DialContext(ctx context.Context, network string, addr string) (net.Conn, error) +} + +func (c *Connector) getDialer(p *connectParams) Dialer { + if c != nil && c.Dialer != nil { + return c.Dialer + } + return createDialer(p) } type Conn struct { @@ -310,12 +322,12 @@ func (d *Driver) open(ctx context.Context, dsn string) (*Conn, error) { if err != nil { return nil, err } - return d.connect(ctx, params) + return d.connect(ctx, nil, params) } // connect to the server, using the provided context for dialing only. -func (d *Driver) connect(ctx context.Context, params connectParams) (*Conn, error) { - sess, err := connect(ctx, d.log, params) +func (d *Driver) connect(ctx context.Context, c *Connector, params connectParams) (*Conn, error) { + sess, err := connect(ctx, c, d.log, params) if err != nil { // main server failed, try fail-over partner if params.failOverPartner == "" { @@ -327,7 +339,7 @@ func (d *Driver) connect(ctx context.Context, params connectParams) (*Conn, erro params.port = params.failOverPort } - sess, err = connect(ctx, d.log, params) + sess, err = connect(ctx, c, d.log, params) if err != nil { // fail-over partner also failed, now fail return nil, err @@ -335,12 +347,12 @@ func (d *Driver) connect(ctx context.Context, params connectParams) (*Conn, erro } conn := &Conn{ + connector: c, sess: sess, transactionCtx: context.Background(), processQueryText: d.processQueryText, connectionGood: true, } - conn.sess.log = d.log return conn, nil } @@ -375,7 +387,7 @@ func (c *Conn) Prepare(query string) (driver.Stmt, error) { func (c *Conn) prepareContext(ctx context.Context, query string) (*Stmt, error) { paramCount := -1 if c.processQueryText { - query, paramCount = parseParams(query) + query, paramCount = querytext.ParseParams(query) } return &Stmt{c, query, paramCount, nil}, nil } @@ -385,7 +397,10 @@ func (s *Stmt) Close() error { } func (s *Stmt) SetQueryNotification(id, options string, timeout time.Duration) { - to := uint32(timeout / time.Second) + // 2.2.5.3.1 Query Notifications Header + // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/e168d373-a7b7-41aa-b6ca-25985466a7e0 + // Timeout in milliseconds in TDS protocol. + to := uint32(timeout / time.Millisecond) if to < 1 { to = 1 } @@ -445,13 +460,13 @@ func (s *Stmt) sendQuery(args []namedValue) (err error) { var params []param if isProc(s.query) { proc.name = s.query - params, _, err = s.makeRPCParams(args, 0) + params, _, err = s.makeRPCParams(args, true) if err != nil { return } } else { var decls []string - params, decls, err = s.makeRPCParams(args, 2) + params, decls, err = s.makeRPCParams(args, false) if err != nil { return } @@ -523,8 +538,12 @@ func isProc(s string) bool { return true } -func (s *Stmt) makeRPCParams(args []namedValue, offset int) ([]param, []string, error) { +func (s *Stmt) makeRPCParams(args []namedValue, isProc bool) ([]param, []string, error) { var err error + var offset int + if !isProc { + offset = 2 + } params := make([]param, len(args)+offset) decls := make([]string, len(args)) for i, val := range args { @@ -535,7 +554,7 @@ func (s *Stmt) makeRPCParams(args []namedValue, offset int) ([]param, []string, var name string if len(val.Name) > 0 { name = "@" + val.Name - } else { + } else if !isProc { name = fmt.Sprintf("@p%d", val.Ordinal) } params[i+offset].Name = name @@ -597,11 +616,13 @@ loop: break loop case doneStruct: if token.isError() { + cancel() return nil, s.c.checkBadConn(token.getError()) } case ReturnStatus: s.c.setReturnStatus(token) case error: + cancel() return nil, s.c.checkBadConn(token) } } @@ -700,6 +721,8 @@ func (rc *Rows) Next(dest []driver.Value) error { if tokdata.isError() { return rc.stmt.c.checkBadConn(tokdata.getError()) } + case ReturnStatus: + rc.stmt.c.setReturnStatus(tokdata) case error: return rc.stmt.c.checkBadConn(tokdata) } @@ -858,29 +881,6 @@ func (r *Result) RowsAffected() (int64, error) { return r.rowsAffected, nil } -func (r *Result) LastInsertId() (int64, error) { - s, err := r.c.Prepare("select cast(@@identity as bigint)") - if err != nil { - return 0, err - } - defer s.Close() - rows, err := s.Query(nil) - if err != nil { - return 0, err - } - defer rows.Close() - dest := make([]driver.Value, 1) - err = rows.Next(dest) - if err != nil { - return 0, err - } - if dest[0] == nil { - return -1, errors.New("There is no generated identity value") - } - lastInsertId := dest[0].(int64) - return lastInsertId, nil -} - var _ driver.Pinger = &Conn{} // Ping is used to check if the remote server is available and satisfies the Pinger interface. diff --git a/vendor/github.com/denisenkom/go-mssqldb/mssql_go110.go b/vendor/github.com/denisenkom/go-mssqldb/mssql_go110.go index 3d5ab57a526..6d76fbad08f 100644 --- a/vendor/github.com/denisenkom/go-mssqldb/mssql_go110.go +++ b/vendor/github.com/denisenkom/go-mssqldb/mssql_go110.go @@ -5,6 +5,7 @@ package mssql import ( "context" "database/sql/driver" + "errors" ) var _ driver.Connector = &Connector{} @@ -34,10 +35,7 @@ func (c *Conn) ResetSession(ctx context.Context) error { // Connect to the server and return a TDS connection. func (c *Connector) Connect(ctx context.Context) (driver.Conn, error) { - conn, err := c.driver.connect(ctx, c.params) - if conn != nil { - conn.connector = c - } + conn, err := c.driver.connect(ctx, c, c.params) if err == nil { err = conn.ResetSession(ctx) } @@ -48,3 +46,7 @@ func (c *Connector) Connect(ctx context.Context) (driver.Conn, error) { func (c *Connector) Driver() driver.Driver { return c.driver } + +func (r *Result) LastInsertId() (int64, error) { + return -1, errors.New("LastInsertId is not supported. Please use the OUTPUT clause or add `select ID = convert(bigint, SCOPE_IDENTITY())` to the end of your query.") +} diff --git a/vendor/github.com/denisenkom/go-mssqldb/mssql_go110pre.go b/vendor/github.com/denisenkom/go-mssqldb/mssql_go110pre.go new file mode 100644 index 00000000000..3baae0c4048 --- /dev/null +++ b/vendor/github.com/denisenkom/go-mssqldb/mssql_go110pre.go @@ -0,0 +1,31 @@ +// +build !go1.10 + +package mssql + +import ( + "database/sql/driver" + "errors" +) + +func (r *Result) LastInsertId() (int64, error) { + s, err := r.c.Prepare("select cast(@@identity as bigint)") + if err != nil { + return 0, err + } + defer s.Close() + rows, err := s.Query(nil) + if err != nil { + return 0, err + } + defer rows.Close() + dest := make([]driver.Value, 1) + err = rows.Next(dest) + if err != nil { + return 0, err + } + if dest[0] == nil { + return -1, errors.New("There is no generated identity value") + } + lastInsertId := dest[0].(int64) + return lastInsertId, nil +} diff --git a/vendor/github.com/denisenkom/go-mssqldb/mssql_go19.go b/vendor/github.com/denisenkom/go-mssqldb/mssql_go19.go index 65a11720da2..a2bd1167ba3 100644 --- a/vendor/github.com/denisenkom/go-mssqldb/mssql_go19.go +++ b/vendor/github.com/denisenkom/go-mssqldb/mssql_go19.go @@ -11,7 +11,7 @@ import ( "time" // "github.com/cockroachdb/apd" - "cloud.google.com/go/civil" + "github.com/golang-sql/civil" ) // Type alias provided for compatibility. @@ -112,6 +112,8 @@ func (c *Conn) CheckNamedValue(nv *driver.NamedValue) error { *v = 0 // By default the return value should be zero. c.returnStatus = v return driver.ErrRemoveArgument + case TVP: + return nil default: var err error nv.Value, err = convertInputParameter(nv.Value) @@ -160,6 +162,29 @@ func (s *Stmt) makeParamExtra(val driver.Value) (res param, err error) { case sql.Out: res, err = s.makeParam(val.Dest) res.Flags = fByRevValue + case TVP: + err = val.check() + if err != nil { + return + } + schema, name, errGetName := getSchemeAndName(val.TypeName) + if errGetName != nil { + return + } + res.ti.UdtInfo.TypeName = name + res.ti.UdtInfo.SchemaName = schema + res.ti.TypeId = typeTvp + columnStr, tvpFieldIndexes, errCalTypes := val.columnTypes() + if errCalTypes != nil { + err = errCalTypes + return + } + res.buffer, err = val.encode(schema, name, columnStr, tvpFieldIndexes) + if err != nil { + return + } + res.ti.Size = len(res.buffer) + default: err = fmt.Errorf("mssql: unknown type for %T", val) } diff --git a/vendor/github.com/denisenkom/go-mssqldb/net.go b/vendor/github.com/denisenkom/go-mssqldb/net.go index e3864d1a222..94858cc74fe 100644 --- a/vendor/github.com/denisenkom/go-mssqldb/net.go +++ b/vendor/github.com/denisenkom/go-mssqldb/net.go @@ -9,9 +9,6 @@ import ( type timeoutConn struct { c net.Conn timeout time.Duration - buf *tdsBuffer - packetPending bool - continueRead bool } func newTimeoutConn(conn net.Conn, timeout time.Duration) *timeoutConn { @@ -22,32 +19,6 @@ func newTimeoutConn(conn net.Conn, timeout time.Duration) *timeoutConn { } func (c *timeoutConn) Read(b []byte) (n int, err error) { - if c.buf != nil { - if c.packetPending { - c.packetPending = false - err = c.buf.FinishPacket() - if err != nil { - err = fmt.Errorf("Cannot send handshake packet: %s", err.Error()) - return - } - c.continueRead = false - } - if !c.continueRead { - var packet packetType - packet, err = c.buf.BeginRead() - if err != nil { - err = fmt.Errorf("Cannot read handshake packet: %s", err.Error()) - return - } - if packet != packPrelogin { - err = fmt.Errorf("unexpected packet %d, expecting prelogin", packet) - return - } - c.continueRead = true - } - n, err = c.buf.Read(b) - return - } if c.timeout > 0 { err = c.c.SetDeadline(time.Now().Add(c.timeout)) if err != nil { @@ -58,17 +29,6 @@ func (c *timeoutConn) Read(b []byte) (n int, err error) { } func (c *timeoutConn) Write(b []byte) (n int, err error) { - if c.buf != nil { - if !c.packetPending { - c.buf.BeginPacket(packPrelogin, false) - c.packetPending = true - } - n, err = c.buf.Write(b) - if err != nil { - return - } - return - } if c.timeout > 0 { err = c.c.SetDeadline(time.Now().Add(c.timeout)) if err != nil { @@ -101,3 +61,108 @@ func (c timeoutConn) SetReadDeadline(t time.Time) error { func (c timeoutConn) SetWriteDeadline(t time.Time) error { panic("Not implemented") } + +// this connection is used during TLS Handshake +// TDS protocol requires TLS handshake messages to be sent inside TDS packets +type tlsHandshakeConn struct { + buf *tdsBuffer + packetPending bool + continueRead bool +} + +func (c *tlsHandshakeConn) Read(b []byte) (n int, err error) { + if c.packetPending { + c.packetPending = false + err = c.buf.FinishPacket() + if err != nil { + err = fmt.Errorf("Cannot send handshake packet: %s", err.Error()) + return + } + c.continueRead = false + } + if !c.continueRead { + var packet packetType + packet, err = c.buf.BeginRead() + if err != nil { + err = fmt.Errorf("Cannot read handshake packet: %s", err.Error()) + return + } + if packet != packPrelogin { + err = fmt.Errorf("unexpected packet %d, expecting prelogin", packet) + return + } + c.continueRead = true + } + return c.buf.Read(b) +} + +func (c *tlsHandshakeConn) Write(b []byte) (n int, err error) { + if !c.packetPending { + c.buf.BeginPacket(packPrelogin, false) + c.packetPending = true + } + return c.buf.Write(b) +} + +func (c *tlsHandshakeConn) Close() error { + panic("Not implemented") +} + +func (c *tlsHandshakeConn) LocalAddr() net.Addr { + panic("Not implemented") +} + +func (c *tlsHandshakeConn) RemoteAddr() net.Addr { + panic("Not implemented") +} + +func (c *tlsHandshakeConn) SetDeadline(t time.Time) error { + panic("Not implemented") +} + +func (c *tlsHandshakeConn) SetReadDeadline(t time.Time) error { + panic("Not implemented") +} + +func (c *tlsHandshakeConn) SetWriteDeadline(t time.Time) error { + panic("Not implemented") +} + +// this connection just delegates all methods to it's wrapped connection +// it also allows switching underlying connection on the fly +// it is needed because tls.Conn does not allow switching underlying connection +type passthroughConn struct { + c net.Conn +} + +func (c passthroughConn) Read(b []byte) (n int, err error) { + return c.c.Read(b) +} + +func (c passthroughConn) Write(b []byte) (n int, err error) { + return c.c.Write(b) +} + +func (c passthroughConn) Close() error { + return c.c.Close() +} + +func (c passthroughConn) LocalAddr() net.Addr { + panic("Not implemented") +} + +func (c passthroughConn) RemoteAddr() net.Addr { + panic("Not implemented") +} + +func (c passthroughConn) SetDeadline(t time.Time) error { + panic("Not implemented") +} + +func (c passthroughConn) SetReadDeadline(t time.Time) error { + panic("Not implemented") +} + +func (c passthroughConn) SetWriteDeadline(t time.Time) error { + panic("Not implemented") +} diff --git a/vendor/github.com/denisenkom/go-mssqldb/ntlm.go b/vendor/github.com/denisenkom/go-mssqldb/ntlm.go index 7c0cc4f785c..3ba48ff8d2c 100644 --- a/vendor/github.com/denisenkom/go-mssqldb/ntlm.go +++ b/vendor/github.com/denisenkom/go-mssqldb/ntlm.go @@ -4,11 +4,14 @@ package mssql import ( "crypto/des" + "crypto/hmac" "crypto/md5" "crypto/rand" "encoding/binary" "errors" + "fmt" "strings" + "time" "unicode/utf16" "golang.org/x/crypto/md4" @@ -198,86 +201,209 @@ func ntlmSessionResponse(clientNonce [8]byte, serverChallenge [8]byte, password return response(hash, passwordHash) } -func (auth *ntlmAuth) NextBytes(bytes []byte) ([]byte, error) { - if string(bytes[0:8]) != "NTLMSSP\x00" { - return nil, errorNTLM +func ntlmHashNoPadding(val string) []byte { + hash := make([]byte, 16) + h := md4.New() + h.Write(utf16le(val)) + h.Sum(hash[:0]) + + return hash +} + +func hmacMD5(passwordHash, data []byte) []byte { + hmacEntity := hmac.New(md5.New, passwordHash) + hmacEntity.Write(data) + + return hmacEntity.Sum(nil) +} + +func getNTLMv2AndLMv2ResponsePayloads(target, username, password string, challenge, nonce [8]byte, targetInfoFields []byte, timestamp time.Time) (ntlmV2Payload, lmV2Payload []byte) { + // NTLMv2 response payload: http://davenport.sourceforge.net/ntlm.html#theNtlmv2Response + + ntlmHash := ntlmHashNoPadding(password) + usernameAndTargetBytes := utf16le(strings.ToUpper(username) + target) + ntlmV2Hash := hmacMD5(ntlmHash, usernameAndTargetBytes) + targetInfoLength := len(targetInfoFields) + blob := make([]byte, 32+targetInfoLength) + binary.BigEndian.PutUint32(blob[:4], 0x01010000) + binary.BigEndian.PutUint32(blob[4:8], 0x00000000) + binary.BigEndian.PutUint64(blob[8:16], uint64(timestamp.UnixNano())) + copy(blob[16:24], nonce[:]) + binary.BigEndian.PutUint32(blob[24:28], 0x00000000) + copy(blob[28:], targetInfoFields) + binary.BigEndian.PutUint32(blob[28+targetInfoLength:], 0x00000000) + challengeLength := len(challenge) + blobLength := len(blob) + challengeAndBlob := make([]byte, challengeLength + blobLength) + copy(challengeAndBlob[:challengeLength], challenge[:]) + copy(challengeAndBlob[challengeLength:], blob) + hashedChallenge := hmacMD5(ntlmV2Hash, challengeAndBlob) + ntlmV2Payload = append(hashedChallenge, blob...) + + // LMv2 response payload: http://davenport.sourceforge.net/ntlm.html#theLmv2Response + ntlmV2hash := hmacMD5(ntlmHash, usernameAndTargetBytes) + challengeAndNonce := make([]byte, 16) + copy(challengeAndNonce[:8], challenge[:]) + copy(challengeAndNonce[8:], nonce[:]) + hashedChallenge = hmacMD5(ntlmV2hash, challengeAndNonce) + lmV2Payload = append(hashedChallenge, nonce[:]...) + + return +} + +func negotiateExtendedSessionSecurity(flags uint32, message []byte, challenge [8]byte, username, password string) (lm, nt []byte, err error) { + nonce := clientChallenge() + + // Official specification: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/b38c36ed-2804-4868-a9ff-8dd3182128e4 + // Unofficial walk through referenced by https://www.freetds.org/userguide/domains.htm: http://davenport.sourceforge.net/ntlm.html + if (flags & _NEGOTIATE_TARGET_INFO) != 0 { + targetInfoFields, target, err := getNTLMv2TargetInfoFields(message) + if err != nil { + return lm, nt, err + } + + nt, lm = getNTLMv2AndLMv2ResponsePayloads(target, username, password, challenge, nonce, targetInfoFields, time.Now()) + + return lm, nt, nil } - if binary.LittleEndian.Uint32(bytes[8:12]) != _CHALLENGE_MESSAGE { - return nil, errorNTLM + + var lm_bytes [24]byte + copy(lm_bytes[:8], nonce[:]) + lm = lm_bytes[:] + nt_bytes := ntlmSessionResponse(nonce, challenge, password) + nt = nt_bytes[:] + + return lm, nt, nil +} + +func getNTLMv2TargetInfoFields(type2Message []byte) (info []byte, target string, err error) { + type2MessageError := "mssql: while parsing NTLMv2 type 2 message, length %d too small for offset %d" + type2MessageLength := len(type2Message) + if type2MessageLength < 20 { + return nil, target, fmt.Errorf(type2MessageError, type2MessageLength, 20) } - flags := binary.LittleEndian.Uint32(bytes[20:24]) - var challenge [8]byte - copy(challenge[:], bytes[24:32]) - var lm, nt []byte - if (flags & _NEGOTIATE_EXTENDED_SESSIONSECURITY) != 0 { - nonce := clientChallenge() - var lm_bytes [24]byte - copy(lm_bytes[:8], nonce[:]) - lm = lm_bytes[:] - nt_bytes := ntlmSessionResponse(nonce, challenge, auth.Password) - nt = nt_bytes[:] - } else { - lm_bytes := lmResponse(challenge, auth.Password) - lm = lm_bytes[:] - nt_bytes := ntResponse(challenge, auth.Password) - nt = nt_bytes[:] + targetNameAllocated := binary.LittleEndian.Uint16(type2Message[14:16]) + targetNameOffset := binary.LittleEndian.Uint32(type2Message[16:20]) + endOfOffset := int(targetNameOffset + uint32(targetNameAllocated)) + if type2MessageLength < endOfOffset { + return nil, target, fmt.Errorf(type2MessageError, type2MessageLength, endOfOffset) + } + + target, err = ucs22str(type2Message[targetNameOffset:targetNameOffset+uint32(targetNameAllocated)]) + if err != nil { + return nil, target, err + } + + targetInformationAllocated := binary.LittleEndian.Uint16(type2Message[42:44]) + targetInformationDataOffset := binary.LittleEndian.Uint32(type2Message[44:48]) + endOfOffset = int(targetInformationDataOffset + uint32(targetInformationAllocated)) + if type2MessageLength < endOfOffset { + return nil, target, fmt.Errorf(type2MessageError, type2MessageLength, endOfOffset) } + + targetInformationBytes := make([]byte, targetInformationAllocated) + copy(targetInformationBytes, type2Message[targetInformationDataOffset:targetInformationDataOffset+uint32(targetInformationAllocated)]) + + return targetInformationBytes, target, nil +} + +func buildNTLMResponsePayload(lm, nt []byte, flags uint32, domain, workstation, username string) ([]byte, error) { lm_len := len(lm) nt_len := len(nt) - - domain16 := utf16le(auth.Domain) + domain16 := utf16le(domain) domain_len := len(domain16) - user16 := utf16le(auth.UserName) + user16 := utf16le(username) user_len := len(user16) - workstation16 := utf16le(auth.Workstation) + workstation16 := utf16le(workstation) workstation_len := len(workstation16) - msg := make([]byte, 88+lm_len+nt_len+domain_len+user_len+workstation_len) copy(msg, []byte("NTLMSSP\x00")) binary.LittleEndian.PutUint32(msg[8:], _AUTHENTICATE_MESSAGE) + // Lm Challenge Response Fields binary.LittleEndian.PutUint16(msg[12:], uint16(lm_len)) binary.LittleEndian.PutUint16(msg[14:], uint16(lm_len)) binary.LittleEndian.PutUint32(msg[16:], 88) + // Nt Challenge Response Fields binary.LittleEndian.PutUint16(msg[20:], uint16(nt_len)) binary.LittleEndian.PutUint16(msg[22:], uint16(nt_len)) binary.LittleEndian.PutUint32(msg[24:], uint32(88+lm_len)) + // Domain Name Fields binary.LittleEndian.PutUint16(msg[28:], uint16(domain_len)) binary.LittleEndian.PutUint16(msg[30:], uint16(domain_len)) binary.LittleEndian.PutUint32(msg[32:], uint32(88+lm_len+nt_len)) + // User Name Fields binary.LittleEndian.PutUint16(msg[36:], uint16(user_len)) binary.LittleEndian.PutUint16(msg[38:], uint16(user_len)) binary.LittleEndian.PutUint32(msg[40:], uint32(88+lm_len+nt_len+domain_len)) + // Workstation Fields binary.LittleEndian.PutUint16(msg[44:], uint16(workstation_len)) binary.LittleEndian.PutUint16(msg[46:], uint16(workstation_len)) binary.LittleEndian.PutUint32(msg[48:], uint32(88+lm_len+nt_len+domain_len+user_len)) + // Encrypted Random Session Key Fields binary.LittleEndian.PutUint16(msg[52:], 0) binary.LittleEndian.PutUint16(msg[54:], 0) binary.LittleEndian.PutUint32(msg[56:], uint32(88+lm_len+nt_len+domain_len+user_len+workstation_len)) + // Negotiate Flags binary.LittleEndian.PutUint32(msg[60:], flags) + // Version binary.LittleEndian.PutUint32(msg[64:], 0) binary.LittleEndian.PutUint32(msg[68:], 0) + // MIC binary.LittleEndian.PutUint32(msg[72:], 0) binary.LittleEndian.PutUint32(msg[76:], 0) binary.LittleEndian.PutUint32(msg[88:], 0) binary.LittleEndian.PutUint32(msg[84:], 0) + // Payload copy(msg[88:], lm) copy(msg[88+lm_len:], nt) copy(msg[88+lm_len+nt_len:], domain16) copy(msg[88+lm_len+nt_len+domain_len:], user16) copy(msg[88+lm_len+nt_len+domain_len+user_len:], workstation16) + return msg, nil } +func (auth *ntlmAuth) NextBytes(bytes []byte) ([]byte, error) { + signature := string(bytes[0:8]) + if signature != "NTLMSSP\x00" { + return nil, errorNTLM + } + + messageTypeIndicator := binary.LittleEndian.Uint32(bytes[8:12]) + if messageTypeIndicator != _CHALLENGE_MESSAGE { + return nil, errorNTLM + } + + var challenge [8]byte + copy(challenge[:], bytes[24:32]) + flags := binary.LittleEndian.Uint32(bytes[20:24]) + if (flags & _NEGOTIATE_EXTENDED_SESSIONSECURITY) != 0 { + lm, nt, err := negotiateExtendedSessionSecurity(flags, bytes, challenge, auth.UserName, auth.Password) + if err != nil { + return nil, err + } + + return buildNTLMResponsePayload(lm, nt, flags, auth.Domain, auth.Workstation, auth.UserName) + } + + lm_bytes := lmResponse(challenge, auth.Password) + lm := lm_bytes[:] + nt_bytes := ntResponse(challenge, auth.Password) + nt := nt_bytes[:] + + return buildNTLMResponsePayload(lm, nt, flags, auth.Domain, auth.Workstation, auth.UserName) +} + func (auth *ntlmAuth) Free() { } diff --git a/vendor/github.com/denisenkom/go-mssqldb/tds.go b/vendor/github.com/denisenkom/go-mssqldb/tds.go index a45711d5517..832c4fd23a0 100644 --- a/vendor/github.com/denisenkom/go-mssqldb/tds.go +++ b/vendor/github.com/denisenkom/go-mssqldb/tds.go @@ -10,13 +10,9 @@ import ( "io" "io/ioutil" "net" - "net/url" - "os" "sort" "strconv" "strings" - "time" - "unicode" "unicode/utf16" "unicode/utf8" ) @@ -50,17 +46,14 @@ func parseInstances(msg []byte) map[string]map[string]string { return results } -func getInstances(ctx context.Context, address string) (map[string]map[string]string, error) { - maxTime := 5 * time.Second - dialer := &net.Dialer{ - Timeout: maxTime, - } - conn, err := dialer.DialContext(ctx, "udp", address+":1434") +func getInstances(ctx context.Context, d Dialer, address string) (map[string]map[string]string, error) { + conn, err := d.DialContext(ctx, "udp", address+":1434") if err != nil { return nil, err } defer conn.Close() - conn.SetDeadline(time.Now().Add(maxTime)) + deadline, _ := ctx.Deadline() + conn.SetDeadline(deadline) _, err = conn.Write([]byte{3}) if err != nil { return nil, err @@ -107,13 +100,15 @@ const ( // prelogin fields // http://msdn.microsoft.com/en-us/library/dd357559.aspx const ( - preloginVERSION = 0 - preloginENCRYPTION = 1 - preloginINSTOPT = 2 - preloginTHREADID = 3 - preloginMARS = 4 - preloginTRACEID = 5 - preloginTERMINATOR = 0xff + preloginVERSION = 0 + preloginENCRYPTION = 1 + preloginINSTOPT = 2 + preloginTHREADID = 3 + preloginMARS = 4 + preloginTRACEID = 5 + preloginFEDAUTHREQUIRED = 6 + preloginNONCEOPT = 7 + preloginTERMINATOR = 0xff ) const ( @@ -252,6 +247,12 @@ const ( fReadOnlyIntent = 32 ) +// OptionFlags3 +// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/773a62b6-ee89-4c02-9e5e-344882630aac +const ( + fExtension = 0x10 +) + type login struct { TDSVersion uint32 PacketSize uint32 @@ -276,6 +277,89 @@ type login struct { SSPI []byte AtchDBFile string ChangePassword string + FeatureExt featureExts +} + +type featureExts struct { + features map[byte]featureExt +} + +type featureExt interface { + featureID() byte + toBytes() []byte +} + +func (e *featureExts) Add(f featureExt) error { + if f == nil { + return nil + } + id := f.featureID() + if _, exists := e.features[id]; exists { + f := "Login error: Feature with ID '%v' is already present in FeatureExt block." + return fmt.Errorf(f, id) + } + if e.features == nil { + e.features = make(map[byte]featureExt) + } + e.features[id] = f + return nil +} + +func (e featureExts) toBytes() []byte { + if len(e.features) == 0 { + return nil + } + var d []byte + for featureID, f := range e.features { + featureData := f.toBytes() + + hdr := make([]byte, 5) + hdr[0] = featureID // FedAuth feature extension BYTE + binary.LittleEndian.PutUint32(hdr[1:], uint32(len(featureData))) // FeatureDataLen DWORD + d = append(d, hdr...) + + d = append(d, featureData...) // FeatureData *BYTE + } + if d != nil { + d = append(d, 0xff) // Terminator + } + return d +} + +type featureExtFedAuthSTS struct { + FedAuthEcho bool + FedAuthToken string + Nonce []byte +} + +func (e *featureExtFedAuthSTS) featureID() byte { + return 0x02 +} + +func (e *featureExtFedAuthSTS) toBytes() []byte { + if e == nil { + return nil + } + + options := byte(0x01) << 1 // 0x01 => STS bFedAuthLibrary 7BIT + if e.FedAuthEcho { + options |= 1 // fFedAuthEcho + } + + d := make([]byte, 5) + d[0] = options + + // looks like string in + // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/f88b63bb-b479-49e1-a87b-deda521da508 + tokenBytes := str2ucs2(e.FedAuthToken) + binary.LittleEndian.PutUint32(d[1:], uint32(len(tokenBytes))) // Should be a signed int32, but since the length is relatively small, this should work + d = append(d, tokenBytes...) + + if len(e.Nonce) == 32 { + d = append(d, e.Nonce...) + } + + return d } type loginHeader struct { @@ -302,7 +386,7 @@ type loginHeader struct { ServerNameOffset uint16 ServerNameLength uint16 ExtensionOffset uint16 - ExtensionLenght uint16 + ExtensionLength uint16 CtlIntNameOffset uint16 CtlIntNameLength uint16 LanguageOffset uint16 @@ -364,6 +448,8 @@ func sendLogin(w *tdsBuffer, login login) error { database := str2ucs2(login.Database) atchdbfile := str2ucs2(login.AtchDBFile) changepassword := str2ucs2(login.ChangePassword) + featureExt := login.FeatureExt.toBytes() + hdr := loginHeader{ TDSVersion: login.TDSVersion, PacketSize: login.PacketSize, @@ -412,7 +498,18 @@ func sendLogin(w *tdsBuffer, login login) error { offset += uint16(len(atchdbfile)) hdr.ChangePasswordOffset = offset offset += uint16(len(changepassword)) - hdr.Length = uint32(offset) + + featureExtOffset := uint32(0) + featureExtLen := len(featureExt) + if featureExtLen > 0 { + hdr.OptionFlags3 |= fExtension + hdr.ExtensionOffset = offset + hdr.ExtensionLength = 4 + offset += hdr.ExtensionLength // DWORD + featureExtOffset = uint32(offset) + } + hdr.Length = uint32(offset) + uint32(featureExtLen) + var err error err = binary.Write(w, binary.LittleEndian, &hdr) if err != nil { @@ -462,6 +559,16 @@ func sendLogin(w *tdsBuffer, login login) error { if err != nil { return err } + if featureExtOffset > 0 { + err = binary.Write(w, binary.LittleEndian, featureExtOffset) + if err != nil { + return err + } + _, err = w.Write(featureExt) + if err != nil { + return err + } + } return w.FinishPacket() } @@ -475,10 +582,9 @@ func readUcs2(r io.Reader, numchars int) (res string, err error) { } func readUsVarChar(r io.Reader) (res string, err error) { - var numchars uint16 - err = binary.Read(r, binary.LittleEndian, &numchars) + numchars, err := readUshort(r) if err != nil { - return "", err + return } return readUcs2(r, int(numchars)) } @@ -498,8 +604,7 @@ func writeUsVarChar(w io.Writer, s string) (err error) { } func readBVarChar(r io.Reader) (res string, err error) { - var numchars uint8 - err = binary.Read(r, binary.LittleEndian, &numchars) + numchars, err := readByte(r) if err != nil { return "", err } @@ -526,8 +631,7 @@ func writeBVarChar(w io.Writer, s string) (err error) { } func readBVarByte(r io.Reader) (res []byte, err error) { - var length uint8 - err = binary.Read(r, binary.LittleEndian, &length) + length, err := readByte(r) if err != nil { return } @@ -655,454 +759,6 @@ func sendAttention(buf *tdsBuffer) error { return buf.FinishPacket() } -type connectParams struct { - logFlags uint64 - port uint64 - host string - instance string - database string - user string - password string - dial_timeout time.Duration - conn_timeout time.Duration - keepAlive time.Duration - encrypt bool - disableEncryption bool - trustServerCertificate bool - certificate string - hostInCertificate string - serverSPN string - workstation string - appname string - typeFlags uint8 - failOverPartner string - failOverPort uint64 - packetSize uint16 -} - -func splitConnectionString(dsn string) (res map[string]string) { - res = map[string]string{} - parts := strings.Split(dsn, ";") - for _, part := range parts { - if len(part) == 0 { - continue - } - lst := strings.SplitN(part, "=", 2) - name := strings.TrimSpace(strings.ToLower(lst[0])) - if len(name) == 0 { - continue - } - var value string = "" - if len(lst) > 1 { - value = strings.TrimSpace(lst[1]) - } - res[name] = value - } - return res -} - -// Splits a URL in the ODBC format -func splitConnectionStringOdbc(dsn string) (map[string]string, error) { - res := map[string]string{} - - type parserState int - const ( - // Before the start of a key - parserStateBeforeKey parserState = iota - - // Inside a key - parserStateKey - - // Beginning of a value. May be bare or braced - parserStateBeginValue - - // Inside a bare value - parserStateBareValue - - // Inside a braced value - parserStateBracedValue - - // A closing brace inside a braced value. - // May be the end of the value or an escaped closing brace, depending on the next character - parserStateBracedValueClosingBrace - - // After a value. Next character should be a semicolon or whitespace. - parserStateEndValue - ) - - var state = parserStateBeforeKey - - var key string - var value string - - for i, c := range dsn { - switch state { - case parserStateBeforeKey: - switch { - case c == '=': - return res, fmt.Errorf("Unexpected character = at index %d. Expected start of key or semi-colon or whitespace.", i) - case !unicode.IsSpace(c) && c != ';': - state = parserStateKey - key += string(c) - } - - case parserStateKey: - switch c { - case '=': - key = normalizeOdbcKey(key) - if len(key) == 0 { - return res, fmt.Errorf("Unexpected end of key at index %d.", i) - } - - state = parserStateBeginValue - - case ';': - // Key without value - key = normalizeOdbcKey(key) - if len(key) == 0 { - return res, fmt.Errorf("Unexpected end of key at index %d.", i) - } - - res[key] = value - key = "" - value = "" - state = parserStateBeforeKey - - default: - key += string(c) - } - - case parserStateBeginValue: - switch { - case c == '{': - state = parserStateBracedValue - case c == ';': - // Empty value - res[key] = value - key = "" - state = parserStateBeforeKey - case unicode.IsSpace(c): - // Ignore whitespace - default: - state = parserStateBareValue - value += string(c) - } - - case parserStateBareValue: - if c == ';' { - res[key] = strings.TrimRightFunc(value, unicode.IsSpace) - key = "" - value = "" - state = parserStateBeforeKey - } else { - value += string(c) - } - - case parserStateBracedValue: - if c == '}' { - state = parserStateBracedValueClosingBrace - } else { - value += string(c) - } - - case parserStateBracedValueClosingBrace: - if c == '}' { - // Escaped closing brace - value += string(c) - state = parserStateBracedValue - continue - } - - // End of braced value - res[key] = value - key = "" - value = "" - - // This character is the first character past the end, - // so it needs to be parsed like the parserStateEndValue state. - state = parserStateEndValue - switch { - case c == ';': - state = parserStateBeforeKey - case unicode.IsSpace(c): - // Ignore whitespace - default: - return res, fmt.Errorf("Unexpected character %c at index %d. Expected semi-colon or whitespace.", c, i) - } - - case parserStateEndValue: - switch { - case c == ';': - state = parserStateBeforeKey - case unicode.IsSpace(c): - // Ignore whitespace - default: - return res, fmt.Errorf("Unexpected character %c at index %d. Expected semi-colon or whitespace.", c, i) - } - } - } - - switch state { - case parserStateBeforeKey: // Okay - case parserStateKey: // Unfinished key. Treat as key without value. - key = normalizeOdbcKey(key) - if len(key) == 0 { - return res, fmt.Errorf("Unexpected end of key at index %d.", len(dsn)) - } - res[key] = value - case parserStateBeginValue: // Empty value - res[key] = value - case parserStateBareValue: - res[key] = strings.TrimRightFunc(value, unicode.IsSpace) - case parserStateBracedValue: - return res, fmt.Errorf("Unexpected end of braced value at index %d.", len(dsn)) - case parserStateBracedValueClosingBrace: // End of braced value - res[key] = value - case parserStateEndValue: // Okay - } - - return res, nil -} - -// Normalizes the given string as an ODBC-format key -func normalizeOdbcKey(s string) string { - return strings.ToLower(strings.TrimRightFunc(s, unicode.IsSpace)) -} - -// Splits a URL of the form sqlserver://username:password@host/instance?param1=value¶m2=value -func splitConnectionStringURL(dsn string) (map[string]string, error) { - res := map[string]string{} - - u, err := url.Parse(dsn) - if err != nil { - return res, err - } - - if u.Scheme != "sqlserver" { - return res, fmt.Errorf("scheme %s is not recognized", u.Scheme) - } - - if u.User != nil { - res["user id"] = u.User.Username() - p, exists := u.User.Password() - if exists { - res["password"] = p - } - } - - host, port, err := net.SplitHostPort(u.Host) - if err != nil { - host = u.Host - } - - if len(u.Path) > 0 { - res["server"] = host + "\\" + u.Path[1:] - } else { - res["server"] = host - } - - if len(port) > 0 { - res["port"] = port - } - - query := u.Query() - for k, v := range query { - if len(v) > 1 { - return res, fmt.Errorf("key %s provided more than once", k) - } - res[strings.ToLower(k)] = v[0] - } - - return res, nil -} - -func parseConnectParams(dsn string) (connectParams, error) { - var p connectParams - - var params map[string]string - if strings.HasPrefix(dsn, "odbc:") { - parameters, err := splitConnectionStringOdbc(dsn[len("odbc:"):]) - if err != nil { - return p, err - } - params = parameters - } else if strings.HasPrefix(dsn, "sqlserver://") { - parameters, err := splitConnectionStringURL(dsn) - if err != nil { - return p, err - } - params = parameters - } else { - params = splitConnectionString(dsn) - } - - strlog, ok := params["log"] - if ok { - var err error - p.logFlags, err = strconv.ParseUint(strlog, 10, 64) - if err != nil { - return p, fmt.Errorf("Invalid log parameter '%s': %s", strlog, err.Error()) - } - } - server := params["server"] - parts := strings.SplitN(server, `\`, 2) - p.host = parts[0] - if p.host == "." || strings.ToUpper(p.host) == "(LOCAL)" || p.host == "" { - p.host = "localhost" - } - if len(parts) > 1 { - p.instance = parts[1] - } - p.database = params["database"] - p.user = params["user id"] - p.password = params["password"] - - p.port = 1433 - strport, ok := params["port"] - if ok { - var err error - p.port, err = strconv.ParseUint(strport, 10, 16) - if err != nil { - f := "Invalid tcp port '%v': %v" - return p, fmt.Errorf(f, strport, err.Error()) - } - } - - // https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/configure-the-network-packet-size-server-configuration-option - // Default packet size remains at 4096 bytes - p.packetSize = 4096 - strpsize, ok := params["packet size"] - if ok { - var err error - psize, err := strconv.ParseUint(strpsize, 0, 16) - if err != nil { - f := "Invalid packet size '%v': %v" - return p, fmt.Errorf(f, strpsize, err.Error()) - } - - // Ensure packet size falls within the TDS protocol range of 512 to 32767 bytes - // NOTE: Encrypted connections have a maximum size of 16383 bytes. If you request - // a higher packet size, the server will respond with an ENVCHANGE request to - // alter the packet size to 16383 bytes. - p.packetSize = uint16(psize) - if p.packetSize < 512 { - p.packetSize = 512 - } else if p.packetSize > 32767 { - p.packetSize = 32767 - } - } - - // https://msdn.microsoft.com/en-us/library/dd341108.aspx - // - // Do not set a connection timeout. Use Context to manage such things. - // Default to zero, but still allow it to be set. - if strconntimeout, ok := params["connection timeout"]; ok { - timeout, err := strconv.ParseUint(strconntimeout, 10, 64) - if err != nil { - f := "Invalid connection timeout '%v': %v" - return p, fmt.Errorf(f, strconntimeout, err.Error()) - } - p.conn_timeout = time.Duration(timeout) * time.Second - } - p.dial_timeout = 15 * time.Second - if strdialtimeout, ok := params["dial timeout"]; ok { - timeout, err := strconv.ParseUint(strdialtimeout, 10, 64) - if err != nil { - f := "Invalid dial timeout '%v': %v" - return p, fmt.Errorf(f, strdialtimeout, err.Error()) - } - p.dial_timeout = time.Duration(timeout) * time.Second - } - - // default keep alive should be 30 seconds according to spec: - // https://msdn.microsoft.com/en-us/library/dd341108.aspx - p.keepAlive = 30 * time.Second - if keepAlive, ok := params["keepalive"]; ok { - timeout, err := strconv.ParseUint(keepAlive, 10, 64) - if err != nil { - f := "Invalid keepAlive value '%s': %s" - return p, fmt.Errorf(f, keepAlive, err.Error()) - } - p.keepAlive = time.Duration(timeout) * time.Second - } - encrypt, ok := params["encrypt"] - if ok { - if strings.EqualFold(encrypt, "DISABLE") { - p.disableEncryption = true - } else { - var err error - p.encrypt, err = strconv.ParseBool(encrypt) - if err != nil { - f := "Invalid encrypt '%s': %s" - return p, fmt.Errorf(f, encrypt, err.Error()) - } - } - } else { - p.trustServerCertificate = true - } - trust, ok := params["trustservercertificate"] - if ok { - var err error - p.trustServerCertificate, err = strconv.ParseBool(trust) - if err != nil { - f := "Invalid trust server certificate '%s': %s" - return p, fmt.Errorf(f, trust, err.Error()) - } - } - p.certificate = params["certificate"] - p.hostInCertificate, ok = params["hostnameincertificate"] - if !ok { - p.hostInCertificate = p.host - } - - serverSPN, ok := params["serverspn"] - if ok { - p.serverSPN = serverSPN - } else { - p.serverSPN = fmt.Sprintf("MSSQLSvc/%s:%d", p.host, p.port) - } - - workstation, ok := params["workstation id"] - if ok { - p.workstation = workstation - } else { - workstation, err := os.Hostname() - if err == nil { - p.workstation = workstation - } - } - - appname, ok := params["app name"] - if !ok { - appname = "go-mssqldb" - } - p.appname = appname - - appintent, ok := params["applicationintent"] - if ok { - if appintent == "ReadOnly" { - p.typeFlags |= fReadOnlyIntent - } - } - - failOverPartner, ok := params["failoverpartner"] - if ok { - p.failOverPartner = failOverPartner - } - - failOverPort, ok := params["failoverport"] - if ok { - var err error - p.failOverPort, err = strconv.ParseUint(failOverPort, 0, 16) - if err != nil { - f := "Invalid tcp port '%v': %v" - return p, fmt.Errorf(f, failOverPort, err.Error()) - } - } - - return p, nil -} - type auth interface { InitialBytes() ([]byte, error) NextBytes([]byte) ([]byte, error) @@ -1112,7 +768,7 @@ type auth interface { // SQL Server AlwaysOn Availability Group Listeners are bound by DNS to a // list of IP addresses. So if there is more than one, try them all and // use the first one that allows a connection. -func dialConnection(ctx context.Context, p connectParams) (conn net.Conn, err error) { +func dialConnection(ctx context.Context, c *Connector, p connectParams) (conn net.Conn, err error) { var ips []net.IP ips, err = net.LookupIP(p.host) if err != nil { @@ -1123,20 +779,20 @@ func dialConnection(ctx context.Context, p connectParams) (conn net.Conn, err er ips = []net.IP{ip} } if len(ips) == 1 { - d := createDialer(&p) - addr := net.JoinHostPort(ips[0].String(), strconv.Itoa(int(p.port))) - conn, err = d.Dial(ctx, addr) + d := c.getDialer(&p) + addr := net.JoinHostPort(ips[0].String(), strconv.Itoa(int(resolveServerPort(p.port)))) + conn, err = d.DialContext(ctx, "tcp", addr) } else { //Try Dials in parallel to avoid waiting for timeouts. connChan := make(chan net.Conn, len(ips)) errChan := make(chan error, len(ips)) - portStr := strconv.Itoa(int(p.port)) + portStr := strconv.Itoa(int(resolveServerPort(p.port))) for _, ip := range ips { go func(ip net.IP) { - d := createDialer(&p) + d := c.getDialer(&p) addr := net.JoinHostPort(ip.String(), portStr) - conn, err := d.Dial(ctx, addr) + conn, err := d.DialContext(ctx, "tcp", addr) if err == nil { connChan <- conn } else { @@ -1169,12 +825,12 @@ func dialConnection(ctx context.Context, p connectParams) (conn net.Conn, err er // Can't do the usual err != nil check, as it is possible to have gotten an error before a successful connection if conn == nil { f := "Unable to open tcp connection with host '%v:%v': %v" - return nil, fmt.Errorf(f, p.host, p.port, err.Error()) + return nil, fmt.Errorf(f, p.host, resolveServerPort(p.port), err.Error()) } return conn, err } -func connect(ctx context.Context, log optionalLogger, p connectParams) (res *tdsSession, err error) { +func connect(ctx context.Context, c *Connector, log optionalLogger, p connectParams) (res *tdsSession, err error) { dialCtx := ctx if p.dial_timeout > 0 { var cancel func() @@ -1182,9 +838,10 @@ func connect(ctx context.Context, log optionalLogger, p connectParams) (res *tds defer cancel() } // if instance is specified use instance resolution service - if p.instance != "" { + if p.instance != "" && p.port == 0 { p.instance = strings.ToUpper(p.instance) - instances, err := getInstances(dialCtx, p.host) + d := c.getDialer(&p) + instances, err := getInstances(dialCtx, d, p.host) if err != nil { f := "Unable to get instances from Sql Server Browser on host %v: %v" return nil, fmt.Errorf(f, p.host, err.Error()) @@ -1194,15 +851,16 @@ func connect(ctx context.Context, log optionalLogger, p connectParams) (res *tds f := "No instance matching '%v' returned from host '%v'" return nil, fmt.Errorf(f, p.instance, p.host) } - p.port, err = strconv.ParseUint(strport, 0, 16) + port, err := strconv.ParseUint(strport, 0, 16) if err != nil { f := "Invalid tcp port returned from Sql Server Browser '%v': %v" return nil, fmt.Errorf(f, strport, err.Error()) } + p.port = port } initiate_connection: - conn, err := dialConnection(dialCtx, p) + conn, err := dialConnection(dialCtx, c, p) if err != nil { return nil, err } @@ -1273,12 +931,12 @@ initiate_connection: // while SQL Server seems to expect one TCP segment per encrypted TDS package. // Setting DynamicRecordSizingDisabled to true disables that algorithm and uses 16384 bytes per TLS package config.DynamicRecordSizingDisabled = true - outbuf.transport = conn - toconn.buf = outbuf - tlsConn := tls.Client(toconn, &config) + // setting up connection handler which will allow wrapping of TLS handshake packets inside TDS stream + handshakeConn := tlsHandshakeConn{buf: outbuf} + passthrough := passthroughConn{c: &handshakeConn} + tlsConn := tls.Client(&passthrough, &config) err = tlsConn.Handshake() - - toconn.buf = nil + passthrough.c = toconn outbuf.transport = tlsConn if err != nil { return nil, fmt.Errorf("TLS Handshake failed: %v", err) @@ -1300,15 +958,23 @@ initiate_connection: AppName: p.appname, TypeFlags: p.typeFlags, } - auth, auth_ok := getAuth(p.user, p.password, p.serverSPN, p.workstation) - if auth_ok { + auth, authOk := getAuth(p.user, p.password, p.serverSPN, p.workstation) + switch { + case p.fedAuthAccessToken != "": // accesstoken ignores user/password + featurext := &featureExtFedAuthSTS{ + FedAuthEcho: len(fields[preloginFEDAUTHREQUIRED]) > 0 && fields[preloginFEDAUTHREQUIRED][0] == 1, + FedAuthToken: p.fedAuthAccessToken, + Nonce: fields[preloginNONCEOPT], + } + login.FeatureExt.Add(featurext) + case authOk: login.SSPI, err = auth.InitialBytes() if err != nil { return nil, err } login.OptionFlags2 |= fIntSecurity defer auth.Free() - } else { + default: login.UserName = p.user login.Password = p.password } @@ -1318,42 +984,43 @@ initiate_connection: } // processing login response - var sspi_msg []byte -continue_login: - tokchan := make(chan tokenStruct, 5) - go processResponse(context.Background(), &sess, tokchan, nil) success := false - for tok := range tokchan { - switch token := tok.(type) { - case sspiMsg: - sspi_msg, err = auth.NextBytes(token) - if err != nil { - return nil, err - } - case loginAckStruct: - success = true - sess.loginAck = token - case error: - return nil, fmt.Errorf("Login error: %s", token.Error()) - case doneStruct: - if token.isError() { - return nil, fmt.Errorf("Login error: %s", token.getError()) + for { + tokchan := make(chan tokenStruct, 5) + go processResponse(context.Background(), &sess, tokchan, nil) + for tok := range tokchan { + switch token := tok.(type) { + case sspiMsg: + sspi_msg, err := auth.NextBytes(token) + if err != nil { + return nil, err + } + if sspi_msg != nil && len(sspi_msg) > 0 { + outbuf.BeginPacket(packSSPIMessage, false) + _, err = outbuf.Write(sspi_msg) + if err != nil { + return nil, err + } + err = outbuf.FinishPacket() + if err != nil { + return nil, err + } + sspi_msg = nil + } + case loginAckStruct: + success = true + sess.loginAck = token + case error: + return nil, fmt.Errorf("Login error: %s", token.Error()) + case doneStruct: + if token.isError() { + return nil, fmt.Errorf("Login error: %s", token.getError()) + } + goto loginEnd } } } - if sspi_msg != nil { - outbuf.BeginPacket(packSSPIMessage, false) - _, err = outbuf.Write(sspi_msg) - if err != nil { - return nil, err - } - err = outbuf.FinishPacket() - if err != nil { - return nil, err - } - sspi_msg = nil - goto continue_login - } +loginEnd: if !success { return nil, fmt.Errorf("Login failed") } @@ -1361,6 +1028,9 @@ continue_login: toconn.Close() p.host = sess.routedServer p.port = uint64(sess.routedPort) + if !p.hostInCertificateProvided { + p.hostInCertificate = sess.routedServer + } goto initiate_connection } return &sess, nil diff --git a/vendor/github.com/denisenkom/go-mssqldb/token.go b/vendor/github.com/denisenkom/go-mssqldb/token.go index 1acac8a5d2b..25385e89dcb 100644 --- a/vendor/github.com/denisenkom/go-mssqldb/token.go +++ b/vendor/github.com/denisenkom/go-mssqldb/token.go @@ -17,20 +17,21 @@ type token byte // token ids const ( - tokenReturnStatus token = 121 // 0x79 - tokenColMetadata token = 129 // 0x81 - tokenOrder token = 169 // 0xA9 - tokenError token = 170 // 0xAA - tokenInfo token = 171 // 0xAB - tokenReturnValue token = 0xAC - tokenLoginAck token = 173 // 0xad - tokenRow token = 209 // 0xd1 - tokenNbcRow token = 210 // 0xd2 - tokenEnvChange token = 227 // 0xE3 - tokenSSPI token = 237 // 0xED - tokenDone token = 253 // 0xFD - tokenDoneProc token = 254 - tokenDoneInProc token = 255 + tokenReturnStatus token = 121 // 0x79 + tokenColMetadata token = 129 // 0x81 + tokenOrder token = 169 // 0xA9 + tokenError token = 170 // 0xAA + tokenInfo token = 171 // 0xAB + tokenReturnValue token = 0xAC + tokenLoginAck token = 173 // 0xad + tokenFeatureExtAck token = 174 // 0xae + tokenRow token = 209 // 0xd1 + tokenNbcRow token = 210 // 0xd2 + tokenEnvChange token = 227 // 0xE3 + tokenSSPI token = 237 // 0xED + tokenDone token = 253 // 0xFD + tokenDoneProc token = 254 + tokenDoneInProc token = 255 ) // done flags @@ -447,6 +448,22 @@ func parseLoginAck(r *tdsBuffer) loginAckStruct { return res } +// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/2eb82f8e-11f0-46dc-b42d-27302fa4701a +func parseFeatureExtAck(r *tdsBuffer) { + // at most 1 featureAck per feature in featureExt + // go-mssqldb will add at most 1 feature, the spec defines 7 different features + for i := 0; i < 8; i++ { + featureID := r.byte() // FeatureID + if featureID == 0xff { + return + } + size := r.uint32() // FeatureAckDataLen + d := make([]byte, size) + r.ReadFull(d) + } + panic("parsed more than 7 featureAck's, protocol implementation error?") +} + // http://msdn.microsoft.com/en-us/library/dd357363.aspx func parseColMetadata72(r *tdsBuffer) (columns []columnStruct) { count := r.uint16() @@ -577,6 +594,8 @@ func processSingleResponse(sess *tdsSession, ch chan tokenStruct, outs map[strin case tokenLoginAck: loginAck := parseLoginAck(sess.buf) ch <- loginAck + case tokenFeatureExtAck: + parseFeatureExtAck(sess.buf) case tokenOrder: order := parseOrder(sess.buf) ch <- order diff --git a/vendor/github.com/denisenkom/go-mssqldb/tvp_go19.go b/vendor/github.com/denisenkom/go-mssqldb/tvp_go19.go new file mode 100644 index 00000000000..64e5e21fbd8 --- /dev/null +++ b/vendor/github.com/denisenkom/go-mssqldb/tvp_go19.go @@ -0,0 +1,231 @@ +// +build go1.9 + +package mssql + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "reflect" + "strings" + "time" +) + +const ( + jsonTag = "json" + tvpTag = "tvp" + skipTagValue = "-" + sqlSeparator = "." +) + +var ( + ErrorEmptyTVPTypeName = errors.New("TypeName must not be empty") + ErrorTypeSlice = errors.New("TVP must be slice type") + ErrorTypeSliceIsEmpty = errors.New("TVP mustn't be null value") + ErrorSkip = errors.New("all fields mustn't skip") + ErrorObjectName = errors.New("wrong tvp name") + ErrorWrongTyping = errors.New("the number of elements in columnStr and tvpFieldIndexes do not align") +) + +//TVP is driver type, which allows supporting Table Valued Parameters (TVP) in SQL Server +type TVP struct { + //TypeName mustn't be default value + TypeName string + //Value must be the slice, mustn't be nil + Value interface{} +} + +func (tvp TVP) check() error { + if len(tvp.TypeName) == 0 { + return ErrorEmptyTVPTypeName + } + if !isProc(tvp.TypeName) { + return ErrorEmptyTVPTypeName + } + if sepCount := getCountSQLSeparators(tvp.TypeName); sepCount > 1 { + return ErrorObjectName + } + valueOf := reflect.ValueOf(tvp.Value) + if valueOf.Kind() != reflect.Slice { + return ErrorTypeSlice + } + if valueOf.IsNil() { + return ErrorTypeSliceIsEmpty + } + if reflect.TypeOf(tvp.Value).Elem().Kind() != reflect.Struct { + return ErrorTypeSlice + } + return nil +} + +func (tvp TVP) encode(schema, name string, columnStr []columnStruct, tvpFieldIndexes []int) ([]byte, error) { + if len(columnStr) != len(tvpFieldIndexes) { + return nil, ErrorWrongTyping + } + preparedBuffer := make([]byte, 0, 20+(10*len(columnStr))) + buf := bytes.NewBuffer(preparedBuffer) + err := writeBVarChar(buf, "") + if err != nil { + return nil, err + } + + writeBVarChar(buf, schema) + writeBVarChar(buf, name) + binary.Write(buf, binary.LittleEndian, uint16(len(columnStr))) + + for i, column := range columnStr { + binary.Write(buf, binary.LittleEndian, uint32(column.UserType)) + binary.Write(buf, binary.LittleEndian, uint16(column.Flags)) + writeTypeInfo(buf, &columnStr[i].ti) + writeBVarChar(buf, "") + } + // The returned error is always nil + buf.WriteByte(_TVP_END_TOKEN) + + conn := new(Conn) + conn.sess = new(tdsSession) + conn.sess.loginAck = loginAckStruct{TDSVersion: verTDS73} + stmt := &Stmt{ + c: conn, + } + + val := reflect.ValueOf(tvp.Value) + for i := 0; i < val.Len(); i++ { + refStr := reflect.ValueOf(val.Index(i).Interface()) + buf.WriteByte(_TVP_ROW_TOKEN) + for columnStrIdx, fieldIdx := range tvpFieldIndexes { + field := refStr.Field(fieldIdx) + tvpVal := field.Interface() + valOf := reflect.ValueOf(tvpVal) + elemKind := field.Kind() + if elemKind == reflect.Ptr && valOf.IsNil() { + switch tvpVal.(type) { + case *bool, *time.Time, *int8, *int16, *int32, *int64, *float32, *float64, *int: + binary.Write(buf, binary.LittleEndian, uint8(0)) + continue + default: + binary.Write(buf, binary.LittleEndian, uint64(_PLP_NULL)) + continue + } + } + if elemKind == reflect.Slice && valOf.IsNil() { + binary.Write(buf, binary.LittleEndian, uint64(_PLP_NULL)) + continue + } + + cval, err := convertInputParameter(tvpVal) + if err != nil { + return nil, fmt.Errorf("failed to convert tvp parameter row col: %s", err) + } + param, err := stmt.makeParam(cval) + if err != nil { + return nil, fmt.Errorf("failed to make tvp parameter row col: %s", err) + } + columnStr[columnStrIdx].ti.Writer(buf, param.ti, param.buffer) + } + } + buf.WriteByte(_TVP_END_TOKEN) + return buf.Bytes(), nil +} + +func (tvp TVP) columnTypes() ([]columnStruct, []int, error) { + val := reflect.ValueOf(tvp.Value) + var firstRow interface{} + if val.Len() != 0 { + firstRow = val.Index(0).Interface() + } else { + firstRow = reflect.New(reflect.TypeOf(tvp.Value).Elem()).Elem().Interface() + } + + tvpRow := reflect.TypeOf(firstRow) + columnCount := tvpRow.NumField() + defaultValues := make([]interface{}, 0, columnCount) + tvpFieldIndexes := make([]int, 0, columnCount) + for i := 0; i < columnCount; i++ { + field := tvpRow.Field(i) + tvpTagValue, isTvpTag := field.Tag.Lookup(tvpTag) + jsonTagValue, isJsonTag := field.Tag.Lookup(jsonTag) + if IsSkipField(tvpTagValue, isTvpTag, jsonTagValue, isJsonTag) { + continue + } + tvpFieldIndexes = append(tvpFieldIndexes, i) + if field.Type.Kind() == reflect.Ptr { + v := reflect.New(field.Type.Elem()) + defaultValues = append(defaultValues, v.Interface()) + continue + } + defaultValues = append(defaultValues, reflect.Zero(field.Type).Interface()) + } + + if columnCount-len(tvpFieldIndexes) == columnCount { + return nil, nil, ErrorSkip + } + + conn := new(Conn) + conn.sess = new(tdsSession) + conn.sess.loginAck = loginAckStruct{TDSVersion: verTDS73} + stmt := &Stmt{ + c: conn, + } + + columnConfiguration := make([]columnStruct, 0, columnCount) + for index, val := range defaultValues { + cval, err := convertInputParameter(val) + if err != nil { + return nil, nil, fmt.Errorf("failed to convert tvp parameter row %d col %d: %s", index, val, err) + } + param, err := stmt.makeParam(cval) + if err != nil { + return nil, nil, err + } + column := columnStruct{ + ti: param.ti, + } + switch param.ti.TypeId { + case typeNVarChar, typeBigVarBin: + column.ti.Size = 0 + } + columnConfiguration = append(columnConfiguration, column) + } + + return columnConfiguration, tvpFieldIndexes, nil +} + +func IsSkipField(tvpTagValue string, isTvpValue bool, jsonTagValue string, isJsonTagValue bool) bool { + if !isTvpValue && !isJsonTagValue { + return false + } else if isTvpValue && tvpTagValue != skipTagValue { + return false + } else if !isTvpValue && isJsonTagValue && jsonTagValue != skipTagValue { + return false + } + return true +} + +func getSchemeAndName(tvpName string) (string, string, error) { + if len(tvpName) == 0 { + return "", "", ErrorEmptyTVPTypeName + } + splitVal := strings.Split(tvpName, ".") + if len(splitVal) > 2 { + return "", "", errors.New("wrong tvp name") + } + if len(splitVal) == 2 { + res := make([]string, 2) + for key, value := range splitVal { + tmp := strings.Replace(value, "[", "", -1) + tmp = strings.Replace(tmp, "]", "", -1) + res[key] = tmp + } + return res[0], res[1], nil + } + tmp := strings.Replace(splitVal[0], "[", "", -1) + tmp = strings.Replace(tmp, "]", "", -1) + + return "", tmp, nil +} + +func getCountSQLSeparators(str string) int { + return strings.Count(str, sqlSeparator) +} diff --git a/vendor/github.com/denisenkom/go-mssqldb/types.go b/vendor/github.com/denisenkom/go-mssqldb/types.go index 3bad788b923..b6e7fb2b526 100644 --- a/vendor/github.com/denisenkom/go-mssqldb/types.go +++ b/vendor/github.com/denisenkom/go-mssqldb/types.go @@ -11,6 +11,7 @@ import ( "time" "github.com/denisenkom/go-mssqldb/internal/cp" + "github.com/denisenkom/go-mssqldb/internal/decimal" ) // fixed-length data types @@ -62,6 +63,7 @@ const ( typeNChar = 0xef typeXml = 0xf1 typeUdt = 0xf0 + typeTvp = 0xf3 // long length types typeText = 0x23 @@ -73,6 +75,10 @@ const _PLP_NULL = 0xFFFFFFFFFFFFFFFF const _UNKNOWN_PLP_LEN = 0xFFFFFFFFFFFFFFFE const _PLP_TERMINATOR = 0x00000000 +// TVP COLUMN FLAGS +const _TVP_END_TOKEN = 0x00 +const _TVP_ROW_TOKEN = 0x01 + // TYPE_INFO rule // http://msdn.microsoft.com/en-us/library/dd358284.aspx type typeInfo struct { @@ -145,6 +151,8 @@ func writeTypeInfo(w io.Writer, ti *typeInfo) (err error) { // those are fixed length // https://msdn.microsoft.com/en-us/library/dd341171.aspx ti.Writer = writeFixedType + case typeTvp: + ti.Writer = writeFixedType default: // all others are VARLENTYPE err = writeVarLen(w, ti) if err != nil { @@ -162,6 +170,7 @@ func writeFixedType(w io.Writer, ti typeInfo, buf []byte) (err error) { // https://msdn.microsoft.com/en-us/library/dd358341.aspx func writeVarLen(w io.Writer, ti *typeInfo) (err error) { switch ti.TypeId { + case typeDateN: ti.Writer = writeByteLenType case typeTimeN, typeDateTime2N, typeDateTimeOffsetN: @@ -203,6 +212,7 @@ func writeVarLen(w io.Writer, ti *typeInfo) (err error) { ti.Writer = writeByteLenType case typeBigVarBin, typeBigVarChar, typeBigBinary, typeBigChar, typeNVarChar, typeNChar, typeXml, typeUdt: + // short len types if ti.Size > 8000 || ti.Size == 0 { if err = binary.Write(w, binary.LittleEndian, uint16(0xffff)); err != nil { @@ -809,12 +819,12 @@ func decodeMoney(buf []byte) []byte { uint64(buf[1])<<40 | uint64(buf[2])<<48 | uint64(buf[3])<<56) - return scaleBytes(strconv.FormatInt(money, 10), 4) + return decimal.ScaleBytes(strconv.FormatInt(money, 10), 4) } func decodeMoney4(buf []byte) []byte { money := int32(binary.LittleEndian.Uint32(buf[0:4])) - return scaleBytes(strconv.FormatInt(int64(money), 10), 4) + return decimal.ScaleBytes(strconv.FormatInt(int64(money), 10), 4) } func decodeGuid(buf []byte) []byte { @@ -826,15 +836,14 @@ func decodeGuid(buf []byte) []byte { func decodeDecimal(prec uint8, scale uint8, buf []byte) []byte { var sign uint8 sign = buf[0] - dec := Decimal{ - positive: sign != 0, - prec: prec, - scale: scale, - } + var dec decimal.Decimal + dec.SetPositive(sign != 0) + dec.SetPrec(prec) + dec.SetScale(scale) buf = buf[1:] l := len(buf) / 4 for i := 0; i < l; i++ { - dec.integer[i] = binary.LittleEndian.Uint32(buf[0:4]) + dec.SetInteger(binary.LittleEndian.Uint32(buf[0:4]), uint8(i)) buf = buf[4:] } return dec.Bytes() @@ -1177,7 +1186,7 @@ func makeDecl(ti typeInfo) string { case typeBigChar, typeChar: return fmt.Sprintf("char(%d)", ti.Size) case typeBigVarChar, typeVarChar: - if ti.Size > 4000 || ti.Size == 0 { + if ti.Size > 8000 || ti.Size == 0 { return fmt.Sprintf("varchar(max)") } else { return fmt.Sprintf("varchar(%d)", ti.Size) @@ -1219,6 +1228,11 @@ func makeDecl(ti typeInfo) string { return ti.UdtInfo.TypeName case typeGuid: return "uniqueidentifier" + case typeTvp: + if ti.UdtInfo.SchemaName != "" { + return fmt.Sprintf("%s.%s READONLY", ti.UdtInfo.SchemaName, ti.UdtInfo.TypeName) + } + return fmt.Sprintf("%s READONLY", ti.UdtInfo.TypeName) default: panic(fmt.Sprintf("not implemented makeDecl for type %#x", ti.TypeId)) } diff --git a/vendor/github.com/denisenkom/go-mssqldb/uniqueidentifier.go b/vendor/github.com/denisenkom/go-mssqldb/uniqueidentifier.go index c8ef3149b19..3ad6f8634a2 100644 --- a/vendor/github.com/denisenkom/go-mssqldb/uniqueidentifier.go +++ b/vendor/github.com/denisenkom/go-mssqldb/uniqueidentifier.go @@ -72,3 +72,9 @@ func (u UniqueIdentifier) Value() (driver.Value, error) { func (u UniqueIdentifier) String() string { return fmt.Sprintf("%X-%X-%X-%X-%X", u[0:4], u[4:6], u[6:8], u[8:10], u[10:]) } + +// MarshalText converts Uniqueidentifier to bytes corresponding to the stringified hexadecimal representation of the Uniqueidentifier +// e.g., "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA" -> [65 65 65 65 65 65 65 65 45 65 65 65 65 45 65 65 65 65 45 65 65 65 65 65 65 65 65 65 65 65 65] +func (u UniqueIdentifier) MarshalText() []byte { + return []byte(u.String()) +} diff --git a/vendor/github.com/golang-sql/civil/CONTRIBUTING.md b/vendor/github.com/golang-sql/civil/CONTRIBUTING.md new file mode 100644 index 00000000000..d0635c3ab56 --- /dev/null +++ b/vendor/github.com/golang-sql/civil/CONTRIBUTING.md @@ -0,0 +1,73 @@ +# Contributing + +1. Sign one of the contributor license agreements below. + +#### Running + +Once you've done the necessary setup, you can run the integration tests by +running: + +``` sh +$ go test -v github.com/golang-sql/civil +``` + +## Contributor License Agreements + +Before we can accept your pull requests you'll need to sign a Contributor +License Agreement (CLA): + +- **If you are an individual writing original source code** and **you own the +intellectual property**, then you'll need to sign an [individual CLA][indvcla]. +- **If you work for a company that wants to allow you to contribute your +work**, then you'll need to sign a [corporate CLA][corpcla]. + +You can sign these electronically (just scroll to the bottom). After that, +we'll be able to accept your pull requests. + +## Contributor Code of Conduct + +As contributors and maintainers of this project, +and in the interest of fostering an open and welcoming community, +we pledge to respect all people who contribute through reporting issues, +posting feature requests, updating documentation, +submitting pull requests or patches, and other activities. + +We are committed to making participation in this project +a harassment-free experience for everyone, +regardless of level of experience, gender, gender identity and expression, +sexual orientation, disability, personal appearance, +body size, race, ethnicity, age, religion, or nationality. + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery +* Personal attacks +* Trolling or insulting/derogatory comments +* Public or private harassment +* Publishing other's private information, +such as physical or electronic +addresses, without explicit permission +* Other unethical or unprofessional conduct. + +Project maintainers have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct. +By adopting this Code of Conduct, +project maintainers commit themselves to fairly and consistently +applying these principles to every aspect of managing this project. +Project maintainers who do not follow or enforce the Code of Conduct +may be permanently removed from the project team. + +This code of conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. + +Instances of abusive, harassing, or otherwise unacceptable behavior +may be reported by opening an issue +or contacting one or more of the project maintainers. + +This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, +available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) + +[gcloudcli]: https://developers.google.com/cloud/sdk/gcloud/ +[indvcla]: https://developers.google.com/open-source/cla/individual +[corpcla]: https://developers.google.com/open-source/cla/corporate \ No newline at end of file diff --git a/vendor/github.com/golang-sql/civil/LICENSE b/vendor/github.com/golang-sql/civil/LICENSE new file mode 100644 index 00000000000..7a4a3ea2424 --- /dev/null +++ b/vendor/github.com/golang-sql/civil/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/vendor/github.com/golang-sql/civil/README.md b/vendor/github.com/golang-sql/civil/README.md new file mode 100644 index 00000000000..3a7956ecda4 --- /dev/null +++ b/vendor/github.com/golang-sql/civil/README.md @@ -0,0 +1,15 @@ +# Civil Date and Time + +[![GoDoc](https://godoc.org/github.com/golang-sql/civil?status.svg)](https://godoc.org/github.com/golang-sql/civil) + +Civil provides Date, Time of Day, and DateTime data types. + +While there are many uses, using specific types when working +with databases make is conceptually eaiser to understand what value +is set in the remote system. + +## Source + +This civil package was extracted and forked from `cloud.google.com/go/civil`. +As such the license and contributing requirements remain the same as that +module. diff --git a/vendor/cloud.google.com/go/civil/civil.go b/vendor/github.com/golang-sql/civil/civil.go similarity index 100% rename from vendor/cloud.google.com/go/civil/civil.go rename to vendor/github.com/golang-sql/civil/civil.go diff --git a/vendor/modules.txt b/vendor/modules.txt index c41813e4481..5d543f60aec 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -4,7 +4,6 @@ 4d63.com/tz # cloud.google.com/go v0.51.0 cloud.google.com/go -cloud.google.com/go/civil cloud.google.com/go/compute/metadata cloud.google.com/go/functions/metadata cloud.google.com/go/iam @@ -320,9 +319,11 @@ github.com/coreos/pkg/dlopen github.com/davecgh/go-spew/spew # github.com/davecgh/go-xdr v0.0.0-20161123171359-e6a2ba005892 github.com/davecgh/go-xdr/xdr2 -# github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f +# github.com/denisenkom/go-mssqldb v0.0.0-20200206145737-bbfc9a55622e github.com/denisenkom/go-mssqldb github.com/denisenkom/go-mssqldb/internal/cp +github.com/denisenkom/go-mssqldb/internal/decimal +github.com/denisenkom/go-mssqldb/internal/querytext # github.com/devigned/tab v0.1.2-0.20190607222403-0c15cf42f9a2 github.com/devigned/tab # github.com/dgrijalva/jwt-go v3.2.1-0.20190620180102-5e25c22bd5d6+incompatible @@ -523,6 +524,8 @@ github.com/gogo/protobuf/proto github.com/gogo/protobuf/protoc-gen-gogo/descriptor github.com/gogo/protobuf/sortkeys github.com/gogo/protobuf/types +# github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe +github.com/golang-sql/civil # github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 github.com/golang/groupcache/lru # github.com/golang/protobuf v1.3.2 diff --git a/x-pack/metricbeat/module/mssql/_meta/docs.asciidoc b/x-pack/metricbeat/module/mssql/_meta/docs.asciidoc index 155e643b4a2..ad32544a465 100644 --- a/x-pack/metricbeat/module/mssql/_meta/docs.asciidoc +++ b/x-pack/metricbeat/module/mssql/_meta/docs.asciidoc @@ -1,4 +1,4 @@ -This is the https://www.microsoft.com/en-us/sql-server/sql-server-2017[Microsoft SQL 2017] Metricbeat module. It is still in beta and under active development to add new Metricsets and introduce enhancements. +This is the https://www.microsoft.com/en-us/sql-server/sql-server-2017[Microsoft SQL 2017] Metricbeat module. It is still under active development to add new Metricsets and introduce enhancements. [float] === Compatibility @@ -23,7 +23,7 @@ The following Metricsets are already included: [float] === Module-specific configuration notes -When configuring the `hosts` option, you can specify native user credentials +When configuring the `hosts` option, you can specify native user credentials as part of the host string with the following format: ---- @@ -45,4 +45,4 @@ metricbeat.modules: period: 10 ---- -Store sensitive values like passwords in the <>. \ No newline at end of file +Store sensitive values like passwords in the <>. diff --git a/x-pack/metricbeat/module/mssql/fields.go b/x-pack/metricbeat/module/mssql/fields.go index f3d9bcc13e2..55c8f0c787a 100644 --- a/x-pack/metricbeat/module/mssql/fields.go +++ b/x-pack/metricbeat/module/mssql/fields.go @@ -19,5 +19,5 @@ func init() { // AssetMssql returns asset data. // This is the base64 encoded gzipped contents of module/mssql. func AssetMssql() string { - return "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" + return "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" } diff --git a/x-pack/metricbeat/module/mssql/performance/_meta/data.json b/x-pack/metricbeat/module/mssql/performance/_meta/data.json index 7f409619ea2..656c0b525fd 100644 --- a/x-pack/metricbeat/module/mssql/performance/_meta/data.json +++ b/x-pack/metricbeat/module/mssql/performance/_meta/data.json @@ -1,45 +1,42 @@ { "@timestamp": "2017-10-12T08:05:34.853Z", - "agent": { - "hostname": "host.example.com", - "name": "host.example.com" - }, "event": { "dataset": "mssql.performance", "duration": 115000, "module": "mssql" }, "metricset": { - "name": "performance" + "name": "performance", + "period": 10000 }, "mssql": { "performance": { "active_temp_tables": 0, - "batch_requests_per_sec": 24249, + "batch_requests_per_sec": 3355, "buffer": { "cache_hit": { - "pct": 0.14 + "pct": 0.22 }, - "checkpoint_pages_per_sec": 182, - "database_pages": 1892, + "checkpoint_pages_per_sec": 295, + "database_pages": 2152, "page_life_expectancy": { - "sec": 8201 + "sec": 1400 }, - "target_pages": 3194880 + "target_pages": 3178496 }, - "compilations_per_sec": 7379, - "connections_reset_per_sec": 2179, - "lock_waits_per_sec": 3, - "logins_per_sec": 7346, - "logouts_per_sec": 7339, - "page_splits_per_sec": 45, - "recompilations_per_sec": 0, + "compilations_per_sec": 1151, + "connections_reset_per_sec": 88, + "lock_waits_per_sec": 6, + "logins_per_sec": 1105, + "logouts_per_sec": 1103, + "page_splits_per_sec": 14, + "recompilations_per_sec": 2, "transactions": 0, - "user_connections": 7 + "user_connections": 2 } }, "service": { - "address": "172.26.0.2", + "address": "172.23.0.2:1433", "type": "mssql" } } \ No newline at end of file diff --git a/x-pack/metricbeat/module/mssql/performance/_meta/fields.yml b/x-pack/metricbeat/module/mssql/performance/_meta/fields.yml index 199be4830fd..4352c237015 100644 --- a/x-pack/metricbeat/module/mssql/performance/_meta/fields.yml +++ b/x-pack/metricbeat/module/mssql/performance/_meta/fields.yml @@ -1,7 +1,7 @@ - name: performance type: group description: performance metricset fetches information about the Performance Counters - release: beta + release: ga fields: - name: page_splits_per_sec type: long diff --git a/x-pack/metricbeat/module/mssql/performance/data_integration_test.go b/x-pack/metricbeat/module/mssql/performance/data_integration_test.go index f7753b98364..f0520a843bd 100644 --- a/x-pack/metricbeat/module/mssql/performance/data_integration_test.go +++ b/x-pack/metricbeat/module/mssql/performance/data_integration_test.go @@ -8,6 +8,9 @@ import ( "net/url" "testing" + "github.com/elastic/beats/v7/libbeat/logp" + "github.com/elastic/beats/v7/libbeat/tests/compose" + _ "github.com/denisenkom/go-mssqldb" "github.com/pkg/errors" "github.com/stretchr/testify/assert" @@ -17,23 +20,13 @@ import ( ) func TestData(t *testing.T) { - t.Skip("Skipping `data.json` generation test") + logp.TestingSetup() + service := compose.EnsureUp(t, "mssql") - _, config, err := getHostURI() - if err != nil { - t.Fatal("error getting config information", err.Error()) - } + f := mbtest.NewReportingMetricSetV2(t, mtest.GetConfig(service.Host(), "performance")) - f := mbtest.NewReportingMetricSetV2(t, config) - events, errs := mbtest.ReportingFetchV2(f) - if len(errs) > 0 { - t.Fatalf("Expected 0 error, had %d. %v\n", len(errs), errs) - } - assert.NotEmpty(t, events) - - if err = mbtest.WriteEventsReporterV2(f, t, ""); err != nil { - t.Fatal("write", err) - } + err := mbtest.WriteEventsReporterV2(f, t, "") + assert.NoError(t, err) } func getHostURI() (string, map[string]interface{}, error) { diff --git a/x-pack/metricbeat/module/mssql/performance/performance.go b/x-pack/metricbeat/module/mssql/performance/performance.go index faea7f09c2b..fb9c4b4c888 100644 --- a/x-pack/metricbeat/module/mssql/performance/performance.go +++ b/x-pack/metricbeat/module/mssql/performance/performance.go @@ -13,7 +13,6 @@ import ( "github.com/pkg/errors" - "github.com/elastic/beats/v7/libbeat/common/cfgwarn" "github.com/elastic/beats/v7/libbeat/logp" "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/beats/v7/x-pack/metricbeat/module/mssql" @@ -49,8 +48,6 @@ type MetricSet struct { // New creates a new instance of the MetricSet. New is responsible for unpacking // any MetricSet specific configuration options if there are any. func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - cfgwarn.Beta("The mssql performance metricset is beta.") - logger := logp.NewLogger("mssql.performance").With("host", base.HostData().SanitizedURI) db, err := mssql.NewConnection(base.HostData().URI) @@ -71,40 +68,40 @@ func New(base mb.BaseMetricSet) (mb.MetricSet, error) { func (m *MetricSet) Fetch(reporter mb.ReporterV2) { var err error var rows *sql.Rows - rows, err = m.db.Query(`SELECT object_name, - counter_name, - instance_name, - cntr_value -FROM sys.dm_os_performance_counters -WHERE counter_name = 'SQL Compilations/sec' - OR counter_name = 'SQL Re-Compilations/sec' - OR counter_name = 'User Connections' - OR counter_name = 'Page splits/sec' - OR ( counter_name = 'Lock Waits/sec' - AND instance_name = '_Total' ) - OR counter_name = 'Page splits/sec' - OR ( object_name = 'SQLServer:Buffer Manager' - AND counter_name = 'Page life expectancy' ) - OR counter_name = 'Batch Requests/sec' - OR ( counter_name = 'Buffer cache hit ratio' - AND object_name = 'SQLServer:Buffer Manager' ) - OR ( counter_name = 'Target pages' - AND object_name = 'SQLServer:Buffer Manager' ) - OR ( counter_name = 'Database pages' - AND object_name = 'SQLServer:Buffer Manager' ) - OR ( counter_name = 'Checkpoint pages/sec' - AND object_name = 'SQLServer:Buffer Manager' ) - OR ( counter_name = 'Lock Waits/sec' - AND instance_name = '_Total' ) - OR ( counter_name = 'Transactions' - AND object_name = 'SQLServer:General Statistics' ) - OR ( counter_name = 'Logins/sec' - AND object_name = 'SQLServer:General Statistics' ) - OR ( counter_name = 'Logouts/sec' - AND object_name = 'SQLServer:General Statistics' ) - OR ( counter_name = 'Connection Reset/sec' - AND object_name = 'SQLServer:General Statistics' ) - OR ( counter_name = 'Active Temp Tables' + rows, err = m.db.Query(`SELECT object_name, + counter_name, + instance_name, + cntr_value +FROM sys.dm_os_performance_counters +WHERE counter_name = 'SQL Compilations/sec' + OR counter_name = 'SQL Re-Compilations/sec' + OR counter_name = 'User Connections' + OR counter_name = 'Page splits/sec' + OR ( counter_name = 'Lock Waits/sec' + AND instance_name = '_Total' ) + OR counter_name = 'Page splits/sec' + OR ( object_name = 'SQLServer:Buffer Manager' + AND counter_name = 'Page life expectancy' ) + OR counter_name = 'Batch Requests/sec' + OR ( counter_name = 'Buffer cache hit ratio' + AND object_name = 'SQLServer:Buffer Manager' ) + OR ( counter_name = 'Target pages' + AND object_name = 'SQLServer:Buffer Manager' ) + OR ( counter_name = 'Database pages' + AND object_name = 'SQLServer:Buffer Manager' ) + OR ( counter_name = 'Checkpoint pages/sec' + AND object_name = 'SQLServer:Buffer Manager' ) + OR ( counter_name = 'Lock Waits/sec' + AND instance_name = '_Total' ) + OR ( counter_name = 'Transactions' + AND object_name = 'SQLServer:General Statistics' ) + OR ( counter_name = 'Logins/sec' + AND object_name = 'SQLServer:General Statistics' ) + OR ( counter_name = 'Logouts/sec' + AND object_name = 'SQLServer:General Statistics' ) + OR ( counter_name = 'Connection Reset/sec' + AND object_name = 'SQLServer:General Statistics' ) + OR ( counter_name = 'Active Temp Tables' AND object_name = 'SQLServer:General Statistics' )`) if err != nil { reporter.Error(errors.Wrapf(err, "error closing rows")) diff --git a/x-pack/metricbeat/module/mssql/performance/performance_integration_test.go b/x-pack/metricbeat/module/mssql/performance/performance_integration_test.go index 884bfe72763..5e039265d2b 100644 --- a/x-pack/metricbeat/module/mssql/performance/performance_integration_test.go +++ b/x-pack/metricbeat/module/mssql/performance/performance_integration_test.go @@ -81,7 +81,7 @@ func TestFetch(t *testing.T) { {key: "connections_reset_per_sec", assertion: int64Assertion(int64HigherThanZero)}, {key: "logouts_per_sec", assertion: int64Assertion(int64HigherThanZero)}, {key: "logins_per_sec", assertion: int64Assertion(int64HigherThanZero)}, - {key: "recompilations_per_sec", assertion: int64Assertion(int64EqualZero)}, + {key: "recompilations_per_sec", assertion: int64Assertion(int64EqualOrHigherThanZero)}, {key: "compilations_per_sec", assertion: int64Assertion(int64HigherThanZero)}, {key: "batch_requests_per_sec", assertion: int64Assertion(int64HigherThanZero)}, {key: "buffer.cache_hit.pct", assertion: float64Assertion(float64HigherThanZero)}, diff --git a/x-pack/metricbeat/module/mssql/transaction_log/_meta/data.json b/x-pack/metricbeat/module/mssql/transaction_log/_meta/data.json index ce46579d561..186c154ee98 100644 --- a/x-pack/metricbeat/module/mssql/transaction_log/_meta/data.json +++ b/x-pack/metricbeat/module/mssql/transaction_log/_meta/data.json @@ -1,16 +1,13 @@ { "@timestamp": "2017-10-12T08:05:34.853Z", - "agent": { - "hostname": "host.example.com", - "name": "host.example.com" - }, "event": { "dataset": "mssql.transaction_log", "duration": 115000, "module": "mssql" }, "metricset": { - "name": "transaction_log" + "name": "transaction_log", + "period": 10000 }, "mssql": { "database": { @@ -20,26 +17,26 @@ "transaction_log": { "space_usage": { "since_last_backup": { - "bytes": 135168 + "bytes": 937984 }, "total": { "bytes": 2088960 }, "used": { - "bytes": 622592, - "pct": 29.80392074584961 + "bytes": 1318912, + "pct": 63.13725662231445 } }, "stats": { "active_size": { - "bytes": 135167.737856 + "bytes": 937983.737856 }, "backup_time": "1900-01-01T00:00:00Z", "recovery_size": { - "bytes": 0.128906 + "bytes": 0.894531 }, "since_last_checkpoint": { - "bytes": 135167.737856 + "bytes": 937983.737856 }, "total_size": { "bytes": 2088959.475712 @@ -48,7 +45,7 @@ } }, "service": { - "address": "172.26.0.2", + "address": "172.23.0.2:1433", "type": "mssql" } } \ No newline at end of file diff --git a/x-pack/metricbeat/module/mssql/transaction_log/_meta/fields.yml b/x-pack/metricbeat/module/mssql/transaction_log/_meta/fields.yml index 5ba421ba079..c03df4084d0 100644 --- a/x-pack/metricbeat/module/mssql/transaction_log/_meta/fields.yml +++ b/x-pack/metricbeat/module/mssql/transaction_log/_meta/fields.yml @@ -1,7 +1,7 @@ - name: transaction_log type: group description: transaction_log metricset will fetch information about the operation and transaction log of each database from a MSSQL instance - release: beta + release: ga fields: - name: space_usage type: group diff --git a/x-pack/metricbeat/module/mssql/transaction_log/data_integration_test.go b/x-pack/metricbeat/module/mssql/transaction_log/data_integration_test.go index fcdad6ccb44..16f5c16eac6 100644 --- a/x-pack/metricbeat/module/mssql/transaction_log/data_integration_test.go +++ b/x-pack/metricbeat/module/mssql/transaction_log/data_integration_test.go @@ -7,14 +7,18 @@ package transaction_log import ( "testing" + "github.com/elastic/beats/v7/libbeat/logp" + "github.com/elastic/beats/v7/libbeat/tests/compose" + mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing" mtest "github.com/elastic/beats/v7/x-pack/metricbeat/module/mssql/testing" ) func TestData(t *testing.T) { - t.Skip("Skipping `data.json` generation test") + logp.TestingSetup() + service := compose.EnsureUp(t, "mssql") - f := mbtest.NewReportingMetricSetV2(t, mtest.GetConfig("transaction_log")) + f := mbtest.NewReportingMetricSetV2(t, mtest.GetConfig(service.Host(), "transaction_log")) err := mbtest.WriteEventsReporterV2(f, t, "") if err != nil { diff --git a/x-pack/metricbeat/module/mssql/transaction_log/transaction_log.go b/x-pack/metricbeat/module/mssql/transaction_log/transaction_log.go index 6047d0161b4..a974ffdab9b 100644 --- a/x-pack/metricbeat/module/mssql/transaction_log/transaction_log.go +++ b/x-pack/metricbeat/module/mssql/transaction_log/transaction_log.go @@ -11,7 +11,6 @@ import ( "github.com/pkg/errors" "github.com/elastic/beats/v7/libbeat/common" - "github.com/elastic/beats/v7/libbeat/common/cfgwarn" "github.com/elastic/beats/v7/libbeat/logp" "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/beats/v7/x-pack/metricbeat/module/mssql" @@ -55,8 +54,6 @@ type MetricSet struct { // New create a new instance of the MetricSet func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - cfgwarn.Beta("The mssql transaction_log metricset is beta.") - logger := logp.NewLogger("mssql.transaction_log").With("host", base.HostData().SanitizedURI) db, err := mssql.NewConnection(base.HostData().URI) @@ -127,7 +124,7 @@ func (m *MetricSet) Close() error { func (m *MetricSet) getLogSpaceUsageForDb(dbName string) (common.MapStr, error) { // According to MS docs a single result is always returned for this query - row := m.db.QueryRow(fmt.Sprintf(`USE %s; SELECT * FROM sys.dm_db_log_space_usage;`, dbName)) + row := m.db.QueryRow(fmt.Sprintf(`USE [%s]; SELECT * FROM sys.dm_db_log_space_usage;`, dbName)) var res logSpace if err := row.Scan(&res.id, &res.totalLogSizeInBytes, &res.usedLogSpaceInBytes, &res.usedLogSpaceInPercent, @@ -153,7 +150,7 @@ func (m *MetricSet) getLogSpaceUsageForDb(dbName string) (common.MapStr, error) } func (m *MetricSet) getLogStats(db dbInfo) (common.MapStr, error) { // According to MS docs a single result is always returned for this query - row := m.db.QueryRow(fmt.Sprintf(`USE %s; SELECT database_id,total_log_size_mb,active_log_size_mb,log_backup_time,log_since_last_log_backup_mb,log_since_last_checkpoint_mb,log_recovery_size_mb FROM sys.dm_db_log_stats(%d);`, db.name, db.id)) + row := m.db.QueryRow(fmt.Sprintf(`USE [%s]; SELECT database_id,total_log_size_mb,active_log_size_mb,log_backup_time,log_since_last_log_backup_mb,log_since_last_checkpoint_mb,log_recovery_size_mb FROM sys.dm_db_log_stats(%d);`, db.name, db.id)) var res logStats if err := row.Scan(&res.databaseID, &res.sizeMB, &res.activeSizeMB, &res.backupTime, &res.sinceLastBackupMB, &res.sinceLastCheckpointMB, &res.recoverySizeMB); err != nil { From 18d34e5411f5bc869c38e500e9a531bcc9546ac2 Mon Sep 17 00:00:00 2001 From: Michal Pristas Date: Tue, 5 May 2020 14:36:06 +0200 Subject: [PATCH 115/116] [Elastic-Agent] Use nested objects so fleet can handle metadata correctly (#18234) * use subobject so kibana can handle correctly * changelog --- x-pack/elastic-agent/CHANGELOG.asciidoc | 1 + .../agent/application/info/agent_metadata.go | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/x-pack/elastic-agent/CHANGELOG.asciidoc b/x-pack/elastic-agent/CHANGELOG.asciidoc index 0fb1c64d273..a1fccd61728 100644 --- a/x-pack/elastic-agent/CHANGELOG.asciidoc +++ b/x-pack/elastic-agent/CHANGELOG.asciidoc @@ -51,3 +51,4 @@ - Enable debug log level for Metricbeat and Filebeat when run under the Elastic Agent. {pull}17935[17935] - Enable introspecting configuration {pull}18124[18124] - Follow home path for all config files {pull}18161[18161] +- Use nested objects so fleet can handle metadata correctly {pull}18234[18234] diff --git a/x-pack/elastic-agent/pkg/agent/application/info/agent_metadata.go b/x-pack/elastic-agent/pkg/agent/application/info/agent_metadata.go index a061f6f4bef..424053276b0 100644 --- a/x-pack/elastic-agent/pkg/agent/application/info/agent_metadata.go +++ b/x-pack/elastic-agent/pkg/agent/application/info/agent_metadata.go @@ -17,9 +17,14 @@ import ( // ECSMeta is a collection of agent related metadata in ECS compliant object form. type ECSMeta struct { - Agent *AgentECSMeta `json:"elastic.agent"` - Host *HostECSMeta `json:"host"` - OS *SystemECSMeta `json:"os"` + Elastic *ElasticECSMeta `json:"elastic"` + Host *HostECSMeta `json:"host"` + OS *SystemECSMeta `json:"os"` +} + +// ElasticECSMeta is a collection of elastic vendor metadata in ECS compliant object form. +type ElasticECSMeta struct { + Agent *AgentECSMeta `json:"agent"` } // AgentECSMeta is a collection of agent metadata in ECS compliant object form. @@ -119,9 +124,11 @@ func (i *AgentInfo) ECSMetadata() (*ECSMeta, error) { info := sysInfo.Info() return &ECSMeta{ - Agent: &AgentECSMeta{ - ID: i.agentID, - Version: release.Version(), + Elastic: &ElasticECSMeta{ + Agent: &AgentECSMeta{ + ID: i.agentID, + Version: release.Version(), + }, }, Host: &HostECSMeta{ Arch: info.Architecture, From 29316f7d22057784cbca723540614dcd0d78bb82 Mon Sep 17 00:00:00 2001 From: Nicolas Ruflin Date: Tue, 5 May 2020 15:08:53 +0200 Subject: [PATCH 116/116] [Agent] Update log message to be Experimental instead of Alpha (#18227) Closes https://github.com/elastic/beats/issues/18214 --- x-pack/elastic-agent/pkg/agent/warn/warn.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/elastic-agent/pkg/agent/warn/warn.go b/x-pack/elastic-agent/pkg/agent/warn/warn.go index d9667f6f284..c3b97079aa1 100644 --- a/x-pack/elastic-agent/pkg/agent/warn/warn.go +++ b/x-pack/elastic-agent/pkg/agent/warn/warn.go @@ -11,7 +11,7 @@ import ( "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/core/logger" ) -const message = "The Elastic Agent is currently in Alpha and should not be used in production" +const message = "The Elastic Agent is currently in Experimental and should not be used in production" // LogNotGA warns the users in the log that the Elastic Agent is not GA. func LogNotGA(log *logger.Logger) {