ssrfs-up-v2 0.21.2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/ssrfs-up.rb ADDED
@@ -0,0 +1,201 @@
1
+ require "aws-sdk-lambda"
2
+ require "uri"
3
+ require "ssrfs-up/version"
4
+ require "ostruct"
5
+ require "ssrf_filter"
6
+ require "cgi"
7
+
8
+ # Common files
9
+ require "openapi_client/lib/openapi_client/api_client"
10
+ require "openapi_client/lib/openapi_client/api_error"
11
+ require "openapi_client/lib/openapi_client/version"
12
+ require "openapi_client/lib/openapi_client/configuration"
13
+
14
+ # Models
15
+ require "openapi_client/lib/openapi_client/models/content_type"
16
+ require "openapi_client/lib/openapi_client/models/method"
17
+ require "openapi_client/lib/openapi_client/models/redirect"
18
+ require "openapi_client/lib/openapi_client/models/request"
19
+ require "openapi_client/lib/openapi_client/models/response"
20
+ require "openapi_client/lib/openapi_client/models/response_error"
21
+ require "openapi_client/lib/openapi_client/models/response_success"
22
+
23
+ # APIs
24
+ require "openapi_client/lib/openapi_client/api/default_api"
25
+ ##
26
+ # This module contains the AWS lambda client and helper methods to easily
27
+ # make requests to it. All methods take a hostname or URI and a hash or options
28
+ # for the request.
29
+ module SSRFsUp
30
+ class Configuration
31
+ attr_accessor :func_name, :invoke_type, :log_type, :region, :test, :proxy
32
+
33
+ def initialize
34
+ @func_name = "arn:aws:lambda:us-west-2:871040364337:function:sec-czi-sec-ssrfs-up:sec-czi-sec-ssrfs-up"
35
+ @invoke_type = "RequestResponse"
36
+ @log_type = "None"
37
+ @region = "us-west-2"
38
+ @test = false
39
+ @proxy = true
40
+ end
41
+ end
42
+
43
+ class << self
44
+ attr_accessor :config, :client
45
+
46
+ # These methods take a string like "www.google.com" or "https://google.com" and parse
47
+ # the respective parameters from the string to make the request. If only a hostname
48
+ # is provided, the default options are applied. A hash of options can also be
49
+ # supplied to configure the request. The set of options can be found at
50
+ # https://github.com/chanzuckerberg/SSRFs-Up/blob/0e18fd30bee3f2b99ff4bc512cb967b83e8d9dcb/openapi.yaml#L97-L119
51
+ def do(method, host, opts = {})
52
+ case method.downcase
53
+ when "get"
54
+ get(host, opts)
55
+ when "put"
56
+ put(host, opts)
57
+ when "post"
58
+ post(host, opts)
59
+ when "patch"
60
+ patch(host, opts)
61
+ when "delete"
62
+ delete(host, opts)
63
+ end
64
+ end
65
+
66
+ # convenience method for making a GET request with do.
67
+ def get(host, opts = {})
68
+ opts[:method] = "GET"
69
+ invoke(host, opts)
70
+ end
71
+
72
+ # convenience method for making a PUT request with do.
73
+ def put(host, opts = {})
74
+ opts[:method] = "PUT"
75
+ invoke(host, opts)
76
+ end
77
+
78
+ # convenience method for making a POST request with do.
79
+ def post(host, opts = {})
80
+ opts[:method] = "POST"
81
+ invoke(host, opts)
82
+ end
83
+
84
+ # convenience method for making a patch request with do.
85
+ def patch(host, opts = {})
86
+ opts[:method] = "PATCH"
87
+ invoke(host, opts)
88
+ end
89
+
90
+ # convenience method for making a DELETE request with do.
91
+ def delete(host, opts = {})
92
+ opts[:method] = "DELETE"
93
+ invoke(host, opts)
94
+ end
95
+
96
+ # takes an ambiguous string or URI and sets the appropriate options based
97
+ # on if it can be parsed as URI object. If it can't, then the string is assumed
98
+ # to be a hostname only.
99
+ def parseAsUri(uri = "")
100
+ uri = uri.to_s
101
+ opts = { :host => uri.split("/")[0].split("?")[0].split("#")[0] }
102
+ u = URI(uri)
103
+
104
+ # if the scheme was present, we can parse most of the options from the URI.
105
+ # otherwise, we can assume the URI was an actual hostname
106
+ unless u.scheme.nil?
107
+ opts[:secure] = !(u.scheme == "http")
108
+ opts[:host] = u.host
109
+ opts[:path] = u.path unless u.path == ""
110
+ opts[:params] = CGI.parse(u.query) unless u.query.nil?
111
+ end
112
+ opts
113
+ end
114
+
115
+ # converts a hash of options to a valid OpenapiClient Request so that it
116
+ # can be properly consumed by the lambda.
117
+ def toOpenAPIClient(opts = {})
118
+ OpenapiClient::Request.new(opts).to_hash
119
+ end
120
+
121
+ # configures the SSRFsUp module and recreates the AWS Lambda Client from
122
+ # the updated configuration.
123
+ def configure
124
+ yield(configuration)
125
+ @client = Aws::Lambda::Client.new({ region: configuration.region, stub_responses: configuration.test })
126
+ end
127
+
128
+ def configuration
129
+ @config ||= Configuration.new
130
+ end
131
+
132
+ def client
133
+ @client ||= Aws::Lambda::Client.new(region: configuration.region)
134
+ end
135
+
136
+ def fast_check(host, opts)
137
+ scheme = opts[:secure] ? "https://" : "http://"
138
+ path = opts[:path].nil? ? "" : opts[:path]
139
+ params = opts[:params].nil? ? "" : "?" + URI.encode_www_form(opts[:params])
140
+ url = scheme + host + path + params
141
+
142
+ filter_opts = { :max_redirects => opts[:redirect].nil? ? 3 : opts[:redirect] }
143
+ filter_opts[:params] = opts[:params] unless opts[:params].nil?
144
+ filter_opts[:body] = opts[:body] unless opts[:body].nil?
145
+ filter_opts[:headers] = opts[:headers] unless opts[:headers].nil?
146
+
147
+ begin
148
+ case opts[:method].downcase
149
+ when "get"
150
+ resp = SsrfFilter.get(url, filter_opts)
151
+ when "put"
152
+ resp = SsrfFilter.put(url, filter_opts)
153
+ when "post"
154
+ resp = SsrfFilter.post(url, filter_opts)
155
+ when "delete"
156
+ resp = SsrfFilter.delete(url, filter_opts)
157
+ when "patch"
158
+ return { status_code: 404, status_text: "Unsupported method", body: "Cannot use patch with fast path." }
159
+ end
160
+
161
+ { status_code: resp.code.to_i, status_text: resp.message, body: resp.body }
162
+ rescue SsrfFilter::PrivateIPAddress => exception
163
+ { status_code: 404, status_text: "Invalid destination", body: exception.to_s }
164
+ end
165
+ end
166
+
167
+ # invokes the lambda with the provided arguments. It handles all lambda
168
+ # related errors so developers should assume the data they receive back is straight
169
+ # from the server they are speaking to.
170
+ def invoke(host = nil, opts = {})
171
+ opts = opts.merge(parseAsUri(host))
172
+ if (!opts[:proxy].nil? && !opts[:proxy]) || !configuration.proxy
173
+ OpenStruct.new(fast_check(opts[:host], opts))
174
+ else
175
+ begin
176
+ resp = client.invoke({
177
+ function_name: configuration.func_name,
178
+ invocation_type: configuration.invoke_type,
179
+ log_type: configuration.log_type,
180
+ payload: payload(opts),
181
+ })
182
+
183
+ if resp["status_code"] == 200
184
+ OpenStruct.new(JSON.parse(resp&.payload&.string))
185
+ else
186
+ OpenStruct.new({ body: "", status_code: resp[status_code], status_text: "500 Error with proxy" })
187
+ end
188
+ rescue StandardError => e
189
+ # fall back to local check if the lambda wasn't reachable.
190
+ OpenStruct.new(fast_check(opts[:host], opts))
191
+ end
192
+ end
193
+ end
194
+
195
+ # payload builds an API client Request object with the proper defaults and
196
+ # returns its JSON serialization.
197
+ def payload(opts = {})
198
+ toOpenAPIClient(opts).to_json
199
+ end
200
+ end
201
+ end
metadata ADDED
@@ -0,0 +1,156 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ssrfs-up-v2
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.21.2
5
+ platform: ruby
6
+ authors:
7
+ - Jake Heath
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-08-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk-lambda
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: '1'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '1'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '1'
33
+ - !ruby/object:Gem::Dependency
34
+ name: ssrf_filter
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.0'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: bundler
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: pry
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: rake
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: rspec
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '3.6'
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: 3.6.0
99
+ type: :development
100
+ prerelease: false
101
+ version_requirements: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - "~>"
104
+ - !ruby/object:Gem::Version
105
+ version: '3.6'
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: 3.6.0
109
+ description: A gem that simplifies connecting to out AWS Lambda used to proxy requests.
110
+ Make your third-party requests secure by default.
111
+ email:
112
+ - jheath@chanzuckerberg.com
113
+ executables: []
114
+ extensions: []
115
+ extra_rdoc_files: []
116
+ files:
117
+ - lib/openapi_client/lib/openapi_client.rb
118
+ - lib/openapi_client/lib/openapi_client/api/default_api.rb
119
+ - lib/openapi_client/lib/openapi_client/api_client.rb
120
+ - lib/openapi_client/lib/openapi_client/api_error.rb
121
+ - lib/openapi_client/lib/openapi_client/configuration.rb
122
+ - lib/openapi_client/lib/openapi_client/models/content_type.rb
123
+ - lib/openapi_client/lib/openapi_client/models/method.rb
124
+ - lib/openapi_client/lib/openapi_client/models/redirect.rb
125
+ - lib/openapi_client/lib/openapi_client/models/request.rb
126
+ - lib/openapi_client/lib/openapi_client/models/response.rb
127
+ - lib/openapi_client/lib/openapi_client/models/response_error.rb
128
+ - lib/openapi_client/lib/openapi_client/models/response_success.rb
129
+ - lib/openapi_client/lib/openapi_client/version.rb
130
+ - lib/ssrfs-up.rb
131
+ - lib/ssrfs-up/version.rb
132
+ homepage: https://github.com/chanzuckerberg/ssrf-proxy
133
+ licenses:
134
+ - MIT
135
+ metadata:
136
+ homepage_uri: https://github.com/chanzuckerberg/ssrf-proxy
137
+ post_install_message:
138
+ rdoc_options: []
139
+ require_paths:
140
+ - lib
141
+ required_ruby_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: 2.3.0
146
+ required_rubygems_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ requirements: []
152
+ rubygems_version: 3.1.6
153
+ signing_key:
154
+ specification_version: 4
155
+ summary: Proxy all requests to avoid SSRF.
156
+ test_files: []