Login
[x]
Log in using an account from:
Fedora Account System
Red Hat Associate
Red Hat Customer
Or login using a Red Hat Bugzilla account
Forgot Password
Login:
Hide Forgot
Create an Account
Red Hat Bugzilla – Attachment 710424 Details for
Bug 919783
CVE-2013-1640 Puppet: catalog request code execution
[?]
New
Simple Search
Advanced Search
My Links
Browse
Requests
Reports
Current State
Search
Tabular reports
Graphical reports
Duplicates
Other Reports
User Changes
Plotly Reports
Bug Status
Bug Severity
Non-Defaults
|
Product Dashboard
Help
Page Help!
Bug Writing Guidelines
What's new
Browser Support Policy
5.0.4.rh83 Release notes
FAQ
Guides index
User guide
Web Services
Contact
Legal
This site requires JavaScript to be enabled to function correctly, please enable it.
[patch]
puppet-3.1.0-CVE-Rollup.patch
puppet-3.1.0-CVE-Rollup.patch (text/plain), 85.09 KB, created by
Kurt Seifried
on 2013-03-15 04:52:22 UTC
(
hide
)
Description:
puppet-3.1.0-CVE-Rollup.patch
Filename:
MIME Type:
Creator:
Kurt Seifried
Created:
2013-03-15 04:52:22 UTC
Size:
85.09 KB
patch
obsolete
>From ee19dc1e98997bd1ef74ab577dcb67de6e0150d1 Mon Sep 17 00:00:00 2001 >From: Nick Lewis <nick@puppetlabs.com> >Date: Wed, 20 Feb 2013 17:28:14 -0800 >Subject: [PATCH] 3.1.0 - 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) Cleanup tests for template functionality > >Some of the previous tests were unneeded (check for simple existance) or >mis-named. This cleans those up. A few other tests were trying to >indirectly test many things at once (the ones about access to instance >variables). Instead of the indirect test this splits it into much more >specific tests and statement of behavior. > >(#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. > >(#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. > >Updating module tool acceptance tests with new expectations. > >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. > >(#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 network/authorization_spec > >This test was assuming the singleton auth config 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. > >We can ( and should ) use grep instead of grep -E > >add quotes around paths for windows interop > >remove tests that do not run on 3.1+ > >run curl against the master on the master > >(#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. >--- > acceptance/tests/modules/search/by_keyword.rb | 2 +- > 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 ++++++ > .../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 | 3 +- > lib/puppet/indirector/resource/active_record.rb | 2 + > lib/puppet/indirector/resource/ral.rb | 3 + > lib/puppet/indirector/resource/store_configs.rb | 3 + > lib/puppet/indirector/resource/validator.rb | 8 ++ > lib/puppet/indirector/rest.rb | 4 + > lib/puppet/indirector/run/local.rb | 4 + > lib/puppet/indirector/terminus.rb | 20 +++ > lib/puppet/network/authconfig.rb | 2 +- > lib/puppet/network/formats.rb | 6 +- > lib/puppet/network/http/handler.rb | 9 +- > lib/puppet/network/http/rack/rest.rb | 9 +- > lib/puppet/network/http/webrick.rb | 1 + > lib/puppet/parser/templatewrapper.rb | 32 ++--- > lib/puppet/util/monkey_patches.rb | 38 +++++ > .../file_serving/terminus_helper_spec.rb | 2 +- > 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 | 18 ++- > spec/unit/indirector/terminus_spec.rb | 45 ++++++ > spec/unit/network/authconfig_spec.rb | 12 ++ > spec/unit/network/authorization_spec.rb | 4 + > spec/unit/network/formats_spec.rb | 12 +- > spec/unit/network/http/connection_spec.rb | 1 - > 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/parser/functions/inline_template_spec.rb | 59 ++------ > spec/unit/parser/functions/template_spec.rb | 99 ++++++------- > spec/unit/parser/templatewrapper_spec.rb | 159 +++++++++------------ > spec/unit/ssl/certificate_request_spec.rb | 2 + > spec/unit/ssl/host_spec.rb | 1 + > spec/unit/util/monkey_patches_spec.rb | 20 +++ > 50 files changed, 866 insertions(+), 236 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-2275_report_acl.rb > create mode 100644 lib/puppet/indirector/errors.rb > create mode 100644 lib/puppet/indirector/resource/validator.rb > >diff --git a/acceptance/tests/modules/search/by_keyword.rb b/acceptance/tests/modules/search/by_keyword.rb >index ae78781..c34abc0 100644 >--- a/acceptance/tests/modules/search/by_keyword.rb >+++ b/acceptance/tests/modules/search/by_keyword.rb >@@ -10,7 +10,7 @@ on master, puppet("module search github") do > assert_equal <<-STDOUT, stdout > \e[mNotice: Searching https://forge.puppetlabs.com ...\e[0m > NAME DESCRIPTION AUTHOR KEYWORDS >-pmtacceptance-git This is a dummy git module... @pmtacceptance git \e[0;32mgithub\e[0m >+pmtacceptance-git UNKNOWN @pmtacceptance git \e[0;32mgithub\e[0m > STDOUT > end > >diff --git a/acceptance/tests/modules/search/by_module_name.rb b/acceptance/tests/modules/search/by_module_name.rb >index 96ef47b..37dd648 100644 >--- a/acceptance/tests/modules/search/by_module_name.rb >+++ b/acceptance/tests/modules/search/by_module_name.rb >@@ -9,7 +9,7 @@ on master, puppet("module search geordi") do > assert_equal <<-STDOUT, stdout > \e[mNotice: Searching https://forge.puppetlabs.com ...\e[0m > NAME DESCRIPTION AUTHOR KEYWORDS >-pmtacceptance-\e[0;32mgeordi\e[0m This is a module that do... @pmtacceptance star trek >+pmtacceptance-\e[0;32mgeordi\e[0m UNKNOWN @pmtacceptance star trek > STDOUT > end > >@@ -30,6 +30,6 @@ on master, puppet("module search tance/ge") do > assert_equal <<-STDOUT, stdout > \e[mNotice: Searching https://forge.puppetlabs.com ...\e[0m > NAME DESCRIPTION AUTHOR KEYWORDS >-pmtaccep\e[0;32mtance-ge\e[0mordi This is a module that do... @pmtacceptance star trek >+pmtaccep\e[0;32mtance-ge\e[0mordi UNKNOWN @pmtacceptance star trek > STDOUT > end >diff --git a/acceptance/tests/security/cve-2013-1640_facter_string.rb b/acceptance/tests/security/cve-2013-1640_facter_string.rb >new file mode 100644 >index 0000000..ac05226 >--- /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 >diff --git a/acceptance/tests/security/cve-2013-1652_improper_query_params.rb b/acceptance/tests/security/cve-2013-1652_improper_query_params.rb >new file mode 100644 >index 0000000..76551d5 >--- /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 >diff --git a/acceptance/tests/security/cve-2013-1652_poison_other_node_cache.rb b/acceptance/tests/security/cve-2013-1652_poison_other_node_cache.rb >new file mode 100644 >index 0000000..b751ba0 >--- /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 >+ skip_test( "This test will only run on Puppet 3.x" ) if >+ on(master, puppet('--version')).stdout =~ /\A2\./ >+ end >+ >+ with_master_running_on( master, "--autosign true" ) do >+ # Ensure agent has a signed cert >+ on master, puppet_agent( '-t' ) >+ >+ certname = on( >+ master, puppet_agent("--configprint certname")).stdout.chomp >+ cert_path = on( >+ master, puppet_agent("--configprint hostcert")).stdout.chomp >+ key_path = on( >+ master, 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 master, "#{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 >diff --git a/acceptance/tests/security/cve-2013-1653_puppet_kick.rb b/acceptance/tests/security/cve-2013-1653_puppet_kick.rb >new file mode 100644 >index 0000000..0ede979 >--- /dev/null >+++ b/acceptance/tests/security/cve-2013-1653_puppet_kick.rb >@@ -0,0 +1,109 @@ >+test_name "CVE 2013-1653: Puppet Kick Remote Code Exploit" do >+ >+ step "Determine suitability of the test" do >+ confine :except, :platform => 'windows' >+ >+ versions = on( hosts, puppet( '--version' )) >+ skip_test( "This test will not run on Puppet 2.6" ) if >+ versions.any? {|r| r.stdout =~ /\A2\.6\./ } >+ end >+ >+ with_master_running_on( master, '--autosign true' ) do >+ on agents, puppet_agent( '-t' ) >+ end >+ >+ def exploit_code( exploiter, exploitee, endpoint, port, file_to_create ) >+ >+ certfile = on( exploiter, puppet_agent( '--configprint hostcert' )).stdout.chomp >+ keyfile = on( exploiter, puppet_agent( '--configprint hostprivkey' )).stdout.chomp >+ >+ exploit = %Q[#!/usr/bin/env ruby >+ require 'puppet' >+ require 'openssl' >+ require 'net/https' >+ >+ yaml = <<EOM >+--- !ruby/object:ERB >+ safe_level: >+ src: |- >+ #coding:US-ASCII >+ _erbout = ''; _erbout.concat(( File.open( '#{file_to_create}', 'w') ).to_s) >+ filename: >+EOM >+ >+ headers = {'Content-Type' => 'text/yaml', 'Accept' => 'yaml'} >+ conn = Net::HTTP.new('#{exploitee}', #{port}) >+ conn.use_ssl = true >+ conn.cert = OpenSSL::X509::Certificate.new(File.read('#{certfile}')) >+ conn.key = OpenSSL::PKey::RSA.new(File.read('#{keyfile}')) >+ conn.verify_mode = OpenSSL::SSL::VERIFY_NONE >+ >+ conn.request_put("/production/#{endpoint}/#{exploiter}", yaml, headers) do |response| >+ response.read_body do |chunk| >+ puts chunk >+ end >+ end ] >+ >+ return exploit >+ end >+ >+ exploited = '/tmp/cve-2013-1653-has-worked' >+ restauth_conf = %q[ >+path /run >+auth yes >+allow * >+] >+ >+ teardown do >+ agents.each do |agent| >+ pidfile = on( agent, puppet_agent("--configprint pidfile") ).stdout.chomp >+ on agent, "[ -f #{pidfile} ] && kill `cat #{pidfile}` || true" >+ on agent, "rm -rf #{exploited}" >+ end >+ end >+ >+ agents.each do |agent| >+ atestdir = agent.tmpdir('puppet-kick-auth') >+ mtestdir = master.tmpdir('puppet-kick-auth') >+ >+ step "Daemonize the agent" do >+ # Lay down a tempory auth.conf that will allow the agent to be kicked >+ create_remote_file(agent, "#{atestdir}/auth.conf", restauth_conf) >+ >+ # Start the agent >+ on(agent, puppet_agent("--debug --daemonize --server #{master} --listen --no-client --rest_authconfig #{atestdir}/auth.conf")) >+ >+ step "Wait for agent to start listening" do >+ timeout = 15 >+ begin >+ Timeout.timeout(timeout) do >+ loop do >+ # 7 is "Could not connect to host", which will happen before it's running >+ result = on(agent, "curl -k https://#{agent}:8139", :acceptable_exit_codes => [0,7]) >+ break if result.exit_code == 0 >+ sleep 1 >+ end >+ end >+ rescue Timeout::Error >+ fail_test "Puppet agent #{agent} failed to start after #{timeout} seconds" >+ end >+ end >+ end >+ >+ step "Attempt to exploit #{agent}" do >+ # Ensure there's no stale data >+ on agent, "rm -rf #{exploited}" >+ on master, "rm -rf #{mtestdir}/exploit.rb" >+ >+ # Copy over our exploit and execute >+ create_remote_file( master, "#{mtestdir}/exploit.rb", exploit_code( master, agent, 'run', 8139, exploited )) >+ on master, "chmod +x #{mtestdir}/exploit.rb" >+ on master, "#{mtestdir}/exploit.rb" >+ >+ # Did it work? >+ fail_test( "Found exploit file #{exploited}" ) if >+ on( agent, "[ ! -f #{exploited} ]", >+ :acceptable_exit_codes => [0,1] ).exit_code == 1 >+ end >+ end >+end >diff --git a/acceptance/tests/security/cve-2013-1654_sslv2_downgrade_agent.rb b/acceptance/tests/security/cve-2013-1654_sslv2_downgrade_agent.rb >new file mode 100644 >index 0000000..d52fb70 >--- /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 'sslserver.rb' | grep -v grep | awk '{ print $2 }' | xargs kill || echo \"ruby sslserver.rb not running\"") >+ on(agent, "rm -rf #{testdir}") >+ end >+ >+ create_remote_file(agent, "#{testdir}/sslserver.rb", sslserver) >+ on agent, "#{which_ruby(agent)} #{testdir}/sslserver.rb &>/dev/null &" >+ >+ timeout = 15 >+ begin >+ Timeout.timeout(timeout) do >+ loop do >+ # 7 is "Could not connect to host", which will happen before it's running >+ result = on(agent, "curl -m1 -k https://#{agent}:#{port}", :acceptable_exit_codes => [0,7,35]) >+ break if result.exit_code == 0 or result.exit_code == 35 >+ sleep 1 >+ end >+ end >+ rescue Timeout::Error >+ fail_test "Insecure Mock Server on #{agent} failed to start after #{timeout} seconds" >+ end >+ >+ on(agent, puppet("agent --debug --test --server #{agent} --masterport #{port}"), :acceptable_exit_codes => [1]) do |test| >+ assert_no_match(/'FOOBAR'/, test.stdout) >+ end >+ else >+ logger.debug( "skipping #{agent} since SSLv2 is not available" ) >+ end >+ end >+end >diff --git a/acceptance/tests/security/cve-2013-1654_sslv2_downgrade_master.rb b/acceptance/tests/security/cve-2013-1654_sslv2_downgrade_master.rb >new file mode 100644 >index 0000000..38141bc >--- /dev/null >+++ b/acceptance/tests/security/cve-2013-1654_sslv2_downgrade_master.rb >@@ -0,0 +1,32 @@ >+test_name "CVE 2013-1654 SSL2 Downgrade of Master connection" do >+ >+ def suitable?(host) >+ ruby = host['puppetbindir'] ? "#{host['puppetbindir']}/ruby" : 'ruby' >+ cmd = <<END >+#{ruby} -ropenssl -e "puts OpenSSL::SSL::SSLContext::METHODS.include?(:SSLv2)" >+END >+ on(host, cmd).stdout.chomp == "true" >+ end >+ >+ if suitable?( master ) >+ with_master_running_on( master, '--autosign true' ) do >+ >+ agent = agents.first >+ on agent, puppet_agent( '-t' ) >+ >+ certfile = on(agent, puppet_agent("--configprint hostcert")).stdout.chomp >+ keyfile = on(agent, puppet_agent("--configprint hostprivkey")).stdout.chomp >+ cafile = on(agent, puppet_agent("--configprint localcacert")).stdout.chomp >+ >+ openssl_call = "openssl s_client -connect #{master}:8140 " + >+ "-cert \"#{certfile}\" -key \"#{keyfile}\" -CAfile \"#{cafile}\" -ssl2 -msg < /dev/null" >+ >+ on(agent, openssl_call, :acceptable_exit_codes => (0..255)) do |test| >+ assert_match /CLIENT-HELLO/, test.stdout >+ assert_no_match /SERVER-HELLO/, test.stdout >+ end >+ end >+ else >+ logger.debug( "Not testing master as SSLv2 isn't available to it" ) >+ end >+end >diff --git a/acceptance/tests/security/cve-2013-1655_safe_yaml_deserialization.rb b/acceptance/tests/security/cve-2013-1655_safe_yaml_deserialization.rb >new file mode 100644 >index 0000000..d128123 >--- /dev/null >+++ b/acceptance/tests/security/cve-2013-1655_safe_yaml_deserialization.rb >@@ -0,0 +1,42 @@ >+test_name "#19393: Safe YAML deserialization" >+step "Verify Puppet safely deserializes YAML encoded objects" >+ >+# Check if the master is running with a Psych YAML engine. If not, don't test. >+check_for_psych_cmd = "ruby -ryaml -e 'exit (defined?(YAML::ENGINE) and YAML::ENGINE.yamler == \"psych\") ? 3 : 7'" >+ >+master_uses_psych = 'unknown' >+on master, check_for_psych_cmd, :acceptable_exit_codes => [3,7] do |result| >+ case result.exit_code >+ when 3 >+ master_uses_psych = true >+ when 7 >+ master_uses_psych = false >+ else >+ raise "Could not determine if the system under test uses the YAML Psych engine." >+ end >+end >+ >+if master_uses_psych >+ with_master_running_on(master) do >+ unsafe_data = "--- !ruby/hash:Array {}" >+ >+ cmd = [ >+ "curl -k -X PUT", >+ "--cacert \"$(puppet master --configprint cacert)\"", >+ "--cert \"$(puppet master --configprint hostcert)\"", >+ "--key \"$(puppet master --configprint hostprivkey)\"", >+ "-H 'Content-Type: text/yaml'", >+ "-d '#{unsafe_data}'", >+ "\"https://#{master}:8140/production/report/$(puppet master --configprint certname)\"" >+ ].join(" ") >+ >+ on master, cmd, :acceptable_exit_codes => [0] do >+ msg = "(#19393) (CVE-2013-1655) Puppet master accepted illegal YAML, " + >+ "expected rejection with message 'Illegal YAML mapping found ... " + >+ "please use ... instead'" >+ assert_match(/Illegal YAML mapping found/, stdout, msg) >+ end >+ end >+else >+ skip_test "Cannot validate CVE-2013-1655 unless the master is running with the Psych YAML engine" >+end >diff --git a/acceptance/tests/security/cve-2013-2275_report_acl.rb b/acceptance/tests/security/cve-2013-2275_report_acl.rb >new file mode 100644 >index 0000000..dae05c5 >--- /dev/null >+++ b/acceptance/tests/security/cve-2013-2275_report_acl.rb >@@ -0,0 +1,30 @@ >+test_name "(#19531) report save access control" >+step "Verify puppet only allows saving reports from the node matching the certificate" >+ >+fake_report = <<-EOYAML >+--- !ruby/object:Puppet::Transaction::Report >+ host: mccune >+ metrics: {} >+ logs: [] >+ kind: inspect >+ puppet_version: "2.7.20" >+ status: failed >+ report_format: 3 >+EOYAML >+ >+with_master_running_on(master) do >+ submit_fake_report_cmd = [ >+ "curl -k -X PUT", >+ "--cacert \"$(puppet master --configprint cacert)\"", >+ "--cert \"$(puppet master --configprint hostcert)\"", >+ "--key \"$(puppet master --configprint hostprivkey)\"", >+ "-H 'Content-Type: text/yaml'", >+ "-d '#{fake_report}'", >+ "\"https://#{master}:8140/production/report/mccune\"", >+ ].join(" ") >+ >+ on master, submit_fake_report_cmd, :acceptable_exit_codes => [0] do >+ msg = "(#19531) (CVE-2013-2275) Puppet master accepted a report for a node that does not match the certname" >+ assert_match(/Forbidden request/, stdout, msg) >+ end >+end >diff --git a/conf/auth.conf b/conf/auth.conf >index 07e5a34..b31906b 100644 >--- a/conf/auth.conf >+++ b/conf/auth.conf >@@ -75,10 +75,10 @@ path /certificate_revocation_list/ca > method find > allow * > >-# allow all nodes to store their reports >-path /report >+# allow all nodes to store their own reports >+path ~ ^/report/([^/]+)$ > method save >-allow * >+allow $1 > > # Allow all nodes to access all file services; this is necessary for > # pluginsync, file serving from modules, and file serving from custom >diff --git a/lib/puppet/indirector/catalog/compiler.rb b/lib/puppet/indirector/catalog/compiler.rb >index 88ee074..6fd3242 100644 >--- a/lib/puppet/indirector/catalog/compiler.rb >+++ b/lib/puppet/indirector/catalog/compiler.rb >@@ -12,7 +12,9 @@ class Puppet::Resource::Catalog::Compiler < Puppet::Indirector::Code > > def extract_facts_from_request(request) > return unless text_facts = request.options[:facts] >- raise ArgumentError, "Facts but no fact format provided for #{request.name}" unless format = request.options[:facts_format] >+ unless format = request.options[:facts_format] >+ raise ArgumentError, "Facts but no fact format provided for #{request.key}" >+ end > > # If the facts were encoded as yaml, then the param reconstitution system > # in Network::HTTP::Handler will automagically deserialize the value. >@@ -21,6 +23,11 @@ class Puppet::Resource::Catalog::Compiler < Puppet::Indirector::Code > else > facts = Puppet::Node::Facts.convert_from(format, text_facts) > end >+ >+ unless facts.name == request.key >+ raise Puppet::Error, "Catalog for #{request.key.inspect} was requested with fact definition for the wrong node (#{facts.name.inspect})." >+ end >+ > facts.add_timestamp > Puppet::Node::Facts.indirection.save(facts) > end >@@ -104,7 +111,11 @@ class Puppet::Resource::Catalog::Compiler < Puppet::Indirector::Code > # to find the node. > def node_from_request(request) > if node = request.options[:use_node] >- return node >+ if request.remote? >+ raise Puppet::Error, "Invalid option use_node for a remote request" >+ else >+ return node >+ end > end > > # We rely on our authorization system to determine whether the connected >diff --git a/lib/puppet/indirector/certificate_status/file.rb b/lib/puppet/indirector/certificate_status/file.rb >index 329a1be..8b7fe9a 100644 >--- a/lib/puppet/indirector/certificate_status/file.rb >+++ b/lib/puppet/indirector/certificate_status/file.rb >@@ -83,4 +83,9 @@ class Puppet::Indirector::CertificateStatus::File < Puppet::Indirector::Code > 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 >diff --git a/lib/puppet/indirector/errors.rb b/lib/puppet/indirector/errors.rb >new file mode 100644 >index 0000000..6b599b8 >--- /dev/null >+++ b/lib/puppet/indirector/errors.rb >@@ -0,0 +1,5 @@ >+require 'puppet/error' >+ >+module Puppet::Indirector >+ class ValidationError < Puppet::Error; end >+end >diff --git a/lib/puppet/indirector/file_bucket_file/file.rb b/lib/puppet/indirector/file_bucket_file/file.rb >index 3b831fd..ca79e78 100644 >--- a/lib/puppet/indirector/file_bucket_file/file.rb >+++ b/lib/puppet/indirector/file_bucket_file/file.rb >@@ -48,6 +48,10 @@ module Puppet::FileBucketFile > instance.to_s > end > >+ def validate_key(request) >+ # There are no ACLs on filebucket files so validating key is not important >+ end >+ > private > > def path_match(dir_path, files_original_path) >diff --git a/lib/puppet/indirector/file_bucket_file/selector.rb b/lib/puppet/indirector/file_bucket_file/selector.rb >index 51fc771..9b32f05 100644 >--- a/lib/puppet/indirector/file_bucket_file/selector.rb >+++ b/lib/puppet/indirector/file_bucket_file/selector.rb >@@ -44,6 +44,10 @@ module Puppet::FileBucketFile > true > end > end >+ >+ def validate_key(request) >+ get_terminus(request).validate(request) >+ end > end > end > >diff --git a/lib/puppet/indirector/indirection.rb b/lib/puppet/indirector/indirection.rb >index 1bceea4..720a7df 100644 >--- a/lib/puppet/indirector/indirection.rb >+++ b/lib/puppet/indirector/indirection.rb >@@ -192,7 +192,7 @@ class Puppet::Indirector::Indirection > result.expiration ||= self.expiration if result.respond_to?(:expiration) > if cache? and request.use_cache? > Puppet.info "Caching #{self.name} for #{request.key}" >- cache.save request(:save, nil, result, options) >+ cache.save request(:save, key, result, options) > end > > return terminus.respond_to?(:filter) ? terminus.filter(result) : result >@@ -303,6 +303,7 @@ class Puppet::Indirector::Indirection > > dest_terminus = terminus(terminus_name) > check_authorization(request, dest_terminus) >+ dest_terminus.validate(request) > > dest_terminus > end >diff --git a/lib/puppet/indirector/resource/active_record.rb b/lib/puppet/indirector/resource/active_record.rb >index caadc24..61b6d7a 100644 >--- a/lib/puppet/indirector/resource/active_record.rb >+++ b/lib/puppet/indirector/resource/active_record.rb >@@ -1,6 +1,8 @@ > require 'puppet/indirector/active_record' >+require 'puppet/indirector/resource/validator' > > class Puppet::Resource::ActiveRecord < Puppet::Indirector::ActiveRecord >+ include Puppet::Resource::Validator > > desc "A component of ActiveRecord storeconfigs. ActiveRecord-based storeconfigs > and inventory are deprecated. See http://links.puppetlabs.com/activerecord-deprecation" >diff --git a/lib/puppet/indirector/resource/ral.rb b/lib/puppet/indirector/resource/ral.rb >index 59ddc36..30c8623 100644 >--- a/lib/puppet/indirector/resource/ral.rb >+++ b/lib/puppet/indirector/resource/ral.rb >@@ -1,4 +1,7 @@ >+require 'puppet/indirector/resource/validator' >+ > class Puppet::Resource::Ral < Puppet::Indirector::Code >+ include Puppet::Resource::Validator > > desc "Manipulate resources with the resource abstraction layer. Only used internally." > >diff --git a/lib/puppet/indirector/resource/store_configs.rb b/lib/puppet/indirector/resource/store_configs.rb >index 6859220..45ffdbd 100644 >--- a/lib/puppet/indirector/resource/store_configs.rb >+++ b/lib/puppet/indirector/resource/store_configs.rb >@@ -1,5 +1,8 @@ > require 'puppet/indirector/store_configs' >+require 'puppet/indirector/resource/validator' >+ > class Puppet::Resource::StoreConfigs < Puppet::Indirector::StoreConfigs >+ include Puppet::Resource::Validator > > desc %q{Part of the "storeconfigs" feature. Should not be directly set by end users.} > >diff --git a/lib/puppet/indirector/resource/validator.rb b/lib/puppet/indirector/resource/validator.rb >new file mode 100644 >index 0000000..dff6ce5 >--- /dev/null >+++ b/lib/puppet/indirector/resource/validator.rb >@@ -0,0 +1,8 @@ >+module Puppet::Resource::Validator >+ def validate_key(request) >+ type, title = request.key.split('/', 2) >+ unless type.downcase == request.instance.type.downcase and title == request.instance.title >+ raise Puppet::Indirector::ValidationError, "Resource instance does not match request key" >+ end >+ end >+end >diff --git a/lib/puppet/indirector/rest.rb b/lib/puppet/indirector/rest.rb >index 48b15d7..59c025a 100644 >--- a/lib/puppet/indirector/rest.rb >+++ b/lib/puppet/indirector/rest.rb >@@ -177,6 +177,10 @@ class Puppet::Indirector::REST < Puppet::Indirector::Terminus > request.do_request(self.class.srv_service, self.class.server, self.class.port) { |request| yield(request) } > end > >+ def validate_key(request) >+ # Validation happens on the remote end >+ end >+ > private > > def environment >diff --git a/lib/puppet/indirector/run/local.rb b/lib/puppet/indirector/run/local.rb >index 10ada0e..fd35b13 100644 >--- a/lib/puppet/indirector/run/local.rb >+++ b/lib/puppet/indirector/run/local.rb >@@ -8,4 +8,8 @@ class Puppet::Run::Local < Puppet::Indirector::Code > def save( request ) > request.instance.run > end >+ >+ def validate_key(request) >+ # No key is necessary for kick >+ end > end >diff --git a/lib/puppet/indirector/terminus.rb b/lib/puppet/indirector/terminus.rb >index d488869..4e74eff 100644 >--- a/lib/puppet/indirector/terminus.rb >+++ b/lib/puppet/indirector/terminus.rb >@@ -1,4 +1,5 @@ > require 'puppet/indirector' >+require 'puppet/indirector/errors' > require 'puppet/indirector/indirection' > require 'puppet/util/instance_loader' > >@@ -142,4 +143,23 @@ class Puppet::Indirector::Terminus > def terminus_type > self.class.terminus_type > end >+ >+ def validate(request) >+ if request.instance >+ validate_model(request) >+ validate_key(request) >+ end >+ end >+ >+ def validate_key(request) >+ unless request.key == request.instance.name >+ raise Puppet::Indirector::ValidationError, "Instance name #{request.instance.name.inspect} does not match requested key #{request.key.inspect}" >+ end >+ end >+ >+ def validate_model(request) >+ unless model === request.instance >+ raise Puppet::Indirector::ValidationError, "Invalid instance type #{request.instance.class.inspect}, expected #{model.inspect}" >+ end >+ end > end >diff --git a/lib/puppet/network/authconfig.rb b/lib/puppet/network/authconfig.rb >index 29f1624..6cd86ae 100644 >--- a/lib/puppet/network/authconfig.rb >+++ b/lib/puppet/network/authconfig.rb >@@ -15,7 +15,7 @@ module Puppet > # to fileserver.conf > { :acl => "/file" }, > { :acl => "/certificate_revocation_list/ca", :method => :find, :authenticated => true }, >- { :acl => "/report", :method => :save, :authenticated => true }, >+ { :acl => "~ ^\/report\/([^\/]+)$", :method => :save, :allow => '$1', :authenticated => true }, > # 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 }, >diff --git a/lib/puppet/network/formats.rb b/lib/puppet/network/formats.rb >index 12bece8..ecbd640 100644 >--- a/lib/puppet/network/formats.rb >+++ b/lib/puppet/network/formats.rb >@@ -3,12 +3,12 @@ require 'puppet/network/format_handler' > 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 @@ Puppet::Network::FormatHandler.create_serialized_formats(:b64_zlib_yaml) do > > 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 >diff --git a/lib/puppet/network/http/handler.rb b/lib/puppet/network/http/handler.rb >index 9166888..14243ce 100644 >--- a/lib/puppet/network/http/handler.rb >+++ b/lib/puppet/network/http/handler.rb >@@ -73,6 +73,8 @@ module Puppet::Network::HTTP::Handler > raise > rescue Exception => e > return do_exception(response, e) >+ ensure >+ cleanup(request) > end > > # Set the response up, with the body and status. >@@ -219,11 +221,14 @@ module Puppet::Network::HTTP::Handler > raise NotImplementedError > end > >- # Retrieve the client certificate from the request if possible > def client_cert(request) > 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 >@@ -237,7 +242,7 @@ module Puppet::Network::HTTP::Handler > 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" >diff --git a/lib/puppet/network/http/rack/rest.rb b/lib/puppet/network/http/rack/rest.rb >index 663e1fd..0ccd955 100644 >--- a/lib/puppet/network/http/rack/rest.rb >+++ b/lib/puppet/network/http/rack/rest.rb >@@ -73,8 +73,6 @@ class Puppet::Network::HTTP::RackREST < Puppet::Network::HTTP::RackHttpHandler > end > > # return the request body >- # request.body has some limitiations, so we need to concat it back >- # into a regular string, which is something puppet can use. > def body(request) > request.body.read > end >@@ -89,6 +87,13 @@ class Puppet::Network::HTTP::RackREST < Puppet::Network::HTTP::RackHttpHandler > OpenSSL::X509::Certificate.new(cert) > end > >+ # Passenger freaks out if we finish handling the request without reading any >+ # part of the body, so make sure we have. >+ def cleanup(request) >+ request.body.read(1) >+ nil >+ end >+ > def extract_client_info(request) > result = {} > result[:ip] = request.ip >diff --git a/lib/puppet/network/http/webrick.rb b/lib/puppet/network/http/webrick.rb >index 11eef62..59f7ae1 100644 >--- a/lib/puppet/network/http/webrick.rb >+++ b/lib/puppet/network/http/webrick.rb >@@ -94,6 +94,7 @@ class Puppet::Network::HTTP::WEBrick > results[:SSLCertificate] = host.certificate.content > results[:SSLStartImmediately] = true > results[:SSLEnable] = true >+ results[:SSLOptions] = OpenSSL::SSL::OP_NO_SSLv2 > > raise Puppet::Error, "Could not find CA certificate" unless Puppet::SSL::Certificate.indirection.find(Puppet::SSL::CA_NAME) > >diff --git a/lib/puppet/parser/templatewrapper.rb b/lib/puppet/parser/templatewrapper.rb >index eaf6a22..999d606 100644 >--- a/lib/puppet/parser/templatewrapper.rb >+++ b/lib/puppet/parser/templatewrapper.rb >@@ -5,8 +5,6 @@ require 'erb' > > class Puppet::Parser::TemplateWrapper > attr_writer :scope >- attr_reader :file >- attr_accessor :string > include Puppet::Util > Puppet::Util.logmethods(self) > >@@ -14,6 +12,10 @@ class Puppet::Parser::TemplateWrapper > @__scope__ = scope > end > >+ def file >+ @__file__ >+ end >+ > def scope > @__scope__ > end >@@ -22,7 +24,7 @@ class Puppet::Parser::TemplateWrapper > # find which line in the template (if any) we were called from > # but defer to when necessary since fetching the caller information on > # every variable lookup can be quite time consuming >- Proc.new { (caller.find { |l| l =~ /#{file}:/ }||"")[/:(\d+):/,1] } >+ Proc.new { (caller.find { |l| l =~ /#{@__file__}:/ }||"")[/:(\d+):/,1] } > end > > def script_line >@@ -63,51 +65,49 @@ class Puppet::Parser::TemplateWrapper > # dead. > def method_missing(name, *args) > if scope.include?(name.to_s) >- return scope[name.to_s, {:file => file,:lineproc => script_line_proc}] >+ return scope[name.to_s, {:file => @__file__, :lineproc => script_line_proc}] > 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 > >@@ -115,6 +115,6 @@ class Puppet::Parser::TemplateWrapper > end > > def to_s >- "template[#{(file ? file : "inline")}]" >+ "template[#{(@__file__ ? @__file__ : "inline")}]" > end > end >diff --git a/lib/puppet/util/monkey_patches.rb b/lib/puppet/util/monkey_patches.rb >index 820cb37..ca19fa4 100644 >--- a/lib/puppet/util/monkey_patches.rb >+++ b/lib/puppet/util/monkey_patches.rb >@@ -41,6 +41,21 @@ end > 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 >@@ -356,3 +371,26 @@ unless Dir.respond_to?(:mktmpdir) > end > end > end >+ >+# (#19151) Reject all SSLv2 ciphers and handshakes >+require 'openssl' >+class OpenSSL::SSL::SSLContext >+ 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' >+ >+ alias __original_initialize initialize >+ private :__original_initialize >+ >+ def initialize(*args) >+ __original_initialize(*args) >+ params = { >+ :options => DEFAULT_PARAMS[:options], >+ :ciphers => DEFAULT_PARAMS[:ciphers], >+ } >+ set_params(params) >+ end >+end >diff --git a/spec/integration/file_serving/terminus_helper_spec.rb b/spec/integration/file_serving/terminus_helper_spec.rb >index 40a50b0..2451531 100755 >--- a/spec/integration/file_serving/terminus_helper_spec.rb >+++ b/spec/integration/file_serving/terminus_helper_spec.rb >@@ -13,7 +13,7 @@ end > describe Puppet::FileServing::TerminusHelper do > it "should be able to recurse on a single file" do > @path = Tempfile.new("fileset_integration") >- request = Puppet::Indirector::Request.new(:metadata, :find, @path.path, :recurse => true) >+ request = Puppet::Indirector::Request.new(:metadata, :find, @path.path, nil, :recurse => true) > > tester = TerminusHelperIntegrationTester.new > lambda { tester.path2instances(request, @path.path) }.should_not raise_error >diff --git a/spec/integration/indirector/catalog/queue_spec.rb b/spec/integration/indirector/catalog/queue_spec.rb >index f0f7526..fe0cc34 100755 >--- a/spec/integration/indirector/catalog/queue_spec.rb >+++ b/spec/integration/indirector/catalog/queue_spec.rb >@@ -6,7 +6,7 @@ require 'puppet/resource/catalog' > describe "Puppet::Resource::Catalog::Queue" do > before do > Puppet::Resource::Catalog.indirection.terminus(:queue) >- @catalog = Puppet::Resource::Catalog.new >+ @catalog = Puppet::Resource::Catalog.new("foo") > > @one = Puppet::Resource.new(:file, "/one") > @two = Puppet::Resource.new(:file, "/two") >diff --git a/spec/integration/resource/catalog_spec.rb b/spec/integration/resource/catalog_spec.rb >index 5877b49..3dc3056 100755 >--- a/spec/integration/resource/catalog_spec.rb >+++ b/spec/integration/resource/catalog_spec.rb >@@ -46,6 +46,7 @@ describe Puppet::Resource::Catalog do > Puppet::Resource::Catalog.indirection.stubs(:terminus).returns terminus > > node = mock 'node' >+ terminus.stubs(:validate) > terminus.expects(:find).with { |request| request.options[:use_node] == node } > Puppet::Resource::Catalog.indirection.find("me", :use_node => node) > end >diff --git a/spec/unit/indirector/catalog/compiler_spec.rb b/spec/unit/indirector/catalog/compiler_spec.rb >index a36c2ba..356b9fa 100755 >--- a/spec/unit/indirector/catalog/compiler_spec.rb >+++ b/spec/unit/indirector/catalog/compiler_spec.rb >@@ -53,13 +53,22 @@ describe Puppet::Resource::Catalog::Compiler do > @request = Puppet::Indirector::Request.new(:catalog, :find, @name, nil, :node => @name) > 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, anything).returns(@node) >@@ -99,6 +108,24 @@ describe Puppet::Resource::Catalog::Compiler do > @compiler.find(@request) > end > >+ it "requires `facts_format` option if facts are passed in" do >+ facts = Puppet::Node::Facts.new("mynode", :afact => "avalue") >+ request = Puppet::Indirector::Request.new(:catalog, :find, "mynode", nil, :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", nil, :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 @@ describe Puppet::Resource::Catalog::Compiler do > before do > Facter.stubs(:value).returns "something" > @compiler = Puppet::Resource::Catalog::Compiler.new >- @request = stub 'request', :options => {} >+ @request = Puppet::Indirector::Request.new(:catalog, :find, "hostname", nil) > > @facts = Puppet::Node::Facts.new('hostname', "fact" => "value", "architecture" => "i386") > Puppet::Node::Facts.indirection.stubs(:save).returns(nil) >diff --git a/spec/unit/indirector/indirection_spec.rb b/spec/unit/indirector/indirection_spec.rb >index 96493bf..0898a27 100755 >--- a/spec/unit/indirector/indirection_spec.rb >+++ b/spec/unit/indirector/indirection_spec.rb >@@ -40,7 +40,7 @@ shared_examples_for "Indirection Delegator" do > end > end > >- request = stub 'request', :key => "me", :options => {} >+ request = Puppet::Indirector::Request.new(:indirection, :find, "me", nil) > > @indirection.stubs(:request).returns request > >@@ -101,6 +101,16 @@ shared_examples_for "Delegation Authorizer" do > end > end > >+shared_examples_for "Request validator" do >+ it "asks the terminus to validate the request" do >+ @terminus.expects(:validate).raises(Puppet::Indirector::ValidationError, "Invalid") >+ @terminus.expects(@method).never >+ expect { >+ @indirection.send(@method, "key") >+ }.to raise_error Puppet::Indirector::ValidationError >+ end >+end >+ > describe Puppet::Indirector::Indirection do > describe "when initializing" do > # (LAK) I've no idea how to test this, really. >@@ -141,6 +151,7 @@ describe Puppet::Indirector::Indirection do > before :each do > @terminus_class = mock 'terminus_class' > @terminus = mock 'terminus' >+ @terminus.stubs(:validate) > @terminus_class.stubs(:new).returns(@terminus) > @cache = stub 'cache', :name => "mycache" > @cache_class = mock 'cache_class' >@@ -211,6 +222,7 @@ describe Puppet::Indirector::Indirection do > > it_should_behave_like "Indirection Delegator" > it_should_behave_like "Delegation Authorizer" >+ it_should_behave_like "Request validator" > > it "should return the results of the delegation" do > @terminus.expects(:find).returns(@instance) >@@ -384,6 +396,7 @@ describe Puppet::Indirector::Indirection do > > it_should_behave_like "Indirection Delegator" > it_should_behave_like "Delegation Authorizer" >+ it_should_behave_like "Request validator" > > it "should return true if the head method returned true" do > @terminus.expects(:head).returns(true) >@@ -501,6 +514,7 @@ describe Puppet::Indirector::Indirection do > > it_should_behave_like "Indirection Delegator" > it_should_behave_like "Delegation Authorizer" >+ it_should_behave_like "Request validator" > > it "should return the result of removing the instance" do > @terminus.stubs(:destroy).returns "yayness" >@@ -539,6 +553,7 @@ describe Puppet::Indirector::Indirection do > > it_should_behave_like "Indirection Delegator" > it_should_behave_like "Delegation Authorizer" >+ it_should_behave_like "Request validator" > > it "should set the expiration date on any instances without one set" do > @terminus.stubs(:search).returns([@instance]) >@@ -707,6 +722,7 @@ describe Puppet::Indirector::Indirection do > before do > @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) > @terminus = mock 'terminus' >+ @terminus.stubs(:validate) > @terminus_class = stub 'terminus class', :new => @terminus > end > >diff --git a/spec/unit/indirector/terminus_spec.rb b/spec/unit/indirector/terminus_spec.rb >index 40797f0..56888e9 100755 >--- a/spec/unit/indirector/terminus_spec.rb >+++ b/spec/unit/indirector/terminus_spec.rb >@@ -9,6 +9,10 @@ describe Puppet::Indirector::Terminus do > class Puppet::AbstractConcept > extend Puppet::Indirector > indirects :abstract_concept >+ attr_accessor :name >+ def initialize(name = "name") >+ @name = name >+ end > end > > class Puppet::AbstractConcept::Freedom < Puppet::Indirector::Code >@@ -23,6 +27,7 @@ describe Puppet::Indirector::Terminus do > 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 provide a method for setting terminus class documentation" do > terminus_class.should respond_to(:desc) >@@ -216,4 +221,44 @@ describe Puppet::Indirector::Terminus do > Puppet::Indirector::Terminus.terminus_classes('my_stuff').should == [:baz, :marathon] > end > end >+ >+ describe "when validating a request" do >+ let :request do >+ Puppet::Indirector::Request.new(indirection.name, :find, "the_key", instance) >+ end >+ >+ describe "`instance.name` does not match the key in the request" do >+ let(:instance) { model.new("wrong_key") } >+ >+ it "raises an error " do >+ expect { >+ terminus.validate(request) >+ }.to raise_error( >+ Puppet::Indirector::ValidationError, >+ /Instance name .* does not match requested key/ >+ ) >+ end >+ end >+ >+ describe "`instance` is not an instance of the model class" do >+ let(:instance) { mock "instance" } >+ >+ it "raises an error" do >+ expect { >+ terminus.validate(request) >+ }.to raise_error( >+ Puppet::Indirector::ValidationError, >+ /Invalid instance type/ >+ ) >+ end >+ end >+ >+ describe "the instance key and class match the request key and model class" do >+ let(:instance) { model.new("the_key") } >+ >+ it "passes" do >+ terminus.validate(request) >+ end >+ end >+ end > end >diff --git a/spec/unit/network/authconfig_spec.rb b/spec/unit/network/authconfig_spec.rb >index 4e57bcb..6814b2c 100755 >--- a/spec/unit/network/authconfig_spec.rb >+++ b/spec/unit/network/authconfig_spec.rb >@@ -78,6 +78,18 @@ describe Puppet::Network::AuthConfig do > @authconfig.rights['/'].should be_empty > @authconfig.rights['/'].authentication.should be_false > end >+ >+ it '(CVE-2013-2275) allows report submission only for the node matching the certname by default' do >+ acl = { >+ :acl => "~ ^\/report\/([^\/]+)$", >+ :method => :save, >+ :allow => '$1', >+ :authenticated => true >+ } >+ @authconfig.stubs(:mk_acl) >+ @authconfig.expects(:mk_acl).with(acl) >+ @authconfig.insert_default_acl >+ end > end > > describe "when checking authorization" do >diff --git a/spec/unit/network/authorization_spec.rb b/spec/unit/network/authorization_spec.rb >index 59d625e..397f55d 100644 >--- a/spec/unit/network/authorization_spec.rb >+++ b/spec/unit/network/authorization_spec.rb >@@ -11,6 +11,10 @@ describe Puppet::Network::Authorization do > > describe "when creating an authconfig object" do > it "creates default ACL entries if no file has been read" do >+ # Other tests may have created an authconfig, so we have to undo that. >+ Puppet::Network::AuthConfigLoader.instance_variable_set(:@auth_config, nil) >+ Puppet::Network::AuthConfigLoader.instance_variable_set(:@auth_config_file, nil) >+ > Puppet::Network::AuthConfigParser.expects(:new_from_file).raises Errno::ENOENT > Puppet::Network::AuthConfig.any_instance.expects(:insert_default_acl) > >diff --git a/spec/unit/network/formats_spec.rb b/spec/unit/network/formats_spec.rb >index df0d62f..d97fbd0 100755 >--- a/spec/unit/network/formats_spec.rb >+++ b/spec/unit/network/formats_spec.rb >@@ -55,15 +55,15 @@ describe "Puppet Network Format" do > @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 @@ describe "Puppet Network Format" do > @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 > >diff --git a/spec/unit/network/http/connection_spec.rb b/spec/unit/network/http/connection_spec.rb >index 1056386..b3755a4 100644 >--- a/spec/unit/network/http/connection_spec.rb >+++ b/spec/unit/network/http/connection_spec.rb >@@ -90,7 +90,6 @@ describe Puppet::Network::HTTP::Connection do > > 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 } >diff --git a/spec/unit/network/http/handler_spec.rb b/spec/unit/network/http/handler_spec.rb >index f24c4fd..eb368a2 100755 >--- a/spec/unit/network/http/handler_spec.rb >+++ b/spec/unit/network/http/handler_spec.rb >@@ -137,6 +137,31 @@ describe Puppet::Network::HTTP::Handler do > @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 >diff --git a/spec/unit/network/http/rack/rest_spec.rb b/spec/unit/network/http/rack/rest_spec.rb >index e4ac4d9..2b5b539 100755 >--- a/spec/unit/network/http/rack/rest_spec.rb >+++ b/spec/unit/network/http/rack/rest_spec.rb >@@ -111,6 +111,23 @@ describe "Puppet::Network::HTTP::RackREST", :if => Puppet.features.rack? do > @handler.set_response(@response, @file, 200) > end > end >+ >+ it "should ensure the body has been read on success" do >+ req = mk_req('/production/report/foo', :method => 'PUT') >+ req.body.expects(:read).at_least_once >+ >+ Puppet::Transaction::Report.stubs(:save) >+ >+ @handler.process(req, @response) >+ end >+ >+ it "should ensure the body has been partially read on failure" do >+ req = mk_req('/production/report/foo') >+ req.body.expects(:read).with(1) >+ req.stubs(:check_authorization).raises(Exception) >+ >+ @handler.process(req, @response) >+ end > end > > describe "and determining the request parameters" do >diff --git a/spec/unit/network/http/webrick_spec.rb b/spec/unit/network/http/webrick_spec.rb >index adc5d1a..24f4150 100755 >--- a/spec/unit/network/http/webrick_spec.rb >+++ b/spec/unit/network/http/webrick_spec.rb >@@ -241,6 +241,10 @@ describe Puppet::Network::HTTP::WEBrick do > server.setup_ssl[:SSLEnable].should be_true > end > >+ it "should reject SSLv2" do >+ server.setup_ssl[:SSLOptions].should == OpenSSL::SSL::OP_NO_SSLv2 >+ end >+ > it "should configure the verification method as 'OpenSSL::SSL::VERIFY_PEER'" do > server.setup_ssl[:SSLVerifyClient].should == OpenSSL::SSL::VERIFY_PEER > end >diff --git a/spec/unit/parser/functions/inline_template_spec.rb b/spec/unit/parser/functions/inline_template_spec.rb >index 2377816..ed0911e 100755 >--- a/spec/unit/parser/functions/inline_template_spec.rb >+++ b/spec/unit/parser/functions/inline_template_spec.rb >@@ -6,58 +6,29 @@ describe "the inline_template function" do > Puppet::Parser::Functions.autoloader.loadall > end > >- before :each do >- node = Puppet::Node.new('localhost') >- compiler = Puppet::Parser::Compiler.new(node) >- @scope = Puppet::Parser::Scope.new(compiler) >- end >- >- it "should exist" do >- Puppet::Parser::Functions.function("inline_template").should == "function_inline_template" >- end >- >- it "should create a TemplateWrapper when called" do >- tw = stub_everything 'template_wrapper' >+ let(:node) { Puppet::Node.new('localhost') } >+ let(:compiler) { Puppet::Parser::Compiler.new(node) } >+ let(:scope) { Puppet::Parser::Scope.new(compiler) } > >- Puppet::Parser::TemplateWrapper.expects(:new).returns(tw) >- >- @scope.function_inline_template(["test"]) >+ it "should concatenate template wrapper outputs for multiple templates" do >+ inline_template("template1", "template2").should == "template1template2" > end > >- it "should pass the template string to TemplateWrapper.result" do >- tw = stub_everything 'template_wrapper' >- Puppet::Parser::TemplateWrapper.stubs(:new).returns(tw) >- >- tw.expects(:result).with("test") >- >- @scope.function_inline_template(["test"]) >+ it "should raise an error if the template raises an error" do >+ expect { inline_template("<% raise 'error' %>") }.to raise_error(Puppet::ParseError) > end > >- it "should return what TemplateWrapper.result returns" do >- tw = stub_everything 'template_wrapper' >- Puppet::Parser::TemplateWrapper.stubs(:new).returns(tw) >- >- tw.expects(:result).returns("template contents evaluated") >- >- @scope.function_inline_template(["test"]).should == "template contents evaluated" >+ it "is not interfered with by a variable called 'string' (#14093)" do >+ scope['string'] = "this is a variable" >+ inline_template("this is a template").should == "this is a template" > end > >- it "should concatenate template wrapper outputs for multiple templates" do >- tw1 = stub_everything "template_wrapper1" >- tw2 = stub_everything "template_wrapper2" >- Puppet::Parser::TemplateWrapper.stubs(:new).returns(tw1,tw2) >- tw1.stubs(:result).returns("result1") >- tw2.stubs(:result).returns("result2") >- >- @scope.function_inline_template(["1","2"]).should == "result1result2" >+ it "has access to a variable called 'string' (#14093)" do >+ scope['string'] = "this is a variable" >+ inline_template("string was: <%= @string %>").should == "string was: this is a variable" > 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) >- tw.stubs(:result).raises >- >- lambda { @scope.function_inline_template(["1"]) }.should raise_error(Puppet::ParseError) >+ def inline_template(*templates) >+ scope.function_inline_template(templates) > end >- > end >diff --git a/spec/unit/parser/functions/template_spec.rb b/spec/unit/parser/functions/template_spec.rb >index bd55649..c873932 100755 >--- a/spec/unit/parser/functions/template_spec.rb >+++ b/spec/unit/parser/functions/template_spec.rb >@@ -10,38 +10,7 @@ describe "the template function" do > let :compiler do Puppet::Parser::Compiler.new(node) end > let :scope do Puppet::Parser::Scope.new(compiler) end > >- it "should exist" do >- Puppet::Parser::Functions.function("template").should == "function_template" >- end >- >- it "should create a TemplateWrapper when called" do >- tw = stub_everything 'template_wrapper' >- >- Puppet::Parser::TemplateWrapper.expects(:new).returns(tw) >- >- scope.function_template(["test"]) >- end >- >- it "should give the template filename to the TemplateWrapper" do >- tw = stub_everything 'template_wrapper' >- Puppet::Parser::TemplateWrapper.stubs(:new).returns(tw) >- >- tw.expects(:file=).with("test") >- >- scope.function_template(["test"]) >- end >- >- it "should return what TemplateWrapper.result returns" do >- tw = stub_everything 'template_wrapper' >- Puppet::Parser::TemplateWrapper.stubs(:new).returns(tw) >- tw.stubs(:file=).with("test") >- >- tw.expects(:result).returns("template contents evaluated") >- >- scope.function_template(["test"]).should == "template contents evaluated" >- end >- >- it "should concatenate template wrapper outputs for multiple templates" do >+ it "concatenates outputs for multiple templates" do > tw1 = stub_everything "template_wrapper1" > tw2 = stub_everything "template_wrapper2" > Puppet::Parser::TemplateWrapper.stubs(:new).returns(tw1,tw2) >@@ -53,7 +22,7 @@ describe "the template function" do > scope.function_template(["1","2"]).should == "result1result2" > end > >- it "should raise an error if the template raises an error" do >+ it "raises an error if the template raises an error" do > tw = stub_everything 'template_wrapper' > Puppet::Parser::TemplateWrapper.stubs(:new).returns(tw) > tw.stubs(:result).raises >@@ -63,40 +32,58 @@ describe "the template function" do > }.to raise_error(Puppet::ParseError, /Failed to parse template/) > end > >- def eval_template(content, *rest) >- File.stubs(:read).with("template").returns(content) >- Puppet::Parser::Files.stubs(:find_template).returns("template") >- scope.function_template(['template'] + rest) >+ context "when accessing scope variables via method calls (deprecated)" do >+ it "raises an error when accessing an undefined variable" do >+ expect { >+ eval_template("template <%= deprecated %>") >+ }.to raise_error(Puppet::ParseError, /Could not find value for 'deprecated'/) >+ end >+ >+ it "looks up the value from the scope" do >+ scope["deprecated"] = "deprecated value" >+ eval_template("template <%= deprecated %>").should == "template deprecated value" >+ end >+ >+ it "still has access to Kernel methods" do >+ expect { eval_template("<%= binding %>") }.to_not raise_error >+ end > end > >- it "should handle legacy template variable access correctly" do >- expect { >- eval_template("template <%= deprecated %>") >- }.to raise_error(Puppet::ParseError) >+ context "when accessing scope variables as instance variables" do >+ it "has access to values" do >+ scope['scope_var'] = "value" >+ eval_template("<%= @scope_var %>").should == "value" >+ end >+ >+ it "get nil accessing a variable that does not exist" do >+ eval_template("<%= @not_defined.nil? %>").should == "true" >+ end >+ >+ it "get nil accessing a variable that is undef" do >+ scope['undef_var'] = :undef >+ eval_template("<%= @undef_var.nil? %>").should == "true" >+ end > end > >- it "should get values from the scope correctly" do >- scope["deprecated"] = "deprecated value" >- eval_template("template <%= deprecated %>").should == "template deprecated value" >+ it "is not interfered with by having a variable named 'string' (#14093)" do >+ scope['string'] = "this output should not be seen" >+ eval_template("some text that is static").should == "some text that is static" > end > >- it "should handle kernel shadows without raising" do >- expect { eval_template("<%= binding %>") }.to_not raise_error >+ it "has access to a variable named 'string' (#14093)" do >+ scope['string'] = "the string value" >+ eval_template("string was: <%= @string %>").should == "string was: the string value" > end > >- it "should not see scopes" do >- scope['myvar'] = 'this is yayness' >+ it "does not have direct access to Scope#lookupvar" do > expect { > eval_template("<%= lookupvar('myvar') %>") >- }.to raise_error(Puppet::ParseError) >+ }.to raise_error(Puppet::ParseError, /Could not find value for 'lookupvar'/) > end > >- { "" => "", false => "false", true => "true" }.each do |input, output| >- it "should support defined variables (#{input.inspect} => #{output.inspect})" do >- scope['yayness'] = input >- expect { >- eval_template("<%= @yayness %>").should == output >- }.to_not raise_error >- 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 >diff --git a/spec/unit/parser/templatewrapper_spec.rb b/spec/unit/parser/templatewrapper_spec.rb >index 530819c..1b95b0a 100755 >--- a/spec/unit/parser/templatewrapper_spec.rb >+++ b/spec/unit/parser/templatewrapper_spec.rb >@@ -3,138 +3,115 @@ require 'spec_helper' > require 'puppet/parser/templatewrapper' > > describe Puppet::Parser::TemplateWrapper do >- before(:each) do >- @known_resource_types = Puppet::Resource::TypeCollection.new("env") >- @compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("mynode")) >- @compiler.environment.stubs(:known_resource_types).returns @known_resource_types >- @scope = Puppet::Parser::Scope.new @compiler >- >- @file = "fake_template" >- Puppet::Parser::Files.stubs(:find_template).returns("/tmp/fake_template") >- FileTest.stubs(:exists?).returns("true") >- File.stubs(:read).with("/tmp/fake_template").returns("template content") >- @tw = Puppet::Parser::TemplateWrapper.new(@scope) >+ let(:known_resource_types) { Puppet::Resource::TypeCollection.new("env") } >+ let(:scope) do >+ compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("mynode")) >+ compiler.environment.stubs(:known_resource_types).returns known_resource_types >+ Puppet::Parser::Scope.new compiler > end > >- def mock_template(source=nil) >- template_mock = mock("template", :result => "woot!") >- ERB.expects(:new).with("template contents", 0, "-").returns(template_mock) >- template_mock.expects(:filename=).with(source) >- end >+ let(:tw) { Puppet::Parser::TemplateWrapper.new(scope) } > >- it "should create a new object TemplateWrapper from a scope" do >- tw = Puppet::Parser::TemplateWrapper.new(@scope) >+ it "marks the file for watching" do >+ full_file_name = given_a_template_file("fake_template", "content") > >- tw.should be_a_kind_of(Puppet::Parser::TemplateWrapper) >+ known_resource_types.expects(:watch_file).with(full_file_name) >+ tw.file = "fake_template" > end > >- 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") >+ it "fails if a template cannot be found" do >+ Puppet::Parser::Files.expects(:find_template).returns nil > >- @tw.file = @file >+ expect { tw.file = "fake_template" }.to raise_error(Puppet::ParseError) > end > >- it "should mark the file for watching" do >- Puppet::Parser::Files.expects(:find_template).returns("/tmp/fake_template") >- File.stubs(:read) >- >- @known_resource_types.expects(:watch_file).with("/tmp/fake_template") >- @tw.file = @file >+ it "stringifies as template[<filename>] for a file based template" do >+ Puppet::Parser::Files.stubs(:find_template).returns("/tmp/fake_template") >+ tw.file = "fake_template" >+ tw.to_s.should eql("template[/tmp/fake_template]") > end > >- it "should fail if a template cannot be found" do >- Puppet::Parser::Files.expects(:find_template).returns nil >- >- lambda { @tw.file = @file }.should raise_error(Puppet::ParseError) >+ it "stringifies as template[inline] for a string-based template" do >+ tw.to_s.should eql("template[inline]") > end > >- it "should turn into a string like template[name] for file based template" do >- @tw.file = @file >- @tw.to_s.should eql("template[/tmp/fake_template]") >- end >+ it "reads and evaluates a file-based template" do >+ given_a_template_file("fake_template", "template contents") > >- it "should turn into a string like template[inline] for string-based template" do >- @tw.to_s.should eql("template[inline]") >+ tw.file = "fake_template" >+ tw.result.should eql("template contents") > end > >- it "should return the processed template contents with a call to result" do >- mock_template("/tmp/fake_template") >- File.expects(:read).with("/tmp/fake_template").returns("template contents") >+ it "provides access to the name of the template via #file" do >+ full_file_name = given_a_template_file("fake_template", "<%= file %>") > >- @tw.file = @file >- @tw.result.should eql("woot!") >+ 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!") >+ it "evaluates a given string as a template" do >+ tw.result("template contents").should eql("template contents") > end > >- it "should return the contents of a variable if called via method_missing" do >- @scope["chicken"] = "is good" >- tw = Puppet::Parser::TemplateWrapper.new(@scope) >- tw.chicken.should eql("is good") >+ it "provides the defined classes with #classes" do >+ catalog = mock 'catalog', :classes => ["class1", "class2"] >+ scope.expects(:catalog).returns( catalog ) >+ tw.classes.should == ["class1", "class2"] > end > >- it "should throw an exception if a variable is called via method_missing and it does not exist" do >- tw = Puppet::Parser::TemplateWrapper.new(@scope) >- lambda { tw.chicken }.should raise_error(Puppet::ParseError) >+ it "provides all the tags with #all_tags" do >+ catalog = mock 'catalog', :tags => ["tag1", "tag2"] >+ scope.expects(:catalog).returns( catalog ) >+ tw.all_tags.should == ["tag1","tag2"] > end > >- it "should allow you to check whether a variable is defined with has_variable?" do >- @scope["chicken"] = "is good" >- tw = Puppet::Parser::TemplateWrapper.new(@scope) >- tw.has_variable?("chicken").should eql(true) >+ it "provides the tags defined in the current scope with #tags" do >+ scope.expects(:tags).returns( ["tag1", "tag2"] ) >+ tw.tags.should == ["tag1","tag2"] > end > >- it "should allow you to check whether a variable is not defined with has_variable?" do >- tw = Puppet::Parser::TemplateWrapper.new(@scope) >- tw.has_variable?("chicken").should eql(false) >+ it "provides access to in-scope variables via method calls" do >+ scope["in_scope_variable"] = "is good" >+ tw.result("<%= in_scope_variable %>").should == "is good" > end > >- it "should allow you to retrieve the defined classes with classes" do >- catalog = mock 'catalog', :classes => ["class1", "class2"] >- @scope.expects(:catalog).returns( catalog ) >- tw = Puppet::Parser::TemplateWrapper.new(@scope) >- tw.classes.should == ["class1", "class2"] >+ it "errors if accessing via method call a variable that does not exist" do >+ expect { tw.result("<%= does_not_exist %>") }.to raise_error(Puppet::ParseError) > end > >- it "should allow you to retrieve all the tags with all_tags" do >- catalog = mock 'catalog', :tags => ["tag1", "tag2"] >- @scope.expects(:catalog).returns( catalog ) >- tw = Puppet::Parser::TemplateWrapper.new(@scope) >- tw.all_tags.should == ["tag1","tag2"] >+ it "reports that variable is available when it is in scope" do >+ scope["in_scope_variable"] = "is good" >+ tw.result("<%= has_variable?('in_scope_variable') %>").should == "true" > end > >- it "should allow you to retrieve the tags defined in the current scope" do >- @scope.expects(:tags).returns( ["tag1", "tag2"] ) >- tw = Puppet::Parser::TemplateWrapper.new(@scope) >- tw.tags.should == ["tag1","tag2"] >+ it "reports that a variable is not available when it is not in scope" do >+ tw.result("<%= has_variable?('not_in_scope_variable') %>").should == "false" > end > >- it "should set all of the scope's variables as instance variables" do >- mock_template >- @scope.expects(:to_hash).returns("one" => "foo") >- @tw.result("template contents") >- >- @tw.instance_variable_get("@one").should == "foo" >+ it "provides access to in-scope variables via instance variables" do >+ scope["one"] = "foo" >+ tw.result("<%= @one %>").should == "foo" > end > > it "should not error out if one of the variables is a symbol" do >- mock_template >- >- @scope.expects(:to_hash).returns(:_timestamp => "1234") >- @tw.result("template contents") >+ scope.expects(:to_hash).returns(:_timestamp => "1234") >+ tw.result("<%= @_timestamp %>").should == "1234" > end > > %w{! . ; :}.each do |badchar| >- it "should translate #{badchar} to _ when setting the instance variables" do >- mock_template >- @scope.expects(:to_hash).returns("one#{badchar}" => "foo") >- @tw.result("template contents") >- >- @tw.instance_variable_get("@one_").should == "foo" >+ it "translates #{badchar} to _ in instance variables" do >+ scope["one#{badchar}"] = "foo" >+ tw.result("<%= @one_ %>").should == "foo" > end > end >+ >+ def given_a_template_file(name, contents) >+ full_name = "/full/path/to/#{name}" >+ Puppet::Parser::Files.stubs(:find_template). >+ with(name, anything()). >+ returns(full_name) >+ File.stubs(:read).with(full_name).returns(contents) >+ >+ full_name >+ end > end >diff --git a/spec/unit/ssl/certificate_request_spec.rb b/spec/unit/ssl/certificate_request_spec.rb >index b6d07c6..7dc41b9 100755 >--- a/spec/unit/ssl/certificate_request_spec.rb >+++ b/spec/unit/ssl/certificate_request_spec.rb >@@ -239,6 +239,7 @@ describe Puppet::SSL::CertificateRequest do > > 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" } > >@@ -252,6 +253,7 @@ describe Puppet::SSL::CertificateRequest do > > 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" } > >diff --git a/spec/unit/ssl/host_spec.rb b/spec/unit/ssl/host_spec.rb >index 373b622..bdf1844 100755 >--- a/spec/unit/ssl/host_spec.rb >+++ b/spec/unit/ssl/host_spec.rb >@@ -490,6 +490,7 @@ describe Puppet::SSL::Host do > @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" > >diff --git a/spec/unit/util/monkey_patches_spec.rb b/spec/unit/util/monkey_patches_spec.rb >index a639d74..8776b4e 100755 >--- a/spec/unit/util/monkey_patches_spec.rb >+++ b/spec/unit/util/monkey_patches_spec.rb >@@ -254,3 +254,23 @@ describe Object, "#instance_variables" do > o.instance_variables.should =~ [:@foo, :@bar, :@baz] > 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 'explicitly disable SSLv2 ciphers using the ! prefix so they cannot be re-added' do >+ cipher_str = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:ciphers] >+ cipher_str.split(':').should include('!SSLv2') >+ end >+ it 'sets parameters on initialization' do >+ described_class.any_instance.expects(:set_params) >+ subject >+ end >+ it 'has no ciphers with version SSLv2 enabled' do >+ ciphers = subject.ciphers.select do |name, version, bits, alg_bits| >+ /SSLv2/.match(version) >+ end >+ ciphers.should be_empty >+ end >+end >-- >1.8.1.1 >
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
Attachments on
bug 919783
: 710424 |
710425
|
710426
|
710427
|
710428