File puppet-2.7.11-CVEs.diff of Package puppet.1506
---
.../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/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/http_pool.rb | 25 +-
lib/puppet/network/rest_authconfig.rb | 2 +-
lib/puppet/parser/templatewrapper.rb | 34 +-
lib/puppet/util/monkey_patches.rb | 103 ++++++
.../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 | 145 ++++----
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 +
53 files changed, 1295 insertions(+), 305 deletions(-)
create mode 100644 acceptance/tests/security/cve-2013-1640_facter_string.rb
create mode 100644 acceptance/tests/security/cve-2013-1652_improper_query_params.rb
create mode 100644 acceptance/tests/security/cve-2013-1652_poison_other_node_cache.rb
create mode 100644 acceptance/tests/security/cve-2013-1653_puppet_kick.rb
create mode 100644 acceptance/tests/security/cve-2013-1654_sslv2_downgrade_agent.rb
create mode 100644 acceptance/tests/security/cve-2013-1654_sslv2_downgrade_master.rb
create mode 100644 acceptance/tests/security/cve-2013-1655_safe_yaml_deserialization.rb
create mode 100644 acceptance/tests/security/cve-2013-2274_all_your_agent_terminii_belong_to_us.rb
create mode 100644 acceptance/tests/security/cve-2013-2274_all_your_master_terminii_belong_to_us.rb
create mode 100644 acceptance/tests/security/cve-2013-2275_report_acl.rb
create mode 100644 lib/puppet/indirector/errors.rb
create mode 100644 lib/puppet/indirector/resource/validator.rb
Index: puppet-2.7.6/acceptance/tests/security/cve-2013-1640_facter_string.rb
===================================================================
--- /dev/null
+++ puppet-2.7.6/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
Index: puppet-2.7.6/acceptance/tests/security/cve-2013-1652_improper_query_params.rb
===================================================================
--- /dev/null
+++ puppet-2.7.6/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
Index: puppet-2.7.6/acceptance/tests/security/cve-2013-1652_poison_other_node_cache.rb
===================================================================
--- /dev/null
+++ puppet-2.7.6/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
Index: puppet-2.7.6/acceptance/tests/security/cve-2013-1653_puppet_kick.rb
===================================================================
--- /dev/null
+++ puppet-2.7.6/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
Index: puppet-2.7.6/acceptance/tests/security/cve-2013-1654_sslv2_downgrade_agent.rb
===================================================================
--- /dev/null
+++ puppet-2.7.6/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
Index: puppet-2.7.6/acceptance/tests/security/cve-2013-1654_sslv2_downgrade_master.rb
===================================================================
--- /dev/null
+++ puppet-2.7.6/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
Index: puppet-2.7.6/acceptance/tests/security/cve-2013-1655_safe_yaml_deserialization.rb
===================================================================
--- /dev/null
+++ puppet-2.7.6/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
Index: puppet-2.7.6/acceptance/tests/security/cve-2013-2274_all_your_agent_terminii_belong_to_us.rb
===================================================================
--- /dev/null
+++ puppet-2.7.6/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
Index: puppet-2.7.6/acceptance/tests/security/cve-2013-2274_all_your_master_terminii_belong_to_us.rb
===================================================================
--- /dev/null
+++ puppet-2.7.6/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
Index: puppet-2.7.6/acceptance/tests/security/cve-2013-2275_report_acl.rb
===================================================================
--- /dev/null
+++ puppet-2.7.6/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
Index: puppet-2.7.6/conf/auth.conf
===================================================================
--- puppet-2.7.6.orig/conf/auth.conf
+++ puppet-2.7.6/conf/auth.conf
@@ -63,10 +63,10 @@ path /certificate_revocation_list/ca
method find
allow *
-# allow all nodes to store their reports
-path /report
+# allow all nodes to store their own reports
+path ~ ^/report/([^/]+)$
method save
-allow *
+allow $1
# inconditionnally allow access to all files services
# which means in practice that fileserver.conf will
Index: puppet-2.7.6/lib/puppet/indirector/catalog/compiler.rb
===================================================================
--- puppet-2.7.6.orig/lib/puppet/indirector/catalog/compiler.rb
+++ puppet-2.7.6/lib/puppet/indirector/catalog/compiler.rb
@@ -13,7 +13,9 @@ class Puppet::Resource::Catalog::Compile
def extract_facts_from_request(request)
return unless text_facts = request.options[:facts]
- raise ArgumentError, "Facts but no fact format provided for #{request.name}" unless format = request.options[:facts_format]
+ unless format = request.options[:facts_format]
+ raise ArgumentError, "Facts but no fact format provided for #{request.key}"
+ end
# If the facts were encoded as yaml, then the param reconstitution system
# in Network::HTTP::Handler will automagically deserialize the value.
@@ -22,6 +24,11 @@ class Puppet::Resource::Catalog::Compile
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::Compile
# 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
Index: puppet-2.7.6/lib/puppet/indirector/certificate_status/file.rb
===================================================================
--- puppet-2.7.6.orig/lib/puppet/indirector/certificate_status/file.rb
+++ puppet-2.7.6/lib/puppet/indirector/certificate_status/file.rb
@@ -79,4 +79,9 @@ class Puppet::Indirector::CertificateSta
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
Index: puppet-2.7.6/lib/puppet/indirector/errors.rb
===================================================================
--- /dev/null
+++ puppet-2.7.6/lib/puppet/indirector/errors.rb
@@ -0,0 +1,5 @@
+require 'puppet/error'
+
+module Puppet::Indirector
+ class ValidationError < Puppet::Error; end
+end
Index: puppet-2.7.6/lib/puppet/indirector/file_bucket_file/file.rb
===================================================================
--- puppet-2.7.6.orig/lib/puppet/indirector/file_bucket_file/file.rb
+++ puppet-2.7.6/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)
Index: puppet-2.7.6/lib/puppet/indirector/indirection.rb
===================================================================
--- puppet-2.7.6.orig/lib/puppet/indirector/indirection.rb
+++ puppet-2.7.6/lib/puppet/indirector/indirection.rb
@@ -302,6 +302,7 @@ class Puppet::Indirector::Indirection
dest_terminus = terminus(terminus_name)
check_authorization(request, dest_terminus)
+ dest_terminus.validate(request)
dest_terminus
end
Index: puppet-2.7.6/lib/puppet/indirector/request.rb
===================================================================
--- puppet-2.7.6.orig/lib/puppet/indirector/request.rb
+++ puppet-2.7.6/lib/puppet/indirector/request.rb
@@ -150,6 +150,10 @@ class Puppet::Indirector::Request
return(uri ? uri : "/#{indirection_name}/#{key}")
end
+ def remote?
+ self.node or self.ip
+ end
+
private
def set_attributes(options)
Index: puppet-2.7.6/lib/puppet/indirector/resource/active_record.rb
===================================================================
--- puppet-2.7.6.orig/lib/puppet/indirector/resource/active_record.rb
+++ puppet-2.7.6/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]
Index: puppet-2.7.6/lib/puppet/indirector/resource/ral.rb
===================================================================
--- puppet-2.7.6.orig/lib/puppet/indirector/resource/ral.rb
+++ puppet-2.7.6/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) }
Index: puppet-2.7.6/lib/puppet/indirector/resource/store_configs.rb
===================================================================
--- puppet-2.7.6.orig/lib/puppet/indirector/resource/store_configs.rb
+++ puppet-2.7.6/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
Index: puppet-2.7.6/lib/puppet/indirector/resource/validator.rb
===================================================================
--- /dev/null
+++ puppet-2.7.6/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
Index: puppet-2.7.6/lib/puppet/indirector/rest.rb
===================================================================
--- puppet-2.7.6.orig/lib/puppet/indirector/rest.rb
+++ puppet-2.7.6/lib/puppet/indirector/rest.rb
@@ -102,6 +102,10 @@ class Puppet::Indirector::REST < Puppet:
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 @@ class Puppet::Indirector::REST < Puppet:
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
Index: puppet-2.7.6/lib/puppet/indirector/run/local.rb
===================================================================
--- puppet-2.7.6.orig/lib/puppet/indirector/run/local.rb
+++ puppet-2.7.6/lib/puppet/indirector/run/local.rb
@@ -5,4 +5,8 @@ class Puppet::Run::Local < Puppet::Indir
def save( request )
request.instance.run
end
+
+ def validate_key(request)
+ # No key is necessary for kick
+ end
end
Index: puppet-2.7.6/lib/puppet/indirector/terminus.rb
===================================================================
--- puppet-2.7.6.orig/lib/puppet/indirector/terminus.rb
+++ puppet-2.7.6/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_key(request)
+ validate_model(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 request.instance.kind_of?(model)
+ raise Puppet::Indirector::ValidationError, "Invalid instance type #{request.instance.class.inspect}, expected #{model.inspect}"
+ end
+ end
end
Index: puppet-2.7.6/lib/puppet/network/formats.rb
===================================================================
--- puppet-2.7.6.orig/lib/puppet/network/formats.rb
+++ puppet-2.7.6/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_se
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
Index: puppet-2.7.6/lib/puppet/network/handler/master.rb
===================================================================
--- puppet-2.7.6.orig/lib/puppet/network/handler/master.rb
+++ puppet-2.7.6/lib/puppet/network/handler/master.rb
@@ -68,7 +68,7 @@ class Puppet::Network::Handler
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"
Index: puppet-2.7.6/lib/puppet/network/handler/report.rb
===================================================================
--- puppet-2.7.6.orig/lib/puppet/network/handler/report.rb
+++ puppet-2.7.6/lib/puppet/network/handler/report.rb
@@ -43,7 +43,7 @@ class Puppet::Network::Handler
# 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
Index: puppet-2.7.6/lib/puppet/network/http/handler.rb
===================================================================
--- puppet-2.7.6.orig/lib/puppet/network/http/handler.rb
+++ puppet-2.7.6/lib/puppet/network/http/handler.rb
@@ -70,6 +70,8 @@ module Puppet::Network::HTTP::Handler
raise
rescue Exception => e
return do_exception(response, e)
+ ensure
+ cleanup(request)
end
# Set the response up, with the body and status.
@@ -217,6 +219,10 @@ module Puppet::Network::HTTP::Handler
raise NotImplementedError
end
+ def cleanup(request)
+ # By default, there is nothing to cleanup.
+ end
+
def decode_params(params)
params.inject({}) do |result, ary|
param, value = ary
@@ -230,7 +236,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"
Index: puppet-2.7.6/lib/puppet/network/http/rack/rest.rb
===================================================================
--- puppet-2.7.6.orig/lib/puppet/network/http/rack/rest.rb
+++ puppet-2.7.6/lib/puppet/network/http/rack/rest.rb
@@ -73,12 +73,17 @@ class Puppet::Network::HTTP::RackREST <
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
Index: puppet-2.7.6/lib/puppet/network/http/webrick.rb
===================================================================
--- puppet-2.7.6.orig/lib/puppet/network/http/webrick.rb
+++ puppet-2.7.6/lib/puppet/network/http/webrick.rb
@@ -104,6 +104,7 @@ class Puppet::Network::HTTP::WEBrick
results[:SSLCertificate] = host.certificate.content
results[:SSLStartImmediately] = true
results[:SSLEnable] = true
+ results[:SSLOptions] = OpenSSL::SSL::OP_NO_SSLv2
raise Puppet::Error, "Could not find CA certificate" unless Puppet::SSL::Certificate.indirection.find(Puppet::SSL::CA_NAME)
Index: puppet-2.7.6/lib/puppet/network/http_pool.rb
===================================================================
--- puppet-2.7.6.orig/lib/puppet/network/http_pool.rb
+++ puppet-2.7.6/lib/puppet/network/http_pool.rb
@@ -11,14 +11,23 @@ module Puppet::Network::HttpPool
# Use cert information from a Puppet client to set up the http object.
def self.cert_setup(http)
- # Just no-op if we don't have certs.
- return false unless FileTest.exist?(Puppet[:hostcert]) and FileTest.exist?(Puppet[:localcacert])
+ if FileTest.exist?(Puppet[:hostcert]) and FileTest.exist?(Puppet[:localcacert])
+ http.cert_store = ssl_host.ssl_store
+ http.ca_file = Puppet[:localcacert]
+ http.cert = ssl_host.certificate.content
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ http.key = ssl_host.key.content
+ else
+ # We don't have the local certificates, so we don't do any verification
+ # or setup at this early stage. REVISIT: Shouldn't we supply the local
+ # certificate details if we have them? The original code didn't.
+ # --daniel 2012-06-03
- http.cert_store = ssl_host.ssl_store
- http.ca_file = Puppet[:localcacert]
- http.cert = ssl_host.certificate.content
- http.verify_mode = OpenSSL::SSL::VERIFY_PEER
- http.key = ssl_host.key.content
+ # Ruby 1.8 defaulted to this, but 1.9 defaults to peer verify, and we
+ # almost always talk to a dedicated, not-standard CA that isn't trusted
+ # out of the box. This forces the expected state.
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
+ end
end
# Retrieve a cached http instance if caching is enabled, else return
Index: puppet-2.7.6/lib/puppet/network/rest_authconfig.rb
===================================================================
--- puppet-2.7.6.orig/lib/puppet/network/rest_authconfig.rb
+++ puppet-2.7.6/lib/puppet/network/rest_authconfig.rb
@@ -13,8 +13,8 @@ module Puppet
# to fileserver.conf
{ :acl => "/file" },
{ :acl => "/certificate_revocation_list/ca", :method => :find, :authenticated => true },
- { :acl => "/report", :method => :save, :authenticated => true },
{ :acl => "/certificate/ca", :method => :find, :authenticated => false },
+ { :acl => "~ ^\/report\/([^\/]+)$", :method => :save, :allow => '$1', :authenticated => true },
{ :acl => "/certificate/", :method => :find, :authenticated => false },
{ :acl => "/certificate_request", :method => [:find, :save], :authenticated => false },
{ :acl => "/status", :method => [:find], :authenticated => true },
Index: puppet-2.7.6/lib/puppet/parser/templatewrapper.rb
===================================================================
--- puppet-2.7.6.orig/lib/puppet/parser/templatewrapper.rb
+++ puppet-2.7.6/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,18 +12,22 @@ class Puppet::Parser::TemplateWrapper
@__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 @@ class Puppet::Parser::TemplateWrapper
# 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 @@ class Puppet::Parser::TemplateWrapper
end
def to_s
- "template[#{(file ? file : "inline")}]"
+ "template[#{(@__file__ ? @__file__ : "inline")}]"
end
end
Index: puppet-2.7.6/lib/puppet/util/monkey_patches.rb
===================================================================
--- puppet-2.7.6.orig/lib/puppet/util/monkey_patches.rb
+++ puppet-2.7.6/lib/puppet/util/monkey_patches.rb
@@ -34,6 +34,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
@@ -130,3 +145,91 @@ class IO
lines
end
end
+
+# The mv method in Ruby 1.8.5 can't mv directories across devices
+# File.rename causes "Invalid cross-device link", which is rescued, but in Ruby
+# 1.8.5 it tries to recover with a copy and unlink, but the unlink causes the
+# error "Is a directory". In newer Rubies remove_entry is used
+# The implementation below is what's used in Ruby 1.8.7 and Ruby 1.9
+if RUBY_VERSION == '1.8.5'
+ require 'fileutils'
+
+ module FileUtils
+ def mv(src, dest, options = {})
+ fu_check_options options, OPT_TABLE['mv']
+ fu_output_message "mv#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
+ return if options[:noop]
+ fu_each_src_dest(src, dest) do |s, d|
+ destent = Entry_.new(d, nil, true)
+ begin
+ if destent.exist?
+ if destent.directory?
+ raise Errno::EEXIST, dest
+ else
+ destent.remove_file if rename_cannot_overwrite_file?
+ end
+ end
+ begin
+ File.rename s, d
+ rescue Errno::EXDEV
+ copy_entry s, d, true
+ if options[:secure]
+ remove_entry_secure s, options[:force]
+ else
+ remove_entry s, options[:force]
+ end
+ end
+ rescue SystemCallError
+ raise unless options[:force]
+ end
+ end
+ end
+ module_function :mv
+
+ alias move mv
+ 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
Index: puppet-2.7.6/spec/integration/indirector/catalog/compiler_spec.rb
===================================================================
--- puppet-2.7.6.orig/spec/integration/indirector/catalog/compiler_spec.rb
+++ puppet-2.7.6/spec/integration/indirector/catalog/compiler_spec.rb
@@ -11,6 +11,7 @@ describe Puppet::Resource::Catalog::Comp
@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 }
Index: puppet-2.7.6/spec/integration/indirector/catalog/queue_spec.rb
===================================================================
--- puppet-2.7.6.orig/spec/integration/indirector/catalog/queue_spec.rb
+++ puppet-2.7.6/spec/integration/indirector/catalog/queue_spec.rb
@@ -6,7 +6,7 @@ require 'puppet/resource/catalog'
describe "Puppet::Resource::Catalog::Queue", :if => Puppet.features.pson? do
before do
Puppet::Resource::Catalog.indirection.terminus(:queue)
- @catalog = Puppet::Resource::Catalog.new
+ @catalog = Puppet::Resource::Catalog.new("foo")
@one = Puppet::Resource.new(:file, "/one")
@two = Puppet::Resource.new(:file, "/two")
Index: puppet-2.7.6/spec/integration/resource/catalog_spec.rb
===================================================================
--- puppet-2.7.6.orig/spec/integration/resource/catalog_spec.rb
+++ puppet-2.7.6/spec/integration/resource/catalog_spec.rb
@@ -48,6 +48,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
Index: puppet-2.7.6/spec/unit/indirector/catalog/compiler_spec.rb
===================================================================
--- puppet-2.7.6.orig/spec/unit/indirector/catalog/compiler_spec.rb
+++ puppet-2.7.6/spec/unit/indirector/catalog/compiler_spec.rb
@@ -53,13 +53,22 @@ describe Puppet::Resource::Catalog::Comp
@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 @@ describe Puppet::Resource::Catalog::Comp
@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 @@ describe Puppet::Resource::Catalog::Comp
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)
Index: puppet-2.7.6/spec/unit/indirector/indirection_spec.rb
===================================================================
--- puppet-2.7.6.orig/spec/unit/indirector/indirection_spec.rb
+++ puppet-2.7.6/spec/unit/indirector/indirection_spec.rb
@@ -40,7 +40,7 @@ shared_examples_for "Indirection Delegat
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 Authoriz
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
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
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 @@ describe Puppet::Indirector::Indirection
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 @@ describe Puppet::Indirector::Indirection
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 @@ describe Puppet::Indirector::Indirection
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 @@ describe Puppet::Indirector::Indirection
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 @@ describe Puppet::Indirector::Indirection
before do
@indirection = Puppet::Indirector::Indirection.new(mock('model'), :test)
@terminus = mock 'terminus'
+ @terminus.stubs(:validate)
@terminus_class = stub 'terminus class', :new => @terminus
end
Index: puppet-2.7.6/spec/unit/indirector/request_spec.rb
===================================================================
--- puppet-2.7.6.orig/spec/unit/indirector/request_spec.rb
+++ puppet-2.7.6/spec/unit/indirector/request_spec.rb
@@ -311,4 +311,26 @@ describe Puppet::Indirector::Request do
lambda { @request.query_string }.should raise_error(ArgumentError)
end
end
+
+ describe "#remote?" do
+ def request(options = {})
+ Puppet::Indirector::Request.new('node', 'find', 'localhost', options)
+ end
+
+ it "should not be unless node or ip is set" do
+ request.should_not be_remote
+ end
+
+ it "should be remote if node is set" do
+ request(:node => 'example.com').should be_remote
+ end
+
+ it "should be remote if ip is set" do
+ request(:ip => '127.0.0.1').should be_remote
+ end
+
+ it "should be remote if node and ip are set" do
+ request(:node => 'example.com', :ip => '127.0.0.1').should be_remote
+ end
+ end
end
Index: puppet-2.7.6/spec/unit/indirector/terminus_spec.rb
===================================================================
--- puppet-2.7.6.orig/spec/unit/indirector/terminus_spec.rb
+++ puppet-2.7.6/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", :name => "the_key" }
- 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
Index: puppet-2.7.6/spec/unit/network/formats_spec.rb
===================================================================
--- puppet-2.7.6.orig/spec/unit/network/formats_spec.rb
+++ puppet-2.7.6/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
Index: puppet-2.7.6/spec/unit/network/http/handler_spec.rb
===================================================================
--- puppet-2.7.6.orig/spec/unit/network/http/handler_spec.rb
+++ puppet-2.7.6/spec/unit/network/http/handler_spec.rb
@@ -125,6 +125,31 @@ describe Puppet::Network::HTTP::Handler
@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
Index: puppet-2.7.6/spec/unit/network/http/rack/rest_spec.rb
===================================================================
--- puppet-2.7.6.orig/spec/unit/network/http/rack/rest_spec.rb
+++ puppet-2.7.6/spec/unit/network/http/rack/rest_spec.rb
@@ -91,6 +91,23 @@ describe "Puppet::Network::HTTP::RackRES
@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
Index: puppet-2.7.6/spec/unit/network/http/webrick_spec.rb
===================================================================
--- puppet-2.7.6.orig/spec/unit/network/http/webrick_spec.rb
+++ puppet-2.7.6/spec/unit/network/http/webrick_spec.rb
@@ -316,6 +316,10 @@ describe Puppet::Network::HTTP::WEBrick,
@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
Index: puppet-2.7.6/spec/unit/network/http_pool_spec.rb
===================================================================
--- puppet-2.7.6.orig/spec/unit/network/http_pool_spec.rb
+++ puppet-2.7.6/spec/unit/network/http_pool_spec.rb
@@ -14,122 +14,119 @@ describe Puppet::Network::HttpPool do
end
describe "when managing http instances" do
- def stub_settings(settings)
- settings.each do |param, value|
- Puppet.settings.stubs(:value).with(param).returns(value)
- end
- end
-
- before do
+ before :each do
# All of the cert stuff is tested elsewhere
Puppet::Network::HttpPool.stubs(:cert_setup)
end
it "should return an http instance created with the passed host and port" do
- http = stub 'http', :use_ssl= => nil, :read_timeout= => nil, :open_timeout= => nil, :started? => false
- Net::HTTP.expects(:new).with("me", 54321, nil, nil).returns(http)
- Puppet::Network::HttpPool.http_instance("me", 54321).should equal(http)
+ http = Puppet::Network::HttpPool.http_instance("me", 54321)
+ http.should be_an_instance_of Net::HTTP
+ http.address.should == 'me'
+ http.port.should == 54321
end
it "should enable ssl on the http instance" do
- Puppet::Network::HttpPool.http_instance("me", 54321).instance_variable_get("@use_ssl").should be_true
+ Puppet::Network::HttpPool.http_instance("me", 54321).should be_use_ssl
end
- it "should set the read timeout" do
- Puppet::Network::HttpPool.http_instance("me", 54321).read_timeout.should == 120
+ context "proxy and timeout settings should propagate" do
+ subject { Puppet::Network::HttpPool.http_instance("me", 54321) }
end
- it "should set the open timeout" do
- Puppet::Network::HttpPool.http_instance("me", 54321).open_timeout.should == 120
- end
-
- it "should create the http instance with the proxy host and port set if the http_proxy is not set to 'none'" do
- stub_settings :http_proxy_host => "myhost", :http_proxy_port => 432, :configtimeout => 120
- Puppet::Network::HttpPool.http_instance("me", 54321).open_timeout.should == 120
+ it "should not set a proxy if the value is 'none'" do
+ Puppet[:http_proxy_host] = 'none'
+ Puppet::Network::HttpPool.http_instance("me", 54321).proxy_address.should be_nil
end
it "should not cache http instances" do
- stub_settings :http_proxy_host => "myhost", :http_proxy_port => 432, :configtimeout => 120
+ Puppet[:http_proxy_host] = "myhost"
+ Puppet[:http_proxy_port] = 432
+ Puppet[:configtimeout] = 120
old = Puppet::Network::HttpPool.http_instance("me", 54321)
Puppet::Network::HttpPool.http_instance("me", 54321).should_not equal(old)
end
end
- describe "when adding certificate information to http instances" do
- before do
- @http = mock 'http'
- [:cert_store=, :verify_mode=, :ca_file=, :cert=, :key=].each { |m| @http.stubs(m) }
- @store = stub 'store'
-
- @cert = stub 'cert', :content => "real_cert"
- @key = stub 'key', :content => "real_key"
- @host = stub 'host', :certificate => @cert, :key => @key, :ssl_store => @store
-
- Puppet[:confdir] = "/sometthing/else"
- Puppet.settings.stubs(:value).returns "/some/file"
- Puppet.settings.stubs(:value).with(:hostcert).returns "/host/cert"
- Puppet.settings.stubs(:value).with(:localcacert).returns "/local/ca/cert"
-
- FileTest.stubs(:exist?).with("/host/cert").returns true
- FileTest.stubs(:exist?).with("/local/ca/cert").returns true
-
- Puppet::Network::HttpPool.stubs(:ssl_host).returns @host
- end
-
- after do
- Puppet.settings.clear
- end
-
- it "should do nothing if no host certificate is on disk" do
- FileTest.expects(:exist?).with("/host/cert").returns false
- @http.expects(:cert=).never
- Puppet::Network::HttpPool.cert_setup(@http)
- end
-
- it "should do nothing if no local certificate is on disk" do
- FileTest.expects(:exist?).with("/local/ca/cert").returns false
- @http.expects(:cert=).never
- Puppet::Network::HttpPool.cert_setup(@http)
- end
-
- it "should add a certificate store from the ssl host" do
- @http.expects(:cert_store=).with(@store)
-
- Puppet::Network::HttpPool.cert_setup(@http)
- end
-
- it "should add the client certificate" do
- @http.expects(:cert=).with("real_cert")
+ describe "when doing SSL setup for http instances" do
+ let :http do
+ http = Net::HTTP.new('localhost', 443)
+ http.use_ssl = true
+ http
+ end
+
+ let :store do stub('store') end
+
+ before :each do
+ Puppet[:hostcert] = '/host/cert'
+ Puppet[:localcacert] = '/local/ca/cert'
+ cert = stub 'cert', :content => 'real_cert'
+ key = stub 'key', :content => 'real_key'
+ host = stub 'host', :certificate => cert, :key => key, :ssl_store => store
+ Puppet::Network::HttpPool.stubs(:ssl_host).returns(host)
+ end
+
+ shared_examples "HTTPS setup without all certificates" do
+ subject { Puppet::Network::HttpPool.cert_setup(http); http }
+
+ it { should be_use_ssl }
+ its(:cert) { should be_nil }
+ its(:ca_file) { should be_nil }
+ its(:key) { should be_nil }
+ its(:verify_mode) { should == OpenSSL::SSL::VERIFY_NONE }
+ end
+
+ context "with neither a host cert or a local CA cert" do
+ before :each do
+ FileTest.stubs(:exist?).with(Puppet[:hostcert]).returns(false)
+ FileTest.stubs(:exist?).with(Puppet[:localcacert]).returns(false)
+ end
- Puppet::Network::HttpPool.cert_setup(@http)
+ include_examples "HTTPS setup without all certificates"
end
- it "should add the client key" do
- @http.expects(:key=).with("real_key")
+ context "with there is no host certificate" do
+ before :each do
+ FileTest.stubs(:exist?).with(Puppet[:hostcert]).returns(false)
+ FileTest.stubs(:exist?).with(Puppet[:localcacert]).returns(true)
+ end
- Puppet::Network::HttpPool.cert_setup(@http)
+ include_examples "HTTPS setup without all certificates"
end
- it "should set the verify mode to OpenSSL::SSL::VERIFY_PEER" do
- @http.expects(:verify_mode=).with(OpenSSL::SSL::VERIFY_PEER)
+ context "with there is no local CA certificate" do
+ before :each do
+ FileTest.stubs(:exist?).with(Puppet[:hostcert]).returns(true)
+ FileTest.stubs(:exist?).with(Puppet[:localcacert]).returns(false)
+ end
- Puppet::Network::HttpPool.cert_setup(@http)
+ include_examples "HTTPS setup without all certificates"
end
- it "should set the ca file" do
- Puppet.settings.stubs(:value).returns "/some/file"
- FileTest.stubs(:exist?).with(Puppet[:hostcert]).returns true
+ context "with both the host and CA cert" do
+ subject { Puppet::Network::HttpPool.cert_setup(http); http }
- Puppet.settings.stubs(:value).with(:localcacert).returns "/ca/cert/file"
- FileTest.stubs(:exist?).with("/ca/cert/file").returns true
- @http.expects(:ca_file=).with("/ca/cert/file")
+ before :each do
+ FileTest.expects(:exist?).with(Puppet[:hostcert]).returns(true)
+ FileTest.expects(:exist?).with(Puppet[:localcacert]).returns(true)
+ end
- Puppet::Network::HttpPool.cert_setup(@http)
+ it { should be_use_ssl }
+ its(:cert_store) { should equal store }
+ its(:cert) { should == "real_cert" }
+ its(:key) { should == "real_key" }
+ its(:verify_mode) { should == OpenSSL::SSL::VERIFY_PEER }
+ its(:ca_file) { should == Puppet[:localcacert] }
end
it "should set up certificate information when creating http instances" do
- Puppet::Network::HttpPool.expects(:cert_setup).with { |i| i.is_a?(Net::HTTP) }
- Puppet::Network::HttpPool.http_instance("one", "two")
+ Puppet::Network::HttpPool.expects(:cert_setup).with do |http|
+ http.should be_an_instance_of Net::HTTP
+ http.address.should == "one"
+ http.port.should == 2
+ end
+
+ Puppet::Network::HttpPool.http_instance("one", 2)
end
end
end
Index: puppet-2.7.6/spec/unit/network/rest_authconfig_spec.rb
===================================================================
--- puppet-2.7.6.orig/spec/unit/network/rest_authconfig_spec.rb
+++ puppet-2.7.6/spec/unit/network/rest_authconfig_spec.rb
@@ -85,6 +85,9 @@ describe Puppet::Network::RestAuthConfig
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 @@ describe Puppet::Network::RestAuthConfig
@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
Index: puppet-2.7.6/spec/unit/parser/functions/inline_template_spec.rb
===================================================================
--- puppet-2.7.6.orig/spec/unit/parser/functions/inline_template_spec.rb
+++ puppet-2.7.6/spec/unit/parser/functions/inline_template_spec.rb
@@ -58,4 +58,17 @@ describe "the inline_template function",
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
Index: puppet-2.7.6/spec/unit/parser/functions/template_spec.rb
===================================================================
--- puppet-2.7.6.orig/spec/unit/parser/functions/template_spec.rb
+++ puppet-2.7.6/spec/unit/parser/functions/template_spec.rb
@@ -53,6 +53,22 @@ describe "the template function", :'fail
@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 @@ describe "the template function", :'fail
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
Index: puppet-2.7.6/spec/unit/parser/templatewrapper_spec.rb
===================================================================
--- puppet-2.7.6.orig/spec/unit/parser/templatewrapper_spec.rb
+++ puppet-2.7.6/spec/unit/parser/templatewrapper_spec.rb
@@ -30,16 +30,14 @@ describe Puppet::Parser::TemplateWrapper
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 @@ describe Puppet::Parser::TemplateWrapper
@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 @@ describe Puppet::Parser::TemplateWrapper
@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
Index: puppet-2.7.6/spec/unit/ssl/certificate_request_spec.rb
===================================================================
--- puppet-2.7.6.orig/spec/unit/ssl/certificate_request_spec.rb
+++ puppet-2.7.6/spec/unit/ssl/certificate_request_spec.rb
@@ -254,6 +254,7 @@ describe Puppet::SSL::CertificateRequest
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 @@ describe Puppet::SSL::CertificateRequest
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" }
Index: puppet-2.7.6/spec/unit/ssl/host_spec.rb
===================================================================
--- puppet-2.7.6.orig/spec/unit/ssl/host_spec.rb
+++ puppet-2.7.6/spec/unit/ssl/host_spec.rb
@@ -450,6 +450,7 @@ describe Puppet::SSL::Host, :fails_on_wi
@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"
Index: puppet-2.7.6/spec/unit/util/monkey_patches_spec.rb
===================================================================
--- puppet-2.7.6.orig/spec/unit/util/monkey_patches_spec.rb
+++ puppet-2.7.6/spec/unit/util/monkey_patches_spec.rb
@@ -53,3 +53,15 @@ describe "Array#combination" do
[1,2,3,4].combination(3).to_a.should == [[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]]
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