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 +4 -4
- data/lib/ritm/certs/ca.rb +12 -3
- data/lib/ritm/certs/certificate.rb +8 -1
- data/lib/ritm/configuration.rb +18 -33
- data/lib/ritm/interception/handlers.rb +2 -2
- data/lib/ritm/interception/http_forwarder.rb +4 -24
- data/lib/ritm/interception/intercept_utils.rb +50 -20
- data/lib/ritm/main.rb +1 -33
- data/lib/ritm/proxy/cert_signing_https_server.rb +2 -2
- data/lib/ritm/version.rb +1 -1
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9f58ad7ec7b60adcb6d5644582b19da348fee831
|
4
|
+
data.tar.gz: 2e024c04e5527f91864055141e5cf39cbe18e326
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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!(
|
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!(
|
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
|
data/lib/ritm/configuration.rb
CHANGED
@@ -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
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
-
|
49
|
-
|
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
|
-
@
|
54
|
+
@settings.intercept[:enabled] = true
|
70
55
|
end
|
71
56
|
|
72
57
|
# Disable interception
|
73
58
|
def disable
|
74
|
-
@
|
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
|
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
|
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
|
-
|
45
|
-
|
46
|
-
|
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
|
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
|
-
|
15
|
-
|
16
|
-
|
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
|
26
|
-
|
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(
|
30
|
-
|
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
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
-
|
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 =
|
45
|
-
ctx.cert =
|
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
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.
|
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-
|
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
|