[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#702972: unblock: puppet/2.7.18-3



On Wed, Mar 13, 2013 at 04:20:40PM +0100, Stig Sandbeck Mathisen wrote:
> The attached debdiff is rather large, but the vast majority of changes are in
> the acceptance and unit tests.  The changes in lib/puppet and conf/auth.conf
> are small by comparison.

Forgot to actually attach the debdiff.  Here it is.
diff -Nru puppet-2.7.18/debian/changelog puppet-2.7.18/debian/changelog
--- puppet-2.7.18/debian/changelog	2012-09-19 11:45:14.000000000 +0200
+++ puppet-2.7.18/debian/changelog	2013-03-13 15:46:03.000000000 +0100
@@ -1,3 +1,17 @@
+puppet (2.7.18-3) unstable; urgency=high
+
+  * Add patch to fix puppet vulnerabilities (CVE-2013-1640, CVE-2013-1652,
+    CVE-2013-1653, CVE-2013-1654, CVE-2013-1655, CVE-2013-2275)
+     - CVE-2013-1640 - Remote code execution on master from authenticated
+       clients
+     - CVE-2013-1652 - Insufficient input validation
+     - CVE-2013-1653 - Remote code execution
+     - CVE-2013-1654 - Protocol downgrade
+     - CVE-2013-1655 - Unauthenticated remote code execution risk
+     - CVE-2013-2275 - Incorrect default report ACL
+
+ -- Stig Sandbeck Mathisen <ssm@debian.org>  Wed, 13 Mar 2013 15:45:25 +0100
+
 puppet (2.7.18-2) unstable; urgency=low
 
   [ Faidon Liambotis ]
diff -Nru puppet-2.7.18/debian/patches/2.7.18-CVE-Rollup.patch puppet-2.7.18/debian/patches/2.7.18-CVE-Rollup.patch
--- puppet-2.7.18/debian/patches/2.7.18-CVE-Rollup.patch	1970-01-01 01:00:00.000000000 +0100
+++ puppet-2.7.18/debian/patches/2.7.18-CVE-Rollup.patch	2013-03-13 15:46:03.000000000 +0100
@@ -0,0 +1,2399 @@
+From 4359f8b0ac0c5ab995943945c12c2142b846805d Mon Sep 17 00:00:00 2001
+From: Nick Lewis <nick@puppetlabs.com>
+Date: Wed, 20 Feb 2013 17:28:14 -0800
+Subject: [PATCH] 2.7.18 - CVE Patch Rollup
+
+This contains patches for CVE-2013-1640, CVE-2013-1652, CVE-2013-1653, CVE-2013-1654, CVE-2013-1655, CVE-2013-2275
+
+(#19393) Safely load YAML from the network
+
+When using Psych (only available in Ruby 1.9), YAML.load may in certain
+cases (!map: and !ruby/hash: tags) use the initialize and []= methods to
+create objects. These methods could potentially allow arbitrary code to
+be executed. We now add a safely_load method which parses the input
+first, checks it for dangerous tags, and fails if they are found. If the
+input is clean, we transform the result (as YAML.load would have done)
+and go on our merry way.
+
+This change is only applied to YAML coming in either HTTP requests or
+responses. YAML from the local system is trusted, as anyone who can
+write that YAML could do damage some other way.
+
+(#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.7.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.
+
+(#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.
+
+(#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, or by calling the
+SSLContext#options= instance method as WEBrick does.  We cannot monkey
+patch SSLContext#options= because this method is implemented as a C
+extension to MRI and as such cannot be easily redefined.  Segfaults
+abound in MRI 1.8.5.
+
+Updating module tool acceptance tests with new expectations.
+
+Conflicts:
+	acceptance/tests/modules/search/by_keyword.rb
+
+Fix module tool acceptance test
+
+This test was failing due to a change on the forge server when rendering
+the description for a module.
+
+This commit changes the test to expect UNKNOWN for the description.
+
+(#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.
+
+(#19392) (CVE-2013-1653) Don't call methods on unvalidated objects
+
+(#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.
+
+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.
+
+(#19392) Don't validate key for certificate_status
+
+The certificate_status file terminus does not use the name attribute of the
+passed in instance. In order to preserve the existing API which doesn't
+require a name on the instance (and so gets the default of Puppet[:certname])
+we skip validation for this terminus.
+
+(#14093) Stub missing expectations
+
+The test was backported in commit e611b95b to 2.7.11, but did not work
+due to the spec test overly stubbing everything. Rather than redo the
+test, this commit simply stubs out the missing compiler, environment,
+and known_resource_types.
+---
+ acceptance/tests/modules/search/by_keyword.rb      |   4 +-
+ acceptance/tests/modules/search/by_module_name.rb  |   4 +-
+ .../tests/security/cve-2013-1640_facter_string.rb  |  14 +
+ .../cve-2013-1652_improper_query_params.rb         |  39 +++
+ .../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/certificate_status/file.rb   |   5 +
+ 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/active_record.rb    |   3 +
+ lib/puppet/indirector/resource/ral.rb              |   4 +
+ lib/puppet/indirector/resource/store_configs.rb    |   3 +
+ lib/puppet/indirector/resource/validator.rb        |   8 +
+ lib/puppet/indirector/rest.rb                      |   8 +
+ lib/puppet/indirector/run/local.rb                 |   4 +
+ lib/puppet/indirector/terminus.rb                  |  20 ++
+ lib/puppet/network/formats.rb                      |   6 +-
+ lib/puppet/network/handler/master.rb               |   2 +-
+ lib/puppet/network/handler/report.rb               |   2 +-
+ lib/puppet/network/http/handler.rb                 |   8 +-
+ lib/puppet/network/http/rack/rest.rb               |   9 +-
+ lib/puppet/network/http/webrick.rb                 |   1 +
+ lib/puppet/network/rest_authconfig.rb              |   2 +-
+ lib/puppet/parser/templatewrapper.rb               |  34 +-
+ lib/puppet/util/monkey_patches.rb                  |  58 ++++
+ .../indirector/catalog/compiler_spec.rb            |   1 +
+ spec/integration/indirector/catalog/queue_spec.rb  |   2 +-
+ spec/integration/resource/catalog_spec.rb          |   1 +
+ spec/unit/indirector/catalog/compiler_spec.rb      |  31 +-
+ spec/unit/indirector/indirection_spec.rb           |  19 +-
+ spec/unit/indirector/request_spec.rb               |  22 ++
+ spec/unit/indirector/terminus_spec.rb              | 368 +++++++++++----------
+ spec/unit/network/formats_spec.rb                  |  12 +-
+ spec/unit/network/http/handler_spec.rb             |  25 ++
+ spec/unit/network/http/rack/rest_spec.rb           |  17 +
+ spec/unit/network/http/webrick_spec.rb             |   4 +
+ spec/unit/network/http_pool_spec.rb                |   1 -
+ spec/unit/network/rest_authconfig_spec.rb          |  17 +-
+ spec/unit/parser/functions/inline_template_spec.rb |  13 +
+ spec/unit/parser/functions/template_spec.rb        |  21 ++
+ spec/unit/parser/templatewrapper_spec.rb           |  23 +-
+ spec/unit/ssl/certificate_request_spec.rb          |   2 +
+ spec/unit/ssl/host_spec.rb                         |   1 +
+ spec/unit/util/monkey_patches_spec.rb              |  12 +
+ 55 files changed, 1170 insertions(+), 228 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
+
+--- /dev/null
++++ b/acceptance/tests/security/cve-2013-1640_facter_string.rb
+@@ -0,0 +1,14 @@
++# 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
++  confine :except, :platform => 'windows'
++
++  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
+--- /dev/null
++++ b/acceptance/tests/security/cve-2013-1652_improper_query_params.rb
+@@ -0,0 +1,39 @@
++require 'json'
++
++test_name "CVE 2013-1652 Improper query parameter validation" do
++  confine :except, :platform => 'windows'
++
++  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
+--- /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
+--- /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
+--- /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
+--- /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
+--- /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
+--- /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 )
++
++    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 => '/bin/touch #{file_to_create}', :logoutput => true)
++      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}/#{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 ))
++      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
+--- /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
+--- /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
+--- a/conf/auth.conf
++++ b/conf/auth.conf
+@@ -63,10 +63,10 @@
+ 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
+--- a/lib/puppet/indirector/catalog/compiler.rb
++++ b/lib/puppet/indirector/catalog/compiler.rb
+@@ -13,7 +13,9 @@
+ 
+   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 @@
+     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.add_timestamp
+     Puppet::Node::Facts.indirection.save(facts)
+   end
+@@ -104,7 +111,11 @@
+   # 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
+--- a/lib/puppet/indirector/certificate_status/file.rb
++++ b/lib/puppet/indirector/certificate_status/file.rb
+@@ -79,4 +79,9 @@
+       nil
+     end
+   end
++
++  def validate_key(request)
++    # We only use desired_state from the instance and use request.key
++    # otherwise, so the name does not need to match
++  end
+ end
+--- /dev/null
++++ b/lib/puppet/indirector/errors.rb
+@@ -0,0 +1,5 @@
++require 'puppet/error'
++
++module Puppet::Indirector
++  class ValidationError < Puppet::Error; end
++end
+--- a/lib/puppet/indirector/file_bucket_file/file.rb
++++ b/lib/puppet/indirector/file_bucket_file/file.rb
+@@ -48,6 +48,10 @@
+       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)
+--- a/lib/puppet/indirector/file_bucket_file/selector.rb
++++ b/lib/puppet/indirector/file_bucket_file/selector.rb
+@@ -44,6 +44,10 @@
+         true
+       end
+     end
++
++    def validate_key(request)
++      get_terminus(request).validate(request)
++    end
+   end
+ end
+ 
+--- a/lib/puppet/indirector/indirection.rb
++++ b/lib/puppet/indirector/indirection.rb
+@@ -309,6 +309,7 @@
+ 
+     dest_terminus = terminus(terminus_name)
+     check_authorization(request, dest_terminus)
++    dest_terminus.validate(request)
+ 
+     dest_terminus
+   end
+--- a/lib/puppet/indirector/request.rb
++++ b/lib/puppet/indirector/request.rb
+@@ -150,6 +150,10 @@
+     return(uri ? uri : "/#{indirection_name}/#{key}")
+   end
+ 
++  def remote?
++    self.node or self.ip
++  end
++
+   private
+ 
+   def set_attributes(options)
+--- a/lib/puppet/indirector/resource/active_record.rb
++++ b/lib/puppet/indirector/resource/active_record.rb
+@@ -1,6 +1,9 @@
+ require 'puppet/indirector/active_record'
++require 'puppet/indirector/resource/validator'
+ 
+ class Puppet::Resource::ActiveRecord < Puppet::Indirector::ActiveRecord
++  include Puppet::Resource::Validator
++
+   def search(request)
+     type   = request_to_type_name(request)
+     host   = request.options[:host]
+--- 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) }
+--- a/lib/puppet/indirector/resource/store_configs.rb
++++ b/lib/puppet/indirector/resource/store_configs.rb
+@@ -1,3 +1,6 @@
+ require 'puppet/indirector/store_configs'
++require 'puppet/indirector/resource/validator'
++
+ class Puppet::Resource::StoreConfigs < Puppet::Indirector::StoreConfigs
++  include Puppet::Resource::Validator
+ end
+--- /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
+--- a/lib/puppet/indirector/rest.rb
++++ b/lib/puppet/indirector/rest.rb
+@@ -102,6 +102,10 @@
+       msg = valid_certnames.length > 1 ? "one of #{valid_certnames.join(', ')}" : valid_certnames.first
+ 
+       raise Puppet::Error, "Server hostname '#{http_connection.address}' did not match server certificate; expected #{msg}"
++    elsif error.message.empty?
++      # This may be because the server is speaking SSLv2 and we
++      # monkey patch OpenSSL::SSL:SSLContext to reject SSLv2.
++      raise error.exception("#{error.class} with no message")
+     else
+       raise
+     end
+@@ -152,6 +156,10 @@
+     deserialize http_put(request, 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
+--- a/lib/puppet/indirector/run/local.rb
++++ b/lib/puppet/indirector/run/local.rb
+@@ -5,4 +5,8 @@
+   def save( request )
+     request.instance.run
+   end
++
++  def validate_key(request)
++    # No key is necessary for kick
++  end
+ end
+--- 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'
+ 
+@@ -142,4 +143,23 @@
+   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
+--- a/lib/puppet/network/formats.rb
++++ b/lib/puppet/network/formats.rb
+@@ -3,12 +3,12 @@
+ Puppet::Network::FormatHandler.create_serialized_formats(:yaml) do
+   # Yaml doesn't need the class name; it's serialized.
+   def intern(klass, text)
+-    YAML.load(text)
++    YAML.safely_load(text)
+   end
+ 
+   # Yaml doesn't need the class name; it's serialized.
+   def intern_multiple(klass, text)
+-    YAML.load(text)
++    YAML.safely_load(text)
+   end
+ 
+   def render(instance)
+@@ -72,7 +72,7 @@
+ 
+   def decode(yaml)
+     requiring_zlib do
+-      YAML.load(Zlib::Inflate.inflate(Base64.decode64(yaml)))
++      YAML.safely_load(Zlib::Inflate.inflate(Base64.decode64(yaml)))
+     end
+   end
+ end
+--- a/lib/puppet/network/handler/master.rb
++++ b/lib/puppet/network/handler/master.rb
+@@ -69,7 +69,7 @@
+         Puppet.debug "Our client is remote"
+ 
+         begin
+-          facts = YAML.load(CGI.unescape(facts))
++          facts = YAML.safely_load(CGI.unescape(facts))
+         rescue => detail
+           raise XMLRPC::FaultException.new(
+             1, "Could not rebuild facts"
+--- a/lib/puppet/network/handler/report.rb
++++ b/lib/puppet/network/handler/report.rb
+@@ -45,7 +45,7 @@
+ 
+       # First convert the report to real objects
+       begin
+-        report = YAML.load(yaml)
++        report = YAML.safely_load(yaml)
+       rescue => detail
+         Puppet.warning "Could not load report: #{detail}"
+         return
+--- a/lib/puppet/network/http/handler.rb
++++ b/lib/puppet/network/http/handler.rb
+@@ -70,6 +70,8 @@
+     raise
+   rescue Exception => e
+     return do_exception(response, e)
++  ensure
++    cleanup(request)
+   end
+ 
+   # Set the response up, with the body and status.
+@@ -220,6 +222,10 @@
+     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
+@@ -233,7 +239,7 @@
+       next result if param == :ip
+       value = CGI.unescape(value)
+       if value =~ /^---/
+-        value = YAML.load(value)
++        value = YAML.safely_load(value)
+       else
+         value = true if value == "true"
+         value = false if value == "false"
+--- a/lib/puppet/network/http/rack/rest.rb
++++ b/lib/puppet/network/http/rack/rest.rb
+@@ -73,12 +73,17 @@
+   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
+--- a/lib/puppet/network/http/webrick.rb
++++ b/lib/puppet/network/http/webrick.rb
+@@ -106,6 +106,7 @@
+     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.indirection.find(Puppet::SSL::CA_NAME)
+ 
+--- a/lib/puppet/network/rest_authconfig.rb
++++ b/lib/puppet/network/rest_authconfig.rb
+@@ -13,7 +13,7 @@
+       # 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 },
+       # These allow `auth any`, because if you can do them anonymously you
+       # should probably also be able to do them when trusted.
+       { :acl => "/certificate/ca", :method => :find, :authenticated => :any },
+--- a/lib/puppet/parser/templatewrapper.rb
++++ b/lib/puppet/parser/templatewrapper.rb
+@@ -5,8 +5,6 @@
+ 
+ class Puppet::Parser::TemplateWrapper
+   attr_writer :scope
+-  attr_reader :file
+-  attr_accessor :string
+   include Puppet::Util
+   Puppet::Util.logmethods(self)
+ 
+@@ -14,18 +12,22 @@
+     @__scope__ = scope
+   end
+ 
++  def file
++    @__file__
++  end
++
+   def scope
+     @__scope__
+   end
+ 
+   def script_line
+     # find which line in the template (if any) we were called from
+-    (caller.find { |l| l =~ /#{file}:/ }||"")[/:(\d+):/,1]
++    (caller.find { |l| l =~ /#{@__file__}:/ }||"")[/:(\d+):/,1]
+   end
+ 
+   # Should return true if a variable is defined, false if it is not
+   def has_variable?(name)
+-    scope.lookupvar(name.to_s, :file => file, :line => script_line) != :undefined
++    scope.lookupvar(name.to_s, :file => @__file__, :line => script_line) != :undefined
+   end
+ 
+   # Allow templates to access the defined classes
+@@ -56,53 +58,51 @@
+   # the missing_method definition here until we declare the syntax finally
+   # dead.
+   def method_missing(name, *args)
+-    value = scope.lookupvar(name.to_s,:file => file,:line => script_line)
++    value = scope.lookupvar(name.to_s,:file => @__file__,:line => script_line)
+     if value != :undefined
+       return value
+     else
+       # Just throw an error immediately, instead of searching for
+       # other missingmethod things or whatever.
+-      raise Puppet::ParseError.new("Could not find value for '#{name}'",@file,script_line)
++      raise Puppet::ParseError.new("Could not find value for '#{name}'", @__file__, script_line)
+     end
+   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.filename = file
++      template = ERB.new(string, 0, "-")
++      template.filename = @__file__
+       result = template.result(binding)
+     end
+ 
+@@ -110,6 +110,6 @@
+   end
+ 
+   def to_s
+-    "template[#{(file ? file : "inline")}]"
++    "template[#{(@__file__ ? @__file__ : "inline")}]"
+   end
+ end
+--- a/lib/puppet/util/monkey_patches.rb
++++ b/lib/puppet/util/monkey_patches.rb
+@@ -34,6 +34,21 @@
+   end
+ }
+ 
++if defined?(YAML::ENGINE) and YAML::ENGINE.yamler == 'psych'
++  def Psych.safely_load(str)
++    result = Psych.parse(str)
++    if invalid_node = result.find { |node| node.tag =~ /!map:(.*)/ || node.tag =~ /!ruby\/hash:(.*)/ }
++      raise ArgumentError, "Illegal YAML mapping found with tag #{invalid_node.tag}; please use !ruby/object:#{$1} instead"
++    else
++      result.to_ruby
++    end
++  end
++else
++  def YAML.safely_load(str)
++    self.load(str)
++  end
++end
++
+ def YAML.dump(*args)
+   ZAML.dump(*args)
+ end
+@@ -215,3 +230,46 @@
+     module_function :move
+   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
+--- a/spec/integration/indirector/catalog/compiler_spec.rb
++++ b/spec/integration/indirector/catalog/compiler_spec.rb
+@@ -11,6 +11,7 @@
+     @catalog = Puppet::Resource::Catalog.new
+     @catalog.add_resource(@one = Puppet::Resource.new(:file, "/one"))
+     @catalog.add_resource(@two = Puppet::Resource.new(:file, "/two"))
++    Puppet::Resource::Catalog.indirection.terminus.stubs(:validate)
+   end
+ 
+   after { Puppet.settings.clear }
+--- a/spec/integration/indirector/catalog/queue_spec.rb
++++ b/spec/integration/indirector/catalog/queue_spec.rb
+@@ -6,7 +6,7 @@
+ 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")
+--- a/spec/integration/resource/catalog_spec.rb
++++ b/spec/integration/resource/catalog_spec.rb
+@@ -48,6 +48,7 @@
+       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.indirection.find("me", :use_node => node)
+     end
+--- a/spec/unit/indirector/catalog/compiler_spec.rb
++++ b/spec/unit/indirector/catalog/compiler_spec.rb
+@@ -53,13 +53,22 @@
+       @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.indirection.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.indirection.expects(:find).with(@name).returns(@node)
+@@ -99,6 +108,24 @@
+       @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.indirection.stubs(:find).returns(@node)
+       config = mock 'config'
+@@ -133,7 +160,7 @@
+     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 = Puppet::Node::Facts.new('hostname', "fact" => "value", "architecture" => "i386")
+       Puppet::Node::Facts.indirection.stubs(:save).returns(nil)
+--- a/spec/unit/indirector/indirection_spec.rb
++++ b/spec/unit/indirector/indirection_spec.rb
+@@ -40,7 +40,7 @@
+       end
+     end
+ 
+-    request = stub 'request', :key => "me", :options => {}
++    request = Puppet::Indirector::Request.new(:indirection, :find, "me", nil)
+ 
+     @indirection.stubs(:request).returns request
+ 
+@@ -101,6 +101,16 @@
+   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
+   describe "when initializing" do
+     # (LAK) I've no idea how to test this, really.
+@@ -141,6 +151,7 @@
+     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'
+@@ -211,6 +222,7 @@
+ 
+       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)
+@@ -251,6 +263,7 @@
+         before do
+           @indirection.cache_class = :cache_terminus
+           @cache_class.stubs(:new).returns(@cache)
++          @cache.stubs(:validate)
+ 
+           @instance.stubs(:expired?).returns false
+         end
+@@ -384,6 +397,7 @@
+ 
+       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)
+@@ -501,6 +515,7 @@
+ 
+       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"
+@@ -539,6 +554,7 @@
+ 
+       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])
+@@ -707,6 +723,7 @@
+     before do
+       @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test)
+       @terminus = mock 'terminus'
++      @terminus.stubs(:validate)
+       @terminus_class = stub 'terminus class', :new => @terminus
+     end
+ 
+--- a/spec/unit/indirector/request_spec.rb
++++ b/spec/unit/indirector/request_spec.rb
+@@ -311,4 +311,26 @@
+       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
+--- a/spec/unit/indirector/terminus_spec.rb
++++ b/spec/unit/indirector/terminus_spec.rb
+@@ -1,250 +1,264 @@
+-#!/usr/bin/env rspec
++#! /usr/bin/env ruby
+ require 'spec_helper'
+ require 'puppet/defaults'
+ require 'puppet/indirector'
+ require 'puppet/indirector/memory'
+ 
+-describe Puppet::Indirector::Terminus, :'fails_on_ruby_1.9.2' => true 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"
++describe Puppet::Indirector::Terminus do
++  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", :'fails_on_ruby_1.9.2' => true 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", :'fails_on_ruby_1.9.2' => true 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 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 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)
++    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 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)
++  describe "when listing terminus classes" do
++    it "should list the terminus files available to load" do
++      Puppet::Util::Autoload.any_instance.stubs(:files_to_load).returns ["/foo/bar/baz", "/max/runs/marathon"]
++      Puppet::Indirector::Terminus.terminus_classes('my_stuff').should == [:baz, :marathon]
++    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", :'fails_on_ruby_1.9.2' => true 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)
+-  end
+-end
++    describe "the instance key and class match the request key and model class" do
++      let(:instance) { model.new("the_key") }
+ 
+-describe Puppet::Indirector::Terminus, " when listing terminus classes" do
+-  it "should list the terminus files available to load" do
+-    Puppet::Util::Autoload.any_instance.stubs(:files_to_load).returns ["/foo/bar/baz", "/max/runs/marathon"]
+-    Puppet::Indirector::Terminus.terminus_classes('my_stuff').should == [:baz, :marathon]
++      it "passes" do
++        terminus.validate(request)
++      end
++    end
+   end
+ end
+--- a/spec/unit/network/formats_spec.rb
++++ b/spec/unit/network/formats_spec.rb
+@@ -55,15 +55,15 @@
+       @yaml.render_multiple(instances).should == "foo"
+     end
+ 
+-    it "should intern by calling 'YAML.load'" do
++    it "should safely load YAML when interning" do
+       text = "foo"
+-      YAML.expects(:load).with("foo").returns "bar"
++      YAML.expects(:safely_load).with("foo").returns "bar"
+       @yaml.intern(String, text).should == "bar"
+     end
+ 
+-    it "should intern multiples by calling 'YAML.load'" do
++    it "should safely load YAML when interning multiples" do
+       text = "foo"
+-      YAML.expects(:load).with("foo").returns "bar"
++      YAML.expects(:safely_load).with("foo").returns "bar"
+       @yaml.intern_multiple(String, text).should == "bar"
+     end
+   end
+@@ -120,10 +120,10 @@
+       @yaml.intern_multiple(String, text).should == "bar"
+     end
+ 
+-    it "should decode by base64 decoding, uncompressing and Yaml loading" do
++    it "should decode by base64 decoding, uncompressing and safely Yaml loading" do
+       Base64.expects(:decode64).with("zorg").returns "foo"
+       Zlib::Inflate.expects(:inflate).with("foo").returns "baz"
+-      YAML.expects(:load).with("baz").returns "bar"
++      YAML.expects(:safely_load).with("baz").returns "bar"
+       @yaml.decode("zorg").should == "bar"
+     end
+ 
+--- a/spec/unit/network/http/handler_spec.rb
++++ b/spec/unit/network/http/handler_spec.rb
+@@ -125,6 +125,31 @@
+       @handler.request_format(@request).should == "s"
+     end
+ 
++    it "should deserialize YAML parameters" do
++      params = {'my_param' => [1,2,3].to_yaml}
++
++      decoded_params = @handler.send(:decode_params, params)
++
++      decoded_params.should == {:my_param => [1,2,3]}
++    end
++
++    it "should accept YAML parameters with !ruby/hash tags on Ruby 1.8", :if => RUBY_VERSION =~ /^1\.8/ do
++      params = {'my_param' => "--- !ruby/hash:Array {}"}
++
++      decoded_params = @handler.send(:decode_params, params)
++
++      decoded_params[:my_param].should be_an(Array)
++    end
++
++    # These are only dangerous with Psych, which is Ruby 1.9-only. Since
++    # there's no real way to change the yamler in Puppet, assume that 1.9 means
++    # Psych, especially in tests.
++    it "should fail if YAML parameters have !ruby/hash tags on Ruby 1.9", :unless => RUBY_VERSION =~ /^1\.8/ do
++      params = {'my_param' => "--- !ruby/hash:Array {}"}
++
++      expect { @handler.send(:decode_params, params) }.to raise_error(ArgumentError, /Illegal YAML mapping found/)
++    end
++
+     describe "when finding a model instance" do
+       before do
+         @indirection.stubs(:find).returns @result
+--- a/spec/unit/network/http/rack/rest_spec.rb
++++ b/spec/unit/network/http/rack/rest_spec.rb
+@@ -91,6 +91,23 @@
+           @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
+--- a/spec/unit/network/http/webrick_spec.rb
++++ b/spec/unit/network/http/webrick_spec.rb
+@@ -320,6 +320,10 @@
+       @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
+--- a/spec/unit/network/http_pool_spec.rb
++++ b/spec/unit/network/http_pool_spec.rb
+@@ -79,7 +79,6 @@
+ 
+       it                { should be_use_ssl }
+       its(:cert)        { should be_nil }
+-      its(:cert_store)  { should be_nil }
+       its(:ca_file)     { should be_nil }
+       its(:key)         { should be_nil }
+       its(:verify_mode) { should == OpenSSL::SSL::VERIFY_NONE }
+--- a/spec/unit/network/rest_authconfig_spec.rb
++++ b/spec/unit/network/rest_authconfig_spec.rb
+@@ -85,6 +85,9 @@
+   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)
+@@ -122,6 +125,18 @@
+       @authconfig.insert_default_acl
+     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.rights.stubs(:[]).returns(true)
++      @authconfig.rights.stubs(:[]).with(acl[:acl]).returns(nil)
+ 
++      @authconfig.expects(:mk_acl).with(acl)
++      @authconfig.insert_default_acl
++    end
++  end
+ end
+--- a/spec/unit/parser/functions/inline_template_spec.rb
++++ b/spec/unit/parser/functions/inline_template_spec.rb
+@@ -58,4 +58,17 @@
+     lambda { @scope.function_inline_template("1") }.should raise_error(Puppet::ParseError)
+   end
+ 
++  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
+--- a/spec/unit/parser/functions/template_spec.rb
++++ b/spec/unit/parser/functions/template_spec.rb
+@@ -53,6 +53,22 @@
+     @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")
++    @scope.stubs(:compiler => stub(:environment => 'production'))
++    @scope.stubs(:known_resource_types => stub(:watch_file))
++
++    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")
++    @scope.stubs(:compiler => stub(:environment => 'production'))
++    @scope.stubs(:known_resource_types => stub(:watch_file))
++
++    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)
+@@ -61,4 +77,9 @@
+     lambda { @scope.function_template("1") }.should raise_error(Puppet::ParseError)
+   end
+ 
++  def eval_template(content)
++    File.stubs(:read).with("template").returns(content)
++    Puppet::Parser::Files.stubs(:find_template).returns("template")
++    @scope.function_template(['template'])
++  end
+ end
+--- a/spec/unit/parser/templatewrapper_spec.rb
++++ b/spec/unit/parser/templatewrapper_spec.rb
+@@ -30,16 +30,14 @@
+ 
+   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
+ 
+@@ -66,6 +64,13 @@
+     @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
+     mock_template
+     @tw.result("template contents").should eql("woot!")
+@@ -139,4 +144,14 @@
+       @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
+--- a/spec/unit/ssl/certificate_request_spec.rb
++++ b/spec/unit/ssl/certificate_request_spec.rb
+@@ -254,6 +254,7 @@
+ 
+         csr = Puppet::SSL::CertificateRequest.new("me")
+         terminus = mock 'terminus'
++        terminus.stubs(:validate)
+         Puppet::SSL::CertificateRequest.indirection.expects(:prepare).returns(terminus)
+         terminus.expects(:save).with { |request| request.instance == csr && request.key == "me" }
+ 
+@@ -267,6 +268,7 @@
+ 
+         csr = Puppet::SSL::CertificateRequest.new("me")
+         terminus = mock 'terminus'
++        terminus.stubs(:validate)
+         Puppet::SSL::CertificateRequest.indirection.expects(:prepare).returns(terminus)
+         terminus.expects(:save).with { |request| request.instance == csr && request.key == "me" }
+ 
+--- a/spec/unit/ssl/host_spec.rb
++++ b/spec/unit/ssl/host_spec.rb
+@@ -489,6 +489,7 @@
+       @request.stubs(:generate)
+       @request.stubs(:name).returns("myname")
+       terminus = stub 'terminus'
++      terminus.stubs(:validate)
+       Puppet::SSL::CertificateRequest.indirection.expects(:prepare).returns(terminus)
+       terminus.expects(:save).with { |req| req.instance == @request && req.key == "myname" }.raises "eh"
+ 
+--- a/spec/unit/util/monkey_patches_spec.rb
++++ b/spec/unit/util/monkey_patches_spec.rb
+@@ -172,3 +172,15 @@
+     end
+   end
+ end
++
++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
diff -Nru puppet-2.7.18/debian/patches/2.7.x-unit-test-fix.patch puppet-2.7.18/debian/patches/2.7.x-unit-test-fix.patch
--- puppet-2.7.18/debian/patches/2.7.x-unit-test-fix.patch	1970-01-01 01:00:00.000000000 +0100
+++ puppet-2.7.18/debian/patches/2.7.x-unit-test-fix.patch	2013-03-13 15:46:03.000000000 +0100
@@ -0,0 +1,40 @@
+From 8fa2f427581ef901792a33a7f3b055decaf093f2 Mon Sep 17 00:00:00 2001
+From: Josh Cooper <josh@puppetlabs.com>
+Date: Sun, 10 Mar 2013 11:54:58 -0700
+Subject: [PATCH] (#19391) Find the catalog for the specified node name
+
+Previously, the test was invoking the Catalog.indirection.find method
+but passing in a Node instance. This used to "work" in 2.7.x, because
+the compiler terminus only looked at `request.key`, in this case the
+node name, and ignored the `request.instance`.
+
+The test appears to verify that we can compile a node with the specified
+facts. But the test doesn't verify that the facts are in the returned
+catalog (they're not). So in reality, this test is passing when it
+shouldn't.
+
+To pass a Node instance through to the compiler, you need to use the
+`use_node` option, e.g. :use_node => node.to_yaml. To pass facts, use
+the use the `facts` option, e.g. :facts => facts.to_yaml.
+
+Since the test passes in a Node instance, the node is cached instead of
+the compiled catalog, which is one of the issues described in
+CVE-2013-1652.
+
+This commit changes the test to pass in the node.name, and it ensures we
+can compile a catalog for it.
+---
+ test/language/snippets.rb | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/test/language/snippets.rb
++++ b/test/language/snippets.rb
+@@ -495,7 +495,7 @@
+ 
+         catalog = nil
+         assert_nothing_raised("Could not compile catalog") {
+-          catalog = Puppet::Resource::Catalog.indirection.find(node)
++          catalog = Puppet::Resource::Catalog.indirection.find(node.name)
+         }
+ 
+         assert_nothing_raised("Could not convert catalog") {
diff -Nru puppet-2.7.18/debian/patches/series puppet-2.7.18/debian/patches/series
--- puppet-2.7.18/debian/patches/series	2012-09-19 11:45:14.000000000 +0200
+++ puppet-2.7.18/debian/patches/series	2013-03-13 15:46:03.000000000 +0100
@@ -1,2 +1,4 @@
+2.7.18-CVE-Rollup.patch
+2.7.x-unit-test-fix.patch
 apache2-passenger-template
 fix_logcheck

Reply to: