module SSRFsUp

This module contains the AWS lambda client and helper methods to easily make requests to it. All methods take a hostname or URI and a hash or options for the request.

Constants

VERSION

Attributes

client[RW]
config[RW]

Public Class Methods

configuration() click to toggle source
# File lib/ssrfs-up.rb, line 128
def configuration
  @config ||= Configuration.new
end
configure() { |configuration| ... } click to toggle source

configures the SSRFsUp module and recreates the AWS Lambda Client from the updated configuration.

# File lib/ssrfs-up.rb, line 123
def configure
  yield(configuration)
  @client = Aws::Lambda::Client.new({ region: configuration.region, stub_responses: configuration.test })
end
delete(host, opts = {}) click to toggle source

convenience method for making a DELETE request with do.

# File lib/ssrfs-up.rb, line 91
def delete(host, opts = {})
  opts[:method] = "DELETE"
  invoke(host, opts)
end
do(method, host, opts = {}) click to toggle source

These methods take a string like “www.google.com” or “google.com” and parse the respective parameters from the string to make the request. If only a hostname is provided, the default options are applied. A hash of options can also be supplied to configure the request. The set of options can be found at github.com/chanzuckerberg/SSRFs-Up/blob/0e18fd30bee3f2b99ff4bc512cb967b83e8d9dcb/openapi.yaml#L97-L119

# File lib/ssrfs-up.rb, line 51
def do(method, host, opts = {})
  case method.downcase
  when "get"
    get(host, opts)
  when "put"
    put(host, opts)
  when "post"
    post(host, opts)
  when "patch"
    patch(host, opts)
  when "delete"
    delete(host, opts)
  end
end
fast_check(host, opts) click to toggle source
# File lib/ssrfs-up.rb, line 136
def fast_check(host, opts)
  scheme = opts[:secure] ? "https://" : "http://"
  path = opts[:path].nil? ? "" : opts[:path]
  params = opts[:params].nil? ? "" : "?" + URI.encode_www_form(opts[:params])
  url = scheme + host + path + params

  filter_opts = { :max_redirects => opts[:redirect].nil? ? 3 : opts[:redirect] }
  filter_opts[:params] = opts[:params] unless opts[:params].nil?
  filter_opts[:body] = opts[:body] unless opts[:body].nil?
  filter_opts[:headers] = opts[:headers] unless opts[:headers].nil?

  begin
    case opts[:method].downcase
    when "get"
      resp = SsrfFilter.get(url, filter_opts)
    when "put"
      resp = SsrfFilter.put(url, filter_opts)
    when "post"
      resp = SsrfFilter.post(url, filter_opts)
    when "delete"
      resp = SsrfFilter.delete(url, filter_opts)
    when "patch"
      return { status_code: 404, status_text: "Unsupported method", body: "Cannot use patch with fast path." }
    end

    { status_code: resp.code.to_i, status_text: resp.message, body: resp.body }
  rescue SsrfFilter::PrivateIPAddress => exception
    { status_code: 404, status_text: "Invalid destination", body: exception.to_s }
  end
end
get(host, opts = {}) click to toggle source

convenience method for making a GET request with do.

# File lib/ssrfs-up.rb, line 67
def get(host, opts = {})
  opts[:method] = "GET"
  invoke(host, opts)
end
invoke(host = nil, opts = {}) click to toggle source

invokes the lambda with the provided arguments. It handles all lambda related errors so developers should assume the data they receive back is straight from the server they are speaking to.

# File lib/ssrfs-up.rb, line 170
def invoke(host = nil, opts = {})
  opts = opts.merge(parseAsUri(host))
  if (!opts[:proxy].nil? && !opts[:proxy]) || !configuration.proxy
    OpenStruct.new(fast_check(opts[:host], opts))
  else
    begin
      resp = client.invoke({
        function_name: configuration.func_name,
        invocation_type: configuration.invoke_type,
        log_type: configuration.log_type,
        payload: payload(opts),
      })

      if resp["status_code"] == 200
        OpenStruct.new(JSON.parse(resp&.payload&.string))
      else
        OpenStruct.new({ body: "", status_code: resp[status_code], status_text: "500 Error with proxy" })
      end
    rescue StandardError => e
      # fall back to local check if the lambda wasn't reachable.
      OpenStruct.new(fast_check(opts[:host], opts))
    end
  end
end
parseAsUri(uri = "") click to toggle source

takes an ambiguous string or URI and sets the appropriate options based on if it can be parsed as URI object. If it can't, then the string is assumed to be a hostname only.

# File lib/ssrfs-up.rb, line 99
def parseAsUri(uri = "")
  uri = uri.to_s
  opts = { :host => uri.split("/")[0].split("?")[0].split("#")[0] }
  u = URI(uri)

  # if the scheme was present, we can parse most of the options from the URI.
  # otherwise, we can assume the URI was an actual hostname
  unless u.scheme.nil?
    opts[:secure] = !(u.scheme == "http")
    opts[:host] = u.host
    opts[:path] = u.path unless u.path == ""
    opts[:params] = CGI.parse(u.query) unless u.query.nil?
  end
  opts
end
patch(host, opts = {}) click to toggle source

convenience method for making a patch request with do.

# File lib/ssrfs-up.rb, line 85
def patch(host, opts = {})
  opts[:method] = "PATCH"
  invoke(host, opts)
end
payload(opts = {}) click to toggle source

payload builds an API client Request object with the proper defaults and returns its JSON serialization.

# File lib/ssrfs-up.rb, line 197
def payload(opts = {})
  toOpenAPIClient(opts).to_json
end
post(host, opts = {}) click to toggle source

convenience method for making a POST request with do.

# File lib/ssrfs-up.rb, line 79
def post(host, opts = {})
  opts[:method] = "POST"
  invoke(host, opts)
end
put(host, opts = {}) click to toggle source

convenience method for making a PUT request with do.

# File lib/ssrfs-up.rb, line 73
def put(host, opts = {})
  opts[:method] = "PUT"
  invoke(host, opts)
end
toOpenAPIClient(opts = {}) click to toggle source

converts a hash of options to a valid OpenapiClient Request so that it can be properly consumed by the lambda.

# File lib/ssrfs-up.rb, line 117
def toOpenAPIClient(opts = {})
  OpenapiClient::Request.new(opts).to_hash
end