rack-cloudflare_middleware 1.0.0 → 1.2.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
  SHA256:
3
- metadata.gz: e108186bec2a6dff38a2f6fa5edcfd196aa4e2199333a61d8c9a888edf65c3be
4
- data.tar.gz: a8e0131c2fd8270784ed1155f114892bc26e3f65bf1eda55a547450bdf272fdb
3
+ metadata.gz: 1806fad87fa57e8317ff1937cd2cd45db0bcef1b89aabb7ba1c3d4aa71434d22
4
+ data.tar.gz: 47811778ab426303239fa18e3e29310e5f212cd54740efc71674061e30787c85
5
5
  SHA512:
6
- metadata.gz: 5762e62571ea9c5dce83039b0616a3074a0ffa0f165430c4cc5dbc5c70ef0d3d34553965abe3f85c3c8e6222f0247290e1acb8a4538538e30bd1b88e6f5bbe7a
7
- data.tar.gz: f04fa0b616f69d62f81568dc29c5e0428511a884e774685a6d869dfbef5765c9b0cac577fc42d1f08d747084369a1ff11baba8853cbbc36b71e8fee20e7773b2
6
+ metadata.gz: '084ac8b92afac4bd6aff7435c56a546c3f4faafaf4124d19a3f542fcef07b43b31ffa1222bccc49d46693deb97db5aad3dfd31f1800cc4dcdd1c7475afe0f806'
7
+ data.tar.gz: a10319a8ec1dabed6022d7efe051b8af63752f134fbafa4c9fc35d3e563780ec3e3d6fb3de26b81ffc637f1ac0c37ae87a6dabae78cbbeadc2a8b0cbb4d37715
@@ -19,9 +19,9 @@ jobs:
19
19
  ruby: ["2.7", "3.0", "3.1", "3.2"]
20
20
  steps:
21
21
  - name: Checkout code
22
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
22
+ uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
23
23
  - name: Install Ruby and gems
24
- uses: ruby/setup-ruby@93287a1fa82c6ddbb6d8db978df4b0119cd8879f
24
+ uses: ruby/setup-ruby@8a45918450651f5e4784b6031db26f4b9f76b251
25
25
  with:
26
26
  bundler-cache: true
27
27
  ruby-version: ${{ matrix.ruby }}
@@ -35,17 +35,17 @@ jobs:
35
35
  contents: read
36
36
  steps:
37
37
  - name: Checkout code
38
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
38
+ uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
39
39
  - name: Install Ruby and gems
40
- uses: ruby/setup-ruby@93287a1fa82c6ddbb6d8db978df4b0119cd8879f
40
+ uses: ruby/setup-ruby@8a45918450651f5e4784b6031db26f4b9f76b251
41
41
  with:
42
42
  bundler-cache: true
43
43
  ruby-version: "3.1"
44
44
  - name: Bundle Audit Check
45
45
  run: bundle exec bundle-audit update && bundle exec bundle-audit check
46
46
  - name: Setup Python
47
- uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435
47
+ uses: actions/setup-python@bd6b4b6205c4dbad673328db7b31b7fab9e241c0
48
48
  with:
49
49
  python-version: "3.10"
50
50
  - name: Run pre-commit
51
- uses: pre-commit/action@efd3bcfec120bd343786e46318186153b7bc8c68
51
+ uses: pre-commit/action@5f528da5c95691c4cf42ff76a4d10854b62cbb82
@@ -7,9 +7,9 @@ jobs:
7
7
  runs-on: ubuntu-latest
8
8
  steps:
9
9
  - name: Checkout code
10
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
10
+ uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
11
11
  - name: Install Ruby and gems
12
- uses: ruby/setup-ruby@93287a1fa82c6ddbb6d8db978df4b0119cd8879f
12
+ uses: ruby/setup-ruby@8a45918450651f5e4784b6031db26f4b9f76b251
13
13
  with:
14
14
  bundler-cache: true
15
15
  ruby-version: "3.2"
@@ -12,6 +12,6 @@ repos:
12
12
  exclude: '^spec/data/'
13
13
  - id: check-merge-conflict
14
14
  - repo: https://github.com/instrumentl/pre-commit-standardrb.git
15
- rev: '1ae56c7524a2d48cd2b7ca1f74bdb0cdd454477e'
15
+ rev: 'b9c5657a92bcc2ebfa9ec295754b0c56877fbed8'
16
16
  hooks:
17
17
  - id: standardrb
data/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ v1.2.0 - 2023-06-05
2
+ -------------------
3
+ - Set `required_ruby_version` in the gemspec
4
+ - Add `trusted_request_proc` kwarg to DenyOthers middleware
5
+
6
+ v1.1.0 - 2023-03-31
7
+ -------------------
8
+ - Expand requirements to allow using Rack 3.x
9
+ - Add `trust_xff_if_private` kwarg to both middlewares
10
+ - Add `on_fail_proc` to DenyOthers middleware
11
+ - Bump various build-time dependencies
12
+
13
+ v1.0.0 - 2023-03-31
14
+ -------------------
15
+ - Initial release
data/Gemfile CHANGED
@@ -4,7 +4,7 @@ source "https://rubygems.org"
4
4
 
5
5
  gemspec
6
6
 
7
- gem "faraday", "~> 1.0"
7
+ gem "faraday", "~> 2.7"
8
8
  gem "rake", "~> 13.0"
9
9
 
10
10
  group :development, :test do
data/Gemfile.lock CHANGED
@@ -1,9 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rack-cloudflare_middleware (1.0.0)
4
+ rack-cloudflare_middleware (1.2.0)
5
5
  faraday (>= 1.0, < 3)
6
- rack (~> 2)
6
+ rack (>= 2, < 4)
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
@@ -20,47 +20,28 @@ GEM
20
20
  crack (0.4.5)
21
21
  rexml
22
22
  diff-lcs (1.5.0)
23
- faraday (1.10.3)
24
- faraday-em_http (~> 1.0)
25
- faraday-em_synchrony (~> 1.0)
26
- faraday-excon (~> 1.1)
27
- faraday-httpclient (~> 1.0)
28
- faraday-multipart (~> 1.0)
29
- faraday-net_http (~> 1.0)
30
- faraday-net_http_persistent (~> 1.0)
31
- faraday-patron (~> 1.0)
32
- faraday-rack (~> 1.0)
33
- faraday-retry (~> 1.0)
23
+ faraday (2.7.5)
24
+ faraday-net_http (>= 2.0, < 3.1)
34
25
  ruby2_keywords (>= 0.0.4)
35
- faraday-em_http (1.0.0)
36
- faraday-em_synchrony (1.0.0)
37
- faraday-excon (1.1.0)
38
- faraday-httpclient (1.0.1)
39
- faraday-multipart (1.0.4)
40
- multipart-post (~> 2)
41
- faraday-net_http (1.0.1)
42
- faraday-net_http_persistent (1.2.0)
43
- faraday-patron (1.0.0)
44
- faraday-rack (1.0.0)
45
- faraday-retry (1.0.3)
26
+ faraday-net_http (3.0.2)
46
27
  hashdiff (1.0.1)
47
28
  json (2.6.3)
48
29
  language_server-protocol (3.17.0.3)
30
+ lint_roller (1.0.0)
49
31
  method_source (1.0.0)
50
- multipart-post (2.3.0)
51
- parallel (1.22.1)
52
- parser (3.2.1.1)
32
+ parallel (1.23.0)
33
+ parser (3.2.2.1)
53
34
  ast (~> 2.4.1)
54
35
  pry (0.14.2)
55
36
  coderay (~> 1.1)
56
37
  method_source (~> 1.0)
57
38
  public_suffix (5.0.1)
58
- rack (2.2.6.4)
39
+ rack (3.0.7)
59
40
  rack-test (2.1.0)
60
41
  rack (>= 1.3)
61
42
  rainbow (3.1.1)
62
43
  rake (13.0.6)
63
- regexp_parser (2.7.0)
44
+ regexp_parser (2.8.0)
64
45
  rexml (3.2.5)
65
46
  rspec (3.12.0)
66
47
  rspec-core (~> 3.12.0)
@@ -78,26 +59,33 @@ GEM
78
59
  diff-lcs (>= 1.2.0, < 2.0)
79
60
  rspec-support (~> 3.12.0)
80
61
  rspec-support (3.12.0)
81
- rubocop (1.48.1)
62
+ rubocop (1.50.2)
82
63
  json (~> 2.3)
83
64
  parallel (~> 1.10)
84
65
  parser (>= 3.2.0.0)
85
66
  rainbow (>= 2.2.2, < 4.0)
86
67
  regexp_parser (>= 1.8, < 3.0)
87
68
  rexml (>= 3.2.5, < 4.0)
88
- rubocop-ast (>= 1.26.0, < 2.0)
69
+ rubocop-ast (>= 1.28.0, < 2.0)
89
70
  ruby-progressbar (~> 1.7)
90
71
  unicode-display_width (>= 2.4.0, < 3.0)
91
- rubocop-ast (1.28.0)
72
+ rubocop-ast (1.28.1)
92
73
  parser (>= 3.2.1.0)
93
74
  rubocop-performance (1.16.0)
94
75
  rubocop (>= 1.7.0, < 2.0)
95
76
  rubocop-ast (>= 0.4.0)
96
77
  ruby-progressbar (1.13.0)
97
78
  ruby2_keywords (0.0.5)
98
- standard (1.25.3)
79
+ standard (1.28.4)
99
80
  language_server-protocol (~> 3.17.0.2)
100
- rubocop (~> 1.48.1)
81
+ lint_roller (~> 1.0)
82
+ rubocop (~> 1.50.2)
83
+ standard-custom (~> 1.0.0)
84
+ standard-performance (~> 1.0.1)
85
+ standard-custom (1.0.0)
86
+ lint_roller (~> 1.0)
87
+ standard-performance (1.0.1)
88
+ lint_roller (~> 1.0)
101
89
  rubocop-performance (~> 1.16.0)
102
90
  thor (1.2.1)
103
91
  unicode-display_width (2.4.2)
@@ -113,7 +101,7 @@ PLATFORMS
113
101
  DEPENDENCIES
114
102
  bundle-audit (~> 0.1.0)
115
103
  bundler (~> 2)
116
- faraday (~> 1.0)
104
+ faraday (~> 2.7)
117
105
  pry
118
106
  rack-cloudflare_middleware!
119
107
  rack-test (~> 2)
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  This is a small Rack middleware for use with the [Cloudflare](https://www.cloudflare.com/) CDN.
2
2
 
3
+ [![CI](https://github.com/instrumentl/rack-cloudflare_middleware/actions/workflows/ci.yml/badge.svg)](https://github.com/instrumentl/rack-cloudflare_middleware/actions/workflows/ci.yml)
4
+ [![Gem Version](https://badge.fury.io/rb/rack-cloudflare_middleware.svg)](https://badge.fury.io/rb/rack-cloudflare_middleware)
5
+
3
6
  We include two middlewares:
4
7
 
5
8
  * `Rack::CloudflareMiddleware::RewriteRemoteAddr` swaps in `CF-Connecting-IP` for `REMOTE_ADDR` if and only if the "real" remote address is a trusted Cloudflare source IP address.
@@ -27,3 +30,25 @@ run Sinatra::Application
27
30
  ```
28
31
 
29
32
  The `allow_private` kwarg to `DenyOthers` controls whether or not private and loopback addresses are allowed through. Whether or not you should set this depends on the exact specifics of your deployment environment; often it should be set in development, but not in production.
33
+
34
+ `DenyOthers` also takes an `on_fail_proc` kwarg which receives the request environment and should return an appropriate error response (as a standard Rack tuple of `(status, headers, body)`). Example usage:
35
+
36
+ ```ruby
37
+ require "rack/cloudflare_middleware"
38
+
39
+ use Rack::CloudflareMiddleware::DenyOthers, on_fail_proc: ->(env) do
40
+ MyLogger.warn "Bad request from #{env["REMOTE_ADDR"]}"
41
+ [403, {"Content-Type" => "text/plain"}, ["you did a bad thing"]]
42
+ end
43
+ ```
44
+
45
+ `DenyOthers` also takes a `trusted_request_proc` which receives a `Rack::Request` object and should return a boolean of whether or not the request is to be allowed through regardless of Source IP. This is primarily intended for healthchecks. Example usage:
46
+
47
+ ```ruby
48
+ require "rack/cloudflare_middleware"
49
+ use Rack::CloudflareMiddleware::DenyOthers, trusted_request_proc: ->(request) do
50
+ request.path.start_with? "/health/check"
51
+ end
52
+ ```
53
+
54
+ Both middlewares also include a convenience called `trust_xff_if_private` mode; this will change them to use the right-most contents of `X-Forwarded-For` as `REMOTE_ADDR` if and only if the actual `REMOTE_ADDR` is a private address. This is a moderately-unsafe option, but may be required if your application provider has made poor choices in routing technologies (and, for example, is required on Heroku). If you're in this state, you should tell your provider to use the PROXY protocol internally instead of `X-Forwarded-For`. There have been many security issues related to Heroku's poor parsing of `X-Forwarded-For` in their router/load-balancer layer, and may be more in the future.
@@ -3,20 +3,31 @@
3
3
  module Rack
4
4
  module CloudflareMiddleware
5
5
  class DenyOthers
6
- def initialize(app, allow_private: false)
6
+ def initialize(app, allow_private: false, trusted_request_proc: nil, on_fail_proc: nil, trust_xff_if_private: false)
7
7
  @allow_private = allow_private
8
+ @trusted_request_proc = trusted_request_proc
9
+ @on_fail_proc = on_fail_proc
10
+ @trust_xff_if_private = trust_xff_if_private
8
11
  @app = app
9
12
  end
10
13
 
11
14
  def call(env)
12
15
  TrustedIps.instance.check_update
13
- remote_addr = IPAddr.new env["REMOTE_ADDR"]
14
- if (@allow_private && (remote_addr.private? || remote_addr.loopback?)) || TrustedIps.instance.include?(remote_addr)
16
+ remote_addr = Rack::CloudflareMiddleware.get_remote_addr(env, @trust_xff_if_private)
17
+ if (@allow_private && (remote_addr.private? || remote_addr.loopback?)) || TrustedIps.instance.include?(remote_addr) || @trusted_request_proc&.call(Rack::Request.new(env))
15
18
  @app.call(env)
19
+ elsif @on_fail_proc.nil?
20
+ default_on_fail(remote_addr)
16
21
  else
17
- ["403", {"Content-Type" => "text/plain"}, ["Forbidden by policy statement"]]
22
+ @on_fail_proc.call(env)
18
23
  end
19
24
  end
25
+
26
+ private
27
+
28
+ def default_on_fail(remote_addr)
29
+ ["403", {"Content-Type" => "text/plain"}, ["Forbidden by policy statement (#{remote_addr})"]]
30
+ end
20
31
  end
21
32
  end
22
33
  end
@@ -3,15 +3,17 @@
3
3
  module Rack
4
4
  module CloudflareMiddleware
5
5
  class RewriteRemoteAddr
6
- def initialize(app)
6
+ def initialize(app, trust_xff_if_private: false)
7
+ @trust_xff_if_private = trust_xff_if_private
7
8
  @app = app
8
9
  end
9
10
 
10
11
  def call(env)
11
12
  TrustedIps.instance.check_update
12
- if TrustedIps.instance.include? env["REMOTE_ADDR"]
13
+ remote_addr = Rack::CloudflareMiddleware.get_remote_addr(env, @trust_xff_if_private)
14
+ if TrustedIps.instance.include? remote_addr
13
15
  unless env["HTTP_CF_CONNECTING_IP"].nil?
14
- env["HTTP_CF_ORIGINAL_REMOTE_ADDR"] = env["REMOTE_ADDR"]
16
+ env["HTTP_CF_ORIGINAL_REMOTE_ADDR"] = remote_addr
15
17
  env["REMOTE_ADDR"] = env["HTTP_CF_CONNECTING_IP"]
16
18
  end
17
19
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Rack
4
4
  module CloudflareMiddleware
5
- VERSION = "1.0.0"
5
+ VERSION = "1.2.0"
6
6
  end
7
7
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "ipaddr"
4
+
3
5
  require_relative "cloudflare_middleware/version"
4
6
  require_relative "cloudflare_middleware/trusted_ips"
5
7
  require_relative "cloudflare_middleware/rewrite_remote_addr"
@@ -7,6 +9,13 @@ require_relative "cloudflare_middleware/deny_others"
7
9
 
8
10
  module Rack
9
11
  module CloudflareMiddleware
10
- attr_accessor :logger
12
+ def self.get_remote_addr(env, trust_xff_if_private)
13
+ if trust_xff_if_private && IPAddr.new(env["REMOTE_ADDR"]).private? &&
14
+ !env["HTTP_X_FORWARDED_FOR"].nil? && env["HTTP_X_FORWARDED_FOR"] != ""
15
+ IPAddr.new(env["HTTP_X_FORWARDED_FOR"].split(",")&.last&.strip)
16
+ else
17
+ IPAddr.new(env["REMOTE_ADDR"])
18
+ end
19
+ end
11
20
  end
12
21
  end
@@ -21,8 +21,10 @@ Gem::Specification.new do |spec|
21
21
  end
22
22
  spec.require_paths = ["lib"]
23
23
 
24
+ spec.required_ruby_version = ">= 2.7"
25
+
24
26
  spec.add_dependency "faraday", ">= 1.0", "< 3"
25
- spec.add_dependency "rack", "~> 2"
27
+ spec.add_dependency "rack", ">= 2", "< 4"
26
28
 
27
29
  spec.add_development_dependency "bundler", "~> 2"
28
30
  spec.add_development_dependency "rake", "~> 13.0"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-cloudflare_middleware
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Brown
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-03-31 00:00:00.000000000 Z
11
+ date: 2023-06-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -34,16 +34,22 @@ dependencies:
34
34
  name: rack
35
35
  requirement: !ruby/object:Gem::Requirement
36
36
  requirements:
37
- - - "~>"
37
+ - - ">="
38
38
  - !ruby/object:Gem::Version
39
39
  version: '2'
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: '4'
40
43
  type: :runtime
41
44
  prerelease: false
42
45
  version_requirements: !ruby/object:Gem::Requirement
43
46
  requirements:
44
- - - "~>"
47
+ - - ">="
45
48
  - !ruby/object:Gem::Version
46
49
  version: '2'
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: '4'
47
53
  - !ruby/object:Gem::Dependency
48
54
  name: bundler
49
55
  requirement: !ruby/object:Gem::Requirement
@@ -113,6 +119,7 @@ files:
113
119
  - ".gitignore"
114
120
  - ".pre-commit-config.yaml"
115
121
  - ".rubocop.yml"
122
+ - CHANGELOG.md
116
123
  - Gemfile
117
124
  - Gemfile.lock
118
125
  - LICENSE.txt
@@ -137,14 +144,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
137
144
  requirements:
138
145
  - - ">="
139
146
  - !ruby/object:Gem::Version
140
- version: '0'
147
+ version: '2.7'
141
148
  required_rubygems_version: !ruby/object:Gem::Requirement
142
149
  requirements:
143
150
  - - ">="
144
151
  - !ruby/object:Gem::Version
145
152
  version: '0'
146
153
  requirements: []
147
- rubygems_version: 3.4.1
154
+ rubygems_version: 3.4.10
148
155
  signing_key:
149
156
  specification_version: 4
150
157
  summary: Rack middleware for handling Cloudflare remote IP headers