castle-rb 4.2.1 → 4.3.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
  SHA256:
3
- metadata.gz: 597a4ac18e086d61da2f12af123772abb29ed477693384a82d1da08118fe416b
4
- data.tar.gz: 3478f6a1432422fddc390f94be0be3f8a3b56dedf4c24522bac07df9a9651218
3
+ metadata.gz: 0dd544ffa6fc2660fc67c058011df5b9d83a8078b48506ab611809166960f501
4
+ data.tar.gz: 1720630a7f1925ba1142208114198cf176a8a3fec5c04be480aa4d2384e03de2
5
5
  SHA512:
6
- metadata.gz: 7487f023f013238558bd36f26fed0ae800b7d720b365870264dc15210a1020f14a97d9c99d019a9774d9884a23485df63482179fba78049f26f47907f2af329e
7
- data.tar.gz: adedcf7725c0d586dae9e230294b424116163b065653c54de473a5685a8a9213762204a8678b7582fdff7a0fb809bf830fd726efe2239aa6bfbd3e917916ab0b
6
+ metadata.gz: 71320c8ff0a5dd2a137723efc76c5530449bdf4635eed38f4c5fcad8bcb9253c5b2557b9113071cd606528eb58d3d66921fa1a728e6ea123d65ab2173e71ad59
7
+ data.tar.gz: d2f57e05bd976a294385808757b148248f01b2319cd99e88277bc18e104d4a2fabbcd2d1aa55057d7fc5a12e4ab7e3ec66086502c74ba6d8e5f6b8e51acfba01
data/README.md CHANGED
@@ -87,17 +87,33 @@ Castle.configure do |config|
87
87
  config.blacklisted = ['HTTP-X-header']
88
88
 
89
89
  # Castle needs the original IP of the client, not the IP of your proxy or load balancer.
90
- # we try to fetch proper ip based on X-Forwarded-For or Remote-Addr headers in that order
91
- # but sometimes proper ip may be stored in different header or order could be different.
92
- # SDK can extract ip automatically for you, but you must configure which ip_headers you would like to use
90
+ # The SDK will only trust the proxy chain as defined in the configuration.
91
+ # We try to fetch the client IP based on X-Forwarded-For or Remote-Addr headers in that order,
92
+ # but sometimes the client IP may be stored in a different header or order.
93
+ # The SDK can be configured to look for the client IP address in headers that you specify.
94
+ # If the specified header or X-Forwarded-For default contains a proxy chain with public IP addresses,
95
+ # then one of the following must be set
96
+ # 1. The trusted_proxies value must match the known proxy IP's
97
+ # 2. The trusted_proxy_depth value must be set to the number of known trusted proxies in the chain (see below)
93
98
  configuration.ip_headers = []
94
99
 
95
100
  # Additionally to make X-Forwarded-For and other headers work better discovering client ip address,
96
101
  # and not the address of a reverse proxy server, you can define trusted proxies
97
102
  # which will help to fetch proper ip from those headers
103
+
104
+ # In order to extract the client IP of the X-Forwarded-For header
105
+ # and not the address of a reverse proxy server, you must define all trusted public proxies
106
+ # you can achieve this by listing all the proxies ip defined by string or regular expressions
107
+ # in trusted_proxies setting
98
108
  configuration.trusted_proxies = []
99
- # *Note: proxies list can be provided as an array of regular expressions
100
- # *Note: default always marked as trusty list is here: Castle::Configuration::TRUSTED_PROXIES
109
+ # or by providing number of trusted proxies used in the chain
110
+ configuration.trusted_proxy_depth = 0
111
+
112
+ # If there is no possibility to define options above and there is no other header which can have client ip
113
+ # then you may set trust_proxy_chain = true to trust all of the proxy IP's in X-Forwarded-For
114
+ configuration.trust_proxy_chain = false
115
+
116
+ # *Note: default list of proxies which is always marked as trusted: Castle::Configuration::TRUSTED_PROXIES
101
117
  end
102
118
  ```
103
119
 
@@ -44,8 +44,9 @@ module Castle
44
44
  X-Castle-Client-Id
45
45
  ].freeze
46
46
 
47
- attr_accessor :host, :port, :request_timeout, :url_prefix
48
- attr_reader :api_secret, :whitelisted, :blacklisted, :failover_strategy, :ip_headers, :trusted_proxies
47
+ attr_accessor :host, :port, :request_timeout, :url_prefix, :trust_proxy_chain
48
+ attr_reader :api_secret, :whitelisted, :blacklisted, :failover_strategy, :ip_headers,
49
+ :trusted_proxies, :trusted_proxy_depth
49
50
 
50
51
  def initialize
51
52
  @formatter = Castle::HeadersFormatter
@@ -63,6 +64,8 @@ module Castle
63
64
  self.api_secret = ENV.fetch('CASTLE_API_SECRET', '')
64
65
  self.ip_headers = [].freeze
65
66
  self.trusted_proxies = [].freeze
67
+ self.trust_proxy_chain = false
68
+ self.trusted_proxy_depth = nil
66
69
  end
67
70
 
68
71
  def api_secret=(value)
@@ -86,13 +89,18 @@ module Castle
86
89
  end
87
90
 
88
91
  # sets trusted proxies
89
- # @param value [Array<String|Regexp>]
92
+ # @param value [Array<String,Regexp>]
90
93
  def trusted_proxies=(value)
91
94
  raise Castle::ConfigurationError, 'trusted proxies must be an Array' unless value.is_a?(Array)
92
95
 
93
96
  @trusted_proxies = value
94
97
  end
95
98
 
99
+ # @param value [String,Number,NilClass]
100
+ def trusted_proxy_depth=(value)
101
+ @trusted_proxy_depth = value.to_i
102
+ end
103
+
96
104
  def valid?
97
105
  !api_secret.to_s.empty? && !host.to_s.empty? && !port.to_s.empty?
98
106
  end
@@ -6,6 +6,8 @@ module Castle
6
6
  class IP
7
7
  # ordered list of ip headers for ip extraction
8
8
  DEFAULT = %w[X-Forwarded-For Remote-Addr].freeze
9
+ # list of header which are used with proxy depth setting
10
+ DEPTH_RELATED = %w[X-Forwarded-For].freeze
9
11
 
10
12
  private_constant :DEFAULT
11
13
 
@@ -14,6 +16,8 @@ module Castle
14
16
  @headers = headers
15
17
  @ip_headers = Castle.config.ip_headers.empty? ? DEFAULT : Castle.config.ip_headers
16
18
  @proxies = Castle.config.trusted_proxies + Castle::Configuration::TRUSTED_PROXIES
19
+ @trust_proxy_chain = Castle.config.trust_proxy_chain
20
+ @trusted_proxy_depth = Castle.config.trusted_proxy_depth
17
21
  end
18
22
 
19
23
  # Order of headers:
@@ -26,13 +30,14 @@ module Castle
26
30
 
27
31
  @ip_headers.each do |ip_header|
28
32
  ips = ips_from(ip_header)
29
- ip_value = remove_proxies(ips).last
33
+ ip_value = remove_proxies(ips)
34
+
30
35
  return ip_value if ip_value
31
36
 
32
37
  all_ips.push(*ips)
33
38
  end
34
39
 
35
- # fallback to first whatever ip
40
+ # fallback to first listed ip
36
41
  all_ips.first
37
42
  end
38
43
 
@@ -41,7 +46,9 @@ module Castle
41
46
  # @param ips [Array<String>]
42
47
  # @return [Array<String>]
43
48
  def remove_proxies(ips)
44
- ips.reject { |ip| proxy?(ip) }
49
+ return ips.first if @trust_proxy_chain
50
+
51
+ ips.reject { |ip| proxy?(ip) }.last
45
52
  end
46
53
 
47
54
  # @param ip [String]
@@ -57,7 +64,18 @@ module Castle
57
64
 
58
65
  return [] unless value
59
66
 
60
- value.strip.split(/[,\s]+/)
67
+ ips = value.strip.split(/[,\s]+/)
68
+
69
+ limit_proxy_depth(ips, header)
70
+ end
71
+
72
+ # @param ips [Array<String>]
73
+ # @param ip_header [String]
74
+ # @return [Array<String>]
75
+ def limit_proxy_depth(ips, ip_header)
76
+ ips.pop(@trusted_proxy_depth) if DEPTH_RELATED.include?(ip_header)
77
+
78
+ ips
61
79
  end
62
80
  end
63
81
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Castle
4
- VERSION = '4.2.1'
4
+ VERSION = '4.3.0'
5
5
  end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe Castle::API::Session do
4
+ describe '#get' do
5
+ it { expect(described_class.get).to eql(described_class.get) }
6
+ it { expect(described_class.get).to eql(described_class.instance.http) }
7
+ end
8
+
9
+ describe '#initialize' do
10
+ subject(:session) { described_class.get }
11
+
12
+ after do
13
+ described_class.instance.reset
14
+ end
15
+
16
+ context 'when ssl false' do
17
+ before do
18
+ Castle.config.host = 'localhost'
19
+ Castle.config.port = 3002
20
+ described_class.instance.reset
21
+ end
22
+
23
+ after do
24
+ Castle.config.host = Castle::Configuration::HOST
25
+ Castle.config.port = Castle::Configuration::PORT
26
+ end
27
+
28
+ it { expect(session).to be_instance_of(Net::HTTP) }
29
+ it { expect(session.address).to eq('localhost') }
30
+ it { expect(session.port).to eq(3002) }
31
+ it { expect(session.use_ssl?).to be false }
32
+ it { expect(session.verify_mode).to be_nil }
33
+ end
34
+
35
+ context 'when ssl true' do
36
+ before do
37
+ described_class.instance.reset
38
+ end
39
+
40
+ it { expect(session).to be_instance_of(Net::HTTP) }
41
+ it { expect(session.address).to eq(Castle.config.host) }
42
+ it { expect(session.port).to eq(Castle.config.port) }
43
+ it { expect(session.use_ssl?).to be true }
44
+ it { expect(session.verify_mode).to eq(OpenSSL::SSL::VERIFY_PEER) }
45
+ end
46
+ end
47
+ end
@@ -44,7 +44,7 @@ describe Castle::Extractors::IP do
44
44
 
45
45
  context 'with all the trusted proxies' do
46
46
  let(:http_x_header) do
47
- '127.0.0.1,10.0.0.1,172.31.0.1,192.168.0.1,::1,fd00::,localhost,unix,unix:/tmp/sock'
47
+ '127.0.0.1,10.0.0.1,172.31.0.1,192.168.0.1'
48
48
  end
49
49
 
50
50
  let(:headers) { { 'Remote-Addr' => '127.0.0.1', 'X-Forwarded-For' => http_x_header } }
@@ -54,6 +54,34 @@ describe Castle::Extractors::IP do
54
54
  end
55
55
  end
56
56
 
57
+ context 'with trust_proxy_chain option' do
58
+ let(:http_x_header) do
59
+ '6.6.6.6, 2.2.2.3, 6.6.6.5'
60
+ end
61
+
62
+ let(:headers) { { 'Remote-Addr' => '6.6.6.4', 'X-Forwarded-For' => http_x_header } }
63
+
64
+ before { Castle.config.trust_proxy_chain = true }
65
+
66
+ it 'selects first available header' do
67
+ expect(extractor.call).to eql('6.6.6.6')
68
+ end
69
+ end
70
+
71
+ context 'with trusted_proxy_depth option' do
72
+ let(:http_x_header) do
73
+ '6.6.6.6, 2.2.2.3, 6.6.6.5'
74
+ end
75
+
76
+ let(:headers) { { 'Remote-Addr' => '6.6.6.4', 'X-Forwarded-For' => http_x_header } }
77
+
78
+ before { Castle.config.trusted_proxy_depth = 1 }
79
+
80
+ it 'selects first available header' do
81
+ expect(extractor.call).to eql('2.2.2.3')
82
+ end
83
+ end
84
+
57
85
  context 'when list of not trusted ips provided in X_FORWARDED_FOR' do
58
86
  let(:headers) do
59
87
  {
@@ -4,18 +4,19 @@ describe Castle::HeadersFilter do
4
4
  subject(:headers) { described_class.new(request).call }
5
5
 
6
6
  let(:env) do
7
- Rack::MockRequest.env_for(
7
+ result = Rack::MockRequest.env_for(
8
8
  '/',
9
9
  'Action-Dispatch.request.content-Type' => 'application/json',
10
10
  'HTTP_AUTHORIZATION' => 'Basic 123456',
11
11
  'HTTP_COOKIE' => '__cid=abcd;other=efgh',
12
- 'http-ok' => 'OK',
13
12
  'HTTP_ACCEPT' => 'application/json',
14
13
  'HTTP_X_FORWARDED_FOR' => '1.2.3.4',
15
14
  'HTTP_USER_AGENT' => 'Mozilla 1234',
16
15
  'TEST' => '1',
17
16
  'REMOTE_ADDR' => '1.2.3.4'
18
17
  )
18
+ result[:HTTP_OK] = 'OK'
19
+ result
19
20
  end
20
21
  let(:filtered) do
21
22
  {
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: castle-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.2.1
4
+ version: 4.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Johan Brissmyr
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-07 00:00:00.000000000 Z
11
+ date: 2020-05-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: appraisal
@@ -75,6 +75,7 @@ files:
75
75
  - spec/integration/rails/support/home_controller.rb
76
76
  - spec/lib/castle/api/request_spec.rb
77
77
  - spec/lib/castle/api/response_spec.rb
78
+ - spec/lib/castle/api/session_spec.rb
78
79
  - spec/lib/castle/api_spec.rb
79
80
  - spec/lib/castle/client_spec.rb
80
81
  - spec/lib/castle/command_spec.rb
@@ -123,7 +124,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
123
124
  - !ruby/object:Gem::Version
124
125
  version: '0'
125
126
  requirements: []
126
- rubygems_version: 3.0.6
127
+ rubygems_version: 3.1.3
127
128
  signing_key:
128
129
  specification_version: 4
129
130
  summary: Castle
@@ -148,6 +149,7 @@ test_files:
148
149
  - spec/lib/castle/utils/merger_spec.rb
149
150
  - spec/lib/castle/command_spec.rb
150
151
  - spec/lib/castle/headers_formatter_spec.rb
152
+ - spec/lib/castle/api/session_spec.rb
151
153
  - spec/lib/castle/api/request_spec.rb
152
154
  - spec/lib/castle/api/response_spec.rb
153
155
  - spec/lib/castle/commands/review_spec.rb