Update to 2.6.12, fixes CVE-2011-3872

This includes and upstream patch to restore Mongrel XMLRPC functionality
(upstream #10244) which was accidentally dropped in 2.6.12.
epel9
Todd Zullinger 13 years ago
parent 62983f0d68
commit 65b5b66a83

@ -0,0 +1,161 @@
From 908aef3579534f7718dfbdeb24fad94591186a3f Mon Sep 17 00:00:00 2001
From: Nick Lewis <nick@puppetlabs.com>
Date: Mon, 24 Oct 2011 10:13:33 -0700
Subject: [PATCH] (#10244) Restore Mongrel XMLRPC functionality
This code was over-eagerly removed, when it turns out to actually
still be necessary for backward compatibility with XMLRPC clients.
---
lib/puppet/network/http_server.rb | 3 +
lib/puppet/network/http_server/mongrel.rb | 130 +++++++++++++++++++++++++++++
2 files changed, 133 insertions(+), 0 deletions(-)
create mode 100644 lib/puppet/network/http_server.rb
create mode 100644 lib/puppet/network/http_server/mongrel.rb
diff --git a/lib/puppet/network/http_server.rb b/lib/puppet/network/http_server.rb
new file mode 100644
index 0000000..e3826a6
--- /dev/null
+++ b/lib/puppet/network/http_server.rb
@@ -0,0 +1,3 @@
+# Just a stub, so we can correctly scope other classes.
+module Puppet::Network::HTTPServer # :nodoc:
+end
diff --git a/lib/puppet/network/http_server/mongrel.rb b/lib/puppet/network/http_server/mongrel.rb
new file mode 100644
index 0000000..ce0401a
--- /dev/null
+++ b/lib/puppet/network/http_server/mongrel.rb
@@ -0,0 +1,129 @@
+#!/usr/bin/env ruby
+# File: 06-11-14-mongrel_xmlrpc.rb
+# Author: Manuel Holtgrewe <purestorm at ggnore.net>
+#
+# Copyright (c) 2006 Manuel Holtgrewe, 2007 Luke Kanies
+#
+# This file is based heavily on a file retrieved from
+# http://ttt.ggnore.net/2006/11/15/xmlrpc-with-mongrel-and-ruby-off-rails/
+
+require 'rubygems'
+require 'mongrel'
+require 'xmlrpc/server'
+require 'puppet/network/xmlrpc/server'
+require 'puppet/network/http_server'
+require 'puppet/network/client_request'
+require 'puppet/network/handler'
+
+require 'resolv'
+
+# This handler can be hooked into Mongrel to accept HTTP requests. After
+# checking whether the request itself is sane, the handler forwards it
+# to an internal instance of XMLRPC::BasicServer to process it.
+#
+# You can access the server by calling the Handler's "xmlrpc_server"
+# attribute accessor method and add XMLRPC handlers there. For example:
+#
+# <pre>
+# handler = XmlRpcHandler.new
+# handler.xmlrpc_server.add_handler("my.add") { |a, b| a.to_i + b.to_i }
+# </pre>
+module Puppet::Network
+ class HTTPServer::Mongrel < ::Mongrel::HttpHandler
+ attr_reader :xmlrpc_server
+
+ def initialize(handlers)
+ if Puppet[:debug]
+ $mongrel_debug_client = true
+ Puppet.debug 'Mongrel client debugging enabled. [$mongrel_debug_client = true].'
+ end
+ # Create a new instance of BasicServer. We are supposed to subclass it
+ # but that does not make sense since we would not introduce any new
+ # behaviour and we have to subclass Mongrel::HttpHandler so our handler
+ # works for Mongrel.
+ @xmlrpc_server = Puppet::Network::XMLRPCServer.new
+ handlers.each do |name|
+ unless handler = Puppet::Network::Handler.handler(name)
+ raise ArgumentError, "Invalid handler #{name}"
+ end
+ @xmlrpc_server.add_handler(handler.interface, handler.new({}))
+ end
+ end
+
+ # This method produces the same results as XMLRPC::CGIServer.serve
+ # from Ruby's stdlib XMLRPC implementation.
+ def process(request, response)
+ # Make sure this has been a POST as required for XMLRPC.
+ request_method = request.params[Mongrel::Const::REQUEST_METHOD] || Mongrel::Const::GET
+ if request_method != "POST"
+ response.start(405) { |head, out| out.write("Method Not Allowed") }
+ return
+ end
+
+ # Make sure the user has sent text/xml data.
+ request_mime = request.params["CONTENT_TYPE"] || "text/plain"
+ if parse_content_type(request_mime).first != "text/xml"
+ response.start(400) { |head, out| out.write("Bad Request") }
+ return
+ end
+
+ # Make sure there is data in the body at all.
+ length = request.params[Mongrel::Const::CONTENT_LENGTH].to_i
+ if length <= 0
+ response.start(411) { |head, out| out.write("Length Required") }
+ return
+ end
+
+ # Check the body to be valid.
+ if request.body.nil? or request.body.size != length
+ response.start(400) { |head, out| out.write("Bad Request") }
+ return
+ end
+
+ info = client_info(request)
+
+ # All checks above passed through
+ response.start(200) do |head, out|
+ head["Content-Type"] = "text/xml; charset=utf-8"
+ begin
+ out.write(@xmlrpc_server.process(request.body, info))
+ rescue => detail
+ puts detail.backtrace
+ raise
+ end
+ end
+ end
+
+ private
+
+ def client_info(request)
+ params = request.params
+ ip = params["HTTP_X_FORWARDED_FOR"] ? params["HTTP_X_FORWARDED_FOR"].split(',').last.strip : params["REMOTE_ADDR"]
+ # JJM #906 The following dn.match regular expression is forgiving
+ # enough to match the two Distinguished Name string contents
+ # coming from Apache, Pound or other reverse SSL proxies.
+ if dn = params[Puppet[:ssl_client_header]] and dn_matchdata = dn.match(/^.*?CN\s*=\s*(.*)/)
+ client = dn_matchdata[1].to_str
+ valid = (params[Puppet[:ssl_client_verify_header]] == 'SUCCESS')
+ else
+ begin
+ client = Resolv.getname(ip)
+ rescue => detail
+ Puppet.err "Could not resolve #{ip}: #{detail}"
+ client = "unknown"
+ end
+ valid = false
+ end
+
+ info = Puppet::Network::ClientRequest.new(client, ip, valid)
+
+ info
+ end
+
+ # Taken from XMLRPC::ParseContentType
+ def parse_content_type(str)
+ a, *b = str.split(";")
+ return a.strip, *b
+ end
+ end
+end
--
1.7.7

@ -1,273 +0,0 @@
From 743e03930758d17ed35fc6b73f7c2c68d8212137 Mon Sep 17 00:00:00 2001
From: Nick Lewis <nick@puppetlabs.com>
Date: Mon, 28 Feb 2011 13:40:18 -0800
Subject: [PATCH/puppet] (#4922) Don't truncate remotely-sourced files on 404
We were 'handling' 404's on remote file content retrieval by returning nil
rather than raising an exception. This caused no content to be written to the
temporary file, but still appeared successful, so the destination file was
overwritten, instead of preserved. Now we just handle 404 like any other
error.
Note that the root cause of these 404s seems to have been #4319, which has been
fixed. However, in the event we do happen to get a 404 here, it's better not to
have code to specifically handle it incorrectly.
Paired-With: Max Martin
Reviewed-By: Matt Robinson
---
lib/puppet/type/file/content.rb | 1 -
spec/unit/type/file/content_spec.rb | 175 ++++++++---------------------------
2 files changed, 38 insertions(+), 138 deletions(-)
diff --git a/lib/puppet/type/file/content.rb b/lib/puppet/type/file/content.rb
index 63c0aaf..1b36acb 100755
--- a/lib/puppet/type/file/content.rb
+++ b/lib/puppet/type/file/content.rb
@@ -194,7 +194,6 @@ module Puppet
connection = Puppet::Network::HttpPool.http_instance(source_or_content.server, source_or_content.port)
connection.request_get(indirection2uri(request), add_accept_encoding({"Accept" => "raw"})) do |response|
case response.code
- when "404"; nil
when /^2/; uncompress(response) { |uncompressor| response.read_body { |chunk| yield uncompressor.uncompress(chunk) } }
else
# Raise the http error if we didn't get a 'success' of some kind.
diff --git a/spec/unit/type/file/content_spec.rb b/spec/unit/type/file/content_spec.rb
index 9178c94..7d23399 100755
--- a/spec/unit/type/file/content_spec.rb
+++ b/spec/unit/type/file/content_spec.rb
@@ -4,15 +4,14 @@ Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f
content = Puppet::Type.type(:file).attrclass(:content)
describe content do
+ include PuppetSpec::Files
before do
- @resource = Puppet::Type.type(:file).new :path => "/foo/bar"
+ @filename = tmpfile('testfile')
+ @resource = Puppet::Type.type(:file).new :path => @filename
+ File.open(@filename, 'w') {|f| f.write "initial file content"}
content.stubs(:standalone?).returns(false)
end
- it "should be a subclass of Property" do
- content.superclass.must == Puppet::Property
- end
-
describe "when determining the checksum type" do
it "should use the type specified in the source checksum if a source is set" do
@resource[:source] = "/foo"
@@ -249,10 +248,10 @@ describe content do
describe "when writing" do
before do
@content = content.new(:resource => @resource)
- @fh = stub_everything
end
it "should attempt to read from the filebucket if no actual content nor source exists" do
+ @fh = File.open(@filename, 'w')
@content.should = "{md5}foo"
@content.resource.bucket.class.any_instance.stubs(:getfile).returns "foo"
@content.write(@fh)
@@ -302,166 +301,68 @@ describe content do
describe "from local source" do
before(:each) do
- @content.stubs(:actual_content).returns(nil)
- @source = stub_everything 'source', :local? => true, :full_path => "/path/to/source"
- @resource.stubs(:parameter).with(:source).returns @source
-
- @sum = stub_everything 'sum'
- @resource.stubs(:parameter).with(:checksum).returns(@sum)
-
- @digest = stub_everything 'digest'
- @sum.stubs(:sum_stream).yields(@digest)
-
- @file = stub_everything 'file'
- File.stubs(:open).yields(@file)
- @file.stubs(:read).with(8192).returns("chunk1").then.returns("chunk2").then.returns(nil)
- end
-
- it "should open the local file" do
- File.expects(:open).with("/path/to/source", "r")
- @content.write(@fh)
- end
+ @resource = Puppet::Type.type(:file).new :path => @filename, :backup => false
+ @sourcename = tmpfile('source')
+ @source_content = "source file content"*10000
+ @sourcefile = File.open(@sourcename, 'w') {|f| f.write @source_content}
- it "should read the local file by chunks" do
- @file.expects(:read).with(8192).returns("chunk1").then.returns(nil)
- @content.write(@fh)
+ @content = @resource.newattr(:content)
+ @source = @resource.newattr(:source)
+ @source.stubs(:metadata).returns stub_everything('metadata', :source => @sourcename, :ftype => 'file')
end
- it "should write each chunk to the file" do
- @fh.expects(:print).with("chunk1").then.with("chunk2")
- @content.write(@fh)
- end
-
- it "should pass each chunk to the current sum stream" do
- @digest.expects(:<<).with("chunk1").then.with("chunk2")
- @content.write(@fh)
+ it "should copy content from the source to the file" do
+ @resource.write(@source)
+ File.read(@filename).should == @source_content
end
it "should return the checksum computed" do
- @sum.stubs(:sum_stream).yields(@digest).returns("checksum")
- @content.write(@fh).should == "checksum"
+ File.open(@filename, 'w') do |file|
+ @content.write(file).should == "{md5}#{Digest::MD5.hexdigest(@source_content)}"
+ end
end
end
describe "from remote source" do
before(:each) do
- @response = stub_everything 'mock response', :code => "404"
+ @resource = Puppet::Type.type(:file).new :path => @filename, :backup => false
+ @response = stub_everything 'response', :code => "200"
+ @source_content = "source file content"*10000
+ @response.stubs(:read_body).multiple_yields(*(["source file content"]*10000))
+
@conn = stub_everything 'connection'
@conn.stubs(:request_get).yields(@response)
Puppet::Network::HttpPool.stubs(:http_instance).returns @conn
- @content.stubs(:actual_content).returns(nil)
- @source = stub_everything 'source', :local? => false, :full_path => "/path/to/source", :server => "server", :port => 1234
- @resource.stubs(:parameter).with(:source).returns @source
-
- @sum = stub_everything 'sum'
- @resource.stubs(:parameter).with(:checksum).returns(@sum)
-
- @digest = stub_everything 'digest'
- @sum.stubs(:sum_stream).yields(@digest)
- end
-
- it "should open a network connection to source server and port" do
- Puppet::Network::HttpPool.expects(:http_instance).with("server", 1234).returns @conn
- @content.write(@fh)
- end
-
- it "should send the correct indirection uri" do
- @conn.expects(:request_get).with { |uri,headers| uri == "/production/file_content/path/to/source" }.yields(@response)
- @content.write(@fh)
+ @content = @resource.newattr(:content)
+ @sourcename = "puppet:///test/foo"
+ @source = @resource.newattr(:source)
+ @source.stubs(:metadata).returns stub_everything('metadata', :source => @sourcename, :ftype => 'file')
end
- it "should return nil if source is not found" do
- @response.expects(:code).returns("404")
- @content.write(@fh).should == nil
+ it "should write the contents to the file" do
+ @resource.write(@source)
+ File.read(@filename).should == @source_content
end
it "should not write anything if source is not found" do
- @response.expects(:code).returns("404")
- @fh.expects(:print).never
- @content.write(@fh).should == nil
+ @response.stubs(:code).returns("404")
+ lambda {@resource.write(@source)}.should raise_error(Net::HTTPError) { |e| e.message =~ /404/ }
+ File.read(@filename).should == "initial file content"
end
it "should raise an HTTP error in case of server error" do
- @response.expects(:code).returns("500")
- lambda { @content.write(@fh) }.should raise_error
- end
-
- it "should write content by chunks" do
- @response.expects(:code).returns("200")
- @response.expects(:read_body).multiple_yields("chunk1","chunk2")
- @fh.expects(:print).with("chunk1").then.with("chunk2")
- @content.write(@fh)
- end
-
- it "should pass each chunk to the current sum stream" do
- @response.expects(:code).returns("200")
- @response.expects(:read_body).multiple_yields("chunk1","chunk2")
- @digest.expects(:<<).with("chunk1").then.with("chunk2")
- @content.write(@fh)
+ @response.stubs(:code).returns("500")
+ lambda { @content.write(@fh) }.should raise_error { |e| e.message.include? @source_content }
end
it "should return the checksum computed" do
- @response.expects(:code).returns("200")
- @response.expects(:read_body).multiple_yields("chunk1","chunk2")
- @sum.expects(:sum_stream).yields(@digest).returns("checksum")
- @content.write(@fh).should == "checksum"
- end
-
- it "should get the current accept encoding header value" do
- @content.expects(:add_accept_encoding)
- @content.write(@fh)
- end
-
- it "should uncompress body on error" do
- @response.expects(:code).returns("500")
- @response.expects(:body).returns("compressed body")
- @content.expects(:uncompress_body).with(@response).returns("uncompressed")
- lambda { @content.write(@fh) }.should raise_error { |e| e.message =~ /uncompressed/ }
- end
-
- it "should uncompress chunk by chunk" do
- uncompressor = stub_everything 'uncompressor'
- @content.expects(:uncompress).with(@response).yields(uncompressor)
- @response.expects(:code).returns("200")
- @response.expects(:read_body).multiple_yields("chunk1","chunk2")
-
- uncompressor.expects(:uncompress).with("chunk1").then.with("chunk2")
- @content.write(@fh)
- end
-
- it "should write uncompressed chunks to the file" do
- uncompressor = stub_everything 'uncompressor'
- @content.expects(:uncompress).with(@response).yields(uncompressor)
- @response.expects(:code).returns("200")
- @response.expects(:read_body).multiple_yields("chunk1","chunk2")
-
- uncompressor.expects(:uncompress).with("chunk1").returns("uncompressed1")
- uncompressor.expects(:uncompress).with("chunk2").returns("uncompressed2")
-
- @fh.expects(:print).with("uncompressed1")
- @fh.expects(:print).with("uncompressed2")
-
- @content.write(@fh)
- end
-
- it "should pass each uncompressed chunk to the current sum stream" do
- uncompressor = stub_everything 'uncompressor'
- @content.expects(:uncompress).with(@response).yields(uncompressor)
- @response.expects(:code).returns("200")
- @response.expects(:read_body).multiple_yields("chunk1","chunk2")
-
- uncompressor.expects(:uncompress).with("chunk1").returns("uncompressed1")
- uncompressor.expects(:uncompress).with("chunk2").returns("uncompressed2")
-
- @digest.expects(:<<).with("uncompressed1").then.with("uncompressed2")
- @content.write(@fh)
+ File.open(@filename, 'w') do |file|
+ @content.write(file).should == "{md5}#{Digest::MD5.hexdigest(@source_content)}"
+ end
end
end
- describe "from a filebucket" do
- end
-
# These are testing the implementation rather than the desired behaviour; while that bites, there are a whole
# pile of other methods in the File type that depend on intimate details of this implementation and vice-versa.
# If these blow up, you are gonna have to review the callers to make sure they don't explode! --daniel 2011-02-01
--
1.7.4.1

@ -1,123 +0,0 @@
From 852fb9744320c253772c85e52b262b0290fb7dd4 Mon Sep 17 00:00:00 2001
From: Matt Robinson <matt@puppetlabs.com>
Date: Tue, 15 Mar 2011 16:13:15 -0700
Subject: [PATCH/puppet] (#5073) Download plugins even if you're filtering on tags
When we eval a resource in transaction.rb it was being skipped when
filtering on tags and downloading the plugins. There's a lot of
complicated conditions for whether to skip a resource, but this is a
condensed version of the path that was causing plugins not to be
downloaded.
skip?
missing_tags?
!ignore_tags?
!host_config
The Puppet::Configurer::Downloader creates separate catalogs and applies them
to get custom facts and plugins, so should be setting host_config to false.
Puppet::Util::Settings also sets host_config to false when you call use on
settings, while normal catalog application defaults to true.
Thanks to Stefan Schulte <stefan.schulte@taunusstein.net> for suggesting
the implementation fix.
---
lib/puppet/configurer/downloader.rb | 1 +
lib/puppet/configurer/plugin_handler.rb | 9 +++++++-
spec/unit/configurer/downloader_spec.rb | 32 +++++++++++++++++++++---------
3 files changed, 31 insertions(+), 11 deletions(-)
diff --git a/lib/puppet/configurer/downloader.rb b/lib/puppet/configurer/downloader.rb
index 1b587ed..b369620 100644
--- a/lib/puppet/configurer/downloader.rb
+++ b/lib/puppet/configurer/downloader.rb
@@ -50,6 +50,7 @@ class Puppet::Configurer::Downloader
def catalog
catalog = Puppet::Resource::Catalog.new
+ catalog.host_config = false
catalog.add_resource(file)
catalog
end
diff --git a/lib/puppet/configurer/plugin_handler.rb b/lib/puppet/configurer/plugin_handler.rb
index cfc6b5a..ae088f2 100644
--- a/lib/puppet/configurer/plugin_handler.rb
+++ b/lib/puppet/configurer/plugin_handler.rb
@@ -9,7 +9,14 @@ module Puppet::Configurer::PluginHandler
# Retrieve facts from the central server.
def download_plugins
return nil unless download_plugins?
- Puppet::Configurer::Downloader.new("plugin", Puppet[:plugindest], Puppet[:pluginsource], Puppet[:pluginsignore]).evaluate.each { |file| load_plugin(file) }
+ plugin_downloader = Puppet::Configurer::Downloader.new(
+ "plugin",
+ Puppet[:plugindest],
+ Puppet[:pluginsource],
+ Puppet[:pluginsignore]
+ )
+
+ plugin_downloader.evaluate.each { |file| load_plugin(file) }
end
def load_plugin(file)
diff --git a/spec/unit/configurer/downloader_spec.rb b/spec/unit/configurer/downloader_spec.rb
index c57f39f..4080263 100755
--- a/spec/unit/configurer/downloader_spec.rb
+++ b/spec/unit/configurer/downloader_spec.rb
@@ -5,6 +5,8 @@ require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet/configurer/downloader'
describe Puppet::Configurer::Downloader do
+ require 'puppet_spec/files'
+ include PuppetSpec::Files
it "should require a name" do
lambda { Puppet::Configurer::Downloader.new }.should raise_error(ArgumentError)
end
@@ -96,25 +98,35 @@ describe Puppet::Configurer::Downloader do
describe "when creating the catalog to do the downloading" do
before do
- @dler = Puppet::Configurer::Downloader.new("foo", "path", "source")
+ @dler = Puppet::Configurer::Downloader.new("foo", "/download/path", "source")
end
it "should create a catalog and add the file to it" do
- file = mock 'file'
- catalog = mock 'catalog'
-
- @dler.expects(:file).returns file
-
- Puppet::Resource::Catalog.expects(:new).returns catalog
- catalog.expects(:add_resource).with(file)
+ catalog = @dler.catalog
+ catalog.resources.size.should == 1
+ catalog.resources.first.class.should == Puppet::Type::File
+ catalog.resources.first.name.should == "/download/path"
+ end
- @dler.catalog.should equal(catalog)
+ it "should specify that it is not managing a host catalog" do
+ @dler.catalog.host_config.should == false
end
+
end
describe "when downloading" do
before do
- @dler = Puppet::Configurer::Downloader.new("foo", "path", "source")
+ @dl_name = tmpfile("downloadpath")
+ source_name = tmpfile("source")
+ File.open(source_name, 'w') {|f| f.write('hola mundo') }
+ @dler = Puppet::Configurer::Downloader.new("foo", @dl_name, source_name)
+ end
+
+ it "should not skip downloaded resources when filtering on tags" do
+ Puppet[:tags] = 'maytag'
+ @dler.evaluate
+
+ File.exists?(@dl_name).should be_true
end
it "should log that it is downloading" do
--
1.7.4.1

@ -1,182 +0,0 @@
From ff9e2425a58bb2b1ab836e440c3344b4012623c5 Mon Sep 17 00:00:00 2001
From: Jacob Helwig <jacob@puppetlabs.com>
Date: Fri, 25 Feb 2011 17:03:56 -0800
Subject: [PATCH/puppet] (#5428) More fully "stub" Puppet::Resource::Reference for use with storedconfigs
The Puppet::Resource::Reference class wasn't stubbing enough of the 0.25.x
behavior to satisfy the needs of storedconfigs. Since P::R::Reference,
and Puppet::Resource were merged as part of 2.6.x, we can pretend that
P::Resource is P::R::Reference for the purposes of loading data from
storedconfigs. This should still satisfy the over-the-wire serialization
needs of 0.25.x.
This also changes internal references to @parameters in
Puppet::Resource(::Reference) to go through a parameters method. This
allows us to "initialize" this instance variable lazily, since loading via
YAML bypasses the normal initialize method.
Paired-with: Daniel Pittman <daniel@puppetlabs.com>
Reviewed-by: Markus Roberts <markus@puppetlabs.com>
---
lib/puppet/resource.rb | 35 +++++++++++++++++++----------------
spec/unit/resource_spec.rb | 26 ++++++++++++++++++++++++--
2 files changed, 43 insertions(+), 18 deletions(-)
diff --git a/lib/puppet/resource.rb b/lib/puppet/resource.rb
index e832804..e47fc7e 100644
--- a/lib/puppet/resource.rb
+++ b/lib/puppet/resource.rb
@@ -5,6 +5,11 @@ require 'puppet/util/pson'
# The simplest resource class. Eventually it will function as the
# base class for all resource-like behaviour.
class Puppet::Resource
+ # This stub class is only needed for serialization compatibility with 0.25.x.
+ # Specifically, it exists to provide a compatibility API when using YAML
+ # serialized objects loaded from StoreConfigs.
+ Reference = Puppet::Resource
+
include Puppet::Util::Tagging
require 'puppet/resource/type_collection_helper'
@@ -104,7 +109,7 @@ class Puppet::Resource
# be overridden at some point, but this works for now.
%w{has_key? keys length delete empty? <<}.each do |method|
define_method(method) do |*args|
- @parameters.send(method, *args)
+ parameters.send(method, *args)
end
end
@@ -112,13 +117,13 @@ class Puppet::Resource
# to lower-case symbols.
def []=(param, value)
validate_parameter(param) if validate_parameters
- @parameters[parameter_name(param)] = value
+ parameters[parameter_name(param)] = value
end
# Return a given parameter's value. Converts all passed names
# to lower-case symbols.
def [](param)
- @parameters[parameter_name(param)]
+ parameters[parameter_name(param)]
end
def ==(other)
@@ -140,11 +145,11 @@ class Puppet::Resource
# Iterate over each param/value pair, as required for Enumerable.
def each
- @parameters.each { |p,v| yield p, v }
+ parameters.each { |p,v| yield p, v }
end
def include?(parameter)
- super || @parameters.keys.include?( parameter_name(parameter) )
+ super || parameters.keys.include?( parameter_name(parameter) )
end
# These two methods are extracted into a Helper
@@ -170,14 +175,6 @@ class Puppet::Resource
end
end
- # This stub class is only needed for serialization compatibility with 0.25.x
- class Reference
- attr_accessor :type,:title
- def initialize(type,title)
- @type,@title = type,title
- end
- end
-
# Create our resource.
def initialize(type, title = nil, attributes = {})
@parameters = {}
@@ -204,7 +201,7 @@ class Puppet::Resource
tag(self.type)
tag(self.title) if valid_tag?(self.title)
- @reference = Reference.new(@type,@title) # for serialization compatibility with 0.25.x
+ @reference = self # for serialization compatibility with 0.25.x
if strict? and ! resource_type
if @type == 'Class'
raise ArgumentError, "Could not find declared class #{title}"
@@ -234,7 +231,7 @@ class Puppet::Resource
# Produce a simple hash of our parameters.
def to_hash
- parse_title.merge @parameters
+ parse_title.merge parameters
end
def to_s
@@ -256,7 +253,7 @@ class Puppet::Resource
# Convert our resource to Puppet code.
def to_manifest
"%s { '%s':\n%s\n}" % [self.type.to_s.downcase, self.title,
- @parameters.collect { |p, v|
+ parameters.collect { |p, v|
if v.is_a? Array
" #{p} => [\'#{v.join("','")}\']"
else
@@ -422,4 +419,10 @@ class Puppet::Resource
return { :name => title.to_s }
end
end
+
+ def parameters
+ # @parameters could have been loaded from YAML, causing it to be nil (by
+ # bypassing initialize).
+ @parameters ||= {}
+ end
end
diff --git a/spec/unit/resource_spec.rb b/spec/unit/resource_spec.rb
index ff31b24..4c1dc49 100755
--- a/spec/unit/resource_spec.rb
+++ b/spec/unit/resource_spec.rb
@@ -99,11 +99,11 @@ describe Puppet::Resource do
end
it 'should fail if strict is set and type does not exist' do
- lambda { Puppet::Resource.new('foo', 'title', {:strict=>true}) }.should raise_error(ArgumentError, 'Invalid resource type foo')
+ lambda { Puppet::Resource.new('foo', 'title', {:strict=>true}) }.should raise_error(ArgumentError, 'Invalid resource type foo')
end
it 'should fail if strict is set and class does not exist' do
- lambda { Puppet::Resource.new('Class', 'foo', {:strict=>true}) }.should raise_error(ArgumentError, 'Could not find declared class foo')
+ lambda { Puppet::Resource.new('Class', 'foo', {:strict=>true}) }.should raise_error(ArgumentError, 'Could not find declared class foo')
end
it "should fail if the title is a hash and the type is not a valid resource reference string" do
@@ -463,6 +463,28 @@ describe Puppet::Resource do
end
end
+ describe "when loading 0.25.x storedconfigs YAML" do
+ before :each do
+ @old_storedconfig_yaml = %q{--- !ruby/object:Puppet::Resource::Reference
+builtin_type:
+title: /tmp/bar
+type: File
+}
+ end
+
+ it "should deserialize a Puppet::Resource::Reference without exceptions" do
+ lambda { YAML.load(@old_storedconfig_yaml) }.should_not raise_error
+ end
+
+ it "should deserialize as a Puppet::Resource::Reference as a Puppet::Resource" do
+ YAML.load(@old_storedconfig_yaml).class.should == Puppet::Resource
+ end
+
+ it "should to_hash properly" do
+ YAML.load(@old_storedconfig_yaml).to_hash.should == { :path => "/tmp/bar" }
+ end
+ end
+
describe "when converting to a RAL resource" do
it "should use the resource type's :new method to create the resource if the resource is of a builtin type" do
resource = Puppet::Resource.new("file", @basepath+"/my/file")
--
1.7.4.1

@ -1,138 +0,0 @@
From 0a92a70a22b7e85ef60ed9b4d4070433b5ec3220 Mon Sep 17 00:00:00 2001
From: Daniel Pittman <daniel@puppetlabs.com>
Date: Sat, 24 Sep 2011 12:44:20 -0700
Subject: [PATCH] Resist directory traversal attacks through indirections.
In various versions of Puppet it was possible to cause a directory traversal
attack through the SSLFile indirection base class. This was variously
triggered through the user-supplied key, or the Subject of the certificate, in
the code.
Now, we detect bad patterns down in the base class for our indirections, and
fail hard on them. This reduces the attack surface with as little disruption
to the overall codebase as possible, making it suitable to deploy as part of
older, stable versions of Puppet.
In the long term we will also address this higher up the stack, to prevent
these problems from reoccurring, but for now this will suffice.
Huge thanks to Kristian Erik Hermansen <kristian.hermansen@gmail.com> for the
responsible disclosure, and useful analysis, around this defect.
Signed-off-by: Daniel Pittman <daniel@puppetlabs.com>
---
lib/puppet/indirector.rb | 7 +++++++
lib/puppet/indirector/ssl_file.rb | 6 +++++-
lib/puppet/indirector/yaml.rb | 5 +++++
spec/unit/indirector/ssl_file_spec.rb | 19 +++++++++++++++++++
spec/unit/indirector/yaml_spec.rb | 14 ++++++++++++++
5 files changed, 50 insertions(+), 1 deletions(-)
diff --git a/lib/puppet/indirector.rb b/lib/puppet/indirector.rb
index e6472f4..fd6bf30 100644
--- a/lib/puppet/indirector.rb
+++ b/lib/puppet/indirector.rb
@@ -68,4 +68,11 @@ module Puppet::Indirector
self.class.indirection.save key, self
end
end
+
+
+ # Helper definition for indirections that handle filenames.
+ BadNameRegexp = Regexp.union(/^\.\./,
+ %r{[\\/]},
+ "\0",
+ /(?i)^[a-z]:/)
end
diff --git a/lib/puppet/indirector/ssl_file.rb b/lib/puppet/indirector/ssl_file.rb
index 531180f..4510499 100644
--- a/lib/puppet/indirector/ssl_file.rb
+++ b/lib/puppet/indirector/ssl_file.rb
@@ -52,8 +52,12 @@ class Puppet::Indirector::SslFile < Puppet::Indirector::Terminus
(collection_directory || file_location) or raise Puppet::DevError, "No file or directory setting provided; terminus #{self.class.name} cannot function"
end
- # Use a setting to determine our path.
def path(name)
+ if name =~ Puppet::Indirector::BadNameRegexp then
+ Puppet.crit("directory traversal detected in #{self.class}: #{name.inspect}")
+ raise ArgumentError, "invalid key"
+ end
+
if ca?(name) and ca_location
ca_location
elsif collection_directory
diff --git a/lib/puppet/indirector/yaml.rb b/lib/puppet/indirector/yaml.rb
index 23997e9..4c488da 100644
--- a/lib/puppet/indirector/yaml.rb
+++ b/lib/puppet/indirector/yaml.rb
@@ -43,6 +43,11 @@ class Puppet::Indirector::Yaml < Puppet::Indirector::Terminus
# Return the path to a given node's file.
def path(name,ext='.yaml')
+ if name =~ Puppet::Indirector::BadNameRegexp then
+ Puppet.crit("directory traversal detected in #{self.class}: #{name.inspect}")
+ raise ArgumentError, "invalid key"
+ end
+
base = Puppet.run_mode.master? ? Puppet[:yamldir] : Puppet[:clientyamldir]
File.join(base, self.class.indirection_name.to_s, name.to_s + ext)
end
diff --git a/spec/unit/indirector/ssl_file_spec.rb b/spec/unit/indirector/ssl_file_spec.rb
index 37098a7..4760bd7 100755
--- a/spec/unit/indirector/ssl_file_spec.rb
+++ b/spec/unit/indirector/ssl_file_spec.rb
@@ -87,6 +87,25 @@ describe Puppet::Indirector::SslFile do
it "should set them in the setting directory, with the certificate name plus '.pem', if a directory setting is available" do
@searcher.path(@cert.name).should == @certpath
end
+
+ ['../foo', '..\\foo', './../foo', '.\\..\\foo',
+ '/foo', '//foo', '\\foo', '\\\\goo',
+ "test\0/../bar", "test\0\\..\\bar",
+ "..\\/bar", "/tmp/bar", "/tmp\\bar", "tmp\\bar",
+ " / bar", " /../ bar", " \\..\\ bar",
+ "c:\\foo", "c:/foo", "\\\\?\\UNC\\bar", "\\\\foo\\bar",
+ "\\\\?\\c:\\foo", "//?/UNC/bar", "//foo/bar",
+ "//?/c:/foo",
+ ].each do |input|
+ it "should resist directory traversal attacks (#{input.inspect})" do
+ expect { @searcher.path(input) }.to raise_error
+ end
+ end
+
+ # REVISIT: Should probably test MS-DOS reserved names here, too, since
+ # they would represent a vulnerability on a Win32 system, should we ever
+ # support that path. Don't forget that 'CON.foo' == 'CON'
+ # --daniel 2011-09-24
end
describe "when finding certificates on disk" do
diff --git a/spec/unit/indirector/yaml_spec.rb b/spec/unit/indirector/yaml_spec.rb
index 86c13c5..c8fadf7 100755
--- a/spec/unit/indirector/yaml_spec.rb
+++ b/spec/unit/indirector/yaml_spec.rb
@@ -63,6 +63,20 @@ describe Puppet::Indirector::Yaml, " when choosing file location" do
it "should use the object's name to determine the file name" do
@store.path(:me).should =~ %r{me.yaml$}
end
+
+ ['../foo', '..\\foo', './../foo', '.\\..\\foo',
+ '/foo', '//foo', '\\foo', '\\\\goo',
+ "test\0/../bar", "test\0\\..\\bar",
+ "..\\/bar", "/tmp/bar", "/tmp\\bar", "tmp\\bar",
+ " / bar", " /../ bar", " \\..\\ bar",
+ "c:\\foo", "c:/foo", "\\\\?\\UNC\\bar", "\\\\foo\\bar",
+ "\\\\?\\c:\\foo", "//?/UNC/bar", "//foo/bar",
+ "//?/c:/foo",
+ ].each do |input|
+ it "should resist directory traversal attacks (#{input.inspect})" do
+ expect { @store.path(input) }.to raise_error
+ end
+ end
end
describe Puppet::Indirector::Yaml, " when storing objects as YAML" do
--
1.7.4.4

@ -1,107 +0,0 @@
From 8d9575775737c08c6cbfdf7f9a22f2ea4ab21b20 Mon Sep 17 00:00:00 2001
From: Ricky Zhou <ricky@fedoraproject.org>
Date: Mon, 29 Aug 2011 16:01:12 -0400
Subject: [PATCH] Drop privileges before creating and chmodding SSH keys.
Previously, potentially abusable chown and chmod calls were performed as
root. This tries to moves as much as possible into code which is run
after privileges have been dropped.
Huge thanks to Ricky Zhou <ricky@fedoraproject.org> for discovering this and
supplying the security fix. Awesome work.
Signed-off-by: Daniel Pittman <daniel@puppetlabs.com>
---
lib/puppet/provider/ssh_authorized_key/parsed.rb | 19 ++++++++++---------
.../provider/ssh_authorized_key/parsed_spec.rb | 16 ++++++++--------
2 files changed, 18 insertions(+), 17 deletions(-)
diff --git a/lib/puppet/provider/ssh_authorized_key/parsed.rb b/lib/puppet/provider/ssh_authorized_key/parsed.rb
index 6a3855c..5243477 100644
--- a/lib/puppet/provider/ssh_authorized_key/parsed.rb
+++ b/lib/puppet/provider/ssh_authorized_key/parsed.rb
@@ -56,21 +56,22 @@ require 'puppet/provider/parsedfile'
def flush
raise Puppet::Error, "Cannot write SSH authorized keys without user" unless @resource.should(:user)
raise Puppet::Error, "User '#{@resource.should(:user)}' does not exist" unless uid = Puppet::Util.uid(@resource.should(:user))
- unless File.exist?(dir = File.dirname(target))
- Puppet.debug "Creating #{dir}"
- Dir.mkdir(dir, dir_perm)
- File.chown(uid, nil, dir)
- end
-
# ParsedFile usually calls backup_target much later in the flush process,
# but our SUID makes that fail to open filebucket files for writing.
# Fortunately, there's already logic to make sure it only ever happens once,
# so calling it here supresses the later attempt by our superclass's flush method.
self.class.backup_target(target)
- Puppet::Util::SUIDManager.asuser(@resource.should(:user)) { super }
- File.chown(uid, nil, target)
- File.chmod(file_perm, target)
+ Puppet::Util::SUIDManager.asuser(@resource.should(:user)) do
+ unless File.exist?(dir = File.dirname(target))
+ Puppet.debug "Creating #{dir}"
+ Dir.mkdir(dir, dir_perm)
+ end
+
+ super
+
+ File.chmod(file_perm, target)
+ end
end
# parse sshv2 option strings, wich is a comma separated list of
diff --git a/spec/unit/provider/ssh_authorized_key/parsed_spec.rb b/spec/unit/provider/ssh_authorized_key/parsed_spec.rb
index 2e5be16..64935df 100755
--- a/spec/unit/provider/ssh_authorized_key/parsed_spec.rb
+++ b/spec/unit/provider/ssh_authorized_key/parsed_spec.rb
@@ -133,15 +133,15 @@ describe provider_class do
@provider.flush
end
- it "should chown the directory to the user" do
+ it "should absolutely not chown the directory to the user" do
uid = Puppet::Util.uid("random_bob")
- File.expects(:chown).with(uid, nil, "/tmp/.ssh_dir")
+ File.expects(:chown).never
@provider.flush
end
- it "should chown the key file to the user" do
+ it "should absolutely not chown the key file to the user" do
uid = Puppet::Util.uid("random_bob")
- File.expects(:chown).with(uid, nil, "/tmp/.ssh_dir/place_to_put_authorized_keys")
+ File.expects(:chown).never
@provider.flush
end
@@ -177,11 +177,11 @@ describe provider_class do
@provider.flush
end
- it "should chown the directory to the user if it creates it" do
+ it "should absolutely not chown the directory to the user if it creates it" do
File.stubs(:exist?).with(@dir).returns false
Dir.stubs(:mkdir).with(@dir,0700)
uid = Puppet::Util.uid("nobody")
- File.expects(:chown).with(uid, nil, @dir)
+ File.expects(:chown).never
@provider.flush
end
@@ -192,9 +192,9 @@ describe provider_class do
@provider.flush
end
- it "should chown the key file to the user" do
+ it "should absolutely not chown the key file to the user" do
uid = Puppet::Util.uid("nobody")
- File.expects(:chown).with(uid, nil, File.expand_path("~nobody/.ssh/authorized_keys"))
+ File.expects(:chown).never
@provider.flush
end
--
1.7.6.4

@ -1,69 +0,0 @@
From 906da37374def334b62722acf84e4b0d1324e1f7 Mon Sep 17 00:00:00 2001
From: Daniel Pittman <daniel@puppetlabs.com>
Date: Wed, 28 Sep 2011 23:35:19 -0700
Subject: [PATCH] (#9792) Predictable temporary filename in ralsh.
When ralsh is used in edit mode the temporary filename is in a shared
directory, and is absolutely predictable. Worse, it won't be touched until
well after the startup of the command.
It can be tricked into writing through a symlink to edit any file on the
system, or to create through it, but worse - the file is reopened with the
same name later, so it can have the target replaced between edit and
operate...
The only possible mitigation comes from the system editor and the behaviour it
has around editing through symbolic links, which is very weak.
This improves this to prefer the current working directory for the temporary
file, and to be somewhat less predictable and more safe in conjuring it into
being.
Signed-off-by: Daniel Pittman <daniel@puppetlabs.com>
---
lib/puppet/application/resource.rb | 27 +++++++++++++++++----------
1 files changed, 17 insertions(+), 10 deletions(-)
diff --git a/lib/puppet/application/resource.rb b/lib/puppet/application/resource.rb
index bc4faf5..3e4147e 100644
--- a/lib/puppet/application/resource.rb
+++ b/lib/puppet/application/resource.rb
@@ -88,18 +88,25 @@ class Puppet::Application::Resource < Puppet::Application
end.map(&format).join("\n")
if options[:edit]
- file = "/tmp/x2puppet-#{Process.pid}.pp"
+ require 'tempfile'
+ # Prefer the current directory, which is more likely to be secure
+ # and, in the case of interactive use, accessible to the user.
+ tmpfile = Tempfile.new('x2puppet', Dir.pwd)
begin
- File.open(file, "w") do |f|
- f.puts text
- end
- ENV["EDITOR"] ||= "vi"
- system(ENV["EDITOR"], file)
- system("puppet -v #{file}")
+ # sync write, so nothing buffers before we invoke the editor.
+ tmpfile.sync = true
+ tmpfile.puts text
+
+ # edit the content
+ system(ENV["EDITOR"] || 'vi', tmpfile.path)
+
+ # ...and, now, pass that file to puppet to apply. Because
+ # many editors rename or replace the original file we need to
+ # feed the pathname, not the file content itself, to puppet.
+ system('puppet -v ' + tmpfile.path)
ensure
- #if FileTest.exists? file
- # File.unlink(file)
- #end
+ # The temporary file will be safely removed.
+ tmpfile.close(true)
end
else
puts text
--
1.7.6.4

@ -1,330 +0,0 @@
From 40f025a9ae0373e2e642f7811face3486bfa34d4 Mon Sep 17 00:00:00 2001
From: Daniel Pittman <daniel@puppetlabs.com>
Date: Thu, 29 Sep 2011 00:07:16 -0700
Subject: [PATCH] (#9793) "secure" indirector file backed terminus base class.
The file base class in the indirector trusted the request key directly, which
made it vulnerable to the same potential for injection attacks as other
terminus base classes.
However, this is somewhat mitigated by the fact that base class is entirely
unused. We can simple eliminate it from the system, because nothing is more
secure than code that doesn't exist.
The only consumer of the code was in the tests, and didn't care what base
class was used, so that was substituted with a continuing class.
Signed-off-by: Daniel Pittman <daniel@puppetlabs.com>
---
lib/puppet/indirector/file.rb | 79 --------------
spec/unit/indirector/file_spec.rb | 181 ---------------------------------
spec/unit/indirector/terminus_spec.rb | 6 +-
3 files changed, 3 insertions(+), 263 deletions(-)
delete mode 100644 lib/puppet/indirector/file.rb
delete mode 100755 spec/unit/indirector/file_spec.rb
diff --git a/lib/puppet/indirector/file.rb b/lib/puppet/indirector/file.rb
deleted file mode 100644
index b3746b7..0000000
--- a/lib/puppet/indirector/file.rb
+++ /dev/null
@@ -1,79 +0,0 @@
-require 'puppet/indirector/terminus'
-
-# Store instances as files, usually serialized using some format.
-class Puppet::Indirector::File < Puppet::Indirector::Terminus
- # Where do we store our data?
- def data_directory
- name = Puppet.run_mode.master? ? :server_datadir : :client_datadir
-
- File.join(Puppet.settings[name], self.class.indirection_name.to_s)
- end
-
- def file_format(path)
- path =~ /\.(\w+)$/ and return $1
- end
-
- def file_path(request)
- File.join(data_directory, request.key + ".#{serialization_format}")
- end
-
- def latest_path(request)
- files = Dir.glob(File.join(data_directory, request.key + ".*"))
- return nil if files.empty?
-
- # Return the newest file.
- files.sort { |a, b| File.stat(b).mtime <=> File.stat(a).mtime }[0]
- end
-
- def serialization_format
- model.default_format
- end
-
- # Remove files on disk.
- def destroy(request)
- begin
- removed = false
- Dir.glob(File.join(data_directory, request.key.to_s + ".*")).each do |file|
- removed = true
- File.unlink(file)
- end
- rescue => detail
- raise Puppet::Error, "Could not remove #{request.key}: #{detail}"
- end
-
- raise Puppet::Error, "Could not find files for #{request.key} to remove" unless removed
- end
-
- # Return a model instance for a given file on disk.
- def find(request)
- return nil unless path = latest_path(request)
- format = file_format(path)
-
- raise ArgumentError, "File format #{format} is not supported by #{self.class.indirection_name}" unless model.support_format?(format)
-
- begin
- return model.convert_from(format, File.read(path))
- rescue => detail
- raise Puppet::Error, "Could not convert path #{path} into a #{self.class.indirection_name}: #{detail}"
- end
- end
-
- # Save a new file to disk.
- def save(request)
- path = file_path(request)
-
- dir = File.dirname(path)
-
- raise Puppet::Error.new("Cannot save #{request.key}; parent directory #{dir} does not exist") unless File.directory?(dir)
-
- begin
- File.open(path, "w") { |f| f.print request.instance.render(serialization_format) }
- rescue => detail
- raise Puppet::Error, "Could not write #{request.key}: #{detail}" % [request.key, detail]
- end
- end
-
- def path(key)
- key
- end
-end
diff --git a/spec/unit/indirector/file_spec.rb b/spec/unit/indirector/file_spec.rb
deleted file mode 100755
index 86673f0..0000000
--- a/spec/unit/indirector/file_spec.rb
+++ /dev/null
@@ -1,181 +0,0 @@
-#!/usr/bin/env ruby
-
-require File.dirname(__FILE__) + '/../../spec_helper'
-require 'puppet/indirector/file'
-
-
-describe Puppet::Indirector::File do
- before :each do
- Puppet::Indirector::Terminus.stubs(:register_terminus_class)
- @model = mock 'model'
- @indirection = stub 'indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model
- Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection)
-
- @file_class = Class.new(Puppet::Indirector::File) do
- def self.to_s
- "Testing::Mytype"
- end
- end
-
- @searcher = @file_class.new
-
- @path = "/my/file"
- @dir = "/my"
-
- @request = stub 'request', :key => @path
- end
-
- describe "when finding files" do
- it "should provide a method to return file contents at a specified path" do
- @searcher.should respond_to(:find)
- end
-
- it "should use the server data directory plus the indirection name if the run_mode is master" do
- Puppet.run_mode.expects(:master?).returns true
- Puppet.settings.expects(:value).with(:server_datadir).returns "/my/dir"
-
- @searcher.data_directory.should == File.join("/my/dir", "mystuff")
- end
-
- it "should use the client data directory plus the indirection name if the run_mode is not master" do
- Puppet.run_mode.expects(:master?).returns false
- Puppet.settings.expects(:value).with(:client_datadir).returns "/my/dir"
-
- @searcher.data_directory.should == File.join("/my/dir", "mystuff")
- end
-
- it "should use the newest file in the data directory matching the indirection key without extension" do
- @searcher.expects(:data_directory).returns "/data/dir"
- @request.stubs(:key).returns "foo"
- Dir.expects(:glob).with("/data/dir/foo.*").returns %w{/data1.stuff /data2.stuff}
-
- stat1 = stub 'data1', :mtime => (Time.now - 5)
- stat2 = stub 'data2', :mtime => Time.now
- File.expects(:stat).with("/data1.stuff").returns stat1
- File.expects(:stat).with("/data2.stuff").returns stat2
-
- @searcher.latest_path(@request).should == "/data2.stuff"
- end
-
- it "should return nil when no files are found" do
- @searcher.stubs(:latest_path).returns nil
-
- @searcher.find(@request).should be_nil
- end
-
- it "should determine the file format from the file extension" do
- @searcher.file_format("/data2.pson").should == "pson"
- end
-
- it "should fail if the model does not support the file format" do
- @searcher.stubs(:latest_path).returns "/my/file.pson"
-
- @model.expects(:support_format?).with("pson").returns false
-
- lambda { @searcher.find(@request) }.should raise_error(ArgumentError)
- end
- end
-
- describe "when saving files" do
- before do
- @content = "my content"
- @file = stub 'file', :content => @content, :path => @path, :name => @path, :render => "mydata"
- @request.stubs(:instance).returns @file
- end
-
- it "should provide a method to save file contents at a specified path" do
- @searcher.should respond_to(:save)
- end
-
- it "should choose the file extension based on the default format of the model" do
- @model.expects(:default_format).returns "pson"
-
- @searcher.serialization_format.should == "pson"
- end
-
- it "should place the file in the data directory, named after the indirection, key, and format" do
- @searcher.stubs(:data_directory).returns "/my/dir"
- @searcher.stubs(:serialization_format).returns "pson"
-
- @request.stubs(:key).returns "foo"
- @searcher.file_path(@request).should == File.join("/my/dir", "foo.pson")
- end
-
- it "should fail intelligently if the file's parent directory does not exist" do
- @searcher.stubs(:file_path).returns "/my/dir/file.pson"
- @searcher.stubs(:serialization_format).returns "pson"
-
- @request.stubs(:key).returns "foo"
- File.expects(:directory?).with(File.join("/my/dir")).returns(false)
-
- proc { @searcher.save(@request) }.should raise_error(Puppet::Error)
- end
-
- it "should render the instance using the file format and print it to the file path" do
- @searcher.stubs(:file_path).returns "/my/file.pson"
- @searcher.stubs(:serialization_format).returns "pson"
-
- File.stubs(:directory?).returns true
-
- @request.instance.expects(:render).with("pson").returns "data"
-
- fh = mock 'filehandle'
- File.expects(:open).with("/my/file.pson", "w").yields fh
- fh.expects(:print).with("data")
-
- @searcher.save(@request)
- end
-
- it "should fail intelligently if a file cannot be written" do
- filehandle = mock 'file'
- File.stubs(:directory?).returns(true)
- File.stubs(:open).yields(filehandle)
- filehandle.expects(:print).raises(ArgumentError)
-
- @searcher.stubs(:file_path).returns "/my/file.pson"
- @model.stubs(:default_format).returns "pson"
-
- @instance.stubs(:render).returns "stuff"
-
- proc { @searcher.save(@request) }.should raise_error(Puppet::Error)
- end
- end
-
- describe "when removing files" do
- it "should provide a method to remove files" do
- @searcher.should respond_to(:destroy)
- end
-
- it "should remove files in all formats found in the data directory that match the request key" do
- @searcher.stubs(:data_directory).returns "/my/dir"
- @request.stubs(:key).returns "me"
-
- Dir.expects(:glob).with(File.join("/my/dir", "me.*")).returns %w{/one /two}
-
- File.expects(:unlink).with("/one")
- File.expects(:unlink).with("/two")
-
- @searcher.destroy(@request)
- end
-
- it "should throw an exception if no file is found" do
- @searcher.stubs(:data_directory).returns "/my/dir"
- @request.stubs(:key).returns "me"
-
- Dir.expects(:glob).with(File.join("/my/dir", "me.*")).returns []
-
- proc { @searcher.destroy(@request) }.should raise_error(Puppet::Error)
- end
-
- it "should fail intelligently if a file cannot be removed" do
- @searcher.stubs(:data_directory).returns "/my/dir"
- @request.stubs(:key).returns "me"
-
- Dir.expects(:glob).with(File.join("/my/dir", "me.*")).returns %w{/one}
-
- File.expects(:unlink).with("/one").raises ArgumentError
-
- proc { @searcher.destroy(@request) }.should raise_error(Puppet::Error)
- end
- end
-end
diff --git a/spec/unit/indirector/terminus_spec.rb b/spec/unit/indirector/terminus_spec.rb
index 826b934..63cb7cc 100755
--- a/spec/unit/indirector/terminus_spec.rb
+++ b/spec/unit/indirector/terminus_spec.rb
@@ -3,7 +3,7 @@
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet/defaults'
require 'puppet/indirector'
-require 'puppet/indirector/file'
+require 'puppet/indirector/memory'
describe Puppet::Indirector::Terminus do
before :each do
@@ -202,14 +202,14 @@ describe Puppet::Indirector::Terminus, " when parsing class constants for indire
@subclass.expects(:indirection=).with(:test_ind)
@subclass.stubs(:name=)
@subclass.stubs(:terminus_type=)
- Puppet::Indirector::File.inherited(@subclass)
+ Puppet::Indirector::Memory.inherited(@subclass)
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::File.inherited(@subclass)
+ Puppet::Indirector::Memory.inherited(@subclass)
end
it "should convert camel case to lower case with underscores as word separators" do
--
1.7.6.4

@ -1,40 +0,0 @@
From bdf728edc4c0b0e0e416f9d3e542b6815a4d3c0a Mon Sep 17 00:00:00 2001
From: Daniel Pittman <daniel@puppetlabs.com>
Date: Thu, 29 Sep 2011 00:32:49 -0700
Subject: [PATCH] (#9794) k5login can overwrite arbitrary files as root
The k5login type is typically used to manage a file in the home directory of a
user; the explicit purpose of the files is to allow access to other users.
It writes to the target file directly, as root, without doing anything to
secure the file. That would allow the owner of the home directory to symlink
to anything on the system, and have it replaced with the correct content of
the file. Which is a fairly obvious escalation to root the next time Puppet
runs.
Now, instead, fix that to securely write the target file in a predictable and
secure fashion, using the `secure_open` helper.
Signed-off-by: Daniel Pittman <daniel@puppetlabs.com>
---
lib/puppet/type/k5login.rb | 4 +++-
1 files changed, 3 insertions(+), 1 deletions(-)
diff --git a/lib/puppet/type/k5login.rb b/lib/puppet/type/k5login.rb
index eac142f..2e87ca9 100644
--- a/lib/puppet/type/k5login.rb
+++ b/lib/puppet/type/k5login.rb
@@ -79,7 +79,9 @@ Puppet::Type.newtype(:k5login) do
private
def write(value)
- File.open(@resource[:name], "w") { |f| f.puts value.join("\n") }
+ Puppet::Util.secure_open(@resource[:name], "w") do |f|
+ f.puts value.join("\n")
+ end
end
end
end
--
1.7.6.4

@ -0,0 +1,17 @@
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.11 (GNU/Linux)
iQIcBAABAgAGBQJOomPfAAoJEBBUt6JL1uww6QYP/0xT7OQnK5TZ0Q94KWHRHmje
UZxqhNKt3+xlH74wNM0W81HWJNvRkhVHu0ez8S3ERnExAdFfXG4lkr1kLmmQhKeN
xLW9xN5A31GU+SnjDhRtzzCujFEeexw4ZlWTKdrWtwvli7P/katInxXlNKqpZujl
IDq4+WhjrJ9/4sE0VqjrlOwfOjJPbFMg5M1MNDkS3P5VffHLhp2wdbeFmQH1TpHi
qhEh+vmJw9WO1+z0v/kgL2S8YQH4kCJ82vGG9xfxF5fIwgrL7xVxU4a1FS4Oypy6
2Vff9tP9iBKGErTUwOSbxeJDkHRuQ3oc2hGTUfR8cmAZ5YUavbbIqbWPvOd142rF
+vDyxpcUO+tSZn4o12Wj+sZww+KuviHyexk3BmxNAPOW2UPMPfU9CcaZdkuKV7d3
CyJ4dWg9YX7wY42C+rh7ztQ9LW4hWGcmdvroknfMMJdrR8ARAby0fbApeB5V42Rk
fuh45I7GRlQMKcMhJR/nJM5/OL1Bjn5nyAkL6JoddJZO0LVBswTmcbgmhJaRlF6M
YL92nFrGmKiltlEoAAslSKgDMkZJCdaTv2PQxrtpMEp6wBYSfNF0h0u92gKEltkJ
/6eXIcyGIQAVWwuLPqgvZXtqMx9irB1xJTr41MVwkqZzy6kct/dAXXrosi+xg9eM
FpTONL84pFqy0qkbbM/Q
=ipk9
-----END PGP SIGNATURE-----

@ -1,17 +0,0 @@
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.10 (GNU/Linux)
iQIcBAABAgAGBQJNeAIeAAoJEBBUt6JL1uwwLqMP/A7L+ikXqLTc3X4gaPuiJcuI
dJW9fmzbOaeU1Bit4CaoA8e3gS2u+GybaxpW8eYhwAVGwws6NeKfoLXk7xXAl0X7
pke5LMWl87vGw7orzeA/ck5QO4Gxel+OsDCXO18kRpnPswX/4FnaH8sF8u+y5kUU
2NuDabKPkMiDLjyC5TdXFP397hj0bRLmJjd2WtYo41nbjBJR6l3JOXWo5LlxGx6+
8fzkqDXiNe/vERXm9As42SCehZ0QybRGEW8SgaXjLt4eXO4YJSpRhShRHP2f9d5C
3pUUnUHVuoQUWuNNA0W7wzS8CSKkmDVHyW5hWsMiyyE0u5THvOHBV8UvQ10QAF90
mvcxiqwJYlxvPhuLGyEqGF+XpMqu4cS+f7ikNWtbacOdTY+uFLg5qSk25RwFGld7
ASvlMP+H/Dp8uaxorCZt3dCTETmGbFWFiBgRrHRef6GR2H6gKqzDaRPoW30MNrVM
RAmb7xHHYeBoDf4msi5TJE546S3xfUwB/XjCNrQ8myuzg6engCgEmKB122BLq/DN
MmTZQx8DViYH+oPNQnOBkuFr3FeGNii+sZzC5VHyir3/8ksSQ4dSnKWbS8p8mGnP
5yR/WrUguN1HCdimKANWyGGfiKmNgADrDlOT3iUhjZs09ZR3IR2mTWVCLFcyu/Ft
ukGK0gYzxjK/2FF6uHoR
=/iZd
-----END PGP SIGNATURE-----

@ -5,32 +5,15 @@
%global confdir conf/redhat
Name: puppet
Version: 2.6.6
Release: 3%{?dist}
Version: 2.6.12
Release: 1%{?dist}
Summary: A network tool for managing many disparate systems
License: GPLv2
URL: http://puppetlabs.com
Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz
Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz.sign
# http://projects.puppetlabs.com/issues/5428
Patch0: 0001-5428-More-fully-stub-Puppet-Resource-Reference-for-u.patch
# http://projects.puppetlabs.com/issues/4922
Patch1: 0001-4922-Don-t-truncate-remotely-sourced-files-on-404.patch
# http://projects.puppetlabs.com/issues/5073
Patch2: 0001-5073-Download-plugins-even-if-you-re-filtering-on-ta.patch
# http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2011-3848
Patch3: 0001-Resist-directory-traversal-attacks-2.6.x.patch
# http://projects.puppetlabs.com/issues/9791
# http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2011-3870
Patch4: 2.6.x-9791-TOCTOU-in-ssh-auth-keys-type.patch
# http://projects.puppetlabs.com/issues/9792
# http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2011-3871
Patch5: 2.6.x-9792-Predictable-temporary-filename-in-ralsh.patch
# http://projects.puppetlabs.com/issues/9794
# http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2011-3869
Patch6: 2.6.x-9794-k5login-can-overwrite-arbitrary-files-as-root.patch
# http://projects.puppetlabs.com/issues/9793
Patch7: 2.6.x-9793-secure-indirector-file-backed-terminus-base-cla.patch
Source0: http://downloads.puppetlabs.com/%{name}/%{name}-%{version}.tar.gz
Source1: http://downloads.puppetlabs.com/%{name}/%{name}-%{version}.tar.gz.asc
# https://projects.puppetlabs.com/issues/10244
Patch0: 0001-10244-Restore-Mongrel-XMLRPC-functionality.patch
Group: System Environment/Base
@ -86,16 +69,8 @@ The server can also function as a certificate authority and file server.
%prep
%setup -q
%patch0 -p1
%patch1 -p1
%patch2 -p1
%patch3 -p1
%patch4 -p1
%patch5 -p1
%patch6 -p1
%patch7 -p1
patch -s -p1 < conf/redhat/rundir-perms.patch
%build
# Fix some rpmlint complaints
for f in mac_dscl.pp mac_dscl_revert.pp \
@ -163,7 +138,7 @@ echo "D /var/run/%{name} 0755 %{name} %{name} -" > \
%files
%defattr(-, root, root, 0755)
%doc CHANGELOG COPYING LICENSE README README.queueing examples
%doc CHANGELOG COPYING LICENSE README.md README.queueing examples
%{_bindir}/pi
%{_bindir}/puppet
%{_bindir}/ralsh
@ -280,6 +255,10 @@ fi
rm -rf %{buildroot}
%changelog
* Sun Oct 23 2011 Todd Zullinger <tmz@pobox.com> - 2.6.12-1
- Update to 2.6.12, fixes CVE-2011-3872
- Add upstream patch to restore Mongrel XMLRPC functionality (upstream #10244)
* Thu Sep 29 2011 Todd Zullinger <tmz@pobox.com> - 2.6.6-3
- Apply upstream patches for CVE-2011-3869, CVE-2011-3870, CVE-2011-3871, and
upstream #9793

@ -1 +1 @@
58315e94ff00aedc4a19177877c3e865 puppet-2.6.6.tar.gz
3851b1a33cde9d697d5c5c21ef795438 puppet-2.6.12.tar.gz

Loading…
Cancel
Save