fluent-plugin-devo 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,31 @@
1
+ # Copyright 2016 Acquia, Inc.
2
+ # Copyright 2021 Devo .Inc.
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_relative 'lookup_from_const'
17
+
18
+ module SyslogTls
19
+ module Severity
20
+ extend LookupFromConst
21
+ EMERG = PANIC = 0
22
+ ALERT = 1
23
+ CRIT = 2
24
+ ERR = ERROR = 3
25
+ WARN = WARNING = 4
26
+ NOTICE = 5
27
+ INFO = 6
28
+ DEBUG = 7
29
+ NONE = 10
30
+ end
31
+ end
@@ -0,0 +1,190 @@
1
+ # Copyright 2016 Acquia, Inc.
2
+ # Copyright 2016-2019 t.e.morgan.
3
+ # Copyright 2021 Devo .Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may 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, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'socket'
18
+ require 'openssl'
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: :TLSv1_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
+ @socket = get_ssl_connection
48
+ begin
49
+ begin
50
+ @socket.connect_nonblock
51
+ rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
52
+ select_with_timeout(@socket, :connect_read) && retry
53
+ rescue IO::WaitWritable
54
+ select_with_timeout(@socket, :connect_write) && retry
55
+ end
56
+ rescue Errno::ETIMEDOUT
57
+ raise 'Socket timeout during connect'
58
+ end
59
+ @last_write = Time.now if idle_timeout
60
+ end
61
+
62
+ def get_tcp_connection
63
+ tcp = nil
64
+
65
+ family = Socket::Constants::AF_UNSPEC
66
+ sock_type = Socket::Constants::SOCK_STREAM
67
+ addr_info = Socket.getaddrinfo(host, port, family, sock_type, nil, nil, false).first
68
+ _, port, _, address, family, sock_type = addr_info
69
+
70
+ begin
71
+ sock_addr = Socket.sockaddr_in(port, address)
72
+ tcp = Socket.new(family, sock_type, 0)
73
+ tcp.setsockopt(Socket::SOL_SOCKET, Socket::Constants::SO_REUSEADDR, true)
74
+ tcp.setsockopt(Socket::SOL_SOCKET, Socket::Constants::SO_REUSEPORT, true)
75
+ tcp.connect_nonblock(sock_addr)
76
+ rescue Errno::EINPROGRESS
77
+ select_with_timeout(tcp, :connect_write)
78
+ begin
79
+ tcp.connect_nonblock(sock_addr)
80
+ rescue Errno::EISCONN
81
+ # all good
82
+ rescue SystemCallError
83
+ tcp.close rescue nil
84
+ raise
85
+ end
86
+ rescue SystemCallError
87
+ tcp.close rescue nil
88
+ raise
89
+ end
90
+
91
+ tcp.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true)
92
+ tcp
93
+ end
94
+
95
+ def get_ssl_connection
96
+ tcp = get_tcp_connection
97
+
98
+ ctx = OpenSSL::SSL::SSLContext.new
99
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
100
+ ctx.ssl_version = ssl_version
101
+
102
+ ctx.verify_hostname = verify_cert_name != false
103
+
104
+ case ca_cert
105
+ when true, 'true', 'system'
106
+ # use system certs, same as openssl cli
107
+ ctx.cert_store = OpenSSL::X509::Store.new
108
+ ctx.cert_store.set_default_paths
109
+ when false, 'false'
110
+ ctx.verify_hostname = false
111
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
112
+ when %r{/$} # ends in /
113
+ ctx.ca_path = ca_cert
114
+ when String
115
+ ctx.ca_file = ca_cert
116
+ end
117
+
118
+ ctx.cert = OpenSSL::X509::Certificate.new(File.read(client_cert)) if client_cert
119
+ ctx.key = OpenSSL::PKey::read(File.read(client_key)) if client_key
120
+ socket = OpenSSL::SSL::SSLSocket.new(tcp, ctx)
121
+ socket.hostname = host
122
+ socket.sync_close = true
123
+ socket
124
+ end
125
+
126
+ # Allow to retry on failed writes
127
+ def write(s)
128
+ if idle_timeout
129
+ if (t=Time.now) > @last_write + idle_timeout
130
+ @socket.close rescue nil
131
+ connect
132
+ else
133
+ @last_write = t
134
+ end
135
+ end
136
+ begin
137
+ retry_id ||= 0
138
+ do_write(s)
139
+ rescue => e
140
+ if (retry_id += 1) < @retries
141
+ @socket.close rescue nil
142
+ connect
143
+ retry
144
+ else
145
+ raise e
146
+ end
147
+ end
148
+ end
149
+
150
+ def do_write(data)
151
+ data.force_encoding('BINARY') # so we can break in the middle of multi-byte characters
152
+ loop do
153
+ sent = 0
154
+ begin
155
+ sent = @socket.write_nonblock(data)
156
+ rescue OpenSSL::SSL::SSLError, Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable => e
157
+ if e.is_a?(OpenSSL::SSL::SSLError) && e.message !~ /write would block/
158
+ raise e
159
+ else
160
+ select_with_timeout(@socket, :write) && retry
161
+ end
162
+ end
163
+
164
+ break if sent >= data.size
165
+ data = data[sent, data.size]
166
+ end
167
+ end
168
+
169
+ def select_with_timeout(tcp, type)
170
+ case type
171
+ when :connect_read
172
+ args = [[tcp], nil, nil, CONNECT_TIMEOUT]
173
+ when :connect_write
174
+ args = [nil, [tcp], nil, CONNECT_TIMEOUT]
175
+ # when :read
176
+ # args = [[tcp], nil, nil, READ_TIMEOUT]
177
+ when :write
178
+ args = [nil, [tcp], nil, WRITE_TIMEOUT]
179
+ else
180
+ raise "Unknown select type #{type}"
181
+ end
182
+ IO.select(*args) || raise("Socket timeout during #{type}")
183
+ end
184
+
185
+ # Forward any methods directly to SSLSocket
186
+ def method_missing(method_sym, *arguments, &block)
187
+ @socket.send(method_sym, *arguments, &block)
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,19 @@
1
+ # Copyright 2016 Acquia, Inc.
2
+ # Copyright 2016-2019 t.e.morgan.
3
+ # Copyright 2021 Devo .Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may 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, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ module SyslogTls
18
+ VERSION = '1.0.0'
19
+ end
@@ -0,0 +1,193 @@
1
+ # Copyright 2016 Acquia, Inc.
2
+ # Copyright 2016-2019 t.e.morgan.
3
+ # Copyright 2021 Devo .Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may 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, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'helper'
18
+ require 'ssl'
19
+ require 'date'
20
+ require 'minitest/mock'
21
+ require 'fluent/plugin/out_devo'
22
+ require 'fluent/test/driver/output'
23
+
24
+ class SyslogTlsOutputTest < Test::Unit::TestCase
25
+ include SSLTestHelper
26
+
27
+ def setup
28
+ Fluent::Test.setup
29
+ @driver = nil
30
+ end
31
+
32
+ def driver(conf='')
33
+ @driver ||= Fluent::Test::Driver::Output.new(Fluent::Plugin::SyslogTlsOutput).configure(conf)
34
+ end
35
+
36
+ def sample_record
37
+ {
38
+ "app_name" => "app",
39
+ "hostname" => "host",
40
+ "procid" => $$,
41
+ "msgid" => 1000,
42
+ "message" => "MESSAGE",
43
+ "severity" => "PANIC",
44
+ }
45
+ end
46
+
47
+ def mock_logger(token='TOKEN')
48
+ io = StringIO.new
49
+ io.set_encoding('utf-8')
50
+ ::SyslogTls::Logger.new(io, token)
51
+ end
52
+
53
+ def test_configure
54
+ config = %{
55
+ host syslog.collection.us1.sumologic.com
56
+ port 6514
57
+ client_cert
58
+ client_key
59
+ verify_cert_name true
60
+ token 1234567890
61
+ }
62
+ instance = driver(config).instance
63
+
64
+ assert_equal 'syslog.collection.us1.sumologic.com', instance.host
65
+ assert_equal '6514', instance.port
66
+ assert_equal '', instance.client_cert
67
+ assert_equal '', instance.client_key
68
+ assert_equal true, instance.verify_cert_name
69
+ assert_equal '1234567890', instance.token
70
+ end
71
+
72
+ def test_default_emit
73
+ config = %{
74
+ host syslog.collection.us1.sumologic.com
75
+ port 6514
76
+ client_cert
77
+ client_key
78
+ }
79
+ instance = driver(config).instance
80
+
81
+ time = Time.now
82
+ record = sample_record
83
+ logger = mock_logger(instance.token)
84
+
85
+ instance.stub(:new_logger, logger) do
86
+ instance.process('test', {time.to_i => record})
87
+ end
88
+
89
+ assert_equal "<134>#{time.to_datetime.strftime("%b %d %H:%M:%S")} #{record.to_json.to_s}\n\n", logger.transport.string
90
+ end
91
+
92
+ def test_message_headers_mapping
93
+ config = %{
94
+ host syslog.collection.us1.sumologic.com
95
+ port 6514
96
+ client_cert
97
+ client_key
98
+ token 1234567890
99
+ hostname_key hostname
100
+ procid_key procid
101
+ app_name_key app_name
102
+ msgid_key msgid
103
+ }
104
+ instance = driver(config).instance
105
+
106
+ time = Time.now
107
+ record = sample_record
108
+ logger = mock_logger
109
+
110
+ instance.stub(:new_logger, logger) do
111
+ instance.process('test', {time.to_i => record})
112
+ end
113
+
114
+ assert_true logger.transport.string.start_with?("<134>#{time.to_datetime.strftime("%b %d %H:%M:%S")} host app")
115
+ end
116
+
117
+ def test_message_severity_mapping
118
+ config = %{
119
+ host syslog.collection.us1.sumologic.com
120
+ port 6514
121
+ client_cert
122
+ client_key
123
+ token 1234567890
124
+ severity_key severity
125
+ }
126
+ instance = driver(config).instance
127
+
128
+ time = Time.now
129
+ record = sample_record
130
+ logger = mock_logger
131
+
132
+ instance.stub(:new_logger, logger) do
133
+ instance.process('test', {time.to_i => record})
134
+ end
135
+
136
+ assert_true logger.transport.string.start_with?("<128>")
137
+ end
138
+
139
+ def test_formatter
140
+ config = %{
141
+ host syslog.collection.us1.sumologic.com
142
+ port 6514
143
+ client_cert
144
+ client_key
145
+ token 1234567890
146
+ format out_file
147
+ localtime false
148
+ }
149
+ instance = driver(config).instance
150
+
151
+ time = Time.now
152
+ record = sample_record
153
+ logger = mock_logger
154
+
155
+ instance.stub(:new_logger, logger) do
156
+ instance.process('test', {time.to_i => record})
157
+ end
158
+
159
+ formatted_time = time.dup.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
160
+ assert_equal "<134>#{time.to_datetime.strftime("%b %d %H:%M:%S")} #{formatted_time}\ttest\t#{record.to_json.to_s}\n\n", logger.transport.string
161
+ end
162
+
163
+ def test_ssl
164
+ time = Time.now
165
+ record = sample_record
166
+
167
+ server = ssl_server
168
+ st = Thread.new {
169
+ client = server.accept
170
+ assert_equal "<134>#{time.to_datetime.strftime("%b %d %H:%M:%S")} host app #{record.to_json.to_s}\n", client.gets
171
+ client.close
172
+ }
173
+
174
+ config = %{
175
+ host localhost
176
+ port #{server.addr[1]}
177
+ client_cert
178
+ client_key
179
+ token 1234567890
180
+ hostname_key hostname
181
+ procid_key procid
182
+ app_name_key app_name
183
+ msgid_key msgid
184
+ }
185
+ instance = driver(config).instance
186
+
187
+ SyslogTls::SSLTransport.stub_any_instance(:get_ssl_connection, ssl_client) do
188
+ instance.process('test', {time.to_i => record})
189
+ end
190
+
191
+ st.join
192
+ end
193
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,29 @@
1
+ # Copyright 2016 Acquia, Inc.
2
+ # Copyright 2021 Devo .Inc.
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 'coveralls'
17
+ require 'simplecov'
18
+
19
+ SimpleCov.start
20
+
21
+ Coveralls.wear! if ENV['TRAVIS']
22
+
23
+ require 'test/unit'
24
+ require 'fluent/test'
25
+ require 'minitest/pride'
26
+ require 'minitest/stub_any_instance'
27
+
28
+ require 'webmock/test_unit'
29
+ WebMock.disable_net_connect!