diff options
Diffstat (limited to 'lib/unicorn')
-rw-r--r-- | lib/unicorn/configurator.rb | 13 | ||||
-rw-r--r-- | lib/unicorn/http_server.rb | 3 | ||||
-rw-r--r-- | lib/unicorn/ssl_client.rb | 6 | ||||
-rw-r--r-- | lib/unicorn/ssl_configurator.rb | 104 | ||||
-rw-r--r-- | lib/unicorn/ssl_server.rb | 42 |
5 files changed, 164 insertions, 4 deletions
diff --git a/lib/unicorn/configurator.rb b/lib/unicorn/configurator.rb index 8816c85..a93c1dc 100644 --- a/lib/unicorn/configurator.rb +++ b/lib/unicorn/configurator.rb @@ -1,5 +1,6 @@ # -*- encoding: binary -*- require 'logger' +require 'unicorn/ssl_configurator' # Implements a simple DSL for configuring a \Unicorn server. # @@ -12,6 +13,7 @@ require 'logger' # See the link:/TUNING.html document for more information on tuning unicorn. class Unicorn::Configurator include Unicorn + include Unicorn::SSLConfigurator # :stopdoc: attr_accessor :set, :config_file, :after_reload @@ -563,13 +565,16 @@ private end end - def set_bool(var, bool) #:nodoc: + def check_bool(var, bool) # :nodoc: case bool when true, false - set[var] = bool - else - raise ArgumentError, "#{var}=#{bool.inspect} not a boolean" + return bool end + raise ArgumentError, "#{var}=#{bool.inspect} not a boolean" + end + + def set_bool(var, bool) #:nodoc: + set[var] = check_bool(var, bool) end def set_hook(var, my_proc, req_arity = 2) #:nodoc: diff --git a/lib/unicorn/http_server.rb b/lib/unicorn/http_server.rb index 65880d4..c78b094 100644 --- a/lib/unicorn/http_server.rb +++ b/lib/unicorn/http_server.rb @@ -1,4 +1,5 @@ # -*- encoding: binary -*- +require "unicorn/ssl_server" # This is the process manager of Unicorn. This manages worker # processes which in turn handle the I/O and application process. @@ -19,6 +20,7 @@ class Unicorn::HttpServer attr_reader :pid, :logger include Unicorn::SocketHelper include Unicorn::HttpResponse + include Unicorn::SSLServer # backwards compatibility with 1.x Worker = Unicorn::Worker @@ -563,6 +565,7 @@ class Unicorn::HttpServer self.timeout /= 2.0 # halve it for select() @config = nil build_app! unless preload_app + ssl_enable! @after_fork = @listener_opts = @orig_app = nil end diff --git a/lib/unicorn/ssl_client.rb b/lib/unicorn/ssl_client.rb new file mode 100644 index 0000000..7b41cd2 --- /dev/null +++ b/lib/unicorn/ssl_client.rb @@ -0,0 +1,6 @@ +# -*- encoding: binary -*- +# :stopdoc: +class Unicorn::SSLClient < Kgio::SSL + alias write kgio_write + alias close kgio_close +end diff --git a/lib/unicorn/ssl_configurator.rb b/lib/unicorn/ssl_configurator.rb new file mode 100644 index 0000000..c92c85e --- /dev/null +++ b/lib/unicorn/ssl_configurator.rb @@ -0,0 +1,104 @@ +# -*- encoding: binary -*- +# :stopdoc: +# This module is included in Unicorn::Configurator +# :startdoc: +# +module Unicorn::SSLConfigurator + def ssl(&block) + ssl_require! + before = @set[:listeners].dup + opts = @set[:ssl_opts] = {} + yield + (@set[:listeners] - before).each do |address| + (@set[:listener_opts][address] ||= {})[:ssl_opts] = opts + end + ensure + @set.delete(:ssl_opts) + end + + def ssl_certificate(file) + ssl_set(:ssl_certificate, file) + end + + def ssl_certificate_key(file) + ssl_set(:ssl_certificate_key, file) + end + + def ssl_client_certificate(file) + ssl_set(:ssl_client_certificate, file) + end + + def ssl_dhparam(file) + ssl_set(:ssl_dhparam, file) + end + + def ssl_ciphers(openssl_cipherlist_spec) + ssl_set(:ssl_ciphers, openssl_cipherlist_spec) + end + + def ssl_crl(file) + ssl_set(:ssl_crl, file) + end + + def ssl_prefer_server_ciphers(bool) + ssl_set(:ssl_prefer_server_ciphers, check_bool(bool)) + end + + def ssl_protocols(list) + ssl_set(:ssl_protocols, list) + end + + def ssl_verify_client(on_off_optional) + ssl_set(:ssl_verify_client, on_off_optional) + end + + def ssl_session_timeout(seconds) + ssl_set(:ssl_session_timeout, seconds) + end + + def ssl_verify_depth(depth) + ssl_set(:ssl_verify_depth, depth) + end + + # Allows specifying an engine for OpenSSL to use. We have not been + # able to successfully test this feature due to a lack of hardware, + # Reports of success or patches to mongrel-unicorn@rubyforge.org is + # greatly appreciated. + def ssl_engine(engine) + ssl_warn_global(:ssl_engine) + ssl_require! + OpenSSL::Engine.load + OpenSSL::Engine.by_id(engine) + @set[:ssl_engine] = engine + end + + def ssl_compression(bool) + # OpenSSL uses the SSL_OP_NO_COMPRESSION flag, Flipper follows suit + # with :ssl_no_compression, but we negate it to avoid exposing double + # negatives to the user. + ssl_set(:ssl_no_compression, check_bool(:ssl_compression, ! bool)) + end + +private + + def ssl_warn_global(func) # :nodoc: + Hash === @set[:ssl_opts] or return + warn("`#{func}' affects all SSL contexts in this process, " \ + "not just this block") + end + + def ssl_set(key, value) # :nodoc: + cur = @set[:ssl_opts] + Hash === cur or + raise ArgumentError, "#{key} must be called inside an `ssl' block" + cur[key] = value + end + + def ssl_require! # :nodoc: + require "flipper" + require "unicorn/ssl_client" + rescue LoadError + warn "install 'kgio-monkey' for SSL support" + raise + end +end diff --git a/lib/unicorn/ssl_server.rb b/lib/unicorn/ssl_server.rb new file mode 100644 index 0000000..c00c3ae --- /dev/null +++ b/lib/unicorn/ssl_server.rb @@ -0,0 +1,42 @@ +# -*- encoding: binary -*- +# :stopdoc: +# this module is meant to be included in Unicorn::HttpServer +# It is an implementation detail and NOT meant for users. +module Unicorn::SSLServer + attr_accessor :ssl_engine + + def ssl_enable! + sni_hostnames = rack_sni_hostnames(@app) + seen = {} # we map a single SSLContext to multiple listeners + listener_ctx = {} + @listener_opts.each do |address, address_opts| + ssl_opts = address_opts[:ssl_opts] or next + listener_ctx[address] = seen[ssl_opts.object_id] ||= begin + unless sni_hostnames.empty? + ssl_opts = ssl_opts.dup + ssl_opts[:sni_hostnames] = sni_hostnames + end + ctx = Flipper.ssl_context(ssl_opts) + # FIXME: make configurable + ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_OFF + ctx + end + end + Unicorn::HttpServer::LISTENERS.each do |listener| + ctx = listener_ctx[sock_name(listener)] or next + listener.extend(Kgio::SSLServer) + listener.ssl_ctx = ctx + listener.kgio_ssl_class = Unicorn::SSLClient + end + end + + # ugh, this depends on Rack internals... + def rack_sni_hostnames(rack_app) # :nodoc: + hostnames = {} + if Rack::URLMap === rack_app + mapping = rack_app.instance_variable_get(:@mapping) + mapping.each { |hostname,_,_,_| hostnames[hostname] = true } + end + hostnames.keys + end +end |