Class: SyslogTls::SSLTransport

Inherits:
Object
  • Object
show all
Defined in:
lib/syslog_tls/ssl_transport.rb

Overview

Supports SSL connection to remote host

Constant Summary collapse

CONNECT_TIMEOUT =
10
WRITE_TIMEOUT =

READ_TIMEOUT = 5

5

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#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) ⇒ SSLTransport

Returns a new instance of SSLTransport.



32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/syslog_tls/ssl_transport.rb', line 32

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)
  @host = host
  @port = port
  @idle_timeout = idle_timeout
  @ca_cert = ca_cert
  @client_cert = client_cert
  @client_key = client_key
  @verify_cert_name = verify_cert_name
  @ssl_version = ssl_version
  @retries = max_retries
  connect
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_sym, *arguments, &block) ⇒ Object

Forward any methods directly to SSLSocket



185
186
187
# File 'lib/syslog_tls/ssl_transport.rb', line 185

def method_missing(method_sym, *arguments, &block)
  @socket.send(method_sym, *arguments, &block)
end

Instance Attribute Details

#ca_certObject (readonly)

Returns the value of attribute ca_cert.



28
29
30
# File 'lib/syslog_tls/ssl_transport.rb', line 28

def ca_cert
  @ca_cert
end

#client_certObject (readonly)

Returns the value of attribute client_cert.



28
29
30
# File 'lib/syslog_tls/ssl_transport.rb', line 28

def client_cert
  @client_cert
end

#client_keyObject (readonly)

Returns the value of attribute client_key.



28
29
30
# File 'lib/syslog_tls/ssl_transport.rb', line 28

def client_key
  @client_key
end

#hostObject (readonly)

Returns the value of attribute host.



28
29
30
# File 'lib/syslog_tls/ssl_transport.rb', line 28

def host
  @host
end

#idle_timeoutObject (readonly)

Returns the value of attribute idle_timeout.



28
29
30
# File 'lib/syslog_tls/ssl_transport.rb', line 28

def idle_timeout
  @idle_timeout
end

#portObject (readonly)

Returns the value of attribute port.



28
29
30
# File 'lib/syslog_tls/ssl_transport.rb', line 28

def port
  @port
end

#retries=(value) ⇒ Object (writeonly)

Sets the attribute retries

Parameters:

  • value

    the value to set the attribute retries to.



30
31
32
# File 'lib/syslog_tls/ssl_transport.rb', line 30

def retries=(value)
  @retries = value
end

#socketObject

Returns the value of attribute socket.



26
27
28
# File 'lib/syslog_tls/ssl_transport.rb', line 26

def socket
  @socket
end

#ssl_versionObject (readonly)

Returns the value of attribute ssl_version.



28
29
30
# File 'lib/syslog_tls/ssl_transport.rb', line 28

def ssl_version
  @ssl_version
end

#verify_cert_nameObject (readonly)

Returns the value of attribute verify_cert_name.



28
29
30
# File 'lib/syslog_tls/ssl_transport.rb', line 28

def verify_cert_name
  @verify_cert_name
end

Instance Method Details

#connectObject



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/syslog_tls/ssl_transport.rb', line 45

def connect
  @socket = get_ssl_connection
  begin
    begin
      @socket.connect_nonblock
    rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
      select_with_timeout(@socket, :connect_read) && retry
    rescue IO::WaitWritable
      select_with_timeout(@socket, :connect_write) && retry
    end
  rescue Errno::ETIMEDOUT
    raise 'Socket timeout during connect'
  end
  @last_write = Time.now if idle_timeout
end

#do_write(data) ⇒ Object



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/syslog_tls/ssl_transport.rb', line 149

def do_write(data)
  data.force_encoding('BINARY') # so we can break in the middle of multi-byte characters
  loop do
    sent = 0
    begin
      sent = @socket.write_nonblock(data)
    rescue OpenSSL::SSL::SSLError, Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable => e
      if e.is_a?(OpenSSL::SSL::SSLError) && e.message !~ /write would block/
        raise e
      else
        select_with_timeout(@socket, :write) && retry
      end
    end

    break if sent >= data.size
    data = data[sent, data.size]
  end
end

#get_ssl_connectionObject



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/syslog_tls/ssl_transport.rb', line 94

def get_ssl_connection
  tcp = get_tcp_connection

  ctx = OpenSSL::SSL::SSLContext.new
  ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
  ctx.min_version = ssl_version

  ctx.verify_hostname = verify_cert_name != false

  case ca_cert
  when true, 'true', 'system'
    # use system certs, same as openssl cli
    ctx.cert_store = OpenSSL::X509::Store.new
    ctx.cert_store.set_default_paths
  when false, 'false'
    ctx.verify_hostname = false
    ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
  when %r{/$} # ends in /
    ctx.ca_path = ca_cert
  when String
    ctx.ca_file = ca_cert
  end

  ctx.cert = OpenSSL::X509::Certificate.new(File.read(client_cert)) if client_cert
  ctx.key = OpenSSL::PKey::read(File.read(client_key)) if client_key
  socket = OpenSSL::SSL::SSLSocket.new(tcp, ctx)
  socket.hostname = host
  socket.sync_close = true
  socket
end

#get_tcp_connectionObject



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/syslog_tls/ssl_transport.rb', line 61

def get_tcp_connection
  tcp = nil

  family = Socket::Constants::AF_UNSPEC
  sock_type = Socket::Constants::SOCK_STREAM
  addr_info = Socket.getaddrinfo(host, port, family, sock_type, nil, nil, false).first
  _, port, _, address, family, sock_type = addr_info

  begin
    sock_addr = Socket.sockaddr_in(port, address)
    tcp = Socket.new(family, sock_type, 0)
    tcp.setsockopt(Socket::SOL_SOCKET, Socket::Constants::SO_REUSEADDR, true)
    tcp.setsockopt(Socket::SOL_SOCKET, Socket::Constants::SO_REUSEPORT, true)
    tcp.connect_nonblock(sock_addr)
  rescue Errno::EINPROGRESS
    select_with_timeout(tcp, :connect_write)
    begin
      tcp.connect_nonblock(sock_addr)
    rescue Errno::EISCONN
      # all good
    rescue SystemCallError
      tcp.close rescue nil
      raise
    end
  rescue SystemCallError
    tcp.close rescue nil
    raise
  end

  tcp.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true)
  tcp
end

#select_with_timeout(tcp, type) ⇒ Object



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/syslog_tls/ssl_transport.rb', line 168

def select_with_timeout(tcp, type)
  case type
  when :connect_read
    args = [[tcp], nil, nil, CONNECT_TIMEOUT]
  when :connect_write
    args = [nil, [tcp], nil, CONNECT_TIMEOUT]
  # when :read
  #   args = [[tcp], nil, nil, READ_TIMEOUT]
  when :write
    args = [nil, [tcp], nil, WRITE_TIMEOUT]
  else
    raise "Unknown select type #{type}"
  end
  IO.select(*args) || raise("Socket timeout during #{type}")
end

#write(s) ⇒ Object

Allow to retry on failed writes



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/syslog_tls/ssl_transport.rb', line 126

def write(s)
  if idle_timeout
    if (t=Time.now) > @last_write + idle_timeout
      @socket.close rescue nil
      connect
    else
      @last_write = t
    end
  end
  begin
    retry_id ||= 0
    do_write(s)
  rescue => e
    if (retry_id += 1) < @retries
      @socket.close rescue nil
      connect
      retry
    else
      raise e
    end
  end
end