summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Hofstaedtler <hofstaedtler@inqnet.at>2009-04-28 12:29:20 +0000
committerJames Turnbull <james@lovedthanlost.net>2009-05-02 09:13:29 +1000
commitd6be4e1206e9285dee9fc4d8cde9608c029d4001 (patch)
tree8027f46a38089b893faa8d9e342cacec65333dfb
parent6e01e7ab403d090f29f13c938ca5b19930c4b408 (diff)
downloadpuppet-d6be4e1206e9285dee9fc4d8cde9608c029d4001.tar.gz
puppet-d6be4e1206e9285dee9fc4d8cde9608c029d4001.tar.xz
puppet-d6be4e1206e9285dee9fc4d8cde9608c029d4001.zip
Add XMLRPC compatibility for Rack
-rw-r--r--lib/puppet/network/http/rack.rb19
-rw-r--r--lib/puppet/network/http/rack/xmlrpc.rb65
-rwxr-xr-xspec/unit/network/http/rack.rb31
-rwxr-xr-xspec/unit/network/http/rack/xmlrpc.rb157
4 files changed, 271 insertions, 1 deletions
diff --git a/lib/puppet/network/http/rack.rb b/lib/puppet/network/http/rack.rb
index 58f49416b..aa81b45f7 100644
--- a/lib/puppet/network/http/rack.rb
+++ b/lib/puppet/network/http/rack.rb
@@ -2,6 +2,7 @@
require 'rack'
require 'puppet/network/http'
require 'puppet/network/http/rack/rest'
+require 'puppet/network/http/rack/xmlrpc'
# An rack application, for running the Puppet HTTP Server.
class Puppet::Network::HTTP::Rack
@@ -14,6 +15,14 @@ class Puppet::Network::HTTP::Rack
@rest_http_handler = Puppet::Network::HTTP::RackREST.new()
protocols.delete :rest
+ # Prepare the XMLRPC handler, for backward compatibility (if requested)
+ if args[:protocols].include?(:xmlrpc)
+ raise ArgumentError, "XMLRPC was requested, but no handlers were given" if !args.include?(:xmlrpc_handlers)
+
+ @xmlrpc_http_handler = Puppet::Network::HTTP::RackXMLRPC.new(args[:xmlrpc_handlers])
+ protocols.delete :xmlrpc
+ end
+
raise ArgumentError, "there were unknown :protocols specified." if !protocols.empty?
end
@@ -27,8 +36,16 @@ class Puppet::Network::HTTP::Rack
response = Rack::Response.new()
Puppet.debug 'Handling request: %s %s' % [request.request_method, request.fullpath]
+ # if we shall serve XMLRPC, have /RPC2 go to the xmlrpc handler
+ if @xmlrpc_http_handler and request.path_info.start_with?('/RPC2')
+ handler = @xmlrpc_http_handler
+ else
+ # everything else is handled by the new REST handler
+ handler = @rest_http_handler
+ end
+
begin
- @rest_http_handler.process(request, response)
+ handler.process(request, response)
rescue => detail
# Send a Status 500 Error on unhandled exceptions.
response.status = 500
diff --git a/lib/puppet/network/http/rack/xmlrpc.rb b/lib/puppet/network/http/rack/xmlrpc.rb
new file mode 100644
index 000000000..ef686cd4f
--- /dev/null
+++ b/lib/puppet/network/http/rack/xmlrpc.rb
@@ -0,0 +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, 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*(.*)/)
+ node = dn_matchdata[1].to_str
+ authenticated = (request.env[Puppet[:ssl_client_verify_header]] == '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
+
diff --git a/spec/unit/network/http/rack.rb b/spec/unit/network/http/rack.rb
index 3d5e4fe63..f639b2d4a 100755
--- a/spec/unit/network/http/rack.rb
+++ b/spec/unit/network/http/rack.rb
@@ -25,6 +25,19 @@ describe "Puppet::Network::HTTP::Rack" do
Puppet::Network::HTTP::Rack.new({:protocols => [:rest]})
end
+ describe "with XMLRPC enabled" do
+
+ it "should require XMLRPC handlers" do
+ Proc.new { Puppet::Network::HTTP::Rack.new({:protocols => [:xmlrpc]}) }.should raise_error(ArgumentError)
+ end
+
+ it "should create a RackXMLRPC instance" do
+ Puppet::Network::HTTP::RackXMLRPC.expects(:new)
+ Puppet::Network::HTTP::Rack.new({:protocols => [:xmlrpc], :xmlrpc_handlers => [:Status]})
+ end
+
+ end
+
end
describe "when called" do
@@ -67,5 +80,23 @@ describe "Puppet::Network::HTTP::Rack" do
end
+ describe "when serving XMLRPC" do
+
+ before :all do
+ @app = Puppet::Network::HTTP::Rack.new({:protocols => [:rest, :xmlrpc], :xmlrpc_handlers => [:Status]})
+ @linted = Rack::Lint.new(@app)
+ end
+
+ before :each do
+ @env = Rack::MockRequest.env_for('/RPC2', :method => 'POST')
+ end
+
+ it "should use RackXMLRPC to serve /RPC2 requests" do
+ Puppet::Network::HTTP::RackXMLRPC.any_instance.expects(:process).once
+ @linted.call(@env)
+ end
+
+ end
+
end
diff --git a/spec/unit/network/http/rack/xmlrpc.rb b/spec/unit/network/http/rack/xmlrpc.rb
new file mode 100755
index 000000000..38316d8bf
--- /dev/null
+++ b/spec/unit/network/http/rack/xmlrpc.rb
@@ -0,0 +1,157 @@
+#!/usr/bin/env ruby
+
+require File.dirname(__FILE__) + '/../../../../spec_helper'
+require 'puppet/network/http/rack'
+require 'puppet/network/http/rack/xmlrpc'
+
+describe "Puppet::Network::HTTP::RackXMLRPC" do
+ confine "Rack is not available" => Puppet.features.rack?
+
+ describe "when initializing" do
+ it "should create an Puppet::Network::XMLRPCServer" do
+ Puppet::Network::XMLRPCServer.expects(:new).returns stub_everything
+ Puppet::Network::HTTP::RackXMLRPC.new([])
+ end
+
+ it "should create each handler" do
+ handler = stub_everything 'handler'
+ Puppet::Network::XMLRPCServer.any_instance.stubs(:add_handler)
+ Puppet::Network::Handler.expects(:handler).returns(handler).times(2)
+ Puppet::Network::HTTP::RackXMLRPC.new([:foo, :bar])
+ end
+
+ it "should add each handler to the XMLRPCserver" do
+ handler = stub_everything 'handler'
+ Puppet::Network::Handler.stubs(:handler).returns(handler)
+ Puppet::Network::XMLRPCServer.any_instance.expects(:add_handler).times(2)
+ Puppet::Network::HTTP::RackXMLRPC.new([:foo, :bar])
+ end
+ end
+
+ describe "when serving a request" do
+
+ before :each do
+ foo_handler = stub_everything 'foo_handler'
+ Puppet::Network::Handler.stubs(:handler).with(:foo).returns foo_handler
+ Puppet::Network::XMLRPCServer.any_instance.stubs(:add_handler)
+ Puppet::Network::XMLRPCServer.any_instance.stubs(:process).returns('<xml/>')
+ @handler = Puppet::Network::HTTP::RackXMLRPC.new([:foo])
+ end
+
+ before :each do
+ @response = Rack::Response.new()
+ end
+
+ def mk_req(opts = {})
+ opts[:method] = 'POST' if !opts[:method]
+ opts['CONTENT_TYPE'] = 'text/xml; foo=bar' if !opts['CONTENT_TYPE']
+ env = Rack::MockRequest.env_for('/RPC2', opts)
+ Rack::Request.new(env)
+ end
+
+ it "should reject non-POST requests" do
+ req = mk_req :method => 'PUT'
+ @handler.process(req, @response)
+ @response.status.should == 405
+ end
+
+ it "should reject non text/xml requests" do
+ req = mk_req 'CONTENT_TYPE' => 'yadda/plain'
+ end
+
+ it "should create a ClientRequest" do
+ cr = Puppet::Network::ClientRequest.new(nil, '127.0.0.1', false)
+ Puppet::Network::ClientRequest.expects(:new).returns cr
+ req = mk_req
+ @handler.process(req, @response)
+ end
+
+ it "should let xmlrpcserver process the request" do
+ Puppet::Network::XMLRPCServer.any_instance.expects(:process).returns('yay')
+ req = mk_req
+ @handler.process(req, @response)
+ end
+
+ it "should report the response as OK" do
+ req = mk_req
+ @handler.process(req, @response)
+ @response.status.should == 200
+ end
+
+ it "should report the response with the correct content type" do
+ req = mk_req
+ @handler.process(req, @response)
+ @response['Content-Type'].should == 'text/xml; charset=utf-8'
+ end
+
+ it "should set 'authenticated' to false if no certificate is present" do
+ req = mk_req
+ Puppet::Network::ClientRequest.expects(:new).with() { |node,ip,authenticated| authenticated == false }
+ @handler.process(req, @response)
+ end
+
+ it "should use the client's ip address" do
+ req = mk_req 'REMOTE_ADDR' => 'ipaddress'
+ Puppet::Network::ClientRequest.expects(:new).with() { |node,ip,authenticated| ip == 'ipaddress' }
+ @handler.process(req, @response)
+ end
+
+ describe "with pre-validated certificates" do
+
+ it "should use the :ssl_client_header to determine the parameter when looking for the certificate" do
+ Puppet.settings.stubs(:value).returns "eh"
+ Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader"
+ req = mk_req "myheader" => "/CN=host.domain.com"
+ @handler.process(req, @response)
+ end
+
+ it "should retrieve the hostname by matching the certificate parameter" do
+ Puppet.settings.stubs(:value).returns "eh"
+ Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader"
+ Puppet::Network::ClientRequest.expects(:new).with() { |node,ip,authenticated| node == "host.domain.com" }
+ req = mk_req "myheader" => "/CN=host.domain.com"
+ @handler.process(req, @response)
+ end
+
+ it "should use the :ssl_client_header to determine the parameter for checking whether the host certificate is valid" do
+ Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader"
+ Puppet.settings.expects(:value).with(:ssl_client_verify_header).returns "myheader"
+ req = mk_req "myheader" => "SUCCESS", "certheader" => "/CN=host.domain.com"
+ @handler.process(req, @response)
+ end
+
+ it "should consider the host authenticated if the validity parameter contains 'SUCCESS'" do
+ Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader"
+ Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader"
+ Puppet::Network::ClientRequest.expects(:new).with() { |node,ip,authenticated| authenticated == true }
+ req = mk_req "myheader" => "SUCCESS", "certheader" => "/CN=host.domain.com"
+ @handler.process(req, @response)
+ end
+
+ it "should consider the host unauthenticated if the validity parameter does not contain 'SUCCESS'" do
+ Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader"
+ Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader"
+ Puppet::Network::ClientRequest.expects(:new).with() { |node,ip,authenticated| authenticated == false }
+ req = mk_req "myheader" => "whatever", "certheader" => "/CN=host.domain.com"
+ @handler.process(req, @response)
+ end
+
+ it "should consider the host unauthenticated if no certificate information is present" do
+ Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader"
+ Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader"
+ Puppet::Network::ClientRequest.expects(:new).with() { |node,ip,authenticated| authenticated == false }
+ req = mk_req "myheader" => nil, "certheader" => "/CN=host.domain.com"
+ @handler.process(req, @response)
+ end
+
+ it "should resolve the node name with an ip address look-up if no certificate is present" do
+ Puppet.settings.stubs(:value).returns "eh"
+ Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader"
+ Resolv.any_instance.expects(:getname).returns("host.domain.com")
+ Puppet::Network::ClientRequest.expects(:new).with() { |node,ip,authenticated| node == "host.domain.com" }
+ req = mk_req "myheader" => nil
+ @handler.process(req, @response)
+ end
+ end
+ end
+end