gelf.fitterpen 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gemtest +0 -0
- data/.travis.yml +17 -0
- data/CHANGELOG +54 -0
- data/CONTRIBUTING.md +1 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +74 -0
- data/LICENSE +20 -0
- data/README.md +63 -0
- data/Rakefile +60 -0
- data/VERSION +1 -0
- data/benchmarks/notifier.rb +39 -0
- data/gelf.gemspec +79 -0
- data/lib/gelf/logger.rb +64 -0
- data/lib/gelf/notifier.rb +287 -0
- data/lib/gelf/severity.rb +50 -0
- data/lib/gelf/transport/tcp.rb +79 -0
- data/lib/gelf/transport/tcp_tls.rb +129 -0
- data/lib/gelf/transport/udp.rb +41 -0
- data/lib/gelf.rb +16 -0
- data/test/helper.rb +11 -0
- data/test/test_logger.rb +247 -0
- data/test/test_notifier.rb +317 -0
- data/test/test_ruby_sender.rb +28 -0
- data/test/test_severity.rb +9 -0
- metadata +156 -0
@@ -0,0 +1,287 @@
|
|
1
|
+
require 'gelf/transport/udp'
|
2
|
+
require 'gelf/transport/tcp'
|
3
|
+
require 'gelf/transport/tcp_tls'
|
4
|
+
|
5
|
+
# replace JSON and #to_json with Yajl if available
|
6
|
+
begin
|
7
|
+
require 'yajl/json_gem'
|
8
|
+
rescue LoadError
|
9
|
+
end
|
10
|
+
|
11
|
+
module GELF
|
12
|
+
# Graylog2 notifier.
|
13
|
+
class Notifier
|
14
|
+
# Maximum number of GELF chunks as per GELF spec
|
15
|
+
MAX_CHUNKS = 128
|
16
|
+
MAX_CHUNK_SIZE_WAN = 1420
|
17
|
+
MAX_CHUNK_SIZE_LAN = 8154
|
18
|
+
|
19
|
+
attr_accessor :enabled, :collect_file_and_line, :rescue_network_errors
|
20
|
+
attr_reader :max_chunk_size, :level, :default_options, :level_mapping
|
21
|
+
|
22
|
+
# +host+ and +port+ are host/ip and port of graylog2-server.
|
23
|
+
# +max_size+ is passed to max_chunk_size=.
|
24
|
+
# +default_options+ is used in notify!
|
25
|
+
def initialize(host = 'localhost', port = 12201, max_size = 'WAN', default_options = {})
|
26
|
+
@enabled = true
|
27
|
+
@collect_file_and_line = true
|
28
|
+
@random = Random.new
|
29
|
+
|
30
|
+
self.level = GELF::DEBUG
|
31
|
+
self.max_chunk_size = max_size
|
32
|
+
self.rescue_network_errors = false
|
33
|
+
|
34
|
+
self.default_options = default_options.dup
|
35
|
+
self.default_options['version'] = SPEC_VERSION
|
36
|
+
self.default_options['host'] ||= Socket.gethostname
|
37
|
+
self.default_options['level'] ||= GELF::UNKNOWN
|
38
|
+
self.default_options['facility'] ||= 'gelf-rb'
|
39
|
+
self.default_options['protocol'] ||= GELF::Protocol::UDP
|
40
|
+
|
41
|
+
self.level_mapping = :logger
|
42
|
+
@sender = create_sender(host, port)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Get a list of receivers.
|
46
|
+
# notifier.addresses # => [['localhost', 12201], ['localhost', 12202]]
|
47
|
+
def addresses
|
48
|
+
@sender.addresses
|
49
|
+
end
|
50
|
+
|
51
|
+
# Set a list of receivers.
|
52
|
+
# notifier.addresses = [['localhost', 12201], ['localhost', 12202]]
|
53
|
+
def addresses=(addrs)
|
54
|
+
@sender.addresses = addrs
|
55
|
+
end
|
56
|
+
|
57
|
+
# +size+ may be a number of bytes, 'WAN' (1420 bytes) or 'LAN' (8154).
|
58
|
+
# Default (safe) value is 'WAN'.
|
59
|
+
def max_chunk_size=(size)
|
60
|
+
case size.to_s.downcase
|
61
|
+
when 'wan'
|
62
|
+
@max_chunk_size = MAX_CHUNK_SIZE_WAN
|
63
|
+
when 'lan'
|
64
|
+
@max_chunk_size = MAX_CHUNK_SIZE_LAN
|
65
|
+
else
|
66
|
+
@max_chunk_size = size.to_int
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def level=(new_level)
|
71
|
+
@level = if new_level.is_a?(Integer)
|
72
|
+
new_level
|
73
|
+
else
|
74
|
+
GELF.const_get(new_level.to_s.upcase)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def default_options=(options)
|
79
|
+
@default_options = self.class.stringify_keys(options)
|
80
|
+
end
|
81
|
+
|
82
|
+
# +mapping+ may be a hash, 'logger' (GELF::LOGGER_MAPPING) or 'direct' (GELF::DIRECT_MAPPING).
|
83
|
+
# Default (compatible) value is 'logger'.
|
84
|
+
def level_mapping=(mapping)
|
85
|
+
case mapping.to_s.downcase
|
86
|
+
when 'logger'
|
87
|
+
@level_mapping = GELF::LOGGER_MAPPING
|
88
|
+
when 'direct'
|
89
|
+
@level_mapping = GELF::DIRECT_MAPPING
|
90
|
+
else
|
91
|
+
@level_mapping = mapping
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def disable
|
96
|
+
@enabled = false
|
97
|
+
end
|
98
|
+
|
99
|
+
def enable
|
100
|
+
@enabled = true
|
101
|
+
end
|
102
|
+
|
103
|
+
# Closes sender
|
104
|
+
def close
|
105
|
+
@sender.close
|
106
|
+
end
|
107
|
+
|
108
|
+
# Same as notify!, but rescues all exceptions (including +ArgumentError+)
|
109
|
+
# and sends them instead.
|
110
|
+
def notify(*args)
|
111
|
+
notify_with_level(nil, *args)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Sends message to Graylog2 server.
|
115
|
+
# +args+ can be:
|
116
|
+
# - hash-like object (any object which responds to +to_hash+, including +Hash+ instance):
|
117
|
+
# notify!(:short_message => 'All your rebase are belong to us', :user => 'AlekSi')
|
118
|
+
# - exception with optional hash-like object:
|
119
|
+
# notify!(SecurityError.new('ALARM!'), :trespasser => 'AlekSi')
|
120
|
+
# - string-like object (anything which responds to +to_s+) with optional hash-like object:
|
121
|
+
# notify!('Plain olde text message', :scribe => 'AlekSi')
|
122
|
+
# Resulted fields are merged with +default_options+, the latter will never overwrite the former.
|
123
|
+
# This method will raise +ArgumentError+ if arguments are wrong. Consider using notify instead.
|
124
|
+
def notify!(*args)
|
125
|
+
notify_with_level!(nil, *args)
|
126
|
+
end
|
127
|
+
|
128
|
+
GELF::Levels.constants.each do |const|
|
129
|
+
define_method(const.downcase) do |*args|
|
130
|
+
level = GELF.const_get(const)
|
131
|
+
notify_with_level(level, *args)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
def create_sender(host, port)
|
138
|
+
addresses = [[host, port]]
|
139
|
+
if default_options['protocol'] == GELF::Protocol::TCP
|
140
|
+
if default_options.key?('tls')
|
141
|
+
tls_options = default_options.delete('tls')
|
142
|
+
GELF::Transport::TCPTLS.new(addresses, tls_options)
|
143
|
+
else
|
144
|
+
GELF::Transport::TCP.new(addresses)
|
145
|
+
end
|
146
|
+
else
|
147
|
+
GELF::Transport::UDP.new(addresses)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def notify_with_level(message_level, *args)
|
152
|
+
notify_with_level!(message_level, *args)
|
153
|
+
rescue SocketError, SystemCallError
|
154
|
+
raise unless rescue_network_errors
|
155
|
+
rescue Exception => exception
|
156
|
+
notify_with_level!(GELF::UNKNOWN, exception)
|
157
|
+
end
|
158
|
+
|
159
|
+
def notify_with_level!(message_level, *args)
|
160
|
+
return unless @enabled
|
161
|
+
hash = extract_hash(*args)
|
162
|
+
hash['level'] = message_level unless message_level.nil?
|
163
|
+
if hash['level'] >= level
|
164
|
+
if default_options['protocol'] == GELF::Protocol::TCP
|
165
|
+
validate_hash(hash)
|
166
|
+
@sender.send(hash.to_json + "\0")
|
167
|
+
else
|
168
|
+
@sender.send_datagrams(datagrams_from_hash(hash))
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def extract_hash(object = nil, args = {})
|
174
|
+
primary_data = if object.respond_to?(:to_hash)
|
175
|
+
object.to_hash
|
176
|
+
elsif object.is_a?(Exception)
|
177
|
+
args['level'] ||= GELF::ERROR
|
178
|
+
self.class.extract_hash_from_exception(object)
|
179
|
+
else
|
180
|
+
args['level'] ||= GELF::INFO
|
181
|
+
{ 'short_message' => object.to_s }
|
182
|
+
end
|
183
|
+
|
184
|
+
#---------------------------------------------------------------------------------------
|
185
|
+
#NOTE:@fitterpen
|
186
|
+
#hash = default_options.merge(self.class.stringify_keys(args.merge(primary_data)))
|
187
|
+
option_hash = default_options.dup
|
188
|
+
option_hash.delete('facility')
|
189
|
+
option_hash.delete('protocol')
|
190
|
+
#---------------------------------------------------------------------------------------
|
191
|
+
hash = self.class.stringify_keys(args.merge(primary_data)).merge(option_hash)
|
192
|
+
convert_hoptoad_keys_to_graylog2(hash)
|
193
|
+
set_file_and_line(hash) if @collect_file_and_line
|
194
|
+
set_timestamp(hash)
|
195
|
+
check_presence_of_mandatory_attributes(hash)
|
196
|
+
hash
|
197
|
+
end
|
198
|
+
|
199
|
+
def self.extract_hash_from_exception(exception)
|
200
|
+
bt = exception.backtrace || ["Backtrace is not available."]
|
201
|
+
{
|
202
|
+
'short_message' => "#{exception.class}: #{exception.message}",
|
203
|
+
'full_message' => "Backtrace:\n" + bt.join("\n")
|
204
|
+
}
|
205
|
+
end
|
206
|
+
|
207
|
+
# Converts Hoptoad-specific keys in +@hash+ to Graylog2-specific.
|
208
|
+
def convert_hoptoad_keys_to_graylog2(hash)
|
209
|
+
if hash['short_message'].to_s.empty?
|
210
|
+
if hash.has_key?('error_class') && hash.has_key?('error_message')
|
211
|
+
hash['short_message'] = hash.delete('error_class') + ': ' + hash.delete('error_message')
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
CALLER_REGEXP = /^(.*):(\d+).*/
|
217
|
+
LIB_GELF_PATTERN = File.join('lib', 'gelf')
|
218
|
+
|
219
|
+
def set_file_and_line(hash)
|
220
|
+
stack = caller
|
221
|
+
frame = stack.find { |f| !f.include?(LIB_GELF_PATTERN) }
|
222
|
+
match = CALLER_REGEXP.match(frame)
|
223
|
+
hash['file'] = match[1]
|
224
|
+
hash['line'] = match[2].to_i
|
225
|
+
end
|
226
|
+
|
227
|
+
def set_timestamp(hash)
|
228
|
+
hash['timestamp'] = Time.now.utc.to_f if hash['timestamp'].nil?
|
229
|
+
end
|
230
|
+
|
231
|
+
def check_presence_of_mandatory_attributes(hash)
|
232
|
+
%w(version short_message host).each do |attribute|
|
233
|
+
if hash[attribute].to_s.empty?
|
234
|
+
raise ArgumentError.new("#{attribute} is missing. Options version, short_message and host must be set.")
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def datagrams_from_hash(hash)
|
240
|
+
data = serialize_hash(hash)
|
241
|
+
datagrams = []
|
242
|
+
|
243
|
+
# Maximum total size is 8192 byte for UDP datagram. Split to chunks if bigger. (GELF v1.0 supports chunking)
|
244
|
+
if data.count > @max_chunk_size
|
245
|
+
id = @random.bytes(8)
|
246
|
+
msg_id = Digest::MD5.digest("#{Time.now.to_f}-#{id}")[0, 8]
|
247
|
+
num, count = 0, (data.count.to_f / @max_chunk_size).ceil
|
248
|
+
if count > MAX_CHUNKS
|
249
|
+
raise ArgumentError, "Data too big (#{data.count} bytes), would create more than #{MAX_CHUNKS} chunks!"
|
250
|
+
end
|
251
|
+
data.each_slice(@max_chunk_size) do |slice|
|
252
|
+
datagrams << "\x1e\x0f" + msg_id + [num, count, *slice].pack('C*')
|
253
|
+
num += 1
|
254
|
+
end
|
255
|
+
else
|
256
|
+
datagrams << data.to_a.pack('C*')
|
257
|
+
end
|
258
|
+
|
259
|
+
datagrams
|
260
|
+
end
|
261
|
+
|
262
|
+
def validate_hash(hash)
|
263
|
+
raise ArgumentError.new("Hash is empty.") if hash.nil? || hash.empty?
|
264
|
+
hash['level'] = @level_mapping[hash['level']]
|
265
|
+
end
|
266
|
+
|
267
|
+
def serialize_hash(hash)
|
268
|
+
validate_hash(hash)
|
269
|
+
|
270
|
+
Zlib::Deflate.deflate(hash.to_json).bytes
|
271
|
+
end
|
272
|
+
|
273
|
+
def self.stringify_keys(data)
|
274
|
+
return data unless data.is_a? Hash
|
275
|
+
|
276
|
+
data.each_with_object({}) do |(key, value), obj|
|
277
|
+
key_s = key.to_s
|
278
|
+
|
279
|
+
if (key != key_s) && data.key?(key_s)
|
280
|
+
raise ArgumentError, "Both #{key.inspect} and #{key_s} are present."
|
281
|
+
end
|
282
|
+
|
283
|
+
obj[key_s] = value
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module GELF
|
2
|
+
# There are two things you should know about log levels/severity:
|
3
|
+
# - syslog defines levels from 0 (Emergency) to 7 (Debug).
|
4
|
+
# 0 (Emergency) and 1 (Alert) levels are reserved for OS kernel.
|
5
|
+
# - Ruby default Logger defines levels from 0 (DEBUG) to 4 (FATAL) and 5 (UNKNOWN).
|
6
|
+
# Note that order is inverted.
|
7
|
+
# For compatibility we define our constants as Ruby Logger, and convert values before
|
8
|
+
# generating GELF message, using defined mapping.
|
9
|
+
|
10
|
+
module Levels
|
11
|
+
DEBUG = 0
|
12
|
+
INFO = 1
|
13
|
+
WARN = 2
|
14
|
+
ERROR = 3
|
15
|
+
FATAL = 4
|
16
|
+
UNKNOWN = 5
|
17
|
+
# Additional native syslog severities. These will work in direct mapping mode
|
18
|
+
# only, for compatibility with syslog sources unrelated to Logger.
|
19
|
+
EMERGENCY = 10
|
20
|
+
ALERT = 11
|
21
|
+
CRITICAL = 12
|
22
|
+
WARNING = 14
|
23
|
+
NOTICE = 15
|
24
|
+
INFORMATIONAL = 16
|
25
|
+
end
|
26
|
+
|
27
|
+
include Levels
|
28
|
+
|
29
|
+
# Maps Ruby Logger levels to syslog levels as SyslogLogger and syslogger gems. This one is default.
|
30
|
+
LOGGER_MAPPING = {DEBUG => 7, # Debug
|
31
|
+
INFO => 6, # Informational
|
32
|
+
WARN => 5, # Notice
|
33
|
+
ERROR => 4, # Warning
|
34
|
+
FATAL => 3, # Error
|
35
|
+
UNKNOWN => 1} # Alert – shouldn't be used
|
36
|
+
|
37
|
+
# Maps Syslog or Ruby Logger levels directly to standard syslog numerical severities.
|
38
|
+
DIRECT_MAPPING = {DEBUG => 7, # Debug
|
39
|
+
INFORMATIONAL => 6, # Informational (syslog source)
|
40
|
+
INFO => 6, # Informational (Logger source)
|
41
|
+
NOTICE => 5, # Notice
|
42
|
+
WARNING => 4, # Warning (syslog source)
|
43
|
+
WARN => 4, # Warning (Logger source)
|
44
|
+
ERROR => 3, # Error
|
45
|
+
CRITICAL => 2, # Critical (syslog source)
|
46
|
+
FATAL => 2, # Critical (Logger source)
|
47
|
+
ALERT => 1, # Alert (syslog source)
|
48
|
+
UNKNOWN => 1, # Alert - shouldn't be used (Logger source)
|
49
|
+
EMERGENCY => 0} # Emergency (syslog source)
|
50
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module GELF
|
2
|
+
module Transport
|
3
|
+
class TCP
|
4
|
+
attr_reader :addresses
|
5
|
+
|
6
|
+
# `addresses` Array of [host, port] pairs
|
7
|
+
def initialize(addresses)
|
8
|
+
@sockets = []
|
9
|
+
self.addresses = addresses
|
10
|
+
end
|
11
|
+
|
12
|
+
def addresses=(addresses)
|
13
|
+
@addresses = addresses.dup.freeze.tap do |addrs|
|
14
|
+
@sockets.each(&:close)
|
15
|
+
@sockets = addrs.map { |peer| connect(*peer) }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def send(message)
|
20
|
+
return if @addresses.empty?
|
21
|
+
loop do
|
22
|
+
connected = @sockets.reject(&:closed?)
|
23
|
+
reconnect_all if connected.empty?
|
24
|
+
break if write_any(connected, message)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def connect(host, port)
|
31
|
+
socket_class.new(host, port)
|
32
|
+
end
|
33
|
+
|
34
|
+
def reconnect_all
|
35
|
+
@sockets = @sockets.each_with_index.map do |old_socket, index|
|
36
|
+
old_socket.closed? ? connect(*@addresses[index]) : old_socket
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def socket_class
|
41
|
+
if defined?(Celluloid::IO::TCPSocket)
|
42
|
+
Celluloid::IO::TCPSocket
|
43
|
+
else
|
44
|
+
::TCPSocket
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def write_any(sockets, message)
|
49
|
+
sockets.shuffle.each do |socket|
|
50
|
+
return true if write_socket(socket, message)
|
51
|
+
end
|
52
|
+
false
|
53
|
+
end
|
54
|
+
|
55
|
+
def write_socket(socket, message)
|
56
|
+
unsafe_write_socket(socket, message)
|
57
|
+
rescue IOError, SystemCallError
|
58
|
+
socket.close unless socket.closed?
|
59
|
+
false
|
60
|
+
end
|
61
|
+
|
62
|
+
def unsafe_write_socket(socket, message)
|
63
|
+
r,w = IO.select([socket], [socket])
|
64
|
+
# Read everything first
|
65
|
+
while r.any? do
|
66
|
+
# don't expect any reads, but a readable socket might
|
67
|
+
# mean the remote end closed, so read it and throw it away.
|
68
|
+
# we'll get an EOFError if it happens.
|
69
|
+
socket.sysread(16384)
|
70
|
+
r = IO.select([socket])
|
71
|
+
end
|
72
|
+
|
73
|
+
# Now send the payload
|
74
|
+
return false unless w.any?
|
75
|
+
return socket.syswrite(message) > 0
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module GELF
|
4
|
+
module Transport
|
5
|
+
# Provides encryption capabilities for TCP connections
|
6
|
+
class TCPTLS < TCP
|
7
|
+
# Supported tls_options:
|
8
|
+
# 'no_default_ca' [Boolean] prevents OpenSSL from using the systems CA store.
|
9
|
+
# 'version' [Symbol] any of :TLSv1, :TLSv1_1, :TLSv1_2 (default)
|
10
|
+
# 'ca' [String] the path to a custom CA store
|
11
|
+
# 'cert' [String, IO] the client certificate file
|
12
|
+
# 'key' [String, IO] the key for the client certificate
|
13
|
+
# 'all_ciphers' [Boolean] allows any ciphers to be used, may be insecure
|
14
|
+
# 'rescue_ssl_errors' [Boolean] similar to rescue_network_errors in notifier.rb, allows SSL exceptions to be raised
|
15
|
+
# 'no_verify' [Boolean] disable peer verification
|
16
|
+
|
17
|
+
attr_accessor :rescue_ssl_errors
|
18
|
+
|
19
|
+
def initialize(addresses, tls_options={})
|
20
|
+
@tls_options = tls_options
|
21
|
+
@rescue_ssl_errors = @tls_options['rescue_ssl_errors']
|
22
|
+
@rescue_ssl_errors if @rescue_ssl_errors.nil?
|
23
|
+
super(addresses)
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def write_socket(socket, message)
|
29
|
+
super(socket, message)
|
30
|
+
rescue OpenSSL::SSL::SSLError
|
31
|
+
socket.close unless socket.closed?
|
32
|
+
raise unless rescue_ssl_errors
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
36
|
+
def connect(host, port)
|
37
|
+
plain_socket = super(host, port)
|
38
|
+
start_tls(plain_socket)
|
39
|
+
rescue OpenSSL::SSL::SSLError
|
40
|
+
plain_socket.close unless plain_socket.closed?
|
41
|
+
raise unless rescue_ssl_errors
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
|
45
|
+
# Initiates TLS communication on the socket
|
46
|
+
def start_tls(plain_socket)
|
47
|
+
ssl_socket_class.new(plain_socket, ssl_context).tap do |ssl_socket|
|
48
|
+
ssl_socket.sync_close = true
|
49
|
+
ssl_socket.connect
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def ssl_socket_class
|
54
|
+
if defined?(Celluloid::IO::SSLSocket)
|
55
|
+
Celluloid::IO::SSLSocket
|
56
|
+
else
|
57
|
+
OpenSSL::SSL::SSLSocket
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def ssl_context
|
62
|
+
@ssl_context ||= OpenSSL::SSL::SSLContext.new.tap do |ctx|
|
63
|
+
ctx.cert_store = ssl_cert_store
|
64
|
+
ctx.ssl_version = tls_version
|
65
|
+
ctx.verify_mode = verify_mode
|
66
|
+
set_certificate_and_key(ctx)
|
67
|
+
restrict_ciphers(ctx) unless @tls_options['all_ciphers']
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def set_certificate_and_key(context)
|
72
|
+
return unless @tls_options['cert'] && @tls_options['key']
|
73
|
+
context.cert = OpenSSL::X509::Certificate.new(resource(@tls_options['cert']))
|
74
|
+
context.key = OpenSSL::PKey::RSA.new(resource(@tls_options['key']))
|
75
|
+
end
|
76
|
+
|
77
|
+
# checks whether {resource} is a filename and tries to read it
|
78
|
+
# otherwise treats it as if it already contains certificate/key data
|
79
|
+
def resource(data)
|
80
|
+
if data.is_a?(String) && File.exist?(data)
|
81
|
+
File.read(data)
|
82
|
+
else
|
83
|
+
data
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Ciphers have to come from the CipherString class, specifically the _TXT_ constants here - https://github.com/jruby/jruby-openssl/blob/master/src/main/java/org/jruby/ext/openssl/CipherStrings.java#L47-L178
|
88
|
+
def restrict_ciphers(ctx)
|
89
|
+
# This CipherString is will allow a variety of 'currently' cryptographically secure ciphers,
|
90
|
+
# while also retaining a broad level of compatibility
|
91
|
+
ctx.ciphers = "TLSv1_2:TLSv1_1:TLSv1:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!DSS:!RC4:!SEED:!ECDSA:!ADH:!IDEA:!3DES"
|
92
|
+
end
|
93
|
+
|
94
|
+
def verify_mode
|
95
|
+
@tls_options['no_verify'] ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
|
96
|
+
end
|
97
|
+
|
98
|
+
# SSL v2&3 are insecure, forces at least TLS v1.0 and defaults to v1.2
|
99
|
+
def tls_version
|
100
|
+
if @tls_options.key?('version') &&
|
101
|
+
OpenSSL::SSL::SSLContext::METHODS.include?(@tls_options['version']) &&
|
102
|
+
@tls_options['version'] =~ /\ATLSv/
|
103
|
+
@tls_options['version']
|
104
|
+
else
|
105
|
+
:TLSv1_2
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def ssl_cert_store
|
110
|
+
OpenSSL::X509::Store.new.tap do |store|
|
111
|
+
unless @tls_options['no_default_ca']
|
112
|
+
store.set_default_paths
|
113
|
+
end
|
114
|
+
|
115
|
+
if @tls_options.key?('ca')
|
116
|
+
ca = @tls_options['ca']
|
117
|
+
if File.directory?(ca)
|
118
|
+
store.add_path(@tls_options['ca'])
|
119
|
+
elsif File.file?(ca)
|
120
|
+
store.add_file(ca)
|
121
|
+
else
|
122
|
+
$stderr.puts "No directory or file: #{ca}"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module GELF
|
2
|
+
module Transport
|
3
|
+
class UDP
|
4
|
+
attr_accessor :addresses
|
5
|
+
|
6
|
+
def initialize(addresses)
|
7
|
+
@addresses = addresses
|
8
|
+
end
|
9
|
+
|
10
|
+
def send_datagrams(datagrams)
|
11
|
+
socket = get_socket
|
12
|
+
idx = get_address_index
|
13
|
+
|
14
|
+
host, port = @addresses[idx]
|
15
|
+
set_address_index((idx + 1) % @addresses.length)
|
16
|
+
datagrams.each do |datagram|
|
17
|
+
socket.send(datagram, 0, host, port)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def close
|
22
|
+
socket = get_socket
|
23
|
+
socket.close if socket
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def get_socket
|
29
|
+
Thread.current[:gelf_udp_socket] ||= UDPSocket.open
|
30
|
+
end
|
31
|
+
|
32
|
+
def get_address_index
|
33
|
+
Thread.current[:gelf_udp_address_idx] ||= 0
|
34
|
+
end
|
35
|
+
|
36
|
+
def set_address_index(value)
|
37
|
+
Thread.current[:gelf_udp_address_idx] = value
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/gelf.rb
ADDED
data/test/helper.rb
ADDED