5
5
require 'fluent/event'
6
6
require 'net/http'
7
7
require 'uri'
8
+ require 'openssl'
9
+ require 'async'
8
10
9
11
class HtttpHelperTest < Test ::Unit ::TestCase
10
12
PORT = unused_port
11
13
NULL_LOGGER = Logger . new ( nil )
14
+ CERT_DIR = File . expand_path ( File . dirname ( __FILE__ ) + '/data/cert/without_ca' )
15
+ CERT_CA_DIR = File . expand_path ( File . dirname ( __FILE__ ) + '/data/cert/with_ca' )
12
16
13
17
class Dummy < Fluent ::Plugin ::TestBase
14
18
helpers :http_server
15
19
end
16
20
17
- def on_driver
21
+ def on_driver ( config = nil )
22
+ config ||= Fluent ::Config . parse ( config || '' , '(name)' , '' )
18
23
Fluent ::Test . setup
19
24
driver = Dummy . new
25
+ driver . configure ( config )
20
26
driver . start
21
27
driver . after_start
22
28
@@ -47,6 +53,12 @@ def on_driver
47
53
end
48
54
end
49
55
56
+ def on_driver_transport ( opts = { } , &block )
57
+ transport_conf = config_element ( 'transport' , 'tls' , opts )
58
+ c = config_element ( 'ROOT' , '' , { } , [ transport_conf ] )
59
+ on_driver ( c , &block )
60
+ end
61
+
50
62
%w[ get head ] . each do |n |
51
63
define_method ( n ) do |uri , header = { } |
52
64
url = URI . parse ( uri )
@@ -56,6 +68,14 @@ def on_driver
56
68
http . request ( req )
57
69
end
58
70
end
71
+
72
+ define_method ( "secure_#{ n } " ) do |uri , header = { } , verify : true , cert_path : nil , selfsigned : true , hostname : false |
73
+ url = URI . parse ( uri )
74
+ headers = { 'Content-Type' => 'application/x-www-form-urlencoded/' } . merge ( header )
75
+ start_https_request ( url . host , url . port , verify : verify , cert_path : cert_path , selfsigned : selfsigned ) do |https |
76
+ https . send ( n , url . path , headers . to_a )
77
+ end
78
+ end
59
79
end
60
80
61
81
%w[ post put patch delete options trace ] . each do |n |
@@ -70,6 +90,91 @@ def on_driver
70
90
end
71
91
end
72
92
93
+ # wrapper for net/http
94
+ Response = Struct . new ( :code , :body , :headers )
95
+
96
+ # Use async-http as http client since net/http can't be set verify_hostname= now
97
+ # will be replaced when net/http supports verify_hostname=
98
+ def start_https_request ( addr , port , verify : true , cert_path : nil , selfsigned : true , hostname : nil )
99
+ context = OpenSSL ::SSL ::SSLContext . new
100
+ context . set_params ( { } )
101
+ if verify
102
+ cert_store = OpenSSL ::X509 ::Store . new
103
+ cert_store . set_default_paths
104
+ if selfsigned && OpenSSL ::X509 . const_defined? ( 'V_FLAG_CHECK_SS_SIGNATURE' )
105
+ cert_store . flags = OpenSSL ::X509 ::V_FLAG_CHECK_SS_SIGNATURE
106
+ end
107
+
108
+ if cert_path
109
+ cert_store . add_file ( cert_path )
110
+ end
111
+
112
+ context . cert_store = cert_store
113
+ if !hostname && context . respond_to? ( :verify_hostname= )
114
+ context . verify_hostname = false # In test code, using hostname to be connected is very difficult
115
+ end
116
+
117
+ context . verify_mode = OpenSSL ::SSL ::VERIFY_PEER
118
+ else
119
+ context . verify_mode = OpenSSL ::SSL ::VERIFY_NONE
120
+ end
121
+
122
+ client = Async ::HTTP ::Client . new ( Async ::HTTP ::Endpoint . parse ( "https://#{ addr } :#{ port } " , ssl_context : context ) )
123
+ reactor = Async ::Reactor . new ( nil , logger : NULL_LOGGER )
124
+
125
+ resp = nil
126
+ error = nil
127
+
128
+ reactor . run do
129
+ begin
130
+ response = yield ( client )
131
+ rescue => e # Async::Reactor rescue all error. handle it by myself
132
+ error = e
133
+ end
134
+
135
+ resp = Response . new ( response . status . to_s , response . body . read , response . headers )
136
+ end
137
+
138
+ if error
139
+ raise error
140
+ else
141
+ resp
142
+ end
143
+ end
144
+
145
+ # def start_https_request(addr, port, verify: true, cert_path: nil, selfsigned: true)
146
+ # https = Net::HTTP.new(addr, port)
147
+ # https.use_ssl = true
148
+
149
+ # if verify
150
+ # cert_store = OpenSSL::X509::Store.new
151
+ # cert_store.set_default_paths
152
+ # if selfsigned && OpenSSL::X509.const_defined?('V_FLAG_CHECK_SS_SIGNATURE')
153
+ # cert_store.flags = OpenSSL::X509::V_FLAG_CHECK_SS_SIGNATURE
154
+ # end
155
+
156
+ # if cert_path
157
+ # cert_store.add_file(cert_path)
158
+ # end
159
+
160
+ # https.cert_store = cert_store
161
+
162
+ # # https.verify_hostname = false
163
+
164
+ # https.verify_mode = OpenSSL::SSL::VERIFY_PEER
165
+ # else
166
+ # https.verify_mode = OpenSSL::SSL::VERIFY_NONE
167
+ # end
168
+
169
+ # # if !hostname && context.respond_to?(:verify_hostname=)
170
+ # # context.verify_hostname = false # In test code, using hostname to be connected is very difficult
171
+ # # end
172
+
173
+ # https.start do
174
+ # yield(https)
175
+ # end
176
+ # end
177
+
73
178
sub_test_case 'Create a HTTP server' do
74
179
test 'monunt given path' do
75
180
on_driver do |driver |
@@ -185,6 +290,95 @@ def on_driver
185
290
end
186
291
end
187
292
293
+ sub_test_case 'create a HTTPS server' do
294
+ test '#configure' do
295
+ driver = Dummy . new
296
+
297
+ transport_conf = config_element ( 'transport' , 'tls' , { 'version' => 'TLSv1_1' } )
298
+ driver . configure ( config_element ( 'ROOT' , '' , { } , [ transport_conf ] ) )
299
+ assert_equal :tls , driver . transport_config . protocol
300
+ assert_equal :TLSv1_1 , driver . transport_config . version
301
+ end
302
+
303
+ sub_test_case '#http_server_create_https_server' do
304
+ test 'can overwrite settings by using tls_context' do
305
+ on_driver_transport ( { 'insecure' => 'false' } ) do |driver |
306
+ tls = { 'insecure' => 'true' } # overwrite
307
+ driver . http_server_create_https_server ( :http_server_helper_test_tls , addr : '127.0.0.1' , port : PORT , logger : NULL_LOGGER , tls_opts : tls ) do |s |
308
+ s . get ( '/example/hello' ) { [ 200 , { 'Content-Type' => 'text/plain' } , 'hello get' ] }
309
+ end
310
+
311
+ resp = secure_get ( "https://127.0.0.1:#{ PORT } /example/hello" , verify : false )
312
+ assert_equal ( '200' , resp . code )
313
+ assert_equal ( 'hello get' , resp . body )
314
+ end
315
+ end
316
+
317
+ test 'with insecure in transport section' do
318
+ on_driver_transport ( { 'insecure' => 'true' } ) do |driver |
319
+ driver . http_server_create_https_server ( :http_server_helper_test_tls , addr : '127.0.0.1' , port : PORT , logger : NULL_LOGGER ) do |s |
320
+ s . get ( '/example/hello' ) { [ 200 , { 'Content-Type' => 'text/plain' } , 'hello get' ] }
321
+ end
322
+
323
+ resp = secure_get ( "https://127.0.0.1:#{ PORT } /example/hello" , verify : false )
324
+ assert_equal ( '200' , resp . code )
325
+ assert_equal ( 'hello get' , resp . body )
326
+
327
+ assert_raise OpenSSL ::SSL ::SSLError do
328
+ secure_get ( "https://127.0.0.1:#{ PORT } /example/hello" )
329
+ end
330
+ end
331
+ end
332
+
333
+ data (
334
+ 'with passphrase' => [ 'apple' , 'cert-pass.pem' , 'cert-key-pass.pem' ] ,
335
+ 'without passphrase' => [ nil , 'cert.pem' , 'cert-key.pem' ] )
336
+ test 'load self-signed cert/key pair, verified from clients using cert files' do |( passphrase , cert , private_key ) |
337
+ cert_path = File . join ( CERT_DIR , cert )
338
+ private_key_path = File . join ( CERT_DIR , private_key )
339
+ opt = { 'insecure' => 'false' , 'private_key_path' => private_key_path , 'cert_path' => cert_path }
340
+ if passphrase
341
+ opt [ 'private_key_passphrase' ] = passphrase
342
+ end
343
+
344
+ on_driver_transport ( opt ) do |driver |
345
+ driver . http_server_create_https_server ( :http_server_helper_test_tls , addr : '127.0.0.1' , port : PORT , logger : NULL_LOGGER ) do |s |
346
+ s . get ( '/example/hello' ) { [ 200 , { 'Content-Type' => 'text/plain' } , 'hello get' ] }
347
+ end
348
+
349
+ resp = secure_get ( "https://127.0.0.1:#{ PORT } /example/hello" , cert_path : cert_path )
350
+ assert_equal ( '200' , resp . code )
351
+ assert_equal ( 'hello get' , resp . body )
352
+ end
353
+ end
354
+
355
+ data (
356
+ 'with passphrase' => [ 'apple' , 'cert-pass.pem' , 'cert-key-pass.pem' , 'ca-cert-pass.pem' ] ,
357
+ 'without passphrase' => [ nil , 'cert.pem' , 'cert-key.pem' , 'ca-cert.pem' ] )
358
+ test 'load cert by private CA cert file, verified from clients using CA cert file' do |( passphrase , cert , cert_key , ca_cert ) |
359
+ cert_path = File . join ( CERT_CA_DIR , cert )
360
+ private_key_path = File . join ( CERT_CA_DIR , cert_key )
361
+
362
+ ca_cert_path = File . join ( CERT_CA_DIR , ca_cert )
363
+
364
+ opt = { 'insecure' => 'false' , 'cert_path' => cert_path , 'private_key_path' => private_key_path }
365
+ if passphrase
366
+ opt [ 'private_key_passphrase' ] = passphrase
367
+ end
368
+
369
+ on_driver_transport ( opt ) do |driver |
370
+ driver . http_server_create_https_server ( :http_server_helper_test_tls , addr : '127.0.0.1' , port : PORT , logger : NULL_LOGGER ) do |s |
371
+ s . get ( '/example/hello' ) { [ 200 , { 'Content-Type' => 'text/plain' } , 'hello get' ] }
372
+ end
373
+
374
+ resp = secure_get ( "https://127.0.0.1:#{ PORT } /example/hello" , cert_path : ca_cert_path )
375
+ assert_equal ( '200' , resp . code )
376
+ assert_equal ( 'hello get' , resp . body )
377
+ end
378
+ end
379
+ end
380
+ end
381
+
188
382
test 'must be called #start and #stop' do
189
383
on_driver do |driver |
190
384
server = flexmock ( 'Server' ) do |watcher |
0 commit comments