ritm 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: dfb89d29700be9091b25f9134b25edf93e7ae313
4
- data.tar.gz: db8b9d6abeeda4efc2a3c9edccf6284c2c2dbd17
3
+ metadata.gz: 96ffb4d3b3d3b382171277d5095c9ba57396b6ee
4
+ data.tar.gz: ca0cafa22dcd94188702deb3d1ee3c4e3e71ed6a
5
5
  SHA512:
6
- metadata.gz: 9e1a13a985ffafa3a5c066e89c3d23fe5b81fb1bed5d90fa2513c40ed73ec6d5a7e526b8a8eba2465364be7780dffd0a58ff1f6336e9dacf8090d28930409e02
7
- data.tar.gz: f8eafca5d1c73d7e480c896771eea5e922b2a302598298bb47314dad8eb10ed0d1a3a003b1f2fab5d5682a3a7a296ad244c1d66c5d1223bfc2803fec2eef88c6
6
+ metadata.gz: f05d3344d6157f1a2a374818b006019400a30d524d6df3db249abc5c9caaa4a4e494a9637bf784133f1cc92af7d34b47899a67d6ebfb1af1a2dc008747224a1f
7
+ data.tar.gz: c0a8e290882acde4b170fd788122186b3499e755395411ae3f35d0d80e5c6ba75546c6bc3c4181f25d24018590b3366850a3bf9fcec598d6a4c0e51edb41df50
@@ -1,5 +1,2 @@
1
1
  require 'ritm/version'
2
- require 'ritm/dispatcher'
3
- require 'ritm/configuration'
4
2
  require 'ritm/main'
5
- require 'ritm/proxy/launcher'
@@ -4,51 +4,52 @@ require 'set'
4
4
  module Ritm
5
5
  # Global Ritm settings
6
6
  class Configuration
7
- DEFAULT_SETTINGS = {
8
- proxy: {
9
- bind_address: '127.0.0.1',
10
- bind_port: 8080
11
- },
12
-
13
- ssl_reverse_proxy: {
14
- bind_address: '127.0.0.1',
15
- bind_port: 8081,
16
- ca: {
17
- pem: nil,
18
- key: nil
19
- }
20
- },
7
+ def default_settings # rubocop:disable Metrics/MethodLength
8
+ {
9
+ proxy: {
10
+ bind_address: '127.0.0.1',
11
+ bind_port: 8080
12
+ },
21
13
 
22
- intercept: {
23
- enabled: true,
24
- request: {
25
- add_headers: {},
26
- strip_headers: [/proxy-*/],
27
- unpack_gzip_deflate: true,
28
- update_content_length: true
14
+ ssl_reverse_proxy: {
15
+ bind_address: '127.0.0.1',
16
+ bind_port: 8081,
17
+ ca: {
18
+ pem: nil,
19
+ key: nil
20
+ }
29
21
  },
30
- response: {
31
- add_headers: { 'connection' => 'close' },
32
- strip_headers: ['strict-transport-security'],
33
- unpack_gzip_deflate: true,
34
- update_content_length: true
22
+
23
+ intercept: {
24
+ enabled: true,
25
+ request: {
26
+ add_headers: {},
27
+ strip_headers: [/proxy-*/],
28
+ unpack_gzip_deflate: true,
29
+ update_content_length: true
30
+ },
31
+ response: {
32
+ add_headers: { 'connection' => 'close' },
33
+ strip_headers: ['strict-transport-security'],
34
+ unpack_gzip_deflate: true,
35
+ update_content_length: true
36
+ },
37
+ process_chunked_encoded_transfer: true
35
38
  },
36
- process_chunked_encoded_transfer: true
37
- },
38
39
 
39
- misc: {
40
- ssl_pass_through: []
40
+ misc: {
41
+ ssl_pass_through: [],
42
+ upstream_proxy: nil
43
+ }
41
44
  }
45
+ end
42
46
 
43
- }.freeze
44
-
45
- def initialize(settings = {})
46
- reset(settings)
47
+ def initialize
48
+ reset
47
49
  end
48
50
 
49
- def reset(settings = {})
50
- settings = DEFAULT_SETTINGS.merge(settings)
51
- @settings = settings.to_properties
51
+ def reset
52
+ @settings = default_settings.to_properties
52
53
  end
53
54
 
54
55
  def method_missing(m, *args, &block)
@@ -59,10 +60,6 @@ module Ritm
59
60
  end
60
61
  end
61
62
 
62
- def respond_to_missing?(method_name, _include_private = false)
63
- @settings.respond_to?(method_name) || super
64
- end
65
-
66
63
  # Re-enable interception
67
64
  def enable
68
65
  @settings.intercept[:enabled] = true
@@ -72,5 +69,9 @@ module Ritm
72
69
  def disable
73
70
  @settings.intercept[:enabled] = false
74
71
  end
72
+
73
+ def respond_to_missing?(method_name, _include_private = false)
74
+ @settings.respond_to?(method_name) || super
75
+ end
75
76
  end
76
77
  end
@@ -1,3 +1,4 @@
1
+ require 'openssl'
1
2
  require 'webrick'
2
3
  require 'webrick/httpproxy'
3
4
  require 'ritm/helpers/utils'
@@ -5,6 +6,10 @@ require 'ritm/helpers/utils'
5
6
  # Patch WEBrick too short max uri length
6
7
  Ritm::Utils.silence_warnings { WEBrick::HTTPRequest::MAX_URI_LENGTH = 4000 }
7
8
 
9
+ # Digest::Digest is deprecated. This has been fixed in certificate_authority master
10
+ # but the project is no longer maintained and the fixed version was never published
11
+ Ritm::Utils.silence_warnings { OpenSSL::Digest::Digest = OpenSSL::Digest }
12
+
8
13
  # Make request method writable
9
14
  WEBrick::HTTPRequest.instance_eval { attr_accessor :request_method, :unparsed_uri }
10
15
 
@@ -1,10 +1,12 @@
1
1
 
2
- dispatcher = Ritm.dispatcher
3
-
4
- DEFAULT_REQUEST_HANDLER = proc do |req|
5
- dispatcher.notify_request(req) if Ritm.conf.intercept.enabled
2
+ def default_request_handler(session)
3
+ proc do |req|
4
+ session.dispatcher.notify_request(req) if session.conf.intercept.enabled
5
+ end
6
6
  end
7
7
 
8
- DEFAULT_RESPONSE_HANDLER = proc do |req, res|
9
- dispatcher.notify_response(req, res) if Ritm.conf.intercept.enabled
8
+ def default_response_handler(session)
9
+ proc do |req, res|
10
+ session.dispatcher.notify_response(req, res) if session.conf.intercept.enabled
11
+ end
10
12
  end
@@ -16,21 +16,22 @@ module Ritm
16
16
  class HTTPForwarder
17
17
  include InterceptUtils
18
18
 
19
- def initialize(request_interceptor, response_interceptor)
19
+ def initialize(request_interceptor, response_interceptor, context_config)
20
20
  @request_interceptor = request_interceptor
21
21
  @response_interceptor = response_interceptor
22
+ @config = context_config
22
23
  # TODO: make SSL verification a configuration setting
23
24
  @client = Faraday.new(ssl: { verify: false }) do |conn|
24
25
  conn.adapter :net_http
25
- # TODO: support customizations (e.g. upstream proxies or different adapters)
26
+ conn.proxy @config.misc.upstream_proxy unless @config.misc.upstream_proxy.nil?
26
27
  end
27
28
  end
28
29
 
29
30
  def forward(request, response)
30
- intercept_request(@request_interceptor, request)
31
+ intercept_request(@request_interceptor, request, @config.intercept.request)
31
32
  faraday_response = faraday_forward request
32
33
  to_webrick_response faraday_response, response
33
- intercept_response(@response_interceptor, request, response)
34
+ intercept_response(@response_interceptor, request, response, @config.intercept.response)
34
35
  end
35
36
 
36
37
  private
@@ -3,18 +3,18 @@ require 'ritm/helpers/encodings'
3
3
  module Ritm
4
4
  # Interceptor callbacks calling logic shared by the HTTP Proxy Server and the SSL Reverse Proxy Server
5
5
  module InterceptUtils
6
- def intercept_request(handler, request)
6
+ def intercept_request(handler, request, intercept_request_settings)
7
7
  return if handler.nil?
8
- preprocess(request, Ritm.conf.intercept.request)
8
+ preprocess(request, intercept_request_settings)
9
9
  handler.call(request)
10
- postprocess(request, Ritm.conf.intercept.request)
10
+ postprocess(request, intercept_request_settings)
11
11
  end
12
12
 
13
- def intercept_response(handler, request, response)
13
+ def intercept_response(handler, request, response, intercept_response_settings)
14
14
  return if handler.nil?
15
- preprocess(response, Ritm.conf.intercept.response)
15
+ preprocess(response, intercept_response_settings)
16
16
  handler.call(request, response)
17
- postprocess(response, Ritm.conf.intercept.response)
17
+ postprocess(response, intercept_response_settings)
18
18
  end
19
19
 
20
20
  private
@@ -4,9 +4,9 @@ require 'ritm/interception/http_forwarder'
4
4
  module Ritm
5
5
  # Actual implementation of the SSL Reverse Proxy service (decoupled from the certificate handling)
6
6
  class RequestInterceptorServlet < WEBrick::HTTPServlet::AbstractServlet
7
- def initialize(server, request_interceptor, response_interceptor)
7
+ def initialize(server, request_interceptor, response_interceptor, conf)
8
8
  super server
9
- @forwarder = HTTPForwarder.new(request_interceptor, response_interceptor)
9
+ @forwarder = HTTPForwarder.new(request_interceptor, response_interceptor, conf)
10
10
  end
11
11
 
12
12
  def service(request, response)
@@ -1,38 +1,18 @@
1
+ require 'ritm/session'
2
+
1
3
  # Main module
2
4
  module Ritm
3
- # Define global settings
4
- def self.configure(&block)
5
- conf.instance_eval(&block)
6
- end
7
-
8
- # Re-enable fuzzing (if it was disabled)
9
- def self.enable
10
- conf.enable
11
- end
12
-
13
- # Disable fuzzing (if it was enabled)
14
- def self.disable
15
- conf.disable
16
- end
17
-
18
- # Access the current config settings
19
- def self.conf
20
- @configuration ||= Configuration.new
21
- end
22
-
23
- def self.dispatcher
24
- @dispatcher ||= Dispatcher.new
25
- end
26
-
27
- def self.add_handler(handler)
28
- dispatcher.add_handler(handler)
29
- end
5
+ GLOBAL_SESSION = Session.new
30
6
 
31
- def self.on_request(&block)
32
- dispatcher.on_request(&block)
7
+ def self.method_missing(m, *args, &block)
8
+ if GLOBAL_SESSION.respond_to?(m)
9
+ GLOBAL_SESSION.send(m, *args, &block)
10
+ else
11
+ super
12
+ end
33
13
  end
34
14
 
35
- def self.on_response(&block)
36
- dispatcher.on_response(&block)
15
+ def self.respond_to_missing?(method_name, _include_private = false)
16
+ GLOBAL_SESSION.respond_to?(method_name) || super
37
17
  end
38
18
  end
@@ -7,19 +7,10 @@ module Ritm
7
7
  module Proxy
8
8
  # Launches the Proxy server and the SSL Reverse Proxy with the given settings
9
9
  class Launcher
10
- # By default settings are read from Ritm::Configuration but you can override some via these named arguments:
11
- # interface [String]: the host/address to bind the main proxy
12
- # proxy_port [Fixnum]: the port where the main proxy listens (the one to be configured in the client)
13
- # ssl_reverse_proxy_port [Fixnum]: the port where the reverse proxy for ssl traffic interception listens
14
- # ca_crt_path [String]: the path to the certification authority certificate
15
- # ca_key_path [String]: the path to the certification authority private key
16
- # request_interceptor [Proc |request|]: the handler for request interception
17
- # response_interceptor [Proc |request, response|]: the handler for response interception
18
- def initialize(**args)
19
- build_settings(**args)
20
-
21
- build_reverse_proxy(@ssl_proxy_host, @ssl_proxy_port, @request_interceptor, @response_interceptor)
22
- build_proxy(@proxy_host, @proxy_port, @https_forward, @request_interceptor, @response_interceptor)
10
+ def initialize(session)
11
+ build_settings(session)
12
+ build_reverse_proxy
13
+ build_proxy
23
14
  end
24
15
 
25
16
  # Starts the service (non blocking)
@@ -36,36 +27,37 @@ module Ritm
36
27
 
37
28
  private
38
29
 
39
- def build_settings(**args)
40
- c = Ritm.conf
41
- @proxy_host = args.fetch(:interface, c.proxy.bind_address)
42
- @proxy_port = args.fetch(:proxy_port, c.proxy.bind_port)
43
- @ssl_proxy_host = c.ssl_reverse_proxy.bind_address
44
- @ssl_proxy_port = args.fetch(:ssl_reverse_proxy_port, c.ssl_reverse_proxy.bind_port)
45
- @https_forward = "#{@ssl_proxy_host}:#{@ssl_proxy_port}"
46
- @request_interceptor = args[:request_interceptor] || DEFAULT_REQUEST_HANDLER
47
- @response_interceptor = args[:response_interceptor] || DEFAULT_RESPONSE_HANDLER
30
+ def build_settings(session)
31
+ @conf = session.conf
32
+ ssl_proxy_host = @conf.ssl_reverse_proxy.bind_address
33
+ ssl_proxy_port = @conf.ssl_reverse_proxy.bind_port
34
+ @https_forward = "#{ssl_proxy_host}:#{ssl_proxy_port}"
35
+ @request_interceptor = default_request_handler(session)
36
+ @response_interceptor = default_response_handler(session)
48
37
 
49
- crt_path = args.fetch(:ca_crt_path, c.ssl_reverse_proxy.ca.pem)
50
- key_path = args.fetch(:ca_key_path, c.ssl_reverse_proxy.ca.key)
38
+ crt_path = @conf.ssl_reverse_proxy.ca.pem
39
+ key_path = @conf.ssl_reverse_proxy.ca.key
51
40
  @certificate = ca_certificate(crt_path, key_path)
52
41
  end
53
42
 
54
- def build_proxy(host, port, https_forward_to, req_intercept, res_intercept)
55
- @http = Ritm::Proxy::ProxyServer.new(Port: port,
43
+ def build_proxy
44
+ @http = Ritm::Proxy::ProxyServer.new(BindAddress: @conf.proxy.bind_address,
45
+ Port: @conf.proxy.bind_port,
56
46
  AccessLog: [],
57
- BindAddress: host,
58
47
  Logger: WEBrick::Log.new(File.open(File::NULL, 'w')),
59
- https_forward: https_forward_to,
48
+ https_forward: @https_forward,
60
49
  ProxyVia: nil,
61
- request_interceptor: req_intercept,
62
- response_interceptor: res_intercept)
50
+ request_interceptor: @request_interceptor,
51
+ response_interceptor: @response_interceptor,
52
+ ritm_conf: @conf)
63
53
  end
64
54
 
65
- def build_reverse_proxy(_host, port, req_intercept, res_intercept)
66
- @https = Ritm::Proxy::SSLReverseProxy.new(port, @certificate,
67
- request_interceptor: req_intercept,
68
- response_interceptor: res_intercept)
55
+ def build_reverse_proxy
56
+ @https = Ritm::Proxy::SSLReverseProxy.new(@conf.ssl_reverse_proxy.bind_port,
57
+ @certificate,
58
+ @conf,
59
+ request_interceptor: @request_interceptor,
60
+ response_interceptor: @response_interceptor)
69
61
  end
70
62
 
71
63
  def ca_certificate(pem, key)
@@ -30,7 +30,7 @@ module Ritm
30
30
  proxy_auth(req, res)
31
31
 
32
32
  # Request modifier handler
33
- intercept_request(@config[:request_interceptor], req)
33
+ intercept_request(@config[:request_interceptor], req, @config[:ritm_conf].intercept.request)
34
34
 
35
35
  begin
36
36
  send("do_#{req.request_method}", req, res)
@@ -41,13 +41,24 @@ module Ritm
41
41
  end
42
42
 
43
43
  # Response modifier handler
44
- intercept_response(@config[:response_interceptor], req, res)
44
+ intercept_response(@config[:response_interceptor], req, res, @config[:ritm_conf].intercept.response)
45
+ end
46
+
47
+ # Override
48
+ def proxy_uri(req, _res)
49
+ if req.request_method == 'CONNECT'
50
+ # Let the reverse proxy handle upstream proxies for https
51
+ nil
52
+ else
53
+ proxy = @config[:ritm_conf].misc.upstream_proxy
54
+ proxy.nil? ? nil : URI.parse(proxy)
55
+ end
45
56
  end
46
57
 
47
58
  private
48
59
 
49
60
  def ssl_pass_through?(destination)
50
- Ritm.conf.misc.ssl_pass_through.each do |matcher|
61
+ @config[:ritm_conf].misc.ssl_pass_through.each do |matcher|
51
62
  case matcher
52
63
  when String
53
64
  return true if destination == matcher
@@ -13,7 +13,7 @@ module Ritm
13
13
  # @param ca [Ritm::CA]: The certificate authority used to sign fake server certificates
14
14
  # @param request_interceptor [Proc]: If given, it will be invoked before proxying the request
15
15
  # @param response_interceptor [Proc]: If give, it will be invoked before sending back the response
16
- def initialize(port, ca, request_interceptor: nil, response_interceptor: nil)
16
+ def initialize(port, ca, conf, request_interceptor: nil, response_interceptor: nil)
17
17
  @ca = ca
18
18
  default_vhost = 'localhost'
19
19
  @server = CertSigningHTTPSServer.new(Port: port,
@@ -22,7 +22,7 @@ module Ritm
22
22
  ca: ca,
23
23
  **vhost_settings(default_vhost))
24
24
 
25
- @server.mount '/', RequestInterceptorServlet, request_interceptor, response_interceptor
25
+ @server.mount '/', RequestInterceptorServlet, request_interceptor, response_interceptor, conf
26
26
  end
27
27
 
28
28
  def start_async
@@ -0,0 +1,57 @@
1
+ require 'ritm/dispatcher'
2
+ require 'ritm/proxy/launcher'
3
+ require 'ritm/configuration'
4
+
5
+ module Ritm
6
+ # Holds the context of a interception session.
7
+ # Changes in the context configuration should affect only this session
8
+ class Session
9
+ attr_reader :conf, :dispatcher
10
+
11
+ def initialize
12
+ @conf = Configuration.new
13
+ @dispatcher = Dispatcher.new
14
+ @proxy = nil
15
+ end
16
+
17
+ # Define configuration settings
18
+ def configure(&block)
19
+ conf.instance_eval(&block)
20
+ end
21
+
22
+ # Re-enable fuzzing (if it was disabled)
23
+ def enable
24
+ conf.enable
25
+ end
26
+
27
+ # Disable fuzzing (if it was enabled)
28
+ def disable
29
+ conf.disable
30
+ end
31
+
32
+ # Start the proxy service
33
+ def start
34
+ raise 'Proxy already started' unless @proxy.nil?
35
+ @proxy = Proxy::Launcher.new(self)
36
+ @proxy.start
37
+ end
38
+
39
+ # Shutdown the proxy service
40
+ def shutdown
41
+ @proxy.shutdown unless @proxy.nil?
42
+ @proxy = nil
43
+ end
44
+
45
+ def add_handler(handler)
46
+ dispatcher.add_handler(handler)
47
+ end
48
+
49
+ def on_request(&block)
50
+ dispatcher.on_request(&block)
51
+ end
52
+
53
+ def on_response(&block)
54
+ dispatcher.on_response(&block)
55
+ end
56
+ end
57
+ end
@@ -1,4 +1,4 @@
1
1
  # Ritm version
2
2
  module Ritm
3
- VERSION = '0.1.0'.freeze
3
+ VERSION = '1.0.0'.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.1.0
4
+ version: 1.0.0
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-07-28 00:00:00.000000000 Z
11
+ date: 2016-12-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -187,6 +187,7 @@ files:
187
187
  - lib/ritm/proxy/launcher.rb
188
188
  - lib/ritm/proxy/proxy_server.rb
189
189
  - lib/ritm/proxy/ssl_reverse_proxy.rb
190
+ - lib/ritm/session.rb
190
191
  - lib/ritm/version.rb
191
192
  homepage: https://github.com/argos83/ritm
192
193
  licenses:
@@ -208,7 +209,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
208
209
  version: '0'
209
210
  requirements: []
210
211
  rubyforge_project:
211
- rubygems_version: 2.4.5.1
212
+ rubygems_version: 2.5.1
212
213
  signing_key:
213
214
  specification_version: 4
214
215
  summary: Ruby In The Middle