Falcon SourceFalconEnvironmentProxy

module Proxy

Provides an environment for hosting a TLS-capable reverse proxy using SNI.

Definitions

def url

The host that this proxy will receive connections for.

Implementation

def url
	"https://[::]:443"
end

def ssl_session_id

The default SSL session identifier.

Implementation

def ssl_session_id
	"falcon"
end

def environments

The services we will proxy to.

Signature

returns Array(Async::Service::Environment)

Implementation

def environments
	[]
end

def hosts

The hosts we will proxy to. This is a hash of SNI authority -> evaluator.

Signature

returns Hash(String, Async::Service::Environment::Evaluator)

Implementation

def hosts
	hosts = {}
	
	environments.each do |environment|
		evaluator = environment.evaluator
		
		# next unless environment.implements?(Falcon::Environment::Application)
		if evaluator.key?(:authority) and evaluator.key?(:ssl_context) and evaluator.key?(:endpoint)
			Console.info(self) {"Proxying #{self.url} to #{evaluator.authority} using #{evaluator.endpoint}"}
			hosts[evaluator.authority] = evaluator
			
			if RUBY_VERSION < '3.1'
				# Ensure the SSL context is set up before forking - it's buggy on Ruby < 3.1:
				evaluator.ssl_context
			end
		end
	end
	
	return hosts
end

def host_context(socket, hostname)

Look up the host context for the given hostname, and update the socket hostname if necessary.

Signature

parameter socket OpenSSL::SSL::SSLSocket

The incoming connection.

parameter hostname String

The negotiated hostname.

Implementation

def host_context(socket, hostname)
	hosts = self.hosts
	
	if host = hosts[hostname]
		Console.logger.debug(self) {"Resolving #{hostname} -> #{host}"}
		
		socket.hostname = hostname
		
		return host.ssl_context
	else
		Console.logger.warn(self, hosts: hosts.keys) {"Unable to resolve #{hostname}!"}
		
		return nil
	end
end

def ssl_context

Generate an SSL context which delegates to Falcon::Environment::Proxy#host_context to multiplex based on hostname.

Implementation

def ssl_context
	@server_context ||= OpenSSL::SSL::SSLContext.new.tap do |context|
		context.servername_cb = Proc.new do |socket, hostname|
			self.host_context(socket, hostname)
		end
		
		context.session_id_context = self.ssl_session_id
		
		context.ssl_version = :TLSv1_2_server
		
		context.set_params(
			ciphers: ::Falcon::TLS::SERVER_CIPHERS,
			verify_mode: ::OpenSSL::SSL::VERIFY_NONE,
		)
		
		context.setup
	end
end

def endpoint

The endpoint the server will bind to.

Implementation

def endpoint
	super.with(
		ssl_context: self.ssl_context,
	)
end