Project

General

Profile

Download (3.37 KB) Statistics
| Branch: | Tag: | Revision:
require 'openssl'
require 'resolv'

module Proxy::Helpers
include Proxy::Log

# Accepts a html error code and a message, which is then returned to the caller after adding to the proxy log
# OR a block which is executed and its errors handled in a similar way.
# If no code is supplied when the block is declared then the html error used is 400.
def log_halt code=nil, exception=nil
message = exception.is_a?(String) ? exception : exception.to_s
begin
if block_given?
return yield
end
rescue => e
exception = e
message += e.message
code = code || 400
end
content_type :json if request.accept?("application/json")
logger.error message, exception
halt code, message
end

# read the HTTPS client certificate from the environment and extract its CN
def https_cert_cn
certificate_raw = request.env['SSL_CLIENT_CERT'].to_s
log_halt 403, 'could not read client cert from environment' if certificate_raw.empty?

begin
certificate = OpenSSL::X509::Certificate.new certificate_raw
if certificate.subject && certificate.subject.to_s =~ /CN=([^\s\/,]+)/i
$1
else
log_halt 403, 'could not read CN from the client certificate'
end
rescue OpenSSL::X509::CertificateError => e
log_halt 403, "could not parse the client certificate\n\n#{e.message}"
end
end

# parses the body as json and returns a hash of the body
# returns empty hash if there is a json parse error, the body is empty or is not a hash
# request.env["CONTENT_TYPE"] must contain application/json in order for the json to be parsed
def parse_json_body
json_data = {}
# if the user has explicitly set the content_type then there must be something worth decoding
# we use a regex because it might contain something else like: application/json;charset=utf-8
# by default the content type will probably be set to "application/x-www-form-urlencoded" unless the
# user changed it. If the user doesn't specify the content type we just ignore the body since a form
# will be parsed into the request.params object for us by sinatra
if request.env["CONTENT_TYPE"] =~ /application\/json/
begin
body_parameters = request.body.read
json_data = JSON.parse(body_parameters)
rescue => e
log_halt 415, "Invalid JSON content in body of request: \n#{e.message}"
end

log_halt 415, "Invalid JSON content in body of request: data must be a hash, not #{json_data.class.name}" unless json_data.is_a?(Hash)
end
json_data
end

# reverse lookup an IP address while verifying it via forward resolv
def remote_fqdn(forward_verify=true)
ip = request.env['REMOTE_ADDR']
log_halt 403, 'could not get remote address from environment' if ip.empty?

begin
dns = Resolv.new
fqdn = dns.getname(ip)
rescue Resolv::ResolvError => e
log_halt 403, "unable to resolve hostname for ip address #{ip}\n\n#{e.message}"
end

unless forward_verify
fqdn
else
begin
forward = dns.getaddresses(fqdn)
rescue Resolv::ResolvError => e
log_halt 403, "could not forward verify the remote hostname - #{fqdn} (#{ip})\n\n#{e.message}"
end

if forward.include?(ip)
fqdn
else
log_halt 403, "untrusted client has no matching forward DNS lookup - #{fqdn} (#{ip})"
end
end
end

end
(6-6/22)