diff --git a/ext/rack/README b/ext/rack/README index 63b8fde7a..3bdcca53f 100644 --- a/ext/rack/README +++ b/ext/rack/README @@ -1,70 +1,73 @@ PUPPETMASTER AS A RACK APPLICATION ================================== puppetmaster can now be hosted as a standard Rack application. A proper config.ru is provided for this. For more details about rack, see http://rack.rubyforge.org/ . Getting started =============== You'll need rack installed, version 1.0.0. Older versions are known not to work. WEBrick ------- WEBrick is currently not supported as a Rack host. You'll be better off just running puppetmasterd directly. Mongrel ------- If you like Mongrel, and want to replicate wiki:UsingMongrel, you could probably start your backend mongrels this way: cd ext/rack for port in `seq 18140 18150`; do rackup --server mongrel --port $port & done rackup is part of the rack gem. Make sure it's in your path. Apache with Passenger (aka mod_rails) ------------------------------------- -Make sure puppetmasterd ran at least once, so the SSL certificates +Make sure puppetmasterd ran at least once, so the CA & SSL certificates got set up. Requirements: - Passenger version 2.2.2 or newer [1] + Passenger version 2.2.2 or newer*** Rack version 1.0.0 Apache 2.x SSL Module loaded Apache configuration snippet is in files/apache2.conf. You need to edit it to reflect your servername. Required puppet.conf settings: [puppetmasterd] ssl_client_header = SSL_CLIENT_S_DN ssl_client_verify_header = SSL_CLIENT_VERIFY To set up most of the boring stuff, you can use this command: puppet --verbose --modulepath ./ext ext/rack/manifest.pp Or use manifest.pp as a starting point for your own module. Note: Passenger will not let applications run as root or the Apache user, instead an implicit setuid will be done, to the user whom owns config.ru. Therefore, config.ru shall be owned by the puppet user. -[1] http://www.modrails.com/install.html - +*** Important note about Passenger versions: + 2.2.2 is known to work. + 2.2.3-2.2.4 are known to *NOT* work. + 2.2.5 (when it is released) is expected to work properly again. + Passenger installation doc: http://www.modrails.com/install.html diff --git a/lib/puppet/network/http/rack/httphandler.rb b/lib/puppet/network/http/rack/httphandler.rb index e14206850..31aa8371e 100644 --- a/lib/puppet/network/http/rack/httphandler.rb +++ b/lib/puppet/network/http/rack/httphandler.rb @@ -1,16 +1,34 @@ require 'openssl' require 'puppet/ssl/certificate' class Puppet::Network::HTTP::RackHttpHandler def initialize() end # do something useful with request (a Rack::Request) and use # response to fill your Rack::Response def process(request, response) raise NotImplementedError, "Your RackHttpHandler subclass is supposed to override service(request)" end + def ssl_client_header(request) + env_or_request_env(Puppet[:ssl_client_header], request) + end + + def ssl_client_verify_header(request) + env_or_request_env(Puppet[:ssl_client_verify_header], request) + end + + # Older Passenger versions passed all Environment vars in app(env), + # but since 2.2.3 they (some?) are really in ENV. + # Mongrel, etc. may also still use request.env. + def env_or_request_env(var, request) + if ENV.include?(var) + ENV[var] + else + request.env[var] + end + end end diff --git a/lib/puppet/network/http/rack/rest.rb b/lib/puppet/network/http/rack/rest.rb index 104751271..bdca651d1 100644 --- a/lib/puppet/network/http/rack/rest.rb +++ b/lib/puppet/network/http/rack/rest.rb @@ -1,79 +1,79 @@ require 'puppet/network/http/handler' require 'puppet/network/http/rack/httphandler' class Puppet::Network::HTTP::RackREST < Puppet::Network::HTTP::RackHttpHandler include Puppet::Network::HTTP::Handler HEADER_ACCEPT = 'HTTP_ACCEPT'.freeze ContentType = 'Content-Type'.freeze def initialize(args={}) super() initialize_for_puppet(args) end def set_content_type(response, format) response[ContentType] = format_to_mime(format) end # produce the body of the response def set_response(response, result, status = 200) response.status = status response.write result end # Retrieve the accept header from the http request. def accept_header(request) request.env[HEADER_ACCEPT] end # Retrieve the accept header from the http request. def content_type_header(request) request.content_type end # Return which HTTP verb was used in this request. def http_method(request) request.request_method end # Return the query params for this request. def params(request) result = decode_params(request.params) result.merge(extract_client_info(request)) end # what path was requested? (this is, without any query parameters) def path(request) request.path end # return the request body # request.body has some limitiations, so we need to concat it back # into a regular string, which is something puppet can use. def body(request) body = '' request.body.each { |part| body += part } body end def extract_client_info(request) result = {} result[:ip] = request.ip # if we find SSL info in the headers, use them to get a hostname. - # try this with :ssl_client_header, which defaults should work for - # Apache with StdEnvVars. - if dn = request.env[Puppet[:ssl_client_header]] and dn_matchdata = dn.match(/^.*?CN\s*=\s*(.*)/) + # try this with :ssl_client_header. + # For Apache you need special configuration, see ext/rack/README. + if dn = ssl_client_header(request) and dn_matchdata = dn.match(/^.*?CN\s*=\s*(.*)/) result[:node] = dn_matchdata[1].to_str - result[:authenticated] = (request.env[Puppet[:ssl_client_verify_header]] == 'SUCCESS') + result[:authenticated] = (ssl_client_verify_header(request) == 'SUCCESS') else result[:node] = resolve_node(result) result[:authenticated] = false end result end end diff --git a/lib/puppet/network/http/rack/xmlrpc.rb b/lib/puppet/network/http/rack/xmlrpc.rb index 4fc9e82fc..9d0f486bc 100644 --- a/lib/puppet/network/http/rack/xmlrpc.rb +++ b/lib/puppet/network/http/rack/xmlrpc.rb @@ -1,65 +1,65 @@ require 'puppet/network/http/rack/httphandler' require 'puppet/network/xmlrpc/server' require 'resolv' class Puppet::Network::HTTP::RackXMLRPC < Puppet::Network::HTTP::RackHttpHandler def initialize(handlers) @xmlrpc_server = Puppet::Network::XMLRPCServer.new handlers.each do |name| Puppet.debug " -> register xmlrpc namespace %s" % name unless handler = Puppet::Network::Handler.handler(name) raise ArgumentError, "Invalid XMLRPC handler %s" % name end @xmlrpc_server.add_handler(handler.interface, handler.new({})) end super() end def process(request, response) # errors are sent as text/plain response['Content-Type'] = 'text/plain' if not request.post? then response.status = 405 response.write 'Method Not Allowed' return end if request.media_type() != "text/xml" then response.status = 400 response.write 'Bad Request' return end # get auth/certificate data client_request = build_client_request(request) response_body = @xmlrpc_server.process(request.body.read(), client_request) response.status = 200 response['Content-Type'] = 'text/xml; charset=utf-8' response.write response_body end def build_client_request(request) ip = request.ip # if we find SSL info in the headers, use them to get a hostname. - # try this with :ssl_client_header, which defaults should work for - # Apache with StdEnvVars. - if dn = request.env[Puppet[:ssl_client_header]] and dn_matchdata = dn.match(/^.*?CN\s*=\s*(.*)/) + # try this with :ssl_client_header. + # For Apache you need special configuration, see ext/rack/README. + if dn = ssl_client_header(request) and dn_matchdata = dn.match(/^.*?CN\s*=\s*(.*)/) node = dn_matchdata[1].to_str - authenticated = (request.env[Puppet[:ssl_client_verify_header]] == 'SUCCESS') + authenticated = (ssl_client_verify_header(request) == 'SUCCESS') else begin node = Resolv.getname(ip) rescue => detail Puppet.err "Could not resolve %s: %s" % [ip, detail] node = "unknown" end authenticated = false end Puppet::Network::ClientRequest.new(node, ip, authenticated) end end