fluent-plugin-elastic-log 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '08f18d938d7e838acc560c6857affc1821290a66bb4c9a203e039ca75de0b1db'
4
- data.tar.gz: 3be561a99555350c031f26d0b1d97de9162a8deee8d32eb2dfb3c41e4008957a
3
+ metadata.gz: 8fc9200f1f1d69c9899d9d0a3daf550ea117c40794bfb0c4aea36bf98514df60
4
+ data.tar.gz: 5d6d1da9522cbd4e4888f791323f1402107714d522bd30a44eb1d04f58bbacfb
5
5
  SHA512:
6
- metadata.gz: 41dc18b2e54f6d852caca69fffacce7ffed2cdf8a46dcbde37f4b9b0120940fd680a535178fbef331b669b9c60ba0154b87596bba741b35fa6d9abce22a361fa
7
- data.tar.gz: b344dc9bf21982ff52a81b0ad0e395969f5e1d9e52efee63e75042d9a0b9a7c29313410a91c19f62d00fe15e5921ee9f1e0a47668017aa4f034be50393a9ad16
6
+ metadata.gz: a98aad4e1f27636f65a677680146fbc0e286f3875f175ec172b2e33f286e60219d24d8945bae7022498c8d92f07a2df2e2e2e35e94d5101eef4c49a1e0f19d1f
7
+ data.tar.gz: 51a82ded9b94c7841b9da3d67611c928b0ec315e276cdd314c09ca15fe4d5aef3e0073d8a2614e21871d31019148fc04bd45227b15f5d9f2717d5a98645ab206
data/README.md CHANGED
@@ -1,40 +1,67 @@
1
1
  # fluent-plugin-elastic-log
2
2
 
3
- [Fluentd](https://fluentd.org/) filter plugin to do something.
3
+ [Fluentd](https://fluentd.org/) filter plugin to process elastic logs.
4
4
 
5
- TODO: write description for you plugin.
5
+ ## plugins
6
6
 
7
- ## Installation
7
+ ### out - elastic_audit_log_metric
8
8
 
9
- ### RubyGems
9
+ process audit logs and transform to metrics.
10
10
 
11
- ```
12
- $ gem install fluent-plugin-elastic-log
11
+ Example:
12
+
13
+ ``` conf
14
+ <match my_tag_pattern>
15
+ @type elastic_audit_log_metric
16
+
17
+ tag elastic_audit_log_metric
18
+ timestamp_key timestamp
19
+ timestamp_format epochmillis
20
+ prefix tags_
21
+ </match>
13
22
  ```
14
23
 
15
- ### Bundler
24
+ parameters are:
25
+ * tag : Tag to emit metric events
26
+
27
+ parameters for input record:
28
+ * categories: Categories selected to be converted to metrics
29
+ * category_key: Category key in input record
30
+ * layer_key: Layer key in input record
31
+ * request_type_key: Request type key in input record
32
+ * cluster_key: Cluster key in input record
33
+ * user_key: Request user key in input record
34
+ * indices_key: Indices key in input record
35
+ * r_indices_key: Resolved indices key in input record
36
+ * timestamp_key: Timestamp key in input record
37
+ * privilege_key: Request privilege key in input record
38
+ * rest_request_path_key: Rest request path key in input record
39
+ * request_body_key: Request body key in input record
40
+
41
+ parameters for output metric:
42
+ * timestamp_format: Timestamp format (iso, epochmillis, epochmillis_str)
43
+ * prefix: Attribute prefix for output metric
44
+ * aggregate_index: Aggregate index (remove ilm suffix, wildcard suffix)
45
+
46
+ More details from the
47
+ [elastic_audit_log_metric output plugin code](lib/fluent/plugin/out_elastic_audit_log_metric.rb#L49)
16
48
 
17
- Add following line to your Gemfile:
49
+ ## Installation
18
50
 
19
- ```ruby
20
- gem "fluent-plugin-elastic-log"
21
- ```
22
51
 
23
- And then execute:
52
+ Manual install, by executing:
24
53
 
25
- ```
26
- $ bundle
27
- ```
54
+ $ gem install fluent-plugin-elastic-log
28
55
 
29
- ## Configuration
56
+ Add to Gemfile with:
30
57
 
31
- You can generate configuration template:
58
+ $ bundle add fluent-plugin-elastic-log
32
59
 
33
- ```
34
- $ fluent-plugin-config-format filter elastic-log
35
- ```
60
+ ## Compatibility
36
61
 
37
- You can copy and paste generated documents here.
62
+ plugin in 1.x.x will work with:
63
+ - ruby >= 2.4.10
64
+ - td-agent >= 3.8.1-0
38
65
 
39
66
  ## Copyright
40
67
 
@@ -5,7 +5,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = 'fluent-plugin-elastic-log'
8
- spec.version = '0.4.1'
8
+ spec.version = '0.5.0'
9
9
  spec.authors = ['Thomas Tych']
10
10
  spec.email = ['thomas.tych@gmail.com']
11
11
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'granted_privileges_metric'
4
+ require_relative 'failed_login_metric'
4
5
 
5
6
  module Fluent
6
7
  module Plugin
@@ -14,7 +15,7 @@ module Fluent
14
15
  end
15
16
 
16
17
  def process(_tag, log_es)
17
- metric_es = MultiEventStream.new
18
+ metric_es = []
18
19
 
19
20
  log_es.each do |time, record|
20
21
  next unless record
@@ -22,7 +23,7 @@ module Fluent
22
23
  next unless conf.categories.include? category
23
24
 
24
25
  new_records = send("generate_#{category.downcase}_metrics_for", record)
25
- new_records&.each { |new_record| metric_es.add(time, new_record) }
26
+ new_records&.each { |new_record| metric_es << [time, new_record] }
26
27
  end
27
28
  metric_es
28
29
  end
@@ -48,6 +49,22 @@ module Fluent
48
49
  ).generate_metrics
49
50
  end
50
51
  # rubocop:enable Metrics/AbcSize
52
+
53
+ # rubocop:disable Metrics/AbcSize
54
+ def generate_failed_login_metrics_for(record)
55
+ FailedLoginMetric.new(
56
+ record: {
57
+ timestamp: record[conf.timestamp_key],
58
+ user: record[conf.user_key],
59
+ cluster: record[conf.cluster_key],
60
+ layer: record[conf.layer_key],
61
+ request_path: record[conf.rest_request_path_key],
62
+ request_body: record[conf.request_body_key]
63
+ },
64
+ conf: conf
65
+ ).generate_metrics
66
+ end
67
+ # rubocop:enable Metrics/AbcSize
51
68
  end
52
69
  end
53
70
  end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+ require 'time'
5
+ require 'json'
6
+
7
+ module Fluent
8
+ module Plugin
9
+ module ElasticLog
10
+ # record to metric converter
11
+ # for FAILED_LOGIN
12
+ class FailedLoginMetric
13
+ attr_reader :record, :conf
14
+
15
+ ELASTIC_URL_PATTERN = %r{(?:/(?<target>[^/]*))?/(?<action>_\w+)}.freeze
16
+ QUERY_TYPE_MAP = {
17
+ '_msearch' => 'msearch',
18
+ '_bulk' => 'bulk',
19
+ '_doc' => 'write',
20
+ '_create' => 'write',
21
+ '_search' => 'search'
22
+ }.freeze
23
+
24
+ INDEX_PATTERN = /-?\*$/.freeze
25
+ ILM_PATTERN = /-\d{6}$/.freeze
26
+
27
+ def initialize(record:, conf:)
28
+ @record = record
29
+ @conf = conf
30
+ end
31
+
32
+ def timestamp
33
+ timestamp = Time.parse(record[:timestamp])
34
+
35
+ return (timestamp.utc.to_f * 1000).to_i if conf.timestamp_format == :epochmillis
36
+ return timestamp.utc.strftime('%s%3N') if conf.timestamp_format == :epochmillis_str
37
+
38
+ timestamp.utc.iso8601(3)
39
+ rescue StandardError
40
+ nil
41
+ end
42
+
43
+ def query_details
44
+ if (match = ELASTIC_URL_PATTERN.match(record[:request_path]))
45
+ return [QUERY_TYPE_MAP.fetch(match[:action], 'other'),
46
+ match[:target]]
47
+ end
48
+ ['other', nil]
49
+ end
50
+
51
+ def base
52
+ {
53
+ 'timestamp' => timestamp,
54
+ 'metric_name' => 'failed_login_count',
55
+ 'metric_value' => 1,
56
+ "#{conf.prefix}user" => record[:user],
57
+ "#{conf.prefix}cluster" => record[:cluster]
58
+ }
59
+ end
60
+
61
+ def bulk_indices
62
+ req_body = record.fetch(:request_body, {})
63
+ return [] if req_body.empty?
64
+
65
+ req_body.each_line.each_slice(2).with_object(Set.new) do |(cmd_line, _data_line), acc|
66
+ cmd = JSON.parse(cmd_line)
67
+ acc << aggregate_index(cmd[cmd.keys.first]['_index'])
68
+ end
69
+ end
70
+
71
+ def msearch_indices
72
+ req_body = record.fetch(:request_body, {})
73
+ return [] if req_body.empty?
74
+
75
+ req_body.each_line.each_slice(2).with_object(Set.new) do |(cmd_line, _data_line), acc|
76
+ cmd = JSON.parse(cmd_line)
77
+ acc << aggregate_index(cmd['index'])
78
+ end
79
+ end
80
+
81
+ def aggregate_index(index)
82
+ return unless index
83
+ return index unless conf.aggregate_index
84
+
85
+ index.sub(INDEX_PATTERN, '').sub(ILM_PATTERN, '')
86
+ end
87
+
88
+ def generate_metrics
89
+ query_action, query_index = query_details
90
+ indices = case query_action
91
+ when 'bulk' then bulk_indices
92
+ when 'msearch' then msearch_indices
93
+ else []
94
+ end
95
+ indices << query_index if query_index || indices.empty?
96
+
97
+ indices.inject([]) do |metrics, index|
98
+ metrics << base.merge("#{conf.prefix}index" => index,
99
+ "#{conf.prefix}query_type" => query_action)
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -29,7 +29,8 @@ module Fluent
29
29
  'indices:monitor/' => 'monitor'
30
30
  }.freeze
31
31
 
32
- ILM_PATTERN = /^(.*)-\d{6}$/.freeze
32
+ INDEX_PATTERN = /-?\*$/.freeze
33
+ ILM_PATTERN = /-\d{6}$/.freeze
33
34
 
34
35
  attr_reader :record, :conf
35
36
 
@@ -69,19 +70,25 @@ module Fluent
69
70
 
70
71
  def indices
71
72
  indices = record[:r_indices] || record[:indices] || [nil]
72
- if conf.aggregate_ilm
73
+ if conf.aggregate_index
73
74
  indices = indices.inject(Set.new) do |acc, index|
74
- aggregated_format = index && index[ILM_PATTERN, 1]
75
- acc << (aggregated_format || index)
76
- end.to_a
75
+ acc << aggregate_index(index)
76
+ end
77
77
  end
78
78
  indices
79
79
  end
80
80
 
81
+ def aggregate_index(index)
82
+ return unless index
83
+ return index unless conf.aggregate_index
84
+
85
+ index.sub(INDEX_PATTERN, '').sub(ILM_PATTERN, '')
86
+ end
87
+
81
88
  def generate_metrics
82
89
  metrics = []
83
- indices.each do |indice|
84
- metrics << base.merge("#{conf.prefix}technical_name" => indice)
90
+ indices.each do |index|
91
+ metrics << base.merge("#{conf.prefix}index" => index)
85
92
  end
86
93
  metrics
87
94
  end
@@ -31,7 +31,7 @@ module Fluent
31
31
 
32
32
  helpers :event_emitter
33
33
 
34
- ALLOWED_CATEGORIES = %w[GRANTED_PRIVILEGES].freeze
34
+ ALLOWED_CATEGORIES = %w[GRANTED_PRIVILEGES FAILED_LOGIN].freeze
35
35
  # FAILED_LOGIN AUTHENTICATED MISSING_PRIVILEGES SSL_EXCEPTION
36
36
  # OPENDISTRO_SECURITY_INDEX_ATTEMPT BAD_HEADERS
37
37
 
@@ -42,6 +42,8 @@ module Fluent
42
42
  DEFAULT_USER_KEY = 'audit_request_effective_user'
43
43
  DEFAULT_INDICES_KEY = 'audit_trace_indices'
44
44
  DEFAULT_R_INDICES_KEY = 'audit_trace_resolved_indices'
45
+ DEFAULT_REST_REQUEST_PATH = 'audit_rest_request_path'
46
+ DEFAULT_REQUEST_BODY = 'audit_request_body'
45
47
  DEFAULT_TIMESTAMP_KEY = '@timestamp'
46
48
  DEFAULT_PRIVILEGE_KEY = 'audit_request_privilege'
47
49
  DEFAULT_PREFIX = ''
@@ -69,13 +71,19 @@ module Fluent
69
71
  config_param :timestamp_key, :string, default: DEFAULT_TIMESTAMP_KEY
70
72
  desc 'Request privilege key'
71
73
  config_param :privilege_key, :string, default: DEFAULT_PRIVILEGE_KEY
74
+ desc 'Rest request path key'
75
+ config_param :rest_request_path_key, :string, default: DEFAULT_REST_REQUEST_PATH
76
+ desc 'Request body key'
77
+ config_param :request_body_key, :string, default: DEFAULT_REQUEST_BODY
72
78
 
73
79
  desc 'Timestamp format'
74
80
  config_param :timestamp_format, :enum, list: %i[iso epochmillis epochmillis_str], default: :iso
75
81
  desc 'Attribute prefix'
76
82
  config_param :prefix, :string, default: DEFAULT_PREFIX
77
- desc 'Aggregate ILM'
78
- config_param :aggregate_ilm, :bool, default: true
83
+ desc 'Aggregate index'
84
+ config_param :aggregate_index, :bool, default: true
85
+ desc 'Events block size'
86
+ config_param :event_stream_size, :integer, default: 1000
79
87
 
80
88
  attr_reader :metric_processor
81
89
 
@@ -109,7 +117,11 @@ module Fluent
109
117
 
110
118
  def process(_tag, es)
111
119
  metrics = metric_processor.process(tag, es) || []
112
- router.emit_stream(tag, metrics) if metrics
120
+ metrics.each_slice(event_stream_size) do |metrics_slice|
121
+ metrics_es = MultiEventStream.new
122
+ metrics_slice.each { |time, record| metrics_es.add(time, record) }
123
+ router.emit_stream(tag, metrics_es)
124
+ end
113
125
  end
114
126
  end
115
127
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-elastic-log
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas Tych
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-06-15 00:00:00.000000000 Z
11
+ date: 2023-06-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bump
@@ -163,6 +163,7 @@ files:
163
163
  - Rakefile
164
164
  - fluent-plugin-elastic-log.gemspec
165
165
  - lib/fluent/plugin/elastic_log/audit_log_to_metric_processor.rb
166
+ - lib/fluent/plugin/elastic_log/failed_login_metric.rb
166
167
  - lib/fluent/plugin/elastic_log/granted_privileges_metric.rb
167
168
  - lib/fluent/plugin/out_elastic_audit_log_metric.rb
168
169
  homepage: https://gitlab.com/ttych/fluent-plugin-elastic-log