fluent-plugin-syslog-tls-with-backoff 2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.coveralls.yml +15 -0
- data/.gitignore +40 -0
- data/.travis.yml +29 -0
- data/CHANGELOG.md +49 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +80 -0
- data/LICENSE +235 -0
- data/README.md +71 -0
- data/Rakefile +24 -0
- data/docs/configuration.md +139 -0
- data/fluent-plugin-syslog-tls.gemspec +43 -0
- data/lib/fluent/plugin/out_syslog_tls.rb +216 -0
- data/lib/syslog_tls/facility.rb +46 -0
- data/lib/syslog_tls/host_backoff_specs.rb +45 -0
- data/lib/syslog_tls/logger.rb +82 -0
- data/lib/syslog_tls/lookup_from_const.rb +36 -0
- data/lib/syslog_tls/protocol.rb +145 -0
- data/lib/syslog_tls/severity.rb +30 -0
- data/lib/syslog_tls/ssl_transport.rb +208 -0
- data/lib/syslog_tls/version.rb +18 -0
- data/test/fluent/test_out_syslog_tls.rb +192 -0
- data/test/helper.rb +25 -0
- data/test/ssl.rb +53 -0
- data/test/syslog_tls/test_logger.rb +48 -0
- data/test/syslog_tls/test_protocol.rb +150 -0
- data/test/syslog_tls/test_ssl_transport.rb +71 -0
- metadata +182 -0
@@ -0,0 +1,145 @@
|
|
1
|
+
# Copyright 2016 Acquia, Inc.
|
2
|
+
# Copyright 2016 t.e.morgan.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
require 'date'
|
17
|
+
|
18
|
+
require_relative 'facility'
|
19
|
+
require_relative 'severity'
|
20
|
+
|
21
|
+
# Syslog protocol https://tools.ietf.org/html/rfc5424
|
22
|
+
module SyslogTls
|
23
|
+
# RFC defined nil value
|
24
|
+
NIL_VALUE = '-'
|
25
|
+
|
26
|
+
# All headers by specification wrapped in single object
|
27
|
+
class Header
|
28
|
+
attr_accessor :version, :hostname, :app_name, :procid, :msgid
|
29
|
+
attr_reader :facility, :severity, :timestamp
|
30
|
+
|
31
|
+
FACILITIES = {}
|
32
|
+
SEVERITIES = {}
|
33
|
+
|
34
|
+
Facility.setup_constants FACILITIES
|
35
|
+
Severity.setup_constants SEVERITIES
|
36
|
+
|
37
|
+
def initialize
|
38
|
+
@timestamp = Time.now
|
39
|
+
@severity = 'INFO'
|
40
|
+
@facility = 'LOCAL0'
|
41
|
+
@version = 1
|
42
|
+
@hostname = NIL_VALUE
|
43
|
+
@app_name = NIL_VALUE
|
44
|
+
@procid = NIL_VALUE
|
45
|
+
@msgid = NIL_VALUE
|
46
|
+
end
|
47
|
+
|
48
|
+
def timestamp=(val)
|
49
|
+
raise ArgumentError.new("Must provide Time object value instead: #{val.inspect}") unless val.is_a?(Time)
|
50
|
+
@timestamp = val
|
51
|
+
end
|
52
|
+
|
53
|
+
def facility=(val)
|
54
|
+
raise ArgumentError.new("Invalid facility value: #{val.inspect}") unless FACILITIES.key?(val)
|
55
|
+
@facility = val
|
56
|
+
end
|
57
|
+
|
58
|
+
def severity=(val)
|
59
|
+
raise ArgumentError.new("Invalid severity value: #{val.inspect}") unless SEVERITIES.key?(val)
|
60
|
+
@severity = val
|
61
|
+
end
|
62
|
+
|
63
|
+
# Priority value is calculated by first multiplying the Facility
|
64
|
+
# number by 8 and then adding the numerical value of the Severity.
|
65
|
+
def pri
|
66
|
+
FACILITIES[facility] * 8 + SEVERITIES[severity]
|
67
|
+
end
|
68
|
+
|
69
|
+
def assemble
|
70
|
+
[
|
71
|
+
"<#{pri}>#{version}",
|
72
|
+
timestamp.to_datetime.rfc3339,
|
73
|
+
hostname,
|
74
|
+
app_name,
|
75
|
+
procid,
|
76
|
+
msgid
|
77
|
+
].join(' ')
|
78
|
+
end
|
79
|
+
|
80
|
+
def to_s
|
81
|
+
assemble
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Structured data field
|
86
|
+
class StructuredData
|
87
|
+
attr_accessor :id, :data
|
88
|
+
|
89
|
+
def initialize(id)
|
90
|
+
@id = id
|
91
|
+
@data = {}
|
92
|
+
end
|
93
|
+
|
94
|
+
# Format data structured data to
|
95
|
+
# [id k="v" ...]
|
96
|
+
def assemble
|
97
|
+
return NIL_VALUE unless id
|
98
|
+
parts = [id]
|
99
|
+
data.each do |k, v|
|
100
|
+
# Characters ", ] and \ must be escaped to prevent any parsing errors
|
101
|
+
v = v.gsub(/(\"|\]|\\)/) { |match| '\\' + match }
|
102
|
+
parts << "#{k}=\"#{v}\""
|
103
|
+
end
|
104
|
+
"[#{parts.join(' ')}]"
|
105
|
+
end
|
106
|
+
|
107
|
+
def to_s
|
108
|
+
assemble
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Message represents full message that can be sent to syslog
|
113
|
+
class Message
|
114
|
+
attr_accessor :structured_data, :msg
|
115
|
+
attr_writer :header
|
116
|
+
|
117
|
+
def initialize
|
118
|
+
@msg = ''
|
119
|
+
@structured_data = []
|
120
|
+
end
|
121
|
+
|
122
|
+
def header
|
123
|
+
@header ||= Header.new
|
124
|
+
end
|
125
|
+
|
126
|
+
def assemble
|
127
|
+
# Start with header
|
128
|
+
out = [header.to_s]
|
129
|
+
# Add all structured data
|
130
|
+
if structured_data.length > 0
|
131
|
+
out << structured_data.map(&:to_s).join('')
|
132
|
+
else
|
133
|
+
out << NIL_VALUE
|
134
|
+
end
|
135
|
+
# Add message
|
136
|
+
out << msg if msg.length > 0
|
137
|
+
# Message must end with new line delimiter
|
138
|
+
out.join(' ') + "\n"
|
139
|
+
end
|
140
|
+
|
141
|
+
def to_s
|
142
|
+
assemble
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# Copyright 2016 Acquia, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require_relative 'lookup_from_const'
|
16
|
+
|
17
|
+
module SyslogTls
|
18
|
+
module Severity
|
19
|
+
extend LookupFromConst
|
20
|
+
EMERG = PANIC = 0
|
21
|
+
ALERT = 1
|
22
|
+
CRIT = 2
|
23
|
+
ERR = ERROR = 3
|
24
|
+
WARN = WARNING = 4
|
25
|
+
NOTICE = 5
|
26
|
+
INFO = 6
|
27
|
+
DEBUG = 7
|
28
|
+
NONE = 10
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
# Copyright 2016 Acquia, Inc.
|
2
|
+
# Copyright 2016-2023 t.e.morgan.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
require 'socket'
|
17
|
+
require 'openssl'
|
18
|
+
require 'pp'
|
19
|
+
|
20
|
+
module SyslogTls
|
21
|
+
# Supports SSL connection to remote host
|
22
|
+
class SSLTransport
|
23
|
+
CONNECT_TIMEOUT = 10
|
24
|
+
# READ_TIMEOUT = 5
|
25
|
+
WRITE_TIMEOUT = 5
|
26
|
+
|
27
|
+
attr_accessor :socket
|
28
|
+
|
29
|
+
attr_reader :host, :port, :idle_timeout, :ca_cert, :client_cert, :client_key, :verify_cert_name, :ssl_version
|
30
|
+
|
31
|
+
attr_writer :retries
|
32
|
+
|
33
|
+
def initialize(host, port, idle_timeout: nil, ca_cert: 'system', client_cert: nil, client_key: nil, verify_cert_name: true, ssl_version: :TLS1_2, max_retries: 1)
|
34
|
+
@host = host
|
35
|
+
@port = port
|
36
|
+
@idle_timeout = idle_timeout
|
37
|
+
@ca_cert = ca_cert
|
38
|
+
@client_cert = client_cert
|
39
|
+
@client_key = client_key
|
40
|
+
@verify_cert_name = verify_cert_name
|
41
|
+
@ssl_version = ssl_version
|
42
|
+
@retries = max_retries
|
43
|
+
connect
|
44
|
+
end
|
45
|
+
|
46
|
+
def connect
|
47
|
+
timwnow = Time.now
|
48
|
+
@socket = get_ssl_connection
|
49
|
+
begin
|
50
|
+
begin
|
51
|
+
@socket.connect_nonblock
|
52
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
|
53
|
+
select_with_timeout(@socket, :connect_read) && retry
|
54
|
+
rescue IO::WaitWritable
|
55
|
+
select_with_timeout(@socket, :connect_write) && retry
|
56
|
+
end
|
57
|
+
rescue Errno::ETIMEDOUT
|
58
|
+
raise 'Socket timeout during connect'
|
59
|
+
end
|
60
|
+
@last_write = Time.now if idle_timeout
|
61
|
+
end
|
62
|
+
|
63
|
+
def get_tcp_connection
|
64
|
+
tcp = nil
|
65
|
+
|
66
|
+
family = Socket::Constants::AF_UNSPEC
|
67
|
+
sock_type = Socket::Constants::SOCK_STREAM
|
68
|
+
addr_info = Socket.getaddrinfo(host, port, family, sock_type, nil, nil, false).first
|
69
|
+
_, port, _, address, family, sock_type = addr_info
|
70
|
+
|
71
|
+
begin
|
72
|
+
sock_addr = Socket.sockaddr_in(port, address)
|
73
|
+
tcp = Socket.new(family, sock_type, 0)
|
74
|
+
tcp.setsockopt(Socket::SOL_SOCKET, Socket::Constants::SO_REUSEADDR, true)
|
75
|
+
tcp.setsockopt(Socket::SOL_SOCKET, Socket::Constants::SO_REUSEPORT, true)
|
76
|
+
tcp.connect_nonblock(sock_addr)
|
77
|
+
rescue Errno::EINPROGRESS
|
78
|
+
select_with_timeout(tcp, :connect_write)
|
79
|
+
begin
|
80
|
+
tcp.connect_nonblock(sock_addr)
|
81
|
+
rescue Errno::EISCONN
|
82
|
+
# all good
|
83
|
+
rescue SystemCallError
|
84
|
+
tcp.close rescue nil
|
85
|
+
raise
|
86
|
+
end
|
87
|
+
rescue SystemCallError
|
88
|
+
tcp.close rescue nil
|
89
|
+
raise
|
90
|
+
end
|
91
|
+
|
92
|
+
tcp.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true)
|
93
|
+
tcp
|
94
|
+
end
|
95
|
+
|
96
|
+
def get_ssl_connection
|
97
|
+
tcp = get_tcp_connection
|
98
|
+
|
99
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
100
|
+
ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
101
|
+
ctx.min_version = ssl_version
|
102
|
+
|
103
|
+
ctx.verify_hostname = verify_cert_name != false
|
104
|
+
|
105
|
+
case ca_cert
|
106
|
+
when true, 'true', 'system'
|
107
|
+
# use system certs, same as openssl cli
|
108
|
+
ctx.cert_store = OpenSSL::X509::Store.new
|
109
|
+
ctx.cert_store.set_default_paths
|
110
|
+
when false, 'false'
|
111
|
+
ctx.verify_hostname = false
|
112
|
+
ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
113
|
+
when %r{/$} # ends in /
|
114
|
+
ctx.ca_path = ca_cert
|
115
|
+
when String
|
116
|
+
ctx.ca_file = ca_cert
|
117
|
+
end
|
118
|
+
|
119
|
+
ctx.cert = OpenSSL::X509::Certificate.new(File.read(client_cert)) if client_cert
|
120
|
+
ctx.key = OpenSSL::PKey::read(File.read(client_key)) if client_key
|
121
|
+
socket = OpenSSL::SSL::SSLSocket.new(tcp, ctx)
|
122
|
+
socket.hostname = host
|
123
|
+
socket.sync_close = true
|
124
|
+
socket
|
125
|
+
end
|
126
|
+
|
127
|
+
# Allow to retry on failed writes
|
128
|
+
def write(s)
|
129
|
+
if idle_timeout
|
130
|
+
if (t=Time.now) > @last_write + idle_timeout
|
131
|
+
@socket.close rescue nil
|
132
|
+
connect
|
133
|
+
else
|
134
|
+
@last_write = t
|
135
|
+
end
|
136
|
+
end
|
137
|
+
begin
|
138
|
+
retry_id ||= 0
|
139
|
+
do_write(s)
|
140
|
+
rescue => e
|
141
|
+
if (retry_id += 1) < @retries
|
142
|
+
@socket.close rescue nil
|
143
|
+
connect
|
144
|
+
retry
|
145
|
+
else
|
146
|
+
raise e
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def do_write(data)
|
152
|
+
data.force_encoding('BINARY') # so we can break in the middle of multi-byte characters
|
153
|
+
loop do
|
154
|
+
sent = 0
|
155
|
+
begin
|
156
|
+
sent = @socket.write_nonblock(data)
|
157
|
+
rescue OpenSSL::SSL::SSLError, Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable => e
|
158
|
+
if e.is_a?(OpenSSL::SSL::SSLError) && e.message !~ /write would block/
|
159
|
+
raise e
|
160
|
+
else
|
161
|
+
select_with_timeout(@socket, :write) && retry
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
break if sent >= data.size
|
166
|
+
data = data[sent, data.size]
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def select_with_timeout(tcp, type)
|
171
|
+
host_ip_port = host + ":" + port.to_s
|
172
|
+
case type
|
173
|
+
when :connect_read
|
174
|
+
args = [[tcp], nil, nil, CONNECT_TIMEOUT]
|
175
|
+
when :connect_write
|
176
|
+
args = [nil, [tcp], nil, CONNECT_TIMEOUT]
|
177
|
+
# when :read
|
178
|
+
# args = [[tcp], nil, nil, READ_TIMEOUT]
|
179
|
+
when :write
|
180
|
+
args = [nil, [tcp], nil, WRITE_TIMEOUT]
|
181
|
+
else
|
182
|
+
raise "Unknown select type #{type}"
|
183
|
+
end
|
184
|
+
if type.to_s == "connect_write"
|
185
|
+
if can_write(host_ip_port) == 1
|
186
|
+
io_select_return = IO.select(*args)
|
187
|
+
ready_sockets, _, _ = io_select_return
|
188
|
+
if !ready_sockets.empty?
|
189
|
+
reset_tries(host_ip_port)
|
190
|
+
io_select_return
|
191
|
+
else
|
192
|
+
increase_retry(host_ip_port)
|
193
|
+
io_select_return || raise("Socket timeout during #{type}")
|
194
|
+
end
|
195
|
+
# else
|
196
|
+
# raise("Failed to write #{type}")
|
197
|
+
end
|
198
|
+
else
|
199
|
+
IO.select(*args) || raise("Socket timeout during #{type}")
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
# Forward any methods directly to SSLSocket
|
204
|
+
def method_missing(method_sym, *arguments, &block)
|
205
|
+
@socket.send(method_sym, *arguments, &block)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Copyright 2016 Acquia, Inc.
|
2
|
+
# Copyright 2016-2019 t.e.morgan.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
module SyslogTls
|
17
|
+
VERSION = '2.1.1'
|
18
|
+
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
# Copyright 2016 Acquia, Inc.
|
2
|
+
# Copyright 2016-2019 t.e.morgan.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
require 'helper'
|
17
|
+
require 'ssl'
|
18
|
+
require 'date'
|
19
|
+
require 'minitest/mock'
|
20
|
+
require 'fluent/plugin/out_syslog_tls'
|
21
|
+
require 'fluent/test/driver/output'
|
22
|
+
|
23
|
+
class SyslogTlsOutputTest < Test::Unit::TestCase
|
24
|
+
include SSLTestHelper
|
25
|
+
|
26
|
+
def setup
|
27
|
+
Fluent::Test.setup
|
28
|
+
@driver = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def driver(conf='')
|
32
|
+
@driver ||= Fluent::Test::Driver::Output.new(Fluent::Plugin::SyslogTlsOutput).configure(conf)
|
33
|
+
end
|
34
|
+
|
35
|
+
def sample_record
|
36
|
+
{
|
37
|
+
"app_name" => "app",
|
38
|
+
"hostname" => "host",
|
39
|
+
"procid" => $$,
|
40
|
+
"msgid" => 1000,
|
41
|
+
"message" => "MESSAGE",
|
42
|
+
"severity" => "PANIC",
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
def mock_logger(token='TOKEN')
|
47
|
+
io = StringIO.new
|
48
|
+
io.set_encoding('utf-8')
|
49
|
+
::SyslogTls::Logger.new(io, token)
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_configure
|
53
|
+
config = %{
|
54
|
+
host syslog.collection.us1.sumologic.com
|
55
|
+
port 6514
|
56
|
+
client_cert
|
57
|
+
client_key
|
58
|
+
verify_cert_name true
|
59
|
+
token 1234567890
|
60
|
+
}
|
61
|
+
instance = driver(config).instance
|
62
|
+
|
63
|
+
assert_equal 'syslog.collection.us1.sumologic.com', instance.host
|
64
|
+
assert_equal '6514', instance.port
|
65
|
+
assert_equal '', instance.client_cert
|
66
|
+
assert_equal '', instance.client_key
|
67
|
+
assert_equal true, instance.verify_cert_name
|
68
|
+
assert_equal '1234567890', instance.token
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_default_emit
|
72
|
+
config = %{
|
73
|
+
host syslog.collection.us1.sumologic.com
|
74
|
+
port 6514
|
75
|
+
client_cert
|
76
|
+
client_key
|
77
|
+
}
|
78
|
+
instance = driver(config).instance
|
79
|
+
|
80
|
+
time = Time.now
|
81
|
+
record = sample_record
|
82
|
+
logger = mock_logger(instance.token)
|
83
|
+
|
84
|
+
instance.stub(:new_logger, logger) do
|
85
|
+
instance.process('test', {time.to_i => record})
|
86
|
+
end
|
87
|
+
|
88
|
+
assert_equal "<134>1 #{time.to_datetime.rfc3339} - - - - - #{record.to_json.to_s}\n\n", logger.transport.string
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_message_headers_mapping
|
92
|
+
config = %{
|
93
|
+
host syslog.collection.us1.sumologic.com
|
94
|
+
port 6514
|
95
|
+
client_cert
|
96
|
+
client_key
|
97
|
+
token 1234567890
|
98
|
+
hostname_key hostname
|
99
|
+
procid_key procid
|
100
|
+
app_name_key app_name
|
101
|
+
msgid_key msgid
|
102
|
+
}
|
103
|
+
instance = driver(config).instance
|
104
|
+
|
105
|
+
time = Time.now
|
106
|
+
record = sample_record
|
107
|
+
logger = mock_logger
|
108
|
+
|
109
|
+
instance.stub(:new_logger, logger) do
|
110
|
+
instance.process('test', {time.to_i => record})
|
111
|
+
end
|
112
|
+
|
113
|
+
assert_true logger.transport.string.start_with?("<134>1 #{time.to_datetime.rfc3339} host app #{$$} 1000 [TOKEN]")
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_message_severity_mapping
|
117
|
+
config = %{
|
118
|
+
host syslog.collection.us1.sumologic.com
|
119
|
+
port 6514
|
120
|
+
client_cert
|
121
|
+
client_key
|
122
|
+
token 1234567890
|
123
|
+
severity_key severity
|
124
|
+
}
|
125
|
+
instance = driver(config).instance
|
126
|
+
|
127
|
+
time = Time.now
|
128
|
+
record = sample_record
|
129
|
+
logger = mock_logger
|
130
|
+
|
131
|
+
instance.stub(:new_logger, logger) do
|
132
|
+
instance.process('test', {time.to_i => record})
|
133
|
+
end
|
134
|
+
|
135
|
+
assert_true logger.transport.string.start_with?("<128>1")
|
136
|
+
end
|
137
|
+
|
138
|
+
def test_formatter
|
139
|
+
config = %{
|
140
|
+
host syslog.collection.us1.sumologic.com
|
141
|
+
port 6514
|
142
|
+
client_cert
|
143
|
+
client_key
|
144
|
+
token 1234567890
|
145
|
+
format out_file
|
146
|
+
localtime false
|
147
|
+
}
|
148
|
+
instance = driver(config).instance
|
149
|
+
|
150
|
+
time = Time.now
|
151
|
+
record = sample_record
|
152
|
+
logger = mock_logger
|
153
|
+
|
154
|
+
instance.stub(:new_logger, logger) do
|
155
|
+
instance.process('test', {time.to_i => record})
|
156
|
+
end
|
157
|
+
|
158
|
+
formatted_time = time.dup.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
159
|
+
assert_equal "<134>1 #{time.to_datetime.rfc3339} - - - - [TOKEN] #{formatted_time}\ttest\t#{record.to_json.to_s}\n\n", logger.transport.string
|
160
|
+
end
|
161
|
+
|
162
|
+
def test_ssl
|
163
|
+
time = Time.now
|
164
|
+
record = sample_record
|
165
|
+
|
166
|
+
server = ssl_server
|
167
|
+
st = Thread.new {
|
168
|
+
client = server.accept
|
169
|
+
assert_equal "<134>1 #{time.to_datetime.rfc3339} host app #{$$} 1000 [1234567890] #{record.to_json.to_s}\n", client.gets
|
170
|
+
client.close
|
171
|
+
}
|
172
|
+
|
173
|
+
config = %{
|
174
|
+
host localhost
|
175
|
+
port #{server.addr[1]}
|
176
|
+
client_cert
|
177
|
+
client_key
|
178
|
+
token 1234567890
|
179
|
+
hostname_key hostname
|
180
|
+
procid_key procid
|
181
|
+
app_name_key app_name
|
182
|
+
msgid_key msgid
|
183
|
+
}
|
184
|
+
instance = driver(config).instance
|
185
|
+
|
186
|
+
SyslogTls::SSLTransport.stub_any_instance(:get_ssl_connection, ssl_client) do
|
187
|
+
instance.process('test', {time.to_i => record})
|
188
|
+
end
|
189
|
+
|
190
|
+
st.join
|
191
|
+
end
|
192
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# Copyright 2016 Acquia, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'simplecov'
|
16
|
+
|
17
|
+
SimpleCov.start
|
18
|
+
|
19
|
+
require 'test/unit'
|
20
|
+
require 'fluent/test'
|
21
|
+
require 'minitest/pride'
|
22
|
+
require 'minitest/stub_any_instance'
|
23
|
+
|
24
|
+
require 'webmock/test_unit'
|
25
|
+
WebMock.disable_net_connect!
|
data/test/ssl.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'openssl'
|
3
|
+
|
4
|
+
module SSLTestHelper
|
5
|
+
def ssl_server(min_version: nil, max_version: nil)
|
6
|
+
@ssl_server ||= begin
|
7
|
+
tcp_server = TCPServer.new("localhost", 33000 + Random.rand(1000))
|
8
|
+
ssl_context = OpenSSL::SSL::SSLContext.new
|
9
|
+
ssl_context.cert = certificate
|
10
|
+
ssl_context.key = rsa_key
|
11
|
+
ssl_context.min_version = min_version if min_version
|
12
|
+
ssl_context.max_version = max_version if max_version
|
13
|
+
OpenSSL::SSL::SSLServer.new(tcp_server, ssl_context)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def ssl_client
|
18
|
+
tcp = TCPSocket.new("localhost", ssl_server.addr[1])
|
19
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
20
|
+
ctx.set_params(verify_mode: OpenSSL::SSL::VERIFY_NONE)
|
21
|
+
ctx.cert = certificate
|
22
|
+
ctx.key = rsa_key
|
23
|
+
OpenSSL::SSL::SSLSocket.new(tcp, ctx)
|
24
|
+
end
|
25
|
+
|
26
|
+
def rsa_key
|
27
|
+
@rsa_key ||= OpenSSL::PKey::RSA.new(2048)
|
28
|
+
end
|
29
|
+
|
30
|
+
def certificate
|
31
|
+
@cert ||= begin
|
32
|
+
cert = OpenSSL::X509::Certificate.new
|
33
|
+
cert.subject = cert.issuer = OpenSSL::X509::Name.parse("/C=BE/O=Test/OU=Test/CN=Test")
|
34
|
+
cert.not_before = Time.now
|
35
|
+
cert.not_after = Time.now + 365 * 24 * 60 * 60
|
36
|
+
cert.public_key = rsa_key.public_key
|
37
|
+
cert.serial = 0x0
|
38
|
+
cert.version = 2
|
39
|
+
|
40
|
+
ef = OpenSSL::X509::ExtensionFactory.new
|
41
|
+
ef.subject_certificate = cert
|
42
|
+
ef.issuer_certificate = cert
|
43
|
+
cert.extensions = [
|
44
|
+
ef.create_extension("basicConstraints","CA:TRUE", true),
|
45
|
+
ef.create_extension("subjectKeyIdentifier", "hash"),
|
46
|
+
# ef.create_extension("keyUsage", "cRLSign,keyCertSign", true),
|
47
|
+
]
|
48
|
+
cert.add_extension ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always")
|
49
|
+
cert.sign(rsa_key, OpenSSL::Digest::SHA1.new)
|
50
|
+
cert
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|