ritm 0.0.1 → 0.0.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2eeab06c6fd5db111b87a1ff18694f4206f571c9
4
- data.tar.gz: b1537590beb540d93cd9161b7a7fee6d4090f3cb
3
+ metadata.gz: 9f58ad7ec7b60adcb6d5644582b19da348fee831
4
+ data.tar.gz: 2e024c04e5527f91864055141e5cf39cbe18e326
5
5
  SHA512:
6
- metadata.gz: a2e1a3bbb3b8c0b9ac87339152c9a3b726ea2295d3c14c48e67952e89a5b40ea5314446e445078b5e98556550faf8de6609af42d6e438509ae4b87604f98fd1a
7
- data.tar.gz: 9ebf2c4991de1026d05cfd9e6226424f9feb0631b1d22a001819195d9d3e215f779a72feb0b65dc1f653c317d6dab62dac49ed45c29f592e37ed38ac0cc8897d
6
+ metadata.gz: 72464b798fc3fa1ee83ebc4f3a3865a9e1d0f77a4e5ee2161475c4659f392cff87395201090ace9f7006043adefbb30972ac709e130663005dc38a6557fbd031
7
+ data.tar.gz: a0cdb6a6999fee1e099706541f4ace66b2217ea6d005452cb5cf72937e819058f661e55b6b6b3678bc03a8e3e67d0d974d4017aacec533b355785d9d9f0c6a58
data/lib/ritm/certs/ca.rb CHANGED
@@ -7,7 +7,7 @@ module Ritm
7
7
  def self.create(common_name: 'RubyInTheMiddle')
8
8
  super(common_name, serial_number: 1) do |cert|
9
9
  cert.signing_entity = true
10
- cert.sign!(signing_profile)
10
+ cert.sign!(ca_signing_profile)
11
11
  yield cert if block_given?
12
12
  end
13
13
  end
@@ -15,17 +15,26 @@ module Ritm
15
15
  def self.load(crt, private_key)
16
16
  super(crt, private_key) do |cert|
17
17
  cert.signing_entity = true
18
- cert.sign!(signing_profile)
18
+ cert.sign!(ca_signing_profile)
19
19
  yield cert if block_given?
20
20
  end
21
21
  end
22
22
 
23
23
  def sign(certificate)
24
24
  certificate.cert.parent = @cert
25
- certificate.cert.sign!
25
+ certificate.cert.sign!(self.class.signing_profile)
26
26
  end
27
27
 
28
28
  def self.signing_profile
29
+ {
30
+ 'extensions' => {
31
+ 'keyUsage' => { 'usage' => %w(keyEncipherment digitalSignature) },
32
+ 'extendedKeyUsage' => { 'usage' => %w(serverAuth clientAuth) }
33
+ }
34
+ }
35
+ end
36
+
37
+ def self.ca_signing_profile
29
38
  { 'extensions' => { 'keyUsage' => { 'usage' => %w(critical keyCertSign keyEncipherment digitalSignature) } } }
30
39
  end
31
40
  end
@@ -16,8 +16,11 @@ module Ritm
16
16
  def self.create(common_name, serial_number: nil)
17
17
  cert = CertificateAuthority::Certificate.new
18
18
  cert.subject.common_name = common_name
19
+ cert.subject.organization = cert.subject.organizational_unit = 'RubyInTheMiddle'
20
+ cert.subject.country = 'AR'
21
+ cert.not_before = cert.not_before - 3600 * 24 * 30 # Substract 30 days
19
22
  cert.serial_number.number = serial_number || common_name.hash.abs
20
- cert.key_material.generate_key
23
+ cert.key_material.generate_key(1024)
21
24
  yield cert if block_given?
22
25
  new cert
23
26
  end
@@ -37,5 +40,9 @@ module Ritm
37
40
  def pem
38
41
  @cert.to_pem
39
42
  end
43
+
44
+ def x509
45
+ @cert.openssl_body
46
+ end
40
47
  end
41
48
  end
@@ -18,41 +18,30 @@ module Ritm
18
18
  key: nil
19
19
  }
20
20
  },
21
-
22
21
  intercept: {
23
- # Is interception enabled
24
22
  enabled: true,
25
-
26
- # Do not intercept requests whose URLs match/start with the given regex/strings (blacklist)
27
- skip_urls: [],
28
-
29
- # Intercepts requests whose URLs match/start with the given regex/strings (whitelist)
30
- # By default everything will be intercepted.
31
- intercept_urls: []
32
- },
33
-
34
- misc: {
35
- add_request_headers: {},
36
- add_response_headers: { 'connection' => 'clone' },
37
-
38
- strip_request_headers: [/proxy-*/],
39
- strip_response_headers: ['strict-transport-security', 'transfer-encoding'],
40
-
41
- unpack_gzip_deflate_in_requests: true,
42
- unpack_gzip_deflate_in_responses: true,
23
+ request: {
24
+ add_headers: {},
25
+ strip_headers: [/proxy-*/],
26
+ unpack_gzip_deflate: true,
27
+ update_content_length: true
28
+ },
29
+ response: {
30
+ add_headers: { 'connection' => 'close' },
31
+ strip_headers: ['strict-transport-security'],
32
+ unpack_gzip_deflate: true,
33
+ update_content_length: true
34
+ },
43
35
  process_chunked_encoded_transfer: true
44
36
  }
45
37
  }.freeze
46
38
 
47
39
  def initialize(settings = {})
48
- settings = DEFAULT_SETTINGS.merge(settings)
49
- @values = {
50
- dispatcher: Dispatcher.new,
51
-
52
- # Is interception enabled
53
- enabled: true
54
- }
40
+ reset(settings)
41
+ end
55
42
 
43
+ def reset(settings = {})
44
+ settings = DEFAULT_SETTINGS.merge(settings)
56
45
  @settings = settings.to_properties
57
46
  end
58
47
 
@@ -60,18 +49,14 @@ module Ritm
60
49
  @settings.send(m, *args, &block)
61
50
  end
62
51
 
63
- def [](setting)
64
- @values[setting]
65
- end
66
-
67
52
  # Re-enable interception
68
53
  def enable
69
- @values[:enabled] = true
54
+ @settings.intercept[:enabled] = true
70
55
  end
71
56
 
72
57
  # Disable interception
73
58
  def disable
74
- @values[:enabled] = false
59
+ @settings.intercept[:enabled] = false
75
60
  end
76
61
  end
77
62
  end
@@ -2,9 +2,9 @@
2
2
  dispatcher = Ritm.dispatcher
3
3
 
4
4
  DEFAULT_REQUEST_HANDLER = proc do |req|
5
- dispatcher.notify_request(req) if Ritm.conf[:enabled]
5
+ dispatcher.notify_request(req) if Ritm.conf.intercept.enabled
6
6
  end
7
7
 
8
8
  DEFAULT_RESPONSE_HANDLER = proc do |req, res|
9
- dispatcher.notify_response(req, res) if Ritm.conf[:enabled]
9
+ dispatcher.notify_response(req, res) if Ritm.conf.intercept.enabled
10
10
  end
@@ -28,7 +28,6 @@ module Ritm
28
28
 
29
29
  def forward(request, response)
30
30
  intercept_request(@request_interceptor, request)
31
-
32
31
  faraday_response = faraday_forward request
33
32
  to_webrick_response faraday_response, response
34
33
  intercept_response(@response_interceptor, request, response)
@@ -41,38 +40,19 @@ module Ritm
41
40
  @client.send req_method do |req|
42
41
  req.url request.request_uri
43
42
  req.body = request.body
44
- add_request_headers(req, request)
45
- end
46
- end
47
-
48
- def add_request_headers(faraday_request, webrick_request)
49
- webrick_request.header.each do |name, value|
50
- faraday_request.headers[name] = value unless strip?(name, Ritm.conf.misc.strip_request_headers)
43
+ request.header.each do |name, value|
44
+ req.headers[name] = value
45
+ end
51
46
  end
52
- Ritm.conf.misc.add_request_headers.each { |k, v| faraday_request.headers[k] = v }
53
47
  end
54
48
 
55
49
  def to_webrick_response(faraday_response, webrick_response)
56
50
  webrick_response.status = faraday_response.status
57
51
  webrick_response.body = faraday_response.body
58
52
  faraday_response.headers.each do |name, value|
59
- webrick_response[name] = value unless strip?(name, Ritm.conf.misc.strip_response_headers)
53
+ webrick_response[name] = value
60
54
  end
61
- Ritm.conf.misc.add_response_headers.each { |k, v| webrick_response[k] = v }
62
55
  webrick_response
63
56
  end
64
-
65
- def strip?(header, rules)
66
- header = header.to_s.downcase
67
- rules.each do |rule|
68
- case rule
69
- when String
70
- return true if header == rule
71
- when Regexp
72
- return true if header =~ rule
73
- end
74
- end
75
- false
76
- end
77
57
  end
78
58
  end
@@ -2,32 +2,42 @@ require 'ritm/helpers/encodings'
2
2
 
3
3
  module Ritm
4
4
  # Interceptor callbacks calling logic shared by the HTTP Proxy Server and the SSL Reverse Proxy Server
5
- # Passes request
6
5
  module InterceptUtils
7
6
  def intercept_request(handler, request)
8
7
  return if handler.nil?
8
+ preprocess(request, Ritm.conf.intercept.request)
9
9
  handler.call(request)
10
+ postprocess(request, Ritm.conf.intercept.request)
10
11
  end
11
12
 
12
13
  def intercept_response(handler, request, response)
13
14
  return if handler.nil?
14
- # TODO: Disable the automated decoding from config
15
- encoding = content_encoding(response)
16
- decoded(encoding, response) do |decoded_response|
17
- handler.call(request, decoded_response)
18
- end
19
-
20
- response.header.delete('content-length') if chunked?(response)
15
+ preprocess(response, Ritm.conf.intercept.response)
16
+ handler.call(request, response)
17
+ postprocess(response, Ritm.conf.intercept.response)
21
18
  end
22
19
 
23
20
  private
24
21
 
25
- def chunked?(response)
26
- response.header.fetch('transfer-encoding', '').casecmp 'chunked'
22
+ def preprocess(req_res, settings)
23
+ headers = header_obj(req_res)
24
+ decode(req_res) if settings.unpack_gzip_deflate
25
+ req_res.header.delete_if { |name, _v| strip?(name, settings.strip_headers) }
26
+ settings.add_headers.each { |name, value| headers[name] = value }
27
+ req_res.header.delete('transfer-encoding') if chunked?(headers)
28
+ end
29
+
30
+ def postprocess(req_res, settings)
31
+ header_obj(req_res)['content-length'] = (req_res.body || '').size.to_s if settings.update_content_length
32
+ end
33
+
34
+ def chunked?(headers)
35
+ headers['transfer-encoding'] && headers['transfer-encoding'].casecmp('chunked')
27
36
  end
28
37
 
29
- def content_encoding(response)
30
- case response.header.fetch('content-encoding', '').downcase
38
+ def content_encoding(req_res)
39
+ ce = header_obj(req_res)['content-encoding'] || ''
40
+ case ce.downcase
31
41
  when 'gzip', 'x-gzip'
32
42
  :gzip
33
43
  when 'deflate'
@@ -37,14 +47,34 @@ module Ritm
37
47
  end
38
48
  end
39
49
 
40
- def decoded(encoding, res)
41
- res.body = Encodings.decode(encoding, res.body)
42
- _content_encoding = res.header.delete('content-encoding')
43
- yield res
44
- # TODO: should it be re-encoded?
45
- # res.body = Encodings.encode(encoding, res.body)
46
- # res.header['content-encoding'] = content_encoding
47
- res.header['content-length'] = res.body.size.to_s
50
+ def header_obj(req_res)
51
+ case req_res
52
+ when WEBrick::HTTPRequest
53
+ req_res
54
+ when WEBrick::HTTPResponse
55
+ req_res.header
56
+ end
57
+ end
58
+
59
+ def decode(req_res)
60
+ encoding = content_encoding(req_res)
61
+ return if encoding == :identity
62
+ req_res.body = Encodings.decode(encoding, req_res.body)
63
+ _content_encoding = req_res.header.delete('content-encoding')
64
+ header_obj(req_res)['content-length'] = (req_res.body || '').size.to_s
65
+ end
66
+
67
+ def strip?(header, rules)
68
+ header = header.to_s.downcase
69
+ rules.each do |rule|
70
+ case rule
71
+ when String
72
+ return true if header == rule
73
+ when Regexp
74
+ return true if header =~ rule
75
+ end
76
+ end
77
+ false
48
78
  end
49
79
  end
50
80
  end
data/lib/ritm/main.rb CHANGED
@@ -21,7 +21,7 @@ module Ritm
21
21
  end
22
22
 
23
23
  def self.dispatcher
24
- conf[:dispatcher]
24
+ @dispatcher ||= Dispatcher.new
25
25
  end
26
26
 
27
27
  def self.add_handler(handler)
@@ -35,36 +35,4 @@ module Ritm
35
35
  def self.on_response(&block)
36
36
  dispatcher.on_response(&block)
37
37
  end
38
-
39
- # private class methods
40
-
41
- def self.intercept?(request)
42
- return false unless conf[:enabled]
43
- url = request.url.to_s
44
- whitelisted?(url) && !blacklisted?(url)
45
- end
46
-
47
- def self.whitelisted?(url)
48
- conf[:attack_urls].empty? || url_matches_any?(url, conf[:attack_urls])
49
- end
50
-
51
- def self.blacklisted?(url)
52
- url_matches_any? url, conf[:skip_urls]
53
- end
54
-
55
- def self.url_matches_any?(url, matchers)
56
- matchers.each do |matcher|
57
- case matcher
58
- when Regexp
59
- return true if url =~ matcher
60
- when String
61
- return true if url.include? matcher
62
- else
63
- raise 'URL matcher should be a String or Regexp'
64
- end
65
- end
66
- false
67
- end
68
-
69
- private_class_method :intercept?, :whitelisted?, :blacklisted?, :url_matches_any?
70
38
  end
@@ -41,8 +41,8 @@ module Ritm
41
41
 
42
42
  def context_with_cert(original_ctx, cert)
43
43
  ctx = original_ctx.dup
44
- ctx.key = OpenSSL::PKey::RSA.new(cert.private_key)
45
- ctx.cert = OpenSSL::X509::Certificate.new(cert.pem)
44
+ ctx.key = cert.private_key
45
+ ctx.cert = cert.x509
46
46
  ctx
47
47
  end
48
48
  end
data/lib/ritm/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  # Ritm version
2
2
  module Ritm
3
- VERSION = '0.0.1'.freeze
3
+ VERSION = '0.0.2'.freeze
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ritm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sebastián Tello
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-05-30 00:00:00.000000000 Z
11
+ date: 2016-06-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0.40'
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.11'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.11'
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: sinatra
99
113
  requirement: !ruby/object:Gem::Requirement