diff options
Diffstat (limited to 'lib/puppet/network/http_server')
| -rw-r--r-- | lib/puppet/network/http_server/mongrel.rb | 139 | ||||
| -rw-r--r-- | lib/puppet/network/http_server/webrick.rb | 163 |
2 files changed, 302 insertions, 0 deletions
diff --git a/lib/puppet/network/http_server/mongrel.rb b/lib/puppet/network/http_server/mongrel.rb new file mode 100644 index 000000000..ba4b048b3 --- /dev/null +++ b/lib/puppet/network/http_server/mongrel.rb @@ -0,0 +1,139 @@ +#!/usr/bin/env ruby +# File: 06-11-14-mongrel_xmlrpc.rb +# Author: Manuel Holtgrewe <purestorm at ggnore.net> +# +# Copyright (c) 2006 Manuel Holtgrewe, 2007 Luke Kanies +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# This file is based heavily on a file retrieved from +# http://ttt.ggnore.net/2006/11/15/xmlrpc-with-mongrel-and-ruby-off-rails/ + +require 'rubygems' +require 'mongrel' +require 'xmlrpc/server' +require 'puppet/network/xmlrpc/server' +require 'puppet/network/http_server' +require 'puppet/network/client_request' +require 'puppet/daemon' + +require 'resolv' + +# This handler can be hooked into Mongrel to accept HTTP requests. After +# checking whether the request itself is sane, the handler forwards it +# to an internal instance of XMLRPC::BasicServer to process it. +# +# You can access the server by calling the Handler's "xmlrpc_server" +# attribute accessor method and add XMLRPC handlers there. For example: +# +# <pre> +# handler = XmlRpcHandler.new +# handler.xmlrpc_server.add_handler("my.add") { |a, b| a.to_i + b.to_i } +# </pre> +module Puppet::Network + class HTTPServer::Mongrel < ::Mongrel::HttpHandler + include Puppet::Daemon + attr_reader :xmlrpc_server + + def initialize(handlers) + # Create a new instance of BasicServer. We are supposed to subclass it + # but that does not make sense since we would not introduce any new + # behaviour and we have to subclass Mongrel::HttpHandler so our handler + # works for Mongrel. + @xmlrpc_server = Puppet::Network::XMLRPCServer.new + handlers.each do |name, args| + unless handler = Puppet::Network::Handler.handler(name) + raise ArgumentError, "Invalid handler %s" % name + end + @xmlrpc_server.add_handler(handler.interface, handler.new(args)) + end + end + + # This method produces the same results as XMLRPC::CGIServer.serve + # from Ruby's stdlib XMLRPC implementation. + def process(request, response) + # Make sure this has been a POST as required for XMLRPC. + request_method = request.params[Mongrel::Const::REQUEST_METHOD] || Mongrel::Const::GET + if request_method != "POST" then + response.start(405) { |head, out| out.write("Method Not Allowed") } + return + end + + # Make sure the user has sent text/xml data. + request_mime = request.params["CONTENT_TYPE"] || "text/plain" + if parse_content_type(request_mime).first != "text/xml" then + response.start(400) { |head, out| out.write("Bad Request") } + return + end + + # Make sure there is data in the body at all. + length = request.params[Mongrel::Const::CONTENT_LENGTH].to_i + if length <= 0 then + response.start(411) { |head, out| out.write("Length Required") } + return + end + + # Check the body to be valid. + if request.body.nil? or request.body.size != length then + response.start(400) { |head, out| out.write("Bad Request") } + return + end + + info = client_info(request) + + # All checks above passed through + response.start(200) do |head, out| + head["Content-Type"] = "text/xml; charset=utf-8" + begin + out.write(@xmlrpc_server.process(request.body, info)) + rescue => detail + puts detail.backtrace + raise + end + end + end + + private + + def client_info(request) + params = request.params + ip = params["REMOTE_ADDR"] + if dn = params[Puppet[:ssl_client_header]] and dn.include?("/CN=") + client = dn.sub("/CN=", '') + valid = (params[Puppet[:ssl_client_verify_header]] == 'SUCCESS') + else + client = Resolv.getname(ip) + valid = false + end + + info = Puppet::Network::ClientRequest.new(client, ip, valid) + + return info + end + + # Taken from XMLRPC::ParseContentType + def parse_content_type(str) + a, *b = str.split(";") + return a.strip, *b + end + end +end + diff --git a/lib/puppet/network/http_server/webrick.rb b/lib/puppet/network/http_server/webrick.rb new file mode 100644 index 000000000..2cf85b7b6 --- /dev/null +++ b/lib/puppet/network/http_server/webrick.rb @@ -0,0 +1,163 @@ +require 'puppet' +require 'puppet/daemon' +require 'webrick' +require 'webrick/https' +require 'fcntl' + +require 'puppet/sslcertificates/support' +require 'puppet/network/xmlrpc/webrick_servlet' +require 'puppet/network/http_server' +require 'puppet/network/client' + +module Puppet + class ServerError < RuntimeError; end + module Network + # The old-school, pure ruby webrick server, which is the default serving + # mechanism. + class HTTPServer::WEBrick < WEBrick::HTTPServer + include Puppet::Daemon + include Puppet::SSLCertificates::Support + + # Read the CA cert and CRL and populate an OpenSSL::X509::Store + # with them, with flags appropriate for checking client + # certificates for revocation + def x509store + if Puppet[:cacrl] == 'none' + # No CRL, no store needed + return nil + end + unless File.exist?(Puppet[:cacrl]) + raise Puppet::Error, "Could not find CRL" + end + crl = OpenSSL::X509::CRL.new(File.read(Puppet[:cacrl])) + store = OpenSSL::X509::Store.new + store.purpose = OpenSSL::X509::PURPOSE_ANY + store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK + unless self.ca_cert + raise Puppet::Error, "No CA certificate" + end + + store.add_file(Puppet[:localcacert]) + store.add_crl(crl) + return store + end + + # Set up the http log. + def httplog + args = [] + + # yuck; separate http logs + file = nil + Puppet.settings.use(:main, :ssl, Puppet[:name]) + if Puppet[:name] == "puppetmasterd" + file = Puppet[:masterhttplog] + else + file = Puppet[:httplog] + end + + # open the log manually to prevent file descriptor leak + file_io = open(file, "a+") + file_io.sync + file_io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) + + args << file_io + if Puppet[:debug] + args << WEBrick::Log::DEBUG + end + + log = WEBrick::Log.new(*args) + + + return log + end + + # Create our server, yo. + def initialize(hash = {}) + Puppet.info "Starting server for Puppet version %s" % Puppet.version + + if handlers = hash[:Handlers] + handler_instances = setup_handlers(handlers) + else + raise ServerError, "A server must have handlers" + end + + unless self.read_cert + if ca = handler_instances.find { |handler| handler.is_a?(Puppet::Network::Handler.ca) } + request_cert(ca) + else + raise Puppet::Error, "No certificate and no CA; cannot get cert" + end + end + + setup_webrick(hash) + + super(hash) + + # make sure children don't inherit the sockets + listeners.each { |sock| + sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) + } + + Puppet.info "Listening on port %s" % hash[:Port] + + # this creates a new servlet for every connection, + # but all servlets have the same list of handlers + # thus, the servlets can have their own state -- passing + # around the requests and such -- but the handlers + # have a global state + + # mount has to be called after the server is initialized + servlet = Puppet::Network::XMLRPC::WEBrickServlet.new( + handler_instances) + self.mount("/RPC2", servlet) + end + + # Create a ca client to set up our cert for us. + def request_cert(ca) + client = Puppet::Network::Client.ca.new(:CA => ca) + unless client.request_cert + raise Puppet::Error, "Could get certificate" + end + end + + # Create all of our handler instances. + def setup_handlers(handlers) + unless handlers.is_a?(Hash) + raise ServerError, "Handlers must have arguments" + end + + handlers.collect { |handler, args| + hclass = nil + unless hclass = Handler.handler(handler) + raise ServerError, "Invalid handler %s" % handler + end + hclass.new(args) + } + end + + # Handle all of the many webrick arguments. + def setup_webrick(hash) + hash[:Port] ||= Puppet[:masterport] + hash[:Logger] ||= self.httplog + hash[:AccessLog] ||= [ + [ self.httplog, WEBrick::AccessLog::COMMON_LOG_FORMAT ], + [ self.httplog, WEBrick::AccessLog::REFERER_LOG_FORMAT ] + ] + + hash[:SSLCertificateStore] = x509store + hash[:SSLCertificate] = self.cert + hash[:SSLPrivateKey] = self.key + hash[:SSLStartImmediately] = true + hash[:SSLEnable] = true + hash[:SSLCACertificateFile] = Puppet[:localcacert] + hash[:SSLVerifyClient] = OpenSSL::SSL::VERIFY_PEER + hash[:SSLCertName] = nil + + if addr = Puppet[:bindaddress] and addr != "" + hash[:BindAddress] = addr + end + end + end + end +end + |
