hoss-agent 1.0.1
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 +7 -0
- data/.github/ISSUE_TEMPLATE/Bug_report.md +40 -0
- data/.github/ISSUE_TEMPLATE/Feature_request.md +17 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +60 -0
- data/.gitignore +27 -0
- data/.rspec +2 -0
- data/Dockerfile +43 -0
- data/Gemfile +105 -0
- data/LICENSE +201 -0
- data/hoss-agent.gemspec +42 -0
- data/lib/hoss/agent.rb +231 -0
- data/lib/hoss/central_config/cache_control.rb +51 -0
- data/lib/hoss/central_config.rb +184 -0
- data/lib/hoss/child_durations.rb +64 -0
- data/lib/hoss/config/bytes.rb +42 -0
- data/lib/hoss/config/duration.rb +40 -0
- data/lib/hoss/config/options.rb +154 -0
- data/lib/hoss/config/regexp_list.rb +30 -0
- data/lib/hoss/config/wildcard_pattern_list.rb +54 -0
- data/lib/hoss/config.rb +304 -0
- data/lib/hoss/context/request/socket.rb +36 -0
- data/lib/hoss/context/request/url.rb +59 -0
- data/lib/hoss/context/request.rb +28 -0
- data/lib/hoss/context/response.rb +47 -0
- data/lib/hoss/context/user.rb +59 -0
- data/lib/hoss/context.rb +64 -0
- data/lib/hoss/context_builder.rb +112 -0
- data/lib/hoss/deprecations.rb +39 -0
- data/lib/hoss/error/exception.rb +70 -0
- data/lib/hoss/error/log.rb +41 -0
- data/lib/hoss/error.rb +49 -0
- data/lib/hoss/error_builder.rb +90 -0
- data/lib/hoss/event.rb +131 -0
- data/lib/hoss/instrumenter.rb +107 -0
- data/lib/hoss/internal_error.rb +23 -0
- data/lib/hoss/logging.rb +70 -0
- data/lib/hoss/metadata/process_info.rb +35 -0
- data/lib/hoss/metadata/service_info.rb +76 -0
- data/lib/hoss/metadata/system_info/container_info.rb +136 -0
- data/lib/hoss/metadata/system_info.rb +47 -0
- data/lib/hoss/metadata.rb +36 -0
- data/lib/hoss/naively_hashable.rb +38 -0
- data/lib/hoss/rails.rb +68 -0
- data/lib/hoss/railtie.rb +42 -0
- data/lib/hoss/report.rb +9 -0
- data/lib/hoss/sinatra.rb +53 -0
- data/lib/hoss/spies/faraday.rb +102 -0
- data/lib/hoss/spies/http.rb +81 -0
- data/lib/hoss/spies/net_http.rb +97 -0
- data/lib/hoss/spies.rb +104 -0
- data/lib/hoss/stacktrace/frame.rb +66 -0
- data/lib/hoss/stacktrace.rb +33 -0
- data/lib/hoss/stacktrace_builder.rb +124 -0
- data/lib/hoss/transport/base.rb +191 -0
- data/lib/hoss/transport/connection/http.rb +139 -0
- data/lib/hoss/transport/connection/proxy_pipe.rb +94 -0
- data/lib/hoss/transport/connection.rb +55 -0
- data/lib/hoss/transport/filters/hash_sanitizer.rb +77 -0
- data/lib/hoss/transport/filters/secrets_filter.rb +48 -0
- data/lib/hoss/transport/filters.rb +60 -0
- data/lib/hoss/transport/headers.rb +74 -0
- data/lib/hoss/transport/serializers/context_serializer.rb +112 -0
- data/lib/hoss/transport/serializers/error_serializer.rb +92 -0
- data/lib/hoss/transport/serializers/event_serializer.rb +73 -0
- data/lib/hoss/transport/serializers/metadata_serializer.rb +92 -0
- data/lib/hoss/transport/serializers/report_serializer.rb +33 -0
- data/lib/hoss/transport/serializers.rb +113 -0
- data/lib/hoss/transport/user_agent.rb +48 -0
- data/lib/hoss/transport/worker.rb +319 -0
- data/lib/hoss/util/inflector.rb +110 -0
- data/lib/hoss/util/lru_cache.rb +65 -0
- data/lib/hoss/util/throttle.rb +52 -0
- data/lib/hoss/util.rb +54 -0
- data/lib/hoss/version.rb +22 -0
- data/lib/hoss-agent.rb +210 -0
- data/lib/hoss.rb +21 -0
- metadata +147 -0
@@ -0,0 +1,191 @@
|
|
1
|
+
# Licensed to Elasticsearch B.V. under one or more contributor
|
2
|
+
# license agreements. See the NOTICE file distributed with
|
3
|
+
# this work for additional information regarding copyright
|
4
|
+
# ownership. Elasticsearch B.V. licenses this file to you under
|
5
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
6
|
+
# not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
12
|
+
# software distributed under the License is distributed on an
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
14
|
+
# KIND, either express or implied. See the License for the
|
15
|
+
# specific language governing permissions and limitations
|
16
|
+
# under the License.
|
17
|
+
|
18
|
+
# frozen_string_literal: true
|
19
|
+
|
20
|
+
require 'hoss/metadata'
|
21
|
+
require 'hoss/transport/user_agent'
|
22
|
+
require 'hoss/transport/headers'
|
23
|
+
require 'hoss/transport/connection'
|
24
|
+
require 'hoss/transport/worker'
|
25
|
+
require 'hoss/transport/serializers'
|
26
|
+
require 'hoss/transport/filters'
|
27
|
+
require 'hoss/transport/connection/http'
|
28
|
+
|
29
|
+
require 'hoss/util/throttle'
|
30
|
+
|
31
|
+
module Hoss
|
32
|
+
module Transport
|
33
|
+
# @api private
|
34
|
+
class Base
|
35
|
+
include Logging
|
36
|
+
|
37
|
+
WATCHER_EXECUTION_INTERVAL = 5
|
38
|
+
WATCHER_TIMEOUT_INTERVAL = 4
|
39
|
+
WORKER_JOIN_TIMEOUT = 5
|
40
|
+
|
41
|
+
def initialize(config)
|
42
|
+
@config = config
|
43
|
+
@queue = SizedQueue.new(config.max_queue_size)
|
44
|
+
|
45
|
+
@serializers = Serializers.new(config)
|
46
|
+
@filters = Filters.new(config)
|
47
|
+
|
48
|
+
@stopped = Concurrent::AtomicBoolean.new
|
49
|
+
@workers = Array.new(config.pool_size)
|
50
|
+
|
51
|
+
@worker_mutex = Mutex.new
|
52
|
+
end
|
53
|
+
|
54
|
+
attr_reader :config, :queue, :filters, :workers, :watcher, :stopped
|
55
|
+
|
56
|
+
def start
|
57
|
+
debug '%s: Starting Transport', pid_str
|
58
|
+
# Set @stopped to false first, in case transport is restarted;
|
59
|
+
# ensure_worker_count requires @stopped to be false
|
60
|
+
# ~estolfo
|
61
|
+
@stopped.make_false unless @stopped.false?
|
62
|
+
|
63
|
+
ensure_worker_count
|
64
|
+
create_watcher
|
65
|
+
end
|
66
|
+
|
67
|
+
def stop
|
68
|
+
debug '%s: Stopping Transport', pid_str
|
69
|
+
|
70
|
+
@stopped.make_true
|
71
|
+
|
72
|
+
stop_watcher
|
73
|
+
stop_workers
|
74
|
+
end
|
75
|
+
|
76
|
+
def submit(resource)
|
77
|
+
if @stopped.true?
|
78
|
+
warn '%s: Transport stopping, no new events accepted', pid_str
|
79
|
+
debug 'Dropping: %s', resource.inspect
|
80
|
+
return false
|
81
|
+
end
|
82
|
+
|
83
|
+
queue.push(resource, true)
|
84
|
+
|
85
|
+
true
|
86
|
+
rescue ThreadError
|
87
|
+
throttled_queue_full_warning
|
88
|
+
nil
|
89
|
+
rescue Exception => e
|
90
|
+
error '%s: Failed adding to the transport queue: %p', pid_str, e.inspect
|
91
|
+
nil
|
92
|
+
end
|
93
|
+
|
94
|
+
def add_filter(key, callback)
|
95
|
+
@filters.add(key, callback)
|
96
|
+
end
|
97
|
+
|
98
|
+
def handle_forking!
|
99
|
+
# We can't just stop and start again because the StopMessage
|
100
|
+
# will then be the first message processed when the transport is
|
101
|
+
# restarted.
|
102
|
+
stop_watcher
|
103
|
+
ensure_worker_count
|
104
|
+
create_watcher
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def pid_str
|
110
|
+
format('[PID:%s]', Process.pid)
|
111
|
+
end
|
112
|
+
|
113
|
+
def create_watcher
|
114
|
+
@watcher = Concurrent::TimerTask.execute(
|
115
|
+
execution_interval: WATCHER_EXECUTION_INTERVAL,
|
116
|
+
timeout_interval: WATCHER_TIMEOUT_INTERVAL
|
117
|
+
) { ensure_worker_count }
|
118
|
+
end
|
119
|
+
|
120
|
+
def ensure_worker_count
|
121
|
+
@worker_mutex.synchronize do
|
122
|
+
return if all_workers_alive?
|
123
|
+
return if stopped.true?
|
124
|
+
|
125
|
+
@workers.map! do |thread|
|
126
|
+
next thread if thread&.alive?
|
127
|
+
|
128
|
+
boot_worker
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def all_workers_alive?
|
134
|
+
!!workers.all? { |t| t&.alive? }
|
135
|
+
end
|
136
|
+
|
137
|
+
def boot_worker
|
138
|
+
debug '%s: Booting worker...', pid_str
|
139
|
+
|
140
|
+
Thread.new do
|
141
|
+
Worker.new(
|
142
|
+
config, queue,
|
143
|
+
serializers: @serializers,
|
144
|
+
filters: @filters
|
145
|
+
).work_forever
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def stop_workers
|
150
|
+
debug '%s: Stopping workers', pid_str
|
151
|
+
|
152
|
+
send_stop_messages
|
153
|
+
|
154
|
+
@worker_mutex.synchronize do
|
155
|
+
workers.each do |thread|
|
156
|
+
next if thread.nil?
|
157
|
+
next if thread.join(WORKER_JOIN_TIMEOUT)
|
158
|
+
|
159
|
+
debug(
|
160
|
+
'%s: Worker did not stop in %ds, killing...',
|
161
|
+
pid_str, WORKER_JOIN_TIMEOUT
|
162
|
+
)
|
163
|
+
thread.kill
|
164
|
+
end
|
165
|
+
|
166
|
+
# Maintain the @worker array size for when transport is restarted
|
167
|
+
@workers.fill(nil)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def send_stop_messages
|
172
|
+
config.pool_size.times { queue.push(Worker::StopMessage.new, true) }
|
173
|
+
rescue ThreadError
|
174
|
+
warn 'Cannot push stop messages to worker queue as it is full'
|
175
|
+
end
|
176
|
+
|
177
|
+
def stop_watcher
|
178
|
+
watcher&.shutdown
|
179
|
+
end
|
180
|
+
|
181
|
+
def throttled_queue_full_warning
|
182
|
+
(@queue_full_log ||= Util::Throttle.new(5) do
|
183
|
+
warn(
|
184
|
+
'%s: Queue is full (%i items), skipping…',
|
185
|
+
pid_str, config.max_queue_size
|
186
|
+
)
|
187
|
+
end).call
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
# Licensed to Elasticsearch B.V. under one or more contributor
|
2
|
+
# license agreements. See the NOTICE file distributed with
|
3
|
+
# this work for additional information regarding copyright
|
4
|
+
# ownership. Elasticsearch B.V. licenses this file to you under
|
5
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
6
|
+
# not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
12
|
+
# software distributed under the License is distributed on an
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
14
|
+
# KIND, either express or implied. See the License for the
|
15
|
+
# specific language governing permissions and limitations
|
16
|
+
# under the License.
|
17
|
+
|
18
|
+
# frozen_string_literal: true
|
19
|
+
|
20
|
+
require 'hoss/transport/connection/proxy_pipe'
|
21
|
+
|
22
|
+
module Hoss
|
23
|
+
module Transport
|
24
|
+
class Connection
|
25
|
+
# @api private
|
26
|
+
class Http
|
27
|
+
include Logging
|
28
|
+
|
29
|
+
def initialize(config, headers: nil)
|
30
|
+
@config = config
|
31
|
+
@headers = headers || Headers.new(config)
|
32
|
+
@client = build_client
|
33
|
+
@closed = Concurrent::AtomicBoolean.new(true)
|
34
|
+
end
|
35
|
+
|
36
|
+
def open(url)
|
37
|
+
@closed.make_false
|
38
|
+
@rd, @wr = ProxyPipe.pipe(compress: @config.http_compression?)
|
39
|
+
@request = open_request_in_thread(url)
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.open(config, url)
|
43
|
+
new(config).tap do |http|
|
44
|
+
http.open(url)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def post(url, body: nil, headers: nil)
|
49
|
+
request(:post, url, body: body, headers: headers)
|
50
|
+
end
|
51
|
+
|
52
|
+
def get(url, headers: nil)
|
53
|
+
request(:get, url, headers: headers)
|
54
|
+
end
|
55
|
+
|
56
|
+
def request(method, url, body: nil, headers: nil)
|
57
|
+
@client.send(
|
58
|
+
method,
|
59
|
+
url,
|
60
|
+
body: body,
|
61
|
+
headers: (headers ? @headers.merge(headers) : @headers).to_h,
|
62
|
+
ssl_context: @config.ssl_context
|
63
|
+
).flush
|
64
|
+
end
|
65
|
+
|
66
|
+
def write(str)
|
67
|
+
@wr.write(str)
|
68
|
+
@wr.bytes_sent
|
69
|
+
end
|
70
|
+
|
71
|
+
def close(reason)
|
72
|
+
return if closed?
|
73
|
+
|
74
|
+
debug '%s: Closing request with reason %s', thread_str, reason
|
75
|
+
@closed.make_true
|
76
|
+
|
77
|
+
@wr&.close(reason)
|
78
|
+
return if @request.nil? || @request&.join(5)
|
79
|
+
|
80
|
+
error(
|
81
|
+
'%s: APM Server not responding in time, terminating request',
|
82
|
+
thread_str
|
83
|
+
)
|
84
|
+
@request.kill
|
85
|
+
end
|
86
|
+
|
87
|
+
def closed?
|
88
|
+
@closed.true?
|
89
|
+
end
|
90
|
+
|
91
|
+
def inspect
|
92
|
+
format(
|
93
|
+
'%s closed: %s>',
|
94
|
+
super.split.first,
|
95
|
+
closed?
|
96
|
+
)
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def thread_str
|
102
|
+
format('[THREAD:%s]', Thread.current.object_id)
|
103
|
+
end
|
104
|
+
|
105
|
+
def open_request_in_thread(url)
|
106
|
+
debug '%s: Opening new request', thread_str
|
107
|
+
Thread.new do
|
108
|
+
begin
|
109
|
+
resp = post(url, body: @rd, headers: @headers.chunked.to_h)
|
110
|
+
|
111
|
+
if resp&.status == 202
|
112
|
+
debug 'APM Server responded with status 202'
|
113
|
+
elsif resp
|
114
|
+
error "APM Server responded with an error:\n%p", resp.body.to_s
|
115
|
+
end
|
116
|
+
rescue Exception => e
|
117
|
+
error(
|
118
|
+
"Couldn't establish connection to APM Server:\n%p", e.inspect
|
119
|
+
)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def build_client
|
125
|
+
client = HTTP.headers(@headers)
|
126
|
+
return client unless @config.proxy_address && @config.proxy_port
|
127
|
+
|
128
|
+
client.via(
|
129
|
+
@config.proxy_address,
|
130
|
+
@config.proxy_port,
|
131
|
+
@config.proxy_username,
|
132
|
+
@config.proxy_password,
|
133
|
+
@config.proxy_headers
|
134
|
+
)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# Licensed to Elasticsearch B.V. under one or more contributor
|
2
|
+
# license agreements. See the NOTICE file distributed with
|
3
|
+
# this work for additional information regarding copyright
|
4
|
+
# ownership. Elasticsearch B.V. licenses this file to you under
|
5
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
6
|
+
# not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
12
|
+
# software distributed under the License is distributed on an
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
14
|
+
# KIND, either express or implied. See the License for the
|
15
|
+
# specific language governing permissions and limitations
|
16
|
+
# under the License.
|
17
|
+
|
18
|
+
# frozen_string_literal: true
|
19
|
+
|
20
|
+
module Hoss
|
21
|
+
module Transport
|
22
|
+
class Connection
|
23
|
+
# @api private
|
24
|
+
class ProxyPipe
|
25
|
+
def initialize(enc = nil, compress: true)
|
26
|
+
rd, wr = IO.pipe(enc)
|
27
|
+
|
28
|
+
@read = rd
|
29
|
+
@write = Write.new(wr, compress: compress)
|
30
|
+
|
31
|
+
# Http.rb<4 calls rewind on the request bodies, but IO::Pipe raises
|
32
|
+
# ~mikker
|
33
|
+
return if HTTP::VERSION.to_i >= 4
|
34
|
+
def rd.rewind; end
|
35
|
+
end
|
36
|
+
|
37
|
+
attr_reader :read, :write
|
38
|
+
|
39
|
+
# @api private
|
40
|
+
class Write
|
41
|
+
include Logging
|
42
|
+
|
43
|
+
def initialize(io, compress: true)
|
44
|
+
@io = io
|
45
|
+
@compress = compress
|
46
|
+
@bytes_sent = Concurrent::AtomicFixnum.new(0)
|
47
|
+
@config = Hoss.agent&.config # this is silly, fix Logging
|
48
|
+
|
49
|
+
return unless compress
|
50
|
+
enable_compression!
|
51
|
+
ObjectSpace.define_finalizer(self, self.class.finalize(@io))
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.finalize(io)
|
55
|
+
proc { io.close }
|
56
|
+
end
|
57
|
+
|
58
|
+
attr_reader :io
|
59
|
+
|
60
|
+
def enable_compression!
|
61
|
+
io.binmode
|
62
|
+
@io = Zlib::GzipWriter.new(io)
|
63
|
+
end
|
64
|
+
|
65
|
+
def close(reason = nil)
|
66
|
+
debug("Closing writer with reason #{reason}")
|
67
|
+
io.close
|
68
|
+
end
|
69
|
+
|
70
|
+
def closed?
|
71
|
+
io.closed?
|
72
|
+
end
|
73
|
+
|
74
|
+
def write(str)
|
75
|
+
io.puts(str).tap do
|
76
|
+
@bytes_sent.update do |curr|
|
77
|
+
@compress ? io.tell : curr + str.bytesize
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def bytes_sent
|
83
|
+
@bytes_sent.value
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.pipe(**args)
|
88
|
+
pipe = new(**args)
|
89
|
+
[pipe.read, pipe.write]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# Licensed to Elasticsearch B.V. under one or more contributor
|
2
|
+
# license agreements. See the NOTICE file distributed with
|
3
|
+
# this work for additional information regarding copyright
|
4
|
+
# ownership. Elasticsearch B.V. licenses this file to you under
|
5
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
6
|
+
# not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
12
|
+
# software distributed under the License is distributed on an
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
14
|
+
# KIND, either express or implied. See the License for the
|
15
|
+
# specific language governing permissions and limitations
|
16
|
+
# under the License.
|
17
|
+
|
18
|
+
# frozen_string_literal: true
|
19
|
+
|
20
|
+
module Hoss
|
21
|
+
module Transport
|
22
|
+
# @api private
|
23
|
+
class Connection
|
24
|
+
include Logging
|
25
|
+
|
26
|
+
def initialize(config)
|
27
|
+
@config = config
|
28
|
+
@metadata = JSON.fast_generate(
|
29
|
+
Serializers::MetadataSerializer.new(config).build(
|
30
|
+
Metadata.new(config)
|
31
|
+
)
|
32
|
+
)
|
33
|
+
@url = @config.ingress_host
|
34
|
+
@mutex = Mutex.new
|
35
|
+
end
|
36
|
+
|
37
|
+
attr_reader :http
|
38
|
+
def write(str)
|
39
|
+
debug "Sending report"
|
40
|
+
uri = URI(@url)
|
41
|
+
req = Net::HTTP::Post.new(
|
42
|
+
uri,
|
43
|
+
'Authorization' => "Bearer #{@config.api_key}", #"Basic " + Base64.encode64("#{@config.api_key}:"),
|
44
|
+
'Content-Type' => 'application/json',
|
45
|
+
'HOSS-SKIP-INSTRUMENTATION' => 'true',
|
46
|
+
)
|
47
|
+
req.body = str
|
48
|
+
res = Net::HTTP.start(uri.hostname, uri.port, :use_ssl => uri.scheme == 'https') do |http|
|
49
|
+
http.request(req)
|
50
|
+
end
|
51
|
+
raise unless res.code == "200"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# Licensed to Elasticsearch B.V. under one or more contributor
|
2
|
+
# license agreements. See the NOTICE file distributed with
|
3
|
+
# this work for additional information regarding copyright
|
4
|
+
# ownership. Elasticsearch B.V. licenses this file to you under
|
5
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
6
|
+
# not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
12
|
+
# software distributed under the License is distributed on an
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
14
|
+
# KIND, either express or implied. See the License for the
|
15
|
+
# specific language governing permissions and limitations
|
16
|
+
# under the License.
|
17
|
+
|
18
|
+
# frozen_string_literal: true
|
19
|
+
|
20
|
+
module Hoss
|
21
|
+
module Transport
|
22
|
+
module Filters
|
23
|
+
class HashSanitizer
|
24
|
+
FILTERED = '[FILTERED]'
|
25
|
+
|
26
|
+
KEY_FILTERS = [
|
27
|
+
/passw(or)?d/i,
|
28
|
+
/auth/i,
|
29
|
+
/^pw$/,
|
30
|
+
/secret/i,
|
31
|
+
/token/i,
|
32
|
+
/api[-._]?key/i,
|
33
|
+
/session[-._]?id/i,
|
34
|
+
/(set[-_])?cookie/i
|
35
|
+
].freeze
|
36
|
+
|
37
|
+
VALUE_FILTERS = [
|
38
|
+
# (probably) credit card number
|
39
|
+
/^\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}$/
|
40
|
+
].freeze
|
41
|
+
|
42
|
+
attr_accessor :key_filters
|
43
|
+
|
44
|
+
def initialize
|
45
|
+
@key_filters = KEY_FILTERS
|
46
|
+
end
|
47
|
+
|
48
|
+
def strip_from!(obj, key_filters = KEY_FILTERS)
|
49
|
+
return unless obj&.is_a?(Hash)
|
50
|
+
|
51
|
+
obj.each do |k, v|
|
52
|
+
if filter_key?(k)
|
53
|
+
next obj[k] = FILTERED
|
54
|
+
end
|
55
|
+
|
56
|
+
case v
|
57
|
+
when Hash
|
58
|
+
strip_from!(v)
|
59
|
+
when String
|
60
|
+
if filter_value?(v)
|
61
|
+
obj[k] = FILTERED
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def filter_key?(key)
|
68
|
+
@key_filters.any? { |regex| regex.match(key) }
|
69
|
+
end
|
70
|
+
|
71
|
+
def filter_value?(value)
|
72
|
+
VALUE_FILTERS.any? { |regex| regex.match(value) }
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# Licensed to Elasticsearch B.V. under one or more contributor
|
2
|
+
# license agreements. See the NOTICE file distributed with
|
3
|
+
# this work for additional information regarding copyright
|
4
|
+
# ownership. Elasticsearch B.V. licenses this file to you under
|
5
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
6
|
+
# not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
12
|
+
# software distributed under the License is distributed on an
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
14
|
+
# KIND, either express or implied. See the License for the
|
15
|
+
# specific language governing permissions and limitations
|
16
|
+
# under the License.
|
17
|
+
|
18
|
+
# frozen_string_literal: true
|
19
|
+
|
20
|
+
require 'hoss/transport/filters/hash_sanitizer'
|
21
|
+
|
22
|
+
module Hoss
|
23
|
+
module Transport
|
24
|
+
module Filters
|
25
|
+
# @api private
|
26
|
+
class SecretsFilter
|
27
|
+
def initialize(config)
|
28
|
+
@config = config
|
29
|
+
@sanitizer = HashSanitizer.new
|
30
|
+
@sanitizer.key_filters += config.custom_key_filters +
|
31
|
+
config.sanitize_field_names
|
32
|
+
end
|
33
|
+
|
34
|
+
def call(payload)
|
35
|
+
@sanitizer.strip_from! payload.dig(:transaction, :context, :request, :headers)
|
36
|
+
@sanitizer.strip_from! payload.dig(:transaction, :context, :request, :env)
|
37
|
+
@sanitizer.strip_from! payload.dig(:transaction, :context, :request, :cookies)
|
38
|
+
@sanitizer.strip_from! payload.dig(:transaction, :context, :response, :headers)
|
39
|
+
@sanitizer.strip_from! payload.dig(:error, :context, :request, :headers)
|
40
|
+
@sanitizer.strip_from! payload.dig(:error, :context, :response, :headers)
|
41
|
+
@sanitizer.strip_from! payload.dig(:transaction, :context, :request, :body)
|
42
|
+
|
43
|
+
payload
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# Licensed to Elasticsearch B.V. under one or more contributor
|
2
|
+
# license agreements. See the NOTICE file distributed with
|
3
|
+
# this work for additional information regarding copyright
|
4
|
+
# ownership. Elasticsearch B.V. licenses this file to you under
|
5
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
6
|
+
# not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
12
|
+
# software distributed under the License is distributed on an
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
14
|
+
# KIND, either express or implied. See the License for the
|
15
|
+
# specific language governing permissions and limitations
|
16
|
+
# under the License.
|
17
|
+
|
18
|
+
# frozen_string_literal: true
|
19
|
+
|
20
|
+
require 'hoss/transport/filters/secrets_filter'
|
21
|
+
|
22
|
+
module Hoss
|
23
|
+
module Transport
|
24
|
+
# @api private
|
25
|
+
module Filters
|
26
|
+
SKIP = :skip
|
27
|
+
|
28
|
+
def self.new(config)
|
29
|
+
Container.new(config)
|
30
|
+
end
|
31
|
+
|
32
|
+
# @api private
|
33
|
+
class Container
|
34
|
+
def initialize(config)
|
35
|
+
@filters = { secrets: SecretsFilter.new(config) }
|
36
|
+
end
|
37
|
+
|
38
|
+
def add(key, filter)
|
39
|
+
@filters = @filters.merge(key => filter)
|
40
|
+
end
|
41
|
+
|
42
|
+
def remove(key)
|
43
|
+
@filters.delete(key)
|
44
|
+
end
|
45
|
+
|
46
|
+
def apply!(payload)
|
47
|
+
@filters.reduce(payload) do |result, (_key, filter)|
|
48
|
+
result = filter.call(result)
|
49
|
+
break SKIP if result.nil?
|
50
|
+
result
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def length
|
55
|
+
@filters.length
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|