fluent-plugin-shodan 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/AUTHORS +1 -0
- data/README.md +125 -0
- data/VERSION +1 -0
- data/lib/fluent/plugin/in_shodan_search.rb +65 -0
- data/test/test_in_shodan_search.rb +59 -0
- metadata +139 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c5065dc8260c9015eebe3ab7e1095d050fdb64b3fa0d0168c414342e31f7960d
|
4
|
+
data.tar.gz: b26a7a5b53c56cc6469433eb5867749ec26c7298958294dcf7c6c5b2f8a335e0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3244f0dd4e8c9d916a83220e2a534631f3c413a15b68ce985dc93a2632691a75b4147c838a2a871136a58c0e753b701304faf2cf86e6b18bd421302162836ac7
|
7
|
+
data.tar.gz: 14b471034818688f951fdb36d0102dccd63bd8f3a6437e6ee8e44de9d8a0d799190241250aaacd7aa631e5a4ab467f36d11ad4e59e2615cbfccfb33e92db174a
|
data/AUTHORS
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
srilumpa <marcandre.doll@gmail.com>
|
data/README.md
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
# fluent-plugin-shodan
|
2
|
+
|
3
|
+
[![Unit tests](https://github.com/srilumpa/fluent-plugin-shodan/actions/workflows/ruby.yml/badge.svg)](https://github.com/srilumpa/fluent-plugin-shodan/actions/workflows/ruby.yml)[![Ruby Gem](https://github.com/srilumpa/fluent-plugin-shodan/actions/workflows/rubygem-push.yml/badge.svg)](https://github.com/srilumpa/fluent-plugin-shodan/actions/workflows/rubygem-push.yml)[![GPR](https://github.com/srilumpa/fluent-plugin-shodan/actions/workflows/gpr-push.yml/badge.svg)](https://github.com/srilumpa/fluent-plugin-shodan/actions/workflows/gpr-push.yml)
|
4
|
+
|
5
|
+
The [Shodan](https://www.shodan.io/) plugin offers [Fluentd](https://fluentd.org/) capacities to gather data from shodan and send them to whatever system you want (on the condition Fluentd has an [output plugin](https://docs.fluentd.org/output) fitting your needs).
|
6
|
+
|
7
|
+
The Shodan plugin can adress three ways of gathering data
|
8
|
+
|
9
|
+
- by querying the [Search API](https://developer.shodan.io/api)
|
10
|
+
- by consuming the [Stream API](https://developer.shodan.io/api/stream) (WIP)
|
11
|
+
- or by consuming the [Alert API](https://developer.shodan.io/api/stream) (WIP)
|
12
|
+
|
13
|
+
The outputed "logs" follow the Shodan [Banner specification](https://datapedia.shodan.io/).
|
14
|
+
|
15
|
+
A valid API key will be necessary for this plugin to work. The Shodan Search plugin will work with a _Free_ account with limited functionnalities, but the Shodans Stream and the Shodan Alert plugins will need a subscription plan to work.
|
16
|
+
|
17
|
+
This plugin relies on the
|
18
|
+
|
19
|
+
## Installation
|
20
|
+
|
21
|
+
### RubyGems
|
22
|
+
|
23
|
+
```
|
24
|
+
$ gem install fluent-plugin-shodan
|
25
|
+
```
|
26
|
+
|
27
|
+
### Bundler
|
28
|
+
|
29
|
+
Add following line to your Gemfile:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
gem "fluent-plugin-shodan"
|
33
|
+
```
|
34
|
+
|
35
|
+
And then execute:
|
36
|
+
|
37
|
+
```
|
38
|
+
$ bundle
|
39
|
+
```
|
40
|
+
|
41
|
+
## Shodan Search
|
42
|
+
|
43
|
+
### Example configuration
|
44
|
+
|
45
|
+
```
|
46
|
+
<source>
|
47
|
+
@type shodan_search
|
48
|
+
interval 15m
|
49
|
+
tag shodan.ssh
|
50
|
+
query ssh
|
51
|
+
api_key 1234567890AZERTYUIOP
|
52
|
+
</source>
|
53
|
+
```
|
54
|
+
|
55
|
+
### How it works
|
56
|
+
|
57
|
+
When Fluentd is started with `in_shodan_search`, it will create a Shodan client and passes to it the API key. It will then query the Shodan API to get the account information to check if the API key is valid. If it is not, an error will be logged and the plugin will stop.
|
58
|
+
|
59
|
+
Once the client is ready, a timer will be set to query the Shodan API a the interval set up in the configuration. One line of "log" will be generated per element contained in the `matches` array from the query result. An other query will be submitted to gather data fro mthe next page if
|
60
|
+
|
61
|
+
- the amount of read entries is lesser than the total available entries
|
62
|
+
- the current read page is not greater than the `max_pages` parameter
|
63
|
+
|
64
|
+
### Plugin helpers
|
65
|
+
|
66
|
+
* [timer](https://docs.fluentd.org/v/1.0/plugin-helper-overview/api-plugin-helper-timer)
|
67
|
+
* See also: [Input Plugin Overview](https://docs.fluentd.org/v/1.0/input#overview)
|
68
|
+
|
69
|
+
### Configuration
|
70
|
+
|
71
|
+
#### api_key (string) (required)
|
72
|
+
|
73
|
+
The API key to connect to the Shodan API.
|
74
|
+
|
75
|
+
#### interval (time) (optional)
|
76
|
+
|
77
|
+
The interval time between running queries.
|
78
|
+
|
79
|
+
Default value: `3600`.
|
80
|
+
|
81
|
+
#### tag (string) (optional)
|
82
|
+
|
83
|
+
The tag to apply to each shodan entries.
|
84
|
+
|
85
|
+
#### query (string) (required)
|
86
|
+
|
87
|
+
The Shodan query to execute.
|
88
|
+
|
89
|
+
#### max_pages (integer) (optional)
|
90
|
+
|
91
|
+
The maximum amount of pages to crawl. A 0 or negative value means to crawl all pages. Note that if you have a free account, querying a page other than the first one will result in a `HTTP 401` response.
|
92
|
+
|
93
|
+
Default value: `1`.
|
94
|
+
|
95
|
+
## Shodan Stream
|
96
|
+
|
97
|
+
WIP
|
98
|
+
|
99
|
+
## Shodan Alert
|
100
|
+
|
101
|
+
WIP
|
102
|
+
|
103
|
+
## Testing
|
104
|
+
|
105
|
+
### Unit tests
|
106
|
+
|
107
|
+
1. Clone this repository
|
108
|
+
2. Install all dependencies with `bundle install`
|
109
|
+
3. Set the `SHODAN_TEST_API_KEY` environment variable with your API key
|
110
|
+
4. Run `rake` or `rake test`
|
111
|
+
|
112
|
+
### Live tests
|
113
|
+
|
114
|
+
On a system where fluentd is installed
|
115
|
+
|
116
|
+
1. Clone this repository
|
117
|
+
2. Build the gem with `gem build fluent-plugin-shodan.gemspec`
|
118
|
+
3. Install the built gem with `fluent-gem install fluent-plugin-shodan-<version>.gem`
|
119
|
+
4. Follow the [debugging guide from FluentD](https://docs.fluentd.org/plugin-development#debugging-plugins)
|
120
|
+
|
121
|
+
## Copyright
|
122
|
+
|
123
|
+
* Copyright(c) 2022 Marc-André Doll
|
124
|
+
* License
|
125
|
+
* Apache License, Version 2.0
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'fluent/plugin/input'
|
2
|
+
require 'shodanz'
|
3
|
+
|
4
|
+
module Fluent::Plugin
|
5
|
+
class ShodanSearch < Input
|
6
|
+
Fluent::Plugin.register_input('shodan_search', self)
|
7
|
+
|
8
|
+
helpers :timer
|
9
|
+
|
10
|
+
desc "The API key to connect to the Shodan API."
|
11
|
+
config_param :api_key, :string, secret: true
|
12
|
+
desc "The interval time between running queries."
|
13
|
+
config_param :interval, :time, default: 3600
|
14
|
+
desc "The tag to apply to each shodan entries."
|
15
|
+
config_param :tag, :string, default: nil
|
16
|
+
desc "The Shodan query to execute."
|
17
|
+
config_param :query, :string
|
18
|
+
desc "The maximum amount of pages to crawl. A 0 or negative value means to crawl all pages."
|
19
|
+
config_param :max_pages, :integer, default: 1
|
20
|
+
|
21
|
+
def configure(conf)
|
22
|
+
super
|
23
|
+
|
24
|
+
@client = Shodanz.client.new(key: @api_key)
|
25
|
+
begin
|
26
|
+
log.info "Shodan client properly registered", client_info: @client.info
|
27
|
+
rescue RuntimeError => exception
|
28
|
+
raise Fluent::ConfigError.new "Invalid Shodan API key"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def multi_workers_ready?
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
36
|
+
def start
|
37
|
+
super
|
38
|
+
|
39
|
+
timer_execute("shodan_#{self.class.name}_#{@tag}".to_sym, @interval, repeat: true, &method(:run))
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def run
|
45
|
+
log.debug "Starting Shodan search", query: @query, max_pages: @max_pages
|
46
|
+
es_time = Fluent::EventTime.now
|
47
|
+
current_page = 0
|
48
|
+
read_entries = 0
|
49
|
+
loop do
|
50
|
+
current_page += 1
|
51
|
+
result = @client.host_search(@query, page: current_page)
|
52
|
+
result['matches'].each do |rec|
|
53
|
+
router.emit(@tag, es_time, rec)
|
54
|
+
end
|
55
|
+
read_entries += result['matches'].length
|
56
|
+
break if (@max_pages >= 0 && current_page >= @max_pages) || read_entries >= result['total']
|
57
|
+
end
|
58
|
+
log.debug "Shodan search ending", query: @query, total_read: read_entries
|
59
|
+
rescue RuntimeError => re
|
60
|
+
log.error "Unable to execute Shodan query", query: @query, page: current_page, error: re
|
61
|
+
rescue => exception
|
62
|
+
log.error "Error executing Shodan query", error: exception
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'fluent/env'
|
3
|
+
require 'fluent/test'
|
4
|
+
require 'fluent/test/driver/input'
|
5
|
+
require 'fluent/test/helpers'
|
6
|
+
require "fluent/plugin/in_shodan_search"
|
7
|
+
|
8
|
+
class ShodanSearchInputTest < Test::Unit::TestCase
|
9
|
+
include Fluent::Test::Helpers
|
10
|
+
|
11
|
+
API_KEY = ENV['SHODAN_TEST_API_KEY']
|
12
|
+
|
13
|
+
def setup
|
14
|
+
Fluent::Test.setup
|
15
|
+
end
|
16
|
+
|
17
|
+
sub_test_case 'Configuration' do
|
18
|
+
test 'is invalid without API key' do
|
19
|
+
assert_raise Fluent::ConfigError do
|
20
|
+
create_driver(%[
|
21
|
+
query 8.8.8.8
|
22
|
+
])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
test 'is invalid if the API key is invalid' do
|
26
|
+
assert_raise Fluent::ConfigError do
|
27
|
+
create_driver(%[
|
28
|
+
api_key 1234567890AZERTYUIOP
|
29
|
+
query 8.8.8.8
|
30
|
+
])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
test 'is invalid without a query' do
|
34
|
+
assert_raise Fluent::ConfigError do
|
35
|
+
create_driver(%[
|
36
|
+
api_key #{API_KEY}
|
37
|
+
])
|
38
|
+
end
|
39
|
+
end
|
40
|
+
test 'query is set correctly' do
|
41
|
+
d = create_driver
|
42
|
+
assert_equal '8.8.8.8', d.instance.query
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
sub_test_case 'Plugin emission' do
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
CONFIG = %[
|
52
|
+
api_key #{API_KEY}
|
53
|
+
query 8.8.8.8
|
54
|
+
]
|
55
|
+
|
56
|
+
def create_driver(conf = CONFIG)
|
57
|
+
Fluent::Test::Driver::Input.new(Fluent::Plugin::ShodanSearch).configure(conf)
|
58
|
+
end
|
59
|
+
end
|
metadata
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fluent-plugin-shodan
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- srilumpa
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-01-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: fluentd
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.12.0
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '2'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.12.0
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '2'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: shodanz
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '2.0'
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '2.0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: io-console
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: bundler
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '2'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '2'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: rake
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '13'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '13'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: test-unit
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '3.5'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '3.5'
|
103
|
+
description:
|
104
|
+
email: marcandre.doll@gmail.com
|
105
|
+
executables: []
|
106
|
+
extensions: []
|
107
|
+
extra_rdoc_files:
|
108
|
+
- README.md
|
109
|
+
files:
|
110
|
+
- AUTHORS
|
111
|
+
- README.md
|
112
|
+
- VERSION
|
113
|
+
- lib/fluent/plugin/in_shodan_search.rb
|
114
|
+
- test/test_in_shodan_search.rb
|
115
|
+
homepage: https://github.com/srilumpa/fluent-plugin-shodan
|
116
|
+
licenses:
|
117
|
+
- Apache-2.0
|
118
|
+
metadata: {}
|
119
|
+
post_install_message:
|
120
|
+
rdoc_options: []
|
121
|
+
require_paths:
|
122
|
+
- lib
|
123
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - ">="
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '0'
|
128
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
requirements: []
|
134
|
+
rubygems_version: 3.0.3.1
|
135
|
+
signing_key:
|
136
|
+
specification_version: 3
|
137
|
+
summary: Fluentd plugin to extract data from Shodan
|
138
|
+
test_files:
|
139
|
+
- test/test_in_shodan_search.rb
|