about summary refs log tree commit homepage
path: root/lib
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2011-09-07 00:36:58 +0000
committerEric Wong <normalperson@yhbt.net>2011-09-15 21:37:40 +0000
commitac346b5abcfa6253bd792091e5fb011774c40d49 (patch)
treeb304b96f42c3ba2cde396de8ed626754ae9d78cc /lib
parentb48c6659b294b37f2c6ff3e75c1c9245522d48d1 (diff)
downloadunicorn-ac346b5abcfa6253bd792091e5fb011774c40d49.tar.gz
This will also be the foundation of SSL support in Rainbows!
and Zbatery.  Some users may also want to use this in
Unicorn on LANs to meet certain security/auditing requirements.
Of course, Nightmare! (in whatever form) should also be able to
use it.
Diffstat (limited to 'lib')
-rw-r--r--lib/unicorn/configurator.rb13
-rw-r--r--lib/unicorn/http_server.rb3
-rw-r--r--lib/unicorn/ssl_client.rb6
-rw-r--r--lib/unicorn/ssl_configurator.rb104
-rw-r--r--lib/unicorn/ssl_server.rb42
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