apigatewayv2_rack 0.1.2 → 0.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: 0670d1d3244283b6842ca14f5c85492687f4b1cb99ce40addcdc43c1e7470a4e
4
- data.tar.gz: 941caa3f4df1d593e3d6bf1c0b249ad2318976940606c5fc9878d4ca9134b439
3
+ metadata.gz: 04c7d7c16ea85d001867d8a248974da31c37a028af40e42ef106ee14ef70a58d
4
+ data.tar.gz: 8685f08020c4afdf89b62561401234df587ffcb0d2cf626288b615f196139541
5
5
  SHA512:
6
- metadata.gz: a50fde7a105b2f4c4a5bb3882866b1278ac9ede7fcbe43b1275f0c98702cf6edcb0a8495544f100a511fedaea4803970107445273f247301a26b3824c2edea7f
7
- data.tar.gz: 1c0b407ece9a1952bf2d986c573b2aeef087d38e59ab0ce0263386c58533c16c7b213ee7c08c947f3e1b673bc6720068a229304610e223001945c39f4a97fe59
6
+ metadata.gz: e7ad2b28601feed547b70d35a28dbe595e3f233825fa616ac022eb8bee29194914dfd566d39e387835b855acadb2f9f599a9d8ea206dc29f1abc189d7fe65efc
7
+ data.tar.gz: 73eab07aa3d0c1360e57d41e173458a957c5fae92c41a803be4c259a3d2891af4558363fde38198a39fa500805f4df478c9f8a44aabfc51509de8fb4e0b2cb83
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.0] - 2023-03-24
4
+
5
+ - `Apigatewayv2Rack.handle_request` now takes a block and pass rack env and `Apigatewayv2Rack::Request` object to allow final modification before passing env to a Rack app.
6
+ - `Apigatewayv2Rack.generate_handler` and `handler_from_rack_config_file` propagates given block to `handle_request` for the enhancement above.
7
+ - Introduce `Apigatewayv2Rack::Middlewares::CloudfrontXff` and `Apigatewayv2Rack::Middlewares::CloudfrontVerify` as a helper middleware.
8
+
9
+ ## [0.1.3] - 2023-03-22
10
+
11
+ - Fixed Errno::EACCES from StringIO when a streaming body (body does not respond to `#each`) is returned
12
+ - Raise error when a response body is nil
13
+
3
14
  ## [0.1.2] - 2023-03-22
4
15
 
5
16
  - Fixed Apigatewayv2Rack.handler_from_rack_config_file didn't work well with Rack 3.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- apigatewayv2_rack (0.1.2)
4
+ apigatewayv2_rack (0.2.0)
5
5
  rack
6
6
 
7
7
  GEM
@@ -33,4 +33,4 @@ DEPENDENCIES
33
33
  rspec (~> 3.0)
34
34
 
35
35
  BUNDLED WITH
36
- 2.3.16
36
+ 2.3.21
data/README.md CHANGED
@@ -43,6 +43,13 @@ p resp.as_json
43
43
 
44
44
  See [./Dockerfile.integration](./Dockerfile.integration) and [./integration](./integration).
45
45
 
46
+ ### Middlewares
47
+
48
+ This gem includes several utility middlewares:
49
+
50
+ - [CloudfrontVerify](./lib/apigatewayv2_rack/middlewares/cloudfront_verify.rb): Verify `x-origin-header` value to protect unwanted direct access.
51
+ - [CloudfrontXff](./lib/apigatewayv2_rack/middlewares/cloudfront_xff.rb): Respect `cloudfront-viewer-address` as `x-forwarded-for` value.
52
+
46
53
  ## Development
47
54
 
48
55
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+ require 'rack/utils'
3
+
4
+ module Apigatewayv2Rack
5
+ module Middlewares
6
+ # Compare X-Origin-Verify header matches the expected value and otherwise returns 403.
7
+ # This is useful to use with CloudFront's origin custom request header to protect from direct access to function.
8
+ #
9
+ # See also: https://www.wellarchitectedlabs.com/security/300_labs/300_multilayered_api_security_with_cognito_and_waf/3_prevent_requests_from_accessing_api_directly/
10
+ class CloudfrontVerify
11
+ # +value+ is an expected string value of x-origin-verify.
12
+ def initialize(app, value)
13
+ @app = app
14
+ @value = value
15
+ end
16
+
17
+ def env_name
18
+ 'HTTP_X_ORIGIN_VERIFY'
19
+ end
20
+
21
+ def call(env)
22
+ given = env[env_name]
23
+
24
+ unless given && Rack::Utils.secure_compare(given, @value)
25
+ env['rack.logger']&.warn("#{self.class.name} protected unwanted access from #{env['REMOTE_ADDR'].inspect}")
26
+ return [401, {'Content-Type' => 'text/plain'}, ['Unauthorized']]
27
+ end
28
+
29
+ @app.call(env)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apigatewayv2Rack
4
+ module Middlewares
5
+ # Apigatewayv2Rack::Middlewares::CloudfrontXff transforms cloudfront-viewer-address to x-forwarded-for value.
6
+ # It is recommended to use with Apigatewayv2Rack::Middlewares::CloudfrontVerify.
7
+ class CloudfrontXff
8
+ # When +replace_remote_addr_with+ is set, REMOTE_ADDR will be replaced with given value; this
9
+ # allows Rack::Request#ip to respect xff on its default ip_filter. Default to 127.0.0.1.
10
+ def initialize(app, replace_remote_addr_with: '127.0.0.1')
11
+ @app = app
12
+ @replace_remote_addr_with = replace_remote_addr_with
13
+ end
14
+
15
+ V6_REGEXP = /^([a-f0-9:]+):(\d+)$/
16
+
17
+ def call(env)
18
+ viewer = env['HTTP_CLOUDFRONT_VIEWER_ADDRESS']
19
+ if viewer
20
+ addr,port = if viewer.include?('.')
21
+ viewer.split(?:, 2)
22
+ else
23
+ viewer.downcase.match(V6_REGEXP)&.to_a[1,2]
24
+ end
25
+
26
+ if addr && port
27
+ env['HTTP_X_APIGATEWAYV2RACK_ORIG_X_FORWARDED_FOR'] = env['HTTP_X_FORWARDED_FOR'] if env['HTTP_X_FORWARDED_FOR']
28
+ env['HTTP_X_APIGATEWAYV2RACK_ORIG_X_FORWARDED_PORT'] = env['HTTP_X_FORWARDED_PORT'] if env['HTTP_X_FORWARDED_PORT']
29
+ env['HTTP_X_FORWARDED_FOR'] = addr
30
+ env['HTTP_X_FORWARDED_PORT'] = port
31
+
32
+ if @replace_remote_addr_with
33
+ env['HTTP_X_APIGATEWAYV2RACK_ORIG_REMOTE_ADDR'] = env['REMOTE_ADDR'] if env['REMOTE_ADDR']
34
+ env['REMOTE_ADDR'] = @replace_remote_addr_with
35
+ end
36
+ end
37
+ end
38
+
39
+ @app.call(env)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -33,6 +33,8 @@ module Apigatewayv2Rack
33
33
 
34
34
  private def consume_body
35
35
  case
36
+ when body.nil?
37
+ raise TypeError, "Rack app returned nil body"
36
38
  # FIXME: Rack::CommonLogger uses Rack::BodyProxy, which performs logging when body is closed, is not compatible with #to_ary on Rack 3 specification
37
39
  # when body.respond_to?(:to_ary)
38
40
  # body.to_ary.join
@@ -42,7 +44,7 @@ module Apigatewayv2Rack
42
44
  body.close if body.respond_to?(:close)
43
45
  buf
44
46
  else
45
- stream = StringIO.new('', 'w')
47
+ stream = StringIO.new(String.new, 'w')
46
48
  body.call(stream)
47
49
  stream.string
48
50
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Apigatewayv2Rack
4
- VERSION = "0.1.2"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -5,30 +5,41 @@ require_relative "apigatewayv2_rack/error"
5
5
  require_relative "apigatewayv2_rack/request"
6
6
  require_relative "apigatewayv2_rack/response"
7
7
 
8
+ require_relative "apigatewayv2_rack/middlewares/cloudfront_xff"
9
+ require_relative "apigatewayv2_rack/middlewares/cloudfront_verify"
10
+
8
11
  module Apigatewayv2Rack
9
12
  # Takes Rack +app+, Lambda +event+ and +context+ of API Gateway V2 event and
10
13
  # returns a HTTP response from +app+ as API Gateway V2 Lambda event format.
11
- def self.handle_request(app:, event:, context:, request_options: {})
14
+ #
15
+ # When block is given, converted Rack env will be passed to make some final
16
+ # modification before passing it to an +app+.
17
+ def self.handle_request(app:, event:, context:, request_options: {}, &block)
12
18
  req = Request.new(event, context, **request_options)
13
- status, headers, body = app.call(req.to_h)
19
+ env = req.to_h
20
+ block&.call(env, req)
21
+ status, headers, body = app.call(env)
14
22
  Response.new(status: status, headers: headers, body: body, elb: req.elb?, multivalued: req.multivalued?).as_json
15
23
  end
16
24
 
17
25
  module Handler
18
- attr_reader :app
19
- def handle(event:, context:)
20
- Apigatewayv2Rack.handle_request(event: event, context: context, app: @app)
26
+ attr_reader :app
27
+ attr_reader :block
28
+ def handle(event:, context:, &givenblock)
29
+ b = givenblock || @block
30
+ Apigatewayv2Rack.handle_request(event: event, context: context, app: @app, &b)
21
31
  end
22
32
  end
23
33
 
24
- def self.generate_handler(app)
34
+ def self.generate_handler(app, &block)
25
35
  m = Module.new
26
36
  m.extend(Handler)
27
37
  m.instance_variable_set(:@app, app)
38
+ m.instance_variable_set(:@block, block)
28
39
  m
29
40
  end
30
41
 
31
- def self.handler_from_rack_config_file(path = './config.ru')
42
+ def self.handler_from_rack_config_file(path = './config.ru', &block)
32
43
  require 'rack'
33
44
  require 'rack/builder'
34
45
  app = if Rack.release[0] == '2'
@@ -36,6 +47,6 @@ module Apigatewayv2Rack
36
47
  else
37
48
  Rack::Builder.load_file(path)
38
49
  end
39
- generate_handler(app)
50
+ generate_handler(app, &block)
40
51
  end
41
52
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: apigatewayv2_rack
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sorah Fukumori
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-03-21 00:00:00.000000000 Z
11
+ date: 2023-03-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -50,6 +50,8 @@ files:
50
50
  - integration/template.jsonnet
51
51
  - lib/apigatewayv2_rack.rb
52
52
  - lib/apigatewayv2_rack/error.rb
53
+ - lib/apigatewayv2_rack/middlewares/cloudfront_verify.rb
54
+ - lib/apigatewayv2_rack/middlewares/cloudfront_xff.rb
53
55
  - lib/apigatewayv2_rack/request.rb
54
56
  - lib/apigatewayv2_rack/response.rb
55
57
  - lib/apigatewayv2_rack/version.rb
@@ -76,7 +78,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
76
78
  - !ruby/object:Gem::Version
77
79
  version: '0'
78
80
  requirements: []
79
- rubygems_version: 3.4.0.dev
81
+ rubygems_version: 3.1.6
80
82
  signing_key:
81
83
  specification_version: 4
82
84
  summary: handle AWS Lambda API Gateway V2 or ALB (ELBv2) lambda request event with