policy_decision_point 0.1.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.
Files changed (117) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/main.yml +16 -0
  3. data/.gitignore +65 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +22 -0
  6. data/.ruby-version +1 -0
  7. data/Gemfile +14 -0
  8. data/README.md +1 -0
  9. data/Rakefile +12 -0
  10. data/bin/console +15 -0
  11. data/bin/htmldiff +29 -0
  12. data/bin/ldiff +29 -0
  13. data/bin/rake +27 -0
  14. data/bin/rspec +27 -0
  15. data/bin/rubocop +27 -0
  16. data/bin/ruby-parse +27 -0
  17. data/bin/ruby-rewrite +27 -0
  18. data/bin/setup +11 -0
  19. data/example/Gemfile +65 -0
  20. data/example/Gemfile.lock +273 -0
  21. data/example/README.md +1 -0
  22. data/example/Rakefile +6 -0
  23. data/example/app/assets/config/manifest.js +2 -0
  24. data/example/app/assets/images/.keep +0 -0
  25. data/example/app/assets/stylesheets/application.css +15 -0
  26. data/example/app/channels/application_cable/channel.rb +4 -0
  27. data/example/app/channels/application_cable/connection.rb +4 -0
  28. data/example/app/controllers/application_controller.rb +2 -0
  29. data/example/app/controllers/concerns/.keep +0 -0
  30. data/example/app/controllers/rpc/hello_world/service.proto +15 -0
  31. data/example/app/controllers/rpc/hello_world/service_pb.rb +22 -0
  32. data/example/app/controllers/rpc/hello_world/service_twirp.rb +17 -0
  33. data/example/app/controllers/rpc/hello_world_server.rb +15 -0
  34. data/example/app/helpers/application_helper.rb +2 -0
  35. data/example/app/javascript/channels/consumer.js +6 -0
  36. data/example/app/javascript/channels/index.js +5 -0
  37. data/example/app/javascript/packs/application.js +13 -0
  38. data/example/app/jobs/application_job.rb +7 -0
  39. data/example/app/mailers/application_mailer.rb +4 -0
  40. data/example/app/models/application_record.rb +3 -0
  41. data/example/app/models/concerns/.keep +0 -0
  42. data/example/app/views/layouts/application.html.erb +16 -0
  43. data/example/app/views/layouts/mailer.html.erb +13 -0
  44. data/example/app/views/layouts/mailer.text.erb +1 -0
  45. data/example/babel.config.js +70 -0
  46. data/example/bin/bundle +114 -0
  47. data/example/bin/rails +27 -0
  48. data/example/bin/rake +5 -0
  49. data/example/bin/setup +36 -0
  50. data/example/bin/spring +14 -0
  51. data/example/bin/webpack +18 -0
  52. data/example/bin/webpack-dev-server +18 -0
  53. data/example/bin/yarn +17 -0
  54. data/example/config.ru +6 -0
  55. data/example/config/application.rb +25 -0
  56. data/example/config/boot.rb +4 -0
  57. data/example/config/cable.yml +10 -0
  58. data/example/config/credentials.yml.enc +1 -0
  59. data/example/config/database.yml +25 -0
  60. data/example/config/environment.rb +5 -0
  61. data/example/config/environments/development.rb +79 -0
  62. data/example/config/environments/production.rb +120 -0
  63. data/example/config/environments/test.rb +60 -0
  64. data/example/config/initializers/application_controller_renderer.rb +8 -0
  65. data/example/config/initializers/assets.rb +14 -0
  66. data/example/config/initializers/backtrace_silencers.rb +8 -0
  67. data/example/config/initializers/content_security_policy.rb +30 -0
  68. data/example/config/initializers/cookies_serializer.rb +5 -0
  69. data/example/config/initializers/filter_parameter_logging.rb +6 -0
  70. data/example/config/initializers/inflections.rb +16 -0
  71. data/example/config/initializers/mime_types.rb +4 -0
  72. data/example/config/initializers/permissions_policy.rb +11 -0
  73. data/example/config/initializers/twirp_rails.rb +4 -0
  74. data/example/config/initializers/wrap_parameters.rb +14 -0
  75. data/example/config/locales/en.yml +33 -0
  76. data/example/config/master.key +1 -0
  77. data/example/config/puma.rb +43 -0
  78. data/example/config/routes.rb +4 -0
  79. data/example/config/spring.rb +6 -0
  80. data/example/config/storage.yml +34 -0
  81. data/example/config/webpack/development.js +5 -0
  82. data/example/config/webpack/environment.js +3 -0
  83. data/example/config/webpack/production.js +5 -0
  84. data/example/config/webpack/test.js +5 -0
  85. data/example/config/webpacker.yml +92 -0
  86. data/example/extensions/x86_64-linux/3.0.0/ffi-1.15.3/ffi_c.so +0 -0
  87. data/example/extensions/x86_64-linux/3.0.0/ffi-1.15.3/gem.build_complete +0 -0
  88. data/example/extensions/x86_64-linux/3.0.0/ffi-1.15.3/gem_make.out +165 -0
  89. data/example/extensions/x86_64-linux/3.0.0/ffi-1.15.3/mkmf.log +132 -0
  90. data/example/package.json +17 -0
  91. data/example/postcss.config.js +12 -0
  92. data/example/public/404.html +67 -0
  93. data/example/public/422.html +67 -0
  94. data/example/public/500.html +66 -0
  95. data/example/public/apple-touch-icon-precomposed.png +0 -0
  96. data/example/public/apple-touch-icon.png +0 -0
  97. data/example/public/favicon.ico +0 -0
  98. data/example/public/robots.txt +1 -0
  99. data/example/storage/.keep +0 -0
  100. data/example/test/application_system_test_case.rb +5 -0
  101. data/example/test/channels/application_cable/connection_test.rb +11 -0
  102. data/example/test/controllers/.keep +0 -0
  103. data/example/test/fixtures/files/.keep +0 -0
  104. data/example/test/helpers/.keep +0 -0
  105. data/example/test/integration/.keep +0 -0
  106. data/example/test/mailers/.keep +0 -0
  107. data/example/test/models/.keep +0 -0
  108. data/example/test/system/.keep +0 -0
  109. data/example/test/test_helper.rb +13 -0
  110. data/example/tmp/cache/bootsnap/load-path-cache +0 -0
  111. data/example/vendor/.keep +0 -0
  112. data/example/yarn.lock +6888 -0
  113. data/lib/policy_decision_point.rb +24 -0
  114. data/lib/policy_decision_point/pdp.rb +155 -0
  115. data/lib/policy_decision_point/version.rb +5 -0
  116. data/policy_decision_point.gemspec +32 -0
  117. metadata +160 -0
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "policy_decision_point/version"
4
+ require_relative "policy_decision_point/pdp"
5
+
6
+ module PolicyDecisionPoint
7
+ ##
8
+ # Represents an authentication attempt failure.
9
+ class AuthnError < StandardError
10
+ def initialize(e = nil)
11
+ super e
12
+ set_backtrace e.backtrace if e
13
+ end
14
+ end
15
+
16
+ ##
17
+ # Represents an authorization attempt failure.
18
+ class AuthzError < StandardError
19
+ def initialize(e = nil)
20
+ super e
21
+ set_backtrace e.backtrace if e
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module PolicyDecisionPoint
6
+ ##
7
+ # Ingress middleware that connects to a PDP to request authz decisions.
8
+ class PDP
9
+
10
+ @@DEFAULT_PORT = 8181
11
+ @@DEFAULT_HOSTNAME = "http://localhost"
12
+ @@DEFAULT_POLICY_PATH = "/authz/allow"
13
+ @@DEFAULT_READ_TIMEOUT_MILLISECONDS = 5000
14
+ @@DEFAULT_CONNECTION_TIMEOUT_MILLISECONDS = 5000
15
+ @@DEFAULT_RETRY_MAX_ATTEMPTS = 2
16
+ @@DEFAULT_RETRY_BACKOFF_MILLISECONDS = 250
17
+
18
+ ##
19
+ # Initializes the PolicyDecisionPoint middleware.
20
+ #
21
+ # @param port [Integer] The port at which the PDP serves authz decisions.
22
+ # @param hostname [String] The host at which the PDP serves authz decisions.
23
+ # @param policy_path [String] The full path to policy (including package and rule) that makes the authz decision.
24
+ # @param read_timeout_milliseconds [Integer] Timeout for reading packets from server.
25
+ # @param connection_timeout_milliseconds [Integer] Timeout for establishing connection to server.
26
+ # @param retry_max_attempts [Integer] Number of retries to server before giving up.
27
+ # @param retry_backoff_milliseconds [Integer] Initial wait time before retry, doubles for every retry.
28
+ def initialize(app,
29
+ port: @@DEFAULT_PORT,
30
+ hostname: @@DEFAULT_HOSTNAME,
31
+ policy_path: @@DEFAULT_POLICY_PATH,
32
+ read_timeout_milliseconds: @@DEFAULT_READ_TIMEOUT_MILLISECONDS,
33
+ connection_timeout_milliseconds: @@DEFAULT_CONNECTION_TIMEOUT_MILLISECONDS,
34
+ retry_max_attempts: @@DEFAULT_RETRY_MAX_ATTEMPTS,
35
+ retry_backoff_milliseconds: @@DEFAULT_RETRY_BACKOFF_MILLISECONDS)
36
+
37
+ @port = port
38
+ @hostname = hostname
39
+ @policy_path = policy_path
40
+ @read_timeout_milliseconds = read_timeout_milliseconds
41
+ @connection_timeout_milliseconds = connection_timeout_milliseconds
42
+ @retry_max_attempts = 2
43
+ @retry_backoff_milliseconds = retry_backoff_milliseconds
44
+
45
+ @pdp_endpoint = endpoint()
46
+
47
+ @client = Faraday.new(
48
+ url: @pdp_endpoint,
49
+ request: {
50
+ read_timeout: @read_timeout_milliseconds,
51
+ open_timeout: @connection_timeout_milliseconds,
52
+ }
53
+ ) do |conn|
54
+ conn.request(:retry, max: 2, interval: @retry_backoff_milliseconds / 1000, backoff_factor: 2)
55
+ end
56
+
57
+ @app = app
58
+ end
59
+
60
+ ##
61
+ # Invoked for incoming requests; calls the PDP with request context to make decision.
62
+ #
63
+ # @raise [Middleware::Ingress::AuthzError] if the request fails authorization.
64
+ def call(env)
65
+ body = input(env).to_json
66
+
67
+ response = @client.post("", body, "Content-Type" => "application/json")
68
+
69
+ if not response.success?
70
+ raise AuthzError.new(
71
+ StandardError.new("Unexpected response #{response.status} from decision endpoint #{@pdp_endpoint}")
72
+ )
73
+ end
74
+
75
+ if JSON.parse(response.body).with_indifferent_access["result"] != true
76
+ raise AuthzError.new(
77
+ StandardError.new("Request was not authorized by decision endpoint #{@pdp_endpoint}")
78
+ )
79
+ end
80
+
81
+ rescue Faraday::Error => error
82
+ raise AuthzError.new(error)
83
+ else
84
+ # If no exception is raised, pass the request to the next middleware.
85
+ @app.call(env)
86
+ end
87
+
88
+ ##
89
+ # Constructs the endpoint to which PDP authz requests are sent.
90
+ #
91
+ # @return [String] the endpoint
92
+ def endpoint
93
+ if not @hostname.include? "://"
94
+ @hostname = "http://" + @hostname
95
+ end
96
+
97
+ @hostname = @hostname.delete_suffix("/")
98
+
99
+ if @port.respond_to?(:to_s)
100
+ @port = @port.to_s
101
+ end
102
+
103
+ if not @port.start_with?(":")
104
+ @port = ":" + @port
105
+ end
106
+
107
+ if not @policy_path.start_with?("/")
108
+ @policy_path = "/" + @policy_path
109
+ end
110
+
111
+ @hostname + @port + "/v1/data" + @policy_path
112
+ end
113
+
114
+ ##
115
+ # Constructs the request context required to make authz decision, which is sent to the PDP.
116
+ # Header values received in the request are normalized. See NOTE below for examples.
117
+ #
118
+ # @return [Hash] the request context
119
+ def input(env)
120
+ req = Rack::Request.new(env)
121
+ headers = ActionDispatch::Http::Headers.from_hash(env)
122
+
123
+ {
124
+ 'input': {
125
+ 'request': {
126
+ 'scheme': req.scheme,
127
+ 'method': req.request_method,
128
+ 'path': req.path,
129
+ 'query': Rack::Utils.parse_nested_query(req.query_string),
130
+
131
+ # NOTE: Rack normalizes all headers by adding prefixes, making uppercase and using underscores.
132
+ # Examples
133
+ # Content-Type -> CONTENT_TYPE
134
+ # Accept -> HTTP_ACCEPT
135
+ # Custom-Header -> HTTP_CUSTOM_HEADER
136
+ #
137
+ # To separate these request headers from other environment headers added by Rails/Rack,
138
+ # the following prefix matching is performed.
139
+ 'headers': headers.to_h.select { |k, v|
140
+ ["HTTP", "CONTENT", "AUTHORIZATION"].any? { |s| k.to_s.starts_with? s }
141
+ },
142
+ },
143
+ 'source': {
144
+ 'ipAddress': req.host,
145
+ 'port': req.port,
146
+ },
147
+ 'destination': {
148
+ 'ipAddress': req.server_name,
149
+ 'port': req.server_port,
150
+ }
151
+ }
152
+ }
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PolicyDecisionPoint
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/policy_decision_point/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "policy_decision_point"
7
+ spec.version = PolicyDecisionPoint::VERSION
8
+ spec.authors = ["Yash Tewari"]
9
+ spec.email = ["yashtewari1996@gmail.com"]
10
+
11
+ spec.summary = "Rails middleware that authorizes incoming requests against a Policy Decision Point, such as an Open Policy Agent server"
12
+ # spec.description = "TODO: Write a longer description or delete this line."
13
+ spec.homepage = "https://build.security"
14
+ spec.required_ruby_version = ">= 2.4.0"
15
+
16
+ spec.metadata["homepage_uri"] = spec.homepage
17
+ spec.metadata["source_code_uri"] = "https://github.com/build-security/opa-rails-middleware"
18
+
19
+ # Specify which files should be added to the gem when it is released.
20
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
22
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
23
+ end
24
+
25
+ spec.require_paths = ["lib"]
26
+
27
+ # Uncomment to register a new dependency of your gem
28
+ # spec.add_dependency "example-gem", "~> 1.0"
29
+
30
+ # For more information and examples about making a new gem, checkout our
31
+ # guide at: https://bundler.io/guides/creating_gem.html
32
+ end
metadata ADDED
@@ -0,0 +1,160 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: policy_decision_point
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Yash Tewari
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-07-11 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email:
15
+ - yashtewari1996@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".github/workflows/main.yml"
21
+ - ".gitignore"
22
+ - ".rspec"
23
+ - ".rubocop.yml"
24
+ - ".ruby-version"
25
+ - Gemfile
26
+ - README.md
27
+ - Rakefile
28
+ - bin/console
29
+ - bin/htmldiff
30
+ - bin/ldiff
31
+ - bin/rake
32
+ - bin/rspec
33
+ - bin/rubocop
34
+ - bin/ruby-parse
35
+ - bin/ruby-rewrite
36
+ - bin/setup
37
+ - example/Gemfile
38
+ - example/Gemfile.lock
39
+ - example/README.md
40
+ - example/Rakefile
41
+ - example/app/assets/config/manifest.js
42
+ - example/app/assets/images/.keep
43
+ - example/app/assets/stylesheets/application.css
44
+ - example/app/channels/application_cable/channel.rb
45
+ - example/app/channels/application_cable/connection.rb
46
+ - example/app/controllers/application_controller.rb
47
+ - example/app/controllers/concerns/.keep
48
+ - example/app/controllers/rpc/hello_world/service.proto
49
+ - example/app/controllers/rpc/hello_world/service_pb.rb
50
+ - example/app/controllers/rpc/hello_world/service_twirp.rb
51
+ - example/app/controllers/rpc/hello_world_server.rb
52
+ - example/app/helpers/application_helper.rb
53
+ - example/app/javascript/channels/consumer.js
54
+ - example/app/javascript/channels/index.js
55
+ - example/app/javascript/packs/application.js
56
+ - example/app/jobs/application_job.rb
57
+ - example/app/mailers/application_mailer.rb
58
+ - example/app/models/application_record.rb
59
+ - example/app/models/concerns/.keep
60
+ - example/app/views/layouts/application.html.erb
61
+ - example/app/views/layouts/mailer.html.erb
62
+ - example/app/views/layouts/mailer.text.erb
63
+ - example/babel.config.js
64
+ - example/bin/bundle
65
+ - example/bin/rails
66
+ - example/bin/rake
67
+ - example/bin/setup
68
+ - example/bin/spring
69
+ - example/bin/webpack
70
+ - example/bin/webpack-dev-server
71
+ - example/bin/yarn
72
+ - example/config.ru
73
+ - example/config/application.rb
74
+ - example/config/boot.rb
75
+ - example/config/cable.yml
76
+ - example/config/credentials.yml.enc
77
+ - example/config/database.yml
78
+ - example/config/environment.rb
79
+ - example/config/environments/development.rb
80
+ - example/config/environments/production.rb
81
+ - example/config/environments/test.rb
82
+ - example/config/initializers/application_controller_renderer.rb
83
+ - example/config/initializers/assets.rb
84
+ - example/config/initializers/backtrace_silencers.rb
85
+ - example/config/initializers/content_security_policy.rb
86
+ - example/config/initializers/cookies_serializer.rb
87
+ - example/config/initializers/filter_parameter_logging.rb
88
+ - example/config/initializers/inflections.rb
89
+ - example/config/initializers/mime_types.rb
90
+ - example/config/initializers/permissions_policy.rb
91
+ - example/config/initializers/twirp_rails.rb
92
+ - example/config/initializers/wrap_parameters.rb
93
+ - example/config/locales/en.yml
94
+ - example/config/master.key
95
+ - example/config/puma.rb
96
+ - example/config/routes.rb
97
+ - example/config/spring.rb
98
+ - example/config/storage.yml
99
+ - example/config/webpack/development.js
100
+ - example/config/webpack/environment.js
101
+ - example/config/webpack/production.js
102
+ - example/config/webpack/test.js
103
+ - example/config/webpacker.yml
104
+ - example/extensions/x86_64-linux/3.0.0/ffi-1.15.3/ffi_c.so
105
+ - example/extensions/x86_64-linux/3.0.0/ffi-1.15.3/gem.build_complete
106
+ - example/extensions/x86_64-linux/3.0.0/ffi-1.15.3/gem_make.out
107
+ - example/extensions/x86_64-linux/3.0.0/ffi-1.15.3/mkmf.log
108
+ - example/package.json
109
+ - example/postcss.config.js
110
+ - example/public/404.html
111
+ - example/public/422.html
112
+ - example/public/500.html
113
+ - example/public/apple-touch-icon-precomposed.png
114
+ - example/public/apple-touch-icon.png
115
+ - example/public/favicon.ico
116
+ - example/public/robots.txt
117
+ - example/storage/.keep
118
+ - example/test/application_system_test_case.rb
119
+ - example/test/channels/application_cable/connection_test.rb
120
+ - example/test/controllers/.keep
121
+ - example/test/fixtures/files/.keep
122
+ - example/test/helpers/.keep
123
+ - example/test/integration/.keep
124
+ - example/test/mailers/.keep
125
+ - example/test/models/.keep
126
+ - example/test/system/.keep
127
+ - example/test/test_helper.rb
128
+ - example/tmp/cache/bootsnap/load-path-cache
129
+ - example/vendor/.keep
130
+ - example/yarn.lock
131
+ - lib/policy_decision_point.rb
132
+ - lib/policy_decision_point/pdp.rb
133
+ - lib/policy_decision_point/version.rb
134
+ - policy_decision_point.gemspec
135
+ homepage: https://build.security
136
+ licenses: []
137
+ metadata:
138
+ homepage_uri: https://build.security
139
+ source_code_uri: https://github.com/build-security/opa-rails-middleware
140
+ post_install_message:
141
+ rdoc_options: []
142
+ require_paths:
143
+ - lib
144
+ required_ruby_version: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: 2.4.0
149
+ required_rubygems_version: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ requirements: []
155
+ rubygems_version: 3.2.15
156
+ signing_key:
157
+ specification_version: 4
158
+ summary: Rails middleware that authorizes incoming requests against a Policy Decision
159
+ Point, such as an Open Policy Agent server
160
+ test_files: []