fluent-plugin-syslog-p 1.0.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.
@@ -0,0 +1,82 @@
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_relative 'protocol'
17
+ require_relative 'ssl_transport'
18
+
19
+ module SyslogTls
20
+ class Logger
21
+ attr_reader :token
22
+ attr_accessor :transport
23
+
24
+ # Logger accepts transport which should implement IO methods
25
+ # close, closed? and write
26
+ def initialize(transport, token=nil)
27
+ @transport = transport
28
+ @default_header = SyslogTls::Header.new
29
+ @default_structured_data = SyslogTls::StructuredData.new(token)
30
+ end
31
+
32
+ # Sets default facility for each message
33
+ def facility(val)
34
+ @default_header.facility = val
35
+ end
36
+
37
+ # Sets default hostname for each message
38
+ def hostname(val)
39
+ @default_header.hostname = val
40
+ end
41
+
42
+ # Sets default app_name for each message
43
+ def app_name(val)
44
+ @default_header.app_name = val
45
+ end
46
+
47
+ # Sets default procid for message
48
+ def procid(val)
49
+ @default_header.procid = val
50
+ end
51
+
52
+ # Check if IO is closed
53
+ def closed?
54
+ transport && transport.closed?
55
+ end
56
+
57
+ def close
58
+ transport.close
59
+ end
60
+
61
+ # Send log message with severity to syslog
62
+ def log(severity, message, time: nil)
63
+ time ||= Time.now
64
+
65
+ m = SyslogTls::Message.new
66
+
67
+ # Include authentication header
68
+ m.structured_data << @default_structured_data
69
+
70
+ # Adjust header with current timestamp and severity
71
+ m.header = @default_header.dup
72
+ m.header.severity = severity
73
+ m.header.timestamp = time
74
+
75
+ yield m.header if block_given?
76
+
77
+ m.msg = message
78
+
79
+ transport.write(m.to_s)
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,36 @@
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
+ module SyslogTls
16
+ module LookupFromConst
17
+ def setup_constants(dst)
18
+ constants.each do |pri|
19
+ cval = const_get pri
20
+
21
+ dst[pri] = cval
22
+ dst[pri.downcase] = cval
23
+
24
+ dst[:"LOG_#{pri.to_s}"] = cval
25
+ dst[:"LOG_#{pri.downcase.to_s}"] = cval
26
+ const_set :"LOG_#{pri.to_s}", cval
27
+
28
+ dst[pri.to_s] = cval
29
+ dst[pri.downcase.to_s] = cval
30
+
31
+ dst[cval] = cval
32
+ dst[cval.to_s] = cval
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,136 @@
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}>#{timestamp.to_datetime.strftime("%b %d %H:%M:%S")}",
72
+ hostname,
73
+ app_name
74
+ ].join(' ')
75
+ end
76
+
77
+ def to_s
78
+ assemble
79
+ end
80
+ end
81
+
82
+ # Structured data field
83
+ class StructuredData
84
+ attr_accessor :id, :data
85
+
86
+ def initialize(id)
87
+ @id = id
88
+ @data = {}
89
+ end
90
+
91
+ # Format data structured data to
92
+ # [id k="v" ...]
93
+ def assemble
94
+ return NIL_VALUE unless id
95
+ parts = [id]
96
+ data.each do |k, v|
97
+ # Characters ", ] and \ must be escaped to prevent any parsing errors
98
+ v = v.gsub(/(\"|\]|\\)/) { |match| '\\' + match }
99
+ parts << "#{k}=\"#{v}\""
100
+ end
101
+ "[#{parts.join(' ')}]"
102
+ end
103
+
104
+ def to_s
105
+ assemble
106
+ end
107
+ end
108
+
109
+ # Message represents full message that can be sent to syslog
110
+ class Message
111
+ attr_accessor :structured_data, :msg
112
+ attr_writer :header
113
+
114
+ def initialize
115
+ @msg = ''
116
+ @structured_data = []
117
+ end
118
+
119
+ def header
120
+ @header ||= Header.new
121
+ end
122
+
123
+ def assemble
124
+ # Start with header
125
+ out = [header.to_s]
126
+ # Add message
127
+ out << msg if msg.length > 0
128
+ # Message must end with new line delimiter
129
+ out.join(' ') + "\n"
130
+ end
131
+
132
+ def to_s
133
+ assemble
134
+ end
135
+ end
136
+ 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,189 @@
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 'socket'
17
+ require 'openssl'
18
+
19
+ module SyslogTls
20
+ # Supports SSL connection to remote host
21
+ class SSLTransport
22
+ CONNECT_TIMEOUT = 10
23
+ # READ_TIMEOUT = 5
24
+ WRITE_TIMEOUT = 5
25
+
26
+ attr_accessor :socket
27
+
28
+ attr_reader :host, :port, :idle_timeout, :ca_cert, :client_cert, :client_key, :verify_cert_name, :ssl_version
29
+
30
+ attr_writer :retries
31
+
32
+ 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)
33
+ @host = host
34
+ @port = port
35
+ @idle_timeout = idle_timeout
36
+ @ca_cert = ca_cert
37
+ @client_cert = client_cert
38
+ @client_key = client_key
39
+ @verify_cert_name = verify_cert_name
40
+ @ssl_version = ssl_version
41
+ @retries = max_retries
42
+ connect
43
+ end
44
+
45
+ def connect
46
+ @socket = get_ssl_connection
47
+ begin
48
+ begin
49
+ @socket.connect_nonblock
50
+ rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
51
+ select_with_timeout(@socket, :connect_read) && retry
52
+ rescue IO::WaitWritable
53
+ select_with_timeout(@socket, :connect_write) && retry
54
+ end
55
+ rescue Errno::ETIMEDOUT
56
+ raise 'Socket timeout during connect'
57
+ end
58
+ @last_write = Time.now if idle_timeout
59
+ end
60
+
61
+ def get_tcp_connection
62
+ tcp = nil
63
+
64
+ family = Socket::Constants::AF_UNSPEC
65
+ sock_type = Socket::Constants::SOCK_STREAM
66
+ addr_info = Socket.getaddrinfo(host, port, family, sock_type, nil, nil, false).first
67
+ _, port, _, address, family, sock_type = addr_info
68
+
69
+ begin
70
+ sock_addr = Socket.sockaddr_in(port, address)
71
+ tcp = Socket.new(family, sock_type, 0)
72
+ tcp.setsockopt(Socket::SOL_SOCKET, Socket::Constants::SO_REUSEADDR, true)
73
+ tcp.setsockopt(Socket::SOL_SOCKET, Socket::Constants::SO_REUSEPORT, true)
74
+ tcp.connect_nonblock(sock_addr)
75
+ rescue Errno::EINPROGRESS
76
+ select_with_timeout(tcp, :connect_write)
77
+ begin
78
+ tcp.connect_nonblock(sock_addr)
79
+ rescue Errno::EISCONN
80
+ # all good
81
+ rescue SystemCallError
82
+ tcp.close rescue nil
83
+ raise
84
+ end
85
+ rescue SystemCallError
86
+ tcp.close rescue nil
87
+ raise
88
+ end
89
+
90
+ tcp.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true)
91
+ tcp
92
+ end
93
+
94
+ def get_ssl_connection
95
+ tcp = get_tcp_connection
96
+
97
+ ctx = OpenSSL::SSL::SSLContext.new
98
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
99
+ ctx.ssl_version = ssl_version
100
+
101
+ ctx.verify_hostname = verify_cert_name != false
102
+
103
+ case ca_cert
104
+ when true, 'true', 'system'
105
+ # use system certs, same as openssl cli
106
+ ctx.cert_store = OpenSSL::X509::Store.new
107
+ ctx.cert_store.set_default_paths
108
+ when false, 'false'
109
+ ctx.verify_hostname = false
110
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
111
+ when %r{/$} # ends in /
112
+ ctx.ca_path = ca_cert
113
+ when String
114
+ ctx.ca_file = ca_cert
115
+ end
116
+
117
+ ctx.cert = OpenSSL::X509::Certificate.new(File.read(client_cert)) if client_cert
118
+ ctx.key = OpenSSL::PKey::read(File.read(client_key)) if client_key
119
+ socket = OpenSSL::SSL::SSLSocket.new(tcp, ctx)
120
+ socket.hostname = host
121
+ socket.sync_close = true
122
+ socket
123
+ end
124
+
125
+ # Allow to retry on failed writes
126
+ def write(s)
127
+ if idle_timeout
128
+ if (t=Time.now) > @last_write + idle_timeout
129
+ @socket.close rescue nil
130
+ connect
131
+ else
132
+ @last_write = t
133
+ end
134
+ end
135
+ begin
136
+ retry_id ||= 0
137
+ do_write(s)
138
+ rescue => e
139
+ if (retry_id += 1) < @retries
140
+ @socket.close rescue nil
141
+ connect
142
+ retry
143
+ else
144
+ raise e
145
+ end
146
+ end
147
+ end
148
+
149
+ def do_write(data)
150
+ data.force_encoding('BINARY') # so we can break in the middle of multi-byte characters
151
+ loop do
152
+ sent = 0
153
+ begin
154
+ sent = @socket.write_nonblock(data)
155
+ rescue OpenSSL::SSL::SSLError, Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable => e
156
+ if e.is_a?(OpenSSL::SSL::SSLError) && e.message !~ /write would block/
157
+ raise e
158
+ else
159
+ select_with_timeout(@socket, :write) && retry
160
+ end
161
+ end
162
+
163
+ break if sent >= data.size
164
+ data = data[sent, data.size]
165
+ end
166
+ end
167
+
168
+ def select_with_timeout(tcp, type)
169
+ case type
170
+ when :connect_read
171
+ args = [[tcp], nil, nil, CONNECT_TIMEOUT]
172
+ when :connect_write
173
+ args = [nil, [tcp], nil, CONNECT_TIMEOUT]
174
+ # when :read
175
+ # args = [[tcp], nil, nil, READ_TIMEOUT]
176
+ when :write
177
+ args = [nil, [tcp], nil, WRITE_TIMEOUT]
178
+ else
179
+ raise "Unknown select type #{type}"
180
+ end
181
+ IO.select(*args) || raise("Socket timeout during #{type}")
182
+ end
183
+
184
+ # Forward any methods directly to SSLSocket
185
+ def method_missing(method_sym, *arguments, &block)
186
+ @socket.send(method_sym, *arguments, &block)
187
+ end
188
+ end
189
+ end