logstash-filter-greynoise 0.1.7 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a71aad2c5c6984ec9021757f07fb9df22117fad154368a65916673602e76b286
4
- data.tar.gz: e29ffb288550c1dae21245d3b22d00bf3149090923bacd4285054bf3aea24aa0
3
+ metadata.gz: 9a8f0682f3292b7e21b6aeb2200b45ad16d1e71b6f60f6c74db54e6a1a7d229d
4
+ data.tar.gz: 248adab329c505788c4a394a839fcaf0a63ca72b76c20a8b4407839206acf191
5
5
  SHA512:
6
- metadata.gz: 5a7970993a6c48f376508e61722f4668cac2b2e614dc359a4aa1bc2903f5efde5150f7bc7ed348260f6d93da751815aea6e015f5c2c3390cea52f147430e5702
7
- data.tar.gz: 773319bb84fb3857b2183177a309a2e6853f399f54228127328e262b1858ab5a2743ec734f43efdd79b5a3905ce2d947d2dcd5fcf94e2db68bc6645d70234c7a
6
+ metadata.gz: f10678522e0686ffd212fa53275da708daa1900bbb4e13e982e91e491f08c2815d39f3d78160a1d9150960108d4377bf9aa4e59463fd7afe4614ac0adfb6056b
7
+ data.tar.gz: 8d07a3c74c739141c08ce930eed271b3c0ea70aa52d4023afbcf0ee9c1970f1df2485a5a365a94958836db8aabfac63b329fbb1334122505ff668656602c7caf
@@ -1,2 +1,19 @@
1
1
  # logstash-filter-greynoise
2
- Example filter plugin. This should help bootstrap your effort to write your own filter plugin!
2
+
3
+ You can build this project on your machine locally or alternative use the docker file to build and run an environment in Docker.
4
+
5
+ To use Docker, run the following:
6
+
7
+ ```
8
+ # build the docker container for local dev
9
+ make build-docker
10
+
11
+ # shell into docker environment
12
+ make shell-docker
13
+
14
+ # set GN key for tests
15
+ export GN_API_KEY=<GN key>
16
+
17
+ # run tests
18
+ bundle exec rspec
19
+ ```
data/README.md CHANGED
@@ -5,7 +5,7 @@ It is fully free and fully open source. The license is Apache 2.0, meaning you a
5
5
 
6
6
  ## Documentation
7
7
 
8
- The Greynoise filter adds information about IP addresses from logstash events via the Greynoise API.
8
+ The GreyNoise filter adds information about IP addresses from logstash events via the GreyNoise API.
9
9
 
10
10
  GreyNoise is a system that collects and analyzes data on Internet-wide scanners.
11
11
  GreyNoise collects data on benign scanners such as Shodan.io, as well as malicious actors like SSH and telnet worms.
@@ -26,19 +26,34 @@ $LS_HOME/bin/logstash-plugin install logstash-filter-greynoise-0.1.7.gem
26
26
  ```
27
27
 
28
28
  ### 2. Filter Configuration
29
- Add the following inside the filter section of your logstash configuration:
30
29
 
31
30
  ```sh
32
31
  filter {
33
32
  greynoise {
34
- ip => "ip_value" # string (required, reference to ip address field)
35
- key => "your_greynoise_key" # string (optional, no default)
36
- target => "greynoise" # string (optional, default = greynoise)
37
- hit_cache_size => 100 # number (optional, default = 0)
38
- hit_cache_ttl => 6 # number (optional, default = 60)
33
+ ip => "ip_value" # string (required, reference to ip address field)
34
+ full_context => true # bool (optional, whether to use context lookup, default false)
35
+ key => "your_greynoise_key" # string (required)
36
+ target => "greynoise" # string (optional, default = greynoise)
37
+ hit_cache_size => 100 # number (optional, default = 0)
38
+ hit_cache_ttl => 6 # number (optional, default = 60)
39
39
  }
40
40
  }
41
41
  ```
42
+ The GreyNoise Logstash filter plugin can be used in two different modes:
43
+
44
+ ##### Quick IP Enrichment
45
+ In this mode documents (default) are enriched with a bool field `seen` which is true if GreyNoise has seen this IP or false otherwise.
46
+
47
+ This mode is faster than doing full enrichment and should be used for better performance on high-volume/-throughput event streams.
48
+ If you have a data pipeline with multiple enrichment points, you can use the boolean field to later enrich the document with IP information from GreyNoise's context endpoint.
49
+
50
+ ##### Full IP Enrichment
51
+
52
+ In this mode (`full_context => true`), documents are enriched with full context from GreyNoise API if the IP has been observed by GreyNoise.
53
+
54
+ This mode is slower than doing quick IP enrichment and might be reasonable for low-volume/-throughput event streams.
55
+
56
+ **NOTE**: Beware that the full context document from GreyNoise API can be large and as such the cache can grow quickly in size. Set the cache size accordingly to prevent high memory consumption by the cache.
42
57
 
43
58
  Print plugin version:
44
59
 
@@ -81,3 +96,5 @@ Programming is not a required skill. Whatever you've seen about open source and
81
96
  It is more important to the community that you are able to contribute.
82
97
 
83
98
  For more information about contributing, see the [CONTRIBUTING](https://github.com/elasticsearch/logstash/blob/master/CONTRIBUTING.md) file.
99
+
100
+ See also Logstash [Elastic's filter plugin development guide](https://www.elastic.co/guide/en/logstash/current/filter-new-plugin.html).
@@ -4,12 +4,17 @@ require "json"
4
4
  require "logstash/namespace"
5
5
  require "ipaddr"
6
6
  require "lru_redux"
7
+ require 'net/http'
8
+ require 'uri'
7
9
 
10
+ VERSION = "1.0.0"
11
+
12
+ class InvalidAPIKey < StandardError
13
+ end
8
14
 
9
15
  # This filter will replace the contents of the default
10
16
  # message field with whatever you specify in the configuration.
11
17
  #
12
- # It is only intended to be used as an .
13
18
  class LogStash::Filters::Greynoise < LogStash::Filters::Base
14
19
 
15
20
  # Setting the config_name here is required. This is how you
@@ -26,8 +31,11 @@ class LogStash::Filters::Greynoise < LogStash::Filters::Base
26
31
  # ip address to use for greynoise query
27
32
  config :ip, :validate => :string, :required => true
28
33
 
34
+ # whether or not to use full context endpoint
35
+ config :full_context, :validate => :boolean, :default => false
36
+
29
37
  # greynoise enterprise api key
30
- config :key, :validate => :string, :default => ""
38
+ config :key, :validate => :string, :required => true
31
39
 
32
40
  # target top level key of hash response
33
41
  config :target, :validate => :string, :default => "greynoise"
@@ -35,6 +43,9 @@ class LogStash::Filters::Greynoise < LogStash::Filters::Base
35
43
  # tag if ip address supplied is invalid
36
44
  config :tag_on_failure, :validate => :string, :default => '_greynoise_filter_invalid_ip'
37
45
 
46
+ # tag if API key not valid or missing
47
+ config :tag_on_auth_failure, :validate => :string, :default => '_greynoise_filter_invalid_api_key'
48
+
38
49
  # set the size of cache for successful requests
39
50
  config :hit_cache_size, :validate => :number, :default => 0
40
51
 
@@ -47,50 +58,36 @@ class LogStash::Filters::Greynoise < LogStash::Filters::Base
47
58
  if @hit_cache_size > 0
48
59
  @hit_cache = LruRedux::TTL::ThreadSafeCache.new(@hit_cache_size, @hit_cache_ttl)
49
60
  end
50
-
51
61
  end
52
62
 
53
- # def register
54
63
 
55
64
  private
56
65
 
57
- def get_free(target_ip)
58
-
59
- uri = URI.parse("http://api.greynoise.io/v1/query/ip")
60
- request = Net::HTTP::Post.new(uri)
61
- request.set_form_data(
62
- "ip" => target_ip,
63
- )
64
-
65
- req_options = {
66
- use_ssl: uri.scheme == "https",
67
- }
68
- response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
69
- http.request(request)
70
- end
71
- if response.is_a?(Net::HTTPSuccess)
72
- JSON.parse(response.body)
73
- else
74
- nil
66
+ def lookup_ip(target_ip, api_key, context = false)
67
+ endpoint = "quick/"
68
+ if context
69
+ endpoint = "context/"
75
70
  end
76
- end
77
71
 
78
-
79
- private
80
-
81
- def get_enterprise(target_ip, api_key)
82
- uri = URI.parse("https://enterprise.api.greynoise.io/v2/noise/context/" + target_ip)
72
+ uri = URI.parse("https://api.greynoise.io/v2/noise/" + endpoint + target_ip)
83
73
  request = Net::HTTP::Get.new(uri)
84
74
  request["Key"] = api_key
85
- request["User-Agent"] = "logstash-filter-greynoise"
75
+ request["User-Agent"] = "logstash-filter-greynoise " + VERSION
86
76
  req_options = {
87
77
  use_ssl: uri.scheme == "https",
88
78
  }
89
- response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
79
+ response = Net::HTTP.start(uri.hostname, uri.port, req_options) { |http|
90
80
  http.request(request)
91
- end
81
+ }
82
+
92
83
  if response.is_a?(Net::HTTPSuccess)
93
- JSON.parse(response.body)
84
+ result = JSON.parse(response.body)
85
+ unless context
86
+ result["seen"] = result.delete("noise")
87
+ end
88
+ result
89
+ elsif response.is_a?(Net::HTTPUnauthorized)
90
+ raise InvalidAPIKey.new
94
91
  else
95
92
  nil
96
93
  end
@@ -109,42 +106,37 @@ class LogStash::Filters::Greynoise < LogStash::Filters::Base
109
106
  if valid
110
107
  @logger.error("Invalid IP address, skipping", :ip => event.sprintf(ip), :event => event.to_hash)
111
108
  event.tag(@tag_on_failure)
112
- else
113
- if @hit_cache
114
- result = @hit_cache[event.sprintf(ip)]
115
- if result
116
- event.set(@target, result)
117
- filter_matched(event)
118
- else
119
- # check if api key exists and has len of 25 or more to prevent forbidden response
120
- if @key.length >= 25
121
- result = get_enterprise(event.sprintf(ip), event.sprintf(key))
122
- # if no key then use alpha(free) api
123
- else
124
- result = get_free(event.sprintf(ip))
125
- end
126
- unless result.nil?
127
- @hit_cache[event.sprintf(ip)] = result
128
- event.set(@target, result)
129
- # filter_matched should go in the last line of our successful code
130
- filter_matched(event)
131
- end
132
- end
133
- else
134
- if @key.length >= 25
135
- result = get_enterprise(event.sprintf(ip), event.sprintf(key))
136
- else
137
- result = get_free(event.sprintf(ip))
138
- end
109
+ return
110
+ end
111
+
112
+ if @hit_cache
113
+ gn_result = @hit_cache[event.sprintf(ip)]
114
+
115
+ # use cached data
116
+ if gn_result
117
+ event.set(@target, gn_result)
118
+ filter_matched(event)
119
+ return
120
+ end
121
+ end
139
122
 
140
- unless result.nil?
141
- event.set(@target, result)
142
- filter_matched(event)
123
+ # use GN API, since not found in cache
124
+ begin
125
+ gn_result = lookup_ip(event.sprintf(ip), event.sprintf(key), @full_context)
126
+ unless gn_result.nil?
127
+ if @hit_cache
128
+ # store in cache
129
+ @hit_cache[event.sprintf(ip)] = gn_result
143
130
  end
131
+
132
+ event.set(@target, gn_result)
133
+ # filter_matched should go in the last line of our successful code
134
+ filter_matched(event)
144
135
  end
136
+ rescue InvalidAPIKey => _
137
+ @logger.error("unauthorized - check API key")
138
+ event.tag(@tag_on_auth_failure)
145
139
  end
146
140
  end
147
141
 
148
- # def filter
149
- end # def LogStash::Filters::Greynoise
150
-
142
+ end
@@ -1,8 +1,8 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'logstash-filter-greynoise'
3
- s.version = '0.1.7'
3
+ s.version = '1.0.0'
4
4
  s.licenses = ['Apache-2.0']
5
- s.summary = 'This greynoise filter takes contents in the ip field and returns greynoise api data (see https://greynoise.io/ for more info).'
5
+ s.summary = 'This greynoise filter takes contents in the IP field and returns GreyNoise API data (see https://developer.greynoise.io/ for more info).'
6
6
  s.description = 'This gem is a Logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/logstash-plugin install logstash-filter-greynoise. This gem is not a stand-alone program'
7
7
  s.homepage = 'https://github.com/nicksherron/logstash-filter-greynoise'
8
8
  s.authors = ['nsherron90']
@@ -18,9 +18,7 @@ Gem::Specification.new do |s|
18
18
  s.metadata = { "logstash_plugin" => "true", "logstash_group" => "filter" }
19
19
 
20
20
  # Gem dependencies
21
- s.add_runtime_dependency "logstash-core-plugin-api", ">= 1.60", "<= 2.99"
22
21
  s.add_development_dependency 'logstash-devutils'
22
+ s.add_runtime_dependency "logstash-core-plugin-api", ">= 1.60", "<= 2.99"
23
23
  s.add_runtime_dependency 'lru_redux', "~> 1.1.0"
24
-
25
-
26
24
  end
@@ -3,33 +3,47 @@ require_relative '../spec_helper'
3
3
  require "logstash/filters/greynoise"
4
4
 
5
5
  describe LogStash::Filters::Greynoise do
6
-
7
6
  describe "defaults" do
8
- let(:config) do <<-CONFIG
7
+ let(:config) do
8
+ api_key = ENV["GN_API_KEY"]
9
+ <<-CONFIG
9
10
  filter {
10
11
  greynoise {
11
- ip => "ip"
12
+ ip => "%{ip}"
13
+ key => "#{api_key}"
12
14
  }
13
15
  }
14
- CONFIG
15
- # end
16
+ CONFIG
17
+ end
16
18
 
17
19
  sample("ip" => "8.8.8.8") do
18
20
  insist { subject }.include?("greynoise")
19
-
20
- expected_fields = %w(greynoise.ip greynoise.seen)
21
+ expected_fields = %w(ip seen code)
21
22
  expected_fields.each do |f|
22
23
  insist { subject.get("greynoise") }.include?(f)
23
24
  end
25
+ insist { subject.get("greynoise").get("code").equal?("0x05") }
26
+ end
27
+
28
+ sample("ip" => "4.2.1.A") do
29
+ insist { subject.get("tags") }.include?("_greynoise_filter_invalid_ip")
30
+ end
31
+ end
32
+
33
+ describe "invalid_key" do
34
+ let(:config) do
35
+ <<-CONFIG
36
+ filter {
37
+ greynoise {
38
+ ip => "%{ip}"
39
+ key => "BAD_KEY"
40
+ }
41
+ }
42
+ CONFIG
24
43
  end
44
+
45
+ sample("ip" => "8.8.8.8") do
46
+ insist { subject.get("tags") }.include?("_greynoise_filter_invalid_api_key")
25
47
  end
26
48
  end
27
49
  end
28
- #
29
- #
30
- # sample("message" => "some text") do
31
- # expect(subject).to include("message")
32
- # expect(subject.get('message')).to eq('Hello World')
33
- # end
34
- # end
35
- # end
metadata CHANGED
@@ -1,49 +1,49 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-filter-greynoise
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - nsherron90
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-06 00:00:00.000000000 Z
11
+ date: 2020-12-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: '1.60'
19
- - - "<="
20
- - !ruby/object:Gem::Version
21
- version: '2.99'
22
- name: logstash-core-plugin-api
18
+ version: '0'
19
+ name: logstash-devutils
20
+ type: :development
23
21
  prerelease: false
24
- type: :runtime
25
22
  version_requirements: !ruby/object:Gem::Requirement
26
23
  requirements:
27
24
  - - ">="
28
25
  - !ruby/object:Gem::Version
29
- version: '1.60'
30
- - - "<="
31
- - !ruby/object:Gem::Version
32
- version: '2.99'
26
+ version: '0'
33
27
  - !ruby/object:Gem::Dependency
34
28
  requirement: !ruby/object:Gem::Requirement
35
29
  requirements:
36
30
  - - ">="
37
31
  - !ruby/object:Gem::Version
38
- version: '0'
39
- name: logstash-devutils
32
+ version: '1.60'
33
+ - - "<="
34
+ - !ruby/object:Gem::Version
35
+ version: '2.99'
36
+ name: logstash-core-plugin-api
37
+ type: :runtime
40
38
  prerelease: false
41
- type: :development
42
39
  version_requirements: !ruby/object:Gem::Requirement
43
40
  requirements:
44
41
  - - ">="
45
42
  - !ruby/object:Gem::Version
46
- version: '0'
43
+ version: '1.60'
44
+ - - "<="
45
+ - !ruby/object:Gem::Version
46
+ version: '2.99'
47
47
  - !ruby/object:Gem::Dependency
48
48
  requirement: !ruby/object:Gem::Requirement
49
49
  requirements:
@@ -51,8 +51,8 @@ dependencies:
51
51
  - !ruby/object:Gem::Version
52
52
  version: 1.1.0
53
53
  name: lru_redux
54
- prerelease: false
55
54
  type: :runtime
55
+ prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - "~>"
@@ -97,12 +97,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
97
97
  - !ruby/object:Gem::Version
98
98
  version: '0'
99
99
  requirements: []
100
- rubyforge_project:
101
- rubygems_version: 2.7.9
100
+ rubygems_version: 3.0.6
102
101
  signing_key:
103
102
  specification_version: 4
104
- summary: This greynoise filter takes contents in the ip field and returns greynoise
105
- api data (see https://greynoise.io/ for more info).
103
+ summary: This greynoise filter takes contents in the IP field and returns GreyNoise
104
+ API data (see https://developer.greynoise.io/ for more info).
106
105
  test_files:
107
106
  - spec/filters/greynoise_spec.rb
108
107
  - spec/spec_helper.rb