This includes and upstream patch to restore Mongrel XMLRPC functionality (upstream #10244) which was accidentally dropped in 2.6.12.epel9
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-----
|
Loading…
Reference in new issue