ritm 0.1.0 → 1.0.0

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: 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