Skip to content

Commit

Permalink
Add script processor (#10850)
Browse files Browse the repository at this point in the history
The script processor executes Javascript code to process an event. The processor uses a pure
Go implementation of ECMAScript 5.1. This can be useful in situations where one of the other
processors doesn’t provide the functionality you need to filter events.

The processor can be configured by embedding Javascript in your configuration file or by pointing
the processor at external file(s). See the included documentation for the full details.

    processors:
    - script:
        type: javascript
        code: >
          function process(event) {
              event.Tag("js");
          }

For observability it can add metrics with a histogram of the execution time and a counter
for the number of exceptions that occur.

There is an optional timeout configuration option to the processor that will
timeout the execution by interrupting the JS code.

Rather than have libbeat force the inclusion of the script processor in all Beats, make each Beat opt into the processor. It is imported by the log processing Beats (filebeat, journalbeat, and winlogbeat).

Benchmark Results

Here's a benchmark to get a rough idea of how long it takes to process an event in the processor's runtime

I updated the benchmark to test each case with and without the timeout enabled.

    BenchmarkBeatEventV0/Put-12                         2000000           707 ns/op
    BenchmarkBeatEventV0/timeout_Put-12                 1000000          1510 ns/op
    BenchmarkBeatEventV0/Object_Put_Key-12              2000000           631 ns/op
    BenchmarkBeatEventV0/timeout_Object_Put_Key-12      1000000          1252 ns/op
    BenchmarkBeatEventV0/Get-12                         2000000           750 ns/op
    BenchmarkBeatEventV0/timeout_Get-12                 1000000          1495 ns/op
    BenchmarkBeatEventV0/Get_Undefined_Key-12           1000000          1039 ns/op
    BenchmarkBeatEventV0/timeout_Get_Undefined_Key-12   1000000          2108 ns/op
    BenchmarkBeatEventV0/fields_get_key-12              2000000          1044 ns/op
    BenchmarkBeatEventV0/timeout_fields_get_key-12      1000000          2051 ns/op
    BenchmarkBeatEventV0/Get_@metadata-12               2000000           750 ns/op
    BenchmarkBeatEventV0/timeout_Get_@metadata-12       1000000          1745 ns/op
    BenchmarkBeatEventV0/Put_@metadata-12               1000000          1048 ns/op
    BenchmarkBeatEventV0/timeout_Put_@metadata-12       500000          2623 ns/op
    BenchmarkBeatEventV0/Delete_@metadata-12            2000000           842 ns/op
    BenchmarkBeatEventV0/timeout_Delete_@metadata-12    1000000          1629 ns/op
    BenchmarkBeatEventV0/Cancel-12                      2000000           759 ns/op
    BenchmarkBeatEventV0/timeout_Cancel-12              1000000          1329 ns/op
    BenchmarkBeatEventV0/Tag-12                         1000000          1189 ns/op
    BenchmarkBeatEventV0/timeout_Tag-12                 1000000          1973 ns/op
    BenchmarkBeatEventV0/AppendTo-12                    2000000           644 ns/op
    BenchmarkBeatEventV0/timeout_AppendTo-12            1000000          1347 ns/op
  • Loading branch information
andrewkroh authored Mar 15, 2019
1 parent e9fc1b6 commit 0693a22
Show file tree
Hide file tree
Showing 121 changed files with 125,054 additions and 11 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d
- Add output test to kafka output {pull}10834[10834]
- Add ip fields to default_field in Elasticsearch template. {pull}11035[11035]
- Gracefully shut down on SIGHUP {pull}10704[10704]
- Add `script` processor that supports using Javascript to process events. {pull}10850[10850]

*Auditbeat*

Expand Down
82 changes: 82 additions & 0 deletions NOTICE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,34 @@ License type (autodetected): Apache-2.0
Apache License 2.0


--------------------------------------------------------------------
Dependency: github.com/dlclark/regexp2
Revision: 7632a260cbaf5e7594fc1544a503456ecd0827f1
License type (autodetected): MIT
./vendor/github.com/dlclark/regexp2/LICENSE:
--------------------------------------------------------------------
The MIT License (MIT)

Copyright (c) Doug Clark

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/docker/distribution
Revision: 1e2f10eb65743fed02f573d31a4587de09afb20e
Expand Down Expand Up @@ -454,6 +482,28 @@ License type (autodetected): Apache-2.0
Apache License 2.0


--------------------------------------------------------------------
Dependency: github.com/dop251/goja
Revision: dd2ac4456e2073f116d6b88741d513addabe0326
License type (autodetected): MIT
./vendor/github.com/dop251/goja/LICENSE:
--------------------------------------------------------------------
Copyright (c) 2016 Dmitry Panov

Copyright (c) 2012 Robert Krimen

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/dustin/go-humanize
Revision: 259d2a102b871d17f30e3cd9881a642961a1e486
Expand Down Expand Up @@ -905,6 +955,38 @@ The above copyright notice and this permission notice shall be included in all c

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/go-sourcemap/sourcemap
Revision: b019cc30c1eaa584753491b0d8f8c1534bf1eb44
License type (autodetected): BSD-2-Clause
./vendor/github.com/go-sourcemap/sourcemap/LICENSE:
--------------------------------------------------------------------
Copyright (c) 2016 The github.com/go-sourcemap/sourcemap Contributors.
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.

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/go-sql-driver/mysql
Version: v1.4.1
Expand Down
3 changes: 3 additions & 0 deletions filebeat/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ import (

cmd "github.com/elastic/beats/libbeat/cmd"
"github.com/elastic/beats/libbeat/cmd/instance"

// Import the script processor.
_ "github.com/elastic/beats/libbeat/processors/script"
)

// Name of this beat
Expand Down
3 changes: 3 additions & 0 deletions journalbeat/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import (

cmd "github.com/elastic/beats/libbeat/cmd"
"github.com/elastic/beats/libbeat/cmd/instance"

// Import the script processor.
_ "github.com/elastic/beats/libbeat/processors/script"
)

// Name of this beat
Expand Down
6 changes: 3 additions & 3 deletions libbeat/common/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ func OverwriteConfigOpts(options []ucfg.Option) {

func LoadFile(path string) (*Config, error) {
if IsStrictPerms() {
if err := ownerHasExclusiveWritePerms(path); err != nil {
if err := OwnerHasExclusiveWritePerms(path); err != nil {
return nil, err
}
}
Expand Down Expand Up @@ -414,10 +414,10 @@ func filterDebugObject(c interface{}) {
}
}

// ownerHasExclusiveWritePerms asserts that the current user or root is the
// OwnerHasExclusiveWritePerms asserts that the current user or root is the
// owner of the config file and that the config file is (at most) writable by
// the owner or root (e.g. group and other cannot have write access).
func ownerHasExclusiveWritePerms(name string) error {
func OwnerHasExclusiveWritePerms(name string) error {
if runtime.GOOS == "windows" {
return nil
}
Expand Down
186 changes: 180 additions & 6 deletions libbeat/docs/processors-using.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -195,19 +195,22 @@ endif::[]
The supported processors are:

* <<add-cloud-metadata,`add_cloud_metadata`>>
* <<add-docker-metadata,`add_docker_metadata`>>
* <<add-host-metadata,`add_host_metadata`>>
* <<add-kubernetes-metadata,`add_kubernetes_metadata`>>
* <<add-locale,`add_locale`>>
* <<add-process-metadata,`add_process_metadata`>>
* <<community-id,`community_id`>>
* <<decode-json-fields,`decode_json_fields`>>
* <<dissect, `dissect`>>
* <<processor-dns, `dns`>>
* <<drop-event,`drop_event`>>
* <<drop-fields,`drop_fields`>>
* <<include-fields,`include_fields`>>
* <<rename-fields,`rename`>>
* <<add-kubernetes-metadata,`add_kubernetes_metadata`>>
* <<add-docker-metadata,`add_docker_metadata`>>
* <<add-host-metadata,`add_host_metadata`>>
* <<dissect, `dissect`>>
* <<processor-dns, `dns`>>
* <<add-process-metadata,`add_process_metadata`>>
ifeval::[("{beatname_lc}"=="filebeat") or ("{beatname_lc}"=="winlogbeat") or ("{beatname_lc}"=="journalbeat")]
* <<processor-script,`script`>>
endif::[]

[[conditions]]
==== Conditions
Expand Down Expand Up @@ -1280,3 +1283,174 @@ set to `true`, this condition will be ignored.
`restricted_fields`:: (Optional) By default, the `process.env` field is not
output, to avoid leaking sensitive data. If `restricted_fields` is `true`, the
field will be present in the output.

ifeval::[("{beatname_lc}"=="filebeat") or ("{beatname_lc}"=="winlogbeat") or ("{beatname_lc}"=="journalbeat")]
[[processor-script]]
=== Script Processor

experimental[]

The script processor executes Javascript code to process an event. The processor
uses a pure Go implementation of ECMAScript 5.1 and has no external
dependencies. This can be useful in situations where one of the other processors
doesn't provide the functionality you need to filter events.

The processor can be configured by embedding Javascript in your configuration
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");
}
----

This loads `filter.js` from disk.

[source,yaml]
----
processors:
- script:
lang: javascript
id: my_filter
file: ${path.config}/filter.js
----

Parameters can be passed to the script by adding `params` to the config.
This allows for a script to be made reusable. When using `params` the
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();
}
}
----

If the script defines a `test()` function it will be invoked when the processor
is loaded. Any exceptions thrown will cause the processor to fail to load. This
can be used to make assertions about the behavior of the script.

[source,javascript]
----
function process(event) {
if (event.Get("event.code") === 1102) {
event.Put("event.action", "cleared");
}
}
function test() {
var event = process(new Event({event: {code: 1102}));
if (event.Get("event.action") !== "cleared") {
throw "expected event.action === cleared";
}
}
----

[float]
==== Configuration options

The `script` processor has the following configuration settings:

`lang`:: This field is required and its value must be `javascript`.

`tag`:: This is an optional identifier that is added to log messages. If defined
it enables metrics logging for this instance of the processor. The metrics
include the number of exceptions and a histogram of the execution times for
the `process` function.

`source`:: Inline Javascript source code.

`file`:: Path to a script file to load. Relative paths are interpreted as
relative to the `path.config` directory. Globs are expanded.

`files`:: List of script files to load. The scripts are concatenated together.
Relative paths are interpreted as relative to the `path.config` directory.
And globs are expanded.

`params`:: A dictionary of parameters that are passed to the `register` of the
script.

`tag_on_exception`:: Tag to add to events in case the Javascript code causes an
exception while processing an event. Defaults to `_js_exception`.

`timeout`:: This sets an execution timeout for the `process` function. When
the `process` function takes longer than the `timeout` period the function
is interrupted. You can set this option to prevent a script from running for
too long (like preventing an infinite `while` loop). By default there is no
timeout.

[float]
==== Event API

The `Event` object passed to the `process` method has the following API.

[frame="topbot",options="header"]
|===
|Method |Description

|`Get(string)`
|Get a value from the event (either a scalar or an object). If the key does not
exist `null` is returned. If no key is provided then an object containing all
fields is returned.

*Example*: `var value = event.Get(key);`

|`Put(string, value)`
|Put a value into the event. If the key was already set then the
previous value is returned. It throws an exception if the key cannot be set
because one of the intermediate values is not an object.

*Example*: `var old = event.Put(key, value);`

|`Rename(string, string)`
|Rename a key in the event. The target key must not exist. It
returns true if the source key was successfully renamed to the target key.

*Example*: `var success = event.Rename("source", "target");`

|`Delete(string)`
|Delete a field from the event. It returns true on success.

*Example*: `var deleted = event.Delete("user.email");`

|`Cancel()`
|Flag the event as cancelled which causes the processor to drop
event.

*Example*: `event.Cancel(); return;`

|`Tag(string)`
|Append a tag to the `tags` field if the tag does not already
exist. Throws an exception if `tags` exists and is not a string or a list of
strings.

*Example*: `event.Tag("user_event");`

|`AppendTo(string, string)`
|`AppendTo` is a specialized `Put` method that converts the existing value to an
array and appends the value if it does not already exist. If there is an
existing value that's not a string or array of strings then an exception is
thrown.

*Example*: `event.AppendTo("error.message", "invalid file hash");`
|===
endif::[]
Loading

0 comments on commit 0693a22

Please sign in to comment.