--- puppet-2.7.1.orig/Rakefile +++ puppet-2.7.1/Rakefile @@ -9,7 +9,7 @@ require "rspec/core/rake_task" module Puppet - PUPPETVERSION = File.read('lib/puppet.rb')[/PUPPETVERSION *= *'(.*)'/,1] or fail "Couldn't find PUPPETVERSION" + PUPPETVERSION = File.read('/usr/lib/ruby/1.8/puppet.rb')[/PUPPETVERSION *= *'(.*)'/,1] or fail "Couldn't find PUPPETVERSION" end Dir['tasks/**/*.rake'].each { |t| load t } --- puppet-2.7.1.orig/test/language/functions.rb +++ puppet-2.7.1/test/language/functions.rb @@ -4,7 +4,6 @@ require 'puppet' require 'puppet/parser/parser' -require 'puppet/network/client' require 'puppettest' require 'puppettest/resourcetesting' --- puppet-2.7.1.orig/test/language/snippets.rb +++ puppet-2.7.1/test/language/snippets.rb @@ -4,8 +4,6 @@ require 'puppet' require 'puppet/parser/parser' -require 'puppet/network/client' -require 'puppet/network/handler' require 'puppettest' class TestSnippets < Test::Unit::TestCase @@ -69,13 +67,6 @@ ast end - def client - args = { - :Listen => false - } - Puppet::Network::Client.new(args) - end - def ast2scope(ast) scope = Puppet::Parser::Scope.new ast.evaluate(scope) --- puppet-2.7.1.orig/test/rails/rails.rb +++ puppet-2.7.1/test/rails/rails.rb @@ -5,7 +5,6 @@ require 'puppet' require 'puppet/rails' require 'puppet/parser/parser' -require 'puppet/network/client' require 'puppettest' require 'puppettest/parsertesting' require 'puppettest/resourcetesting' --- puppet-2.7.1.orig/test/ral/type/filesources.rb +++ puppet-2.7.1/test/ral/type/filesources.rb @@ -227,66 +227,6 @@ file end - def test_unmountedNetworkSources - server = nil - mounts = { - "/" => "root", - "/noexistokay" => "noexist" - } - - fileserverconf = mkfileserverconf(mounts) - - Puppet[:autosign] = true - Puppet[:masterport] = @port - Puppet[:certdnsnames] = "localhost" - - serverpid = nil - assert_nothing_raised("Could not start on port #{@port}") { - - server = Puppet::Network::HTTPServer::WEBrick.new( - - :Port => @port, - - :Handlers => { - :CA => {}, # so that certs autogenerate - :FileServer => { - :Config => fileserverconf - } - } - ) - - } - - serverpid = fork { - assert_nothing_raised { - #trap(:INT) { server.shutdown; Kernel.exit! } - trap(:INT) { server.shutdown } - server.start - } - } - @@tmppids << serverpid - - sleep(1) - - name = File.join(tmpdir, "nosourcefile") - - file = Puppet::Type.type(:file).new( - - :source => "puppet://localhost/noexist/file", - - :name => name - ) - - assert_raise Puppet::Error do - file.retrieve - end - - comp = mk_catalog(file) - comp.apply - - assert(!FileTest.exists?(name), "File with no source exists anyway") - end - def test_sourcepaths files = [] 3.times { --- puppet-2.7.1.orig/test/lib/puppettest/fakes.rb +++ puppet-2.7.1/test/lib/puppettest/fakes.rb @@ -1,4 +1,4 @@ -require File.expand_path(File.join(File.dirname(__FILE__), '../../../lib/puppet/util')) +require '/usr/lib/ruby/1.8/puppet/util' module PuppetTest # A baseclass for the faketypes. --- puppet-2.7.1.orig/test/lib/puppettest/exetest.rb +++ puppet-2.7.1/test/lib/puppettest/exetest.rb @@ -50,7 +50,7 @@ args += " --confdir #{Puppet[:confdir]}" args += " --rundir #{File.join(Puppet[:vardir], "run")}" args += " --vardir #{Puppet[:vardir]}" - args += " --certdnsnames #{Puppet[:certdnsnames]}" + args += " --dns_alt_names #{Puppet[:master_dns_alt_names]}" args += " --masterport #{@@port}" args += " --user #{Puppet::Util::SUIDManager.uid}" args += " --group #{Puppet::Util::SUIDManager.gid}" --- puppet-2.7.1.orig/test/lib/puppettest/servertest.rb +++ puppet-2.7.1/test/lib/puppettest/servertest.rb @@ -1,5 +1,4 @@ require 'puppettest' -require 'puppet/network/http_server/webrick' module PuppetTest::ServerTest include PuppetTest --- puppet-2.7.1.orig/spec/spec_helper.rb +++ puppet-2.7.1/spec/spec_helper.rb @@ -1,3 +1,6 @@ +unless defined? SPEC_HELPER_IS_LOADED +SPEC_HELPER_IS_LOADED = 1 + dir = File.expand_path(File.dirname(__FILE__)) $LOAD_PATH.unshift File.join(dir, 'lib') @@ -74,3 +77,5 @@ GC.enable end end + +end --- puppet-2.7.1.orig/spec/unit/ssl/certificate_authority_spec.rb +++ puppet-2.7.1/spec/unit/ssl/certificate_authority_spec.rb @@ -199,8 +199,9 @@ request = mock 'request' Puppet::SSL::CertificateRequest.expects(:new).with(@ca.host.name).returns request request.expects(:generate).with(@ca.host.key) + request.stubs(:request_extensions => []) - @ca.expects(:sign).with(@host.name, :ca, request) + @ca.expects(:sign).with(@host.name, false, request) @ca.stubs :generate_password @@ -243,10 +244,10 @@ Puppet::SSL::Certificate.indirection.stubs(:save) # Stub out the factory - @factory = stub 'factory', :result => "my real cert" - Puppet::SSL::CertificateFactory.stubs(:new).returns @factory + Puppet::SSL::CertificateFactory.stubs(:build).returns "my real cert" - @request = stub 'request', :content => "myrequest", :name => @name + @request_content = stub "request content stub", :subject => @name + @request = stub 'request', :name => @name, :request_extensions => [], :subject_alt_names => [], :content => @request_content # And the inventory @inventory = stub 'inventory', :add => nil @@ -297,37 +298,45 @@ it "should not look up a certificate request for the host" do Puppet::SSL::CertificateRequest.indirection.expects(:find).never - @ca.sign(@name, :ca, @request) + @ca.sign(@name, true, @request) end it "should use a certificate type of :ca" do - Puppet::SSL::CertificateFactory.expects(:new).with do |*args| + Puppet::SSL::CertificateFactory.expects(:build).with do |*args| args[0] == :ca - end.returns @factory + end.returns "my real cert" @ca.sign(@name, :ca, @request) end it "should pass the provided CSR as the CSR" do - Puppet::SSL::CertificateFactory.expects(:new).with do |*args| - args[1] == "myrequest" - end.returns @factory + Puppet::SSL::CertificateFactory.expects(:build).with do |*args| + args[1] == @request + end.returns "my real cert" @ca.sign(@name, :ca, @request) end it "should use the provided CSR's content as the issuer" do - Puppet::SSL::CertificateFactory.expects(:new).with do |*args| - args[2] == "myrequest" - end.returns @factory + Puppet::SSL::CertificateFactory.expects(:build).with do |*args| + args[2].subject == "myhost" + end.returns "my real cert" @ca.sign(@name, :ca, @request) end it "should pass the next serial as the serial number" do - Puppet::SSL::CertificateFactory.expects(:new).with do |*args| + Puppet::SSL::CertificateFactory.expects(:build).with do |*args| args[3] == @serial - end.returns @factory + end.returns "my real cert" @ca.sign(@name, :ca, @request) end + it "should sign the certificate request even if it contains alt names" do + @request.stubs(:subject_alt_names).returns %w[DNS:foo DNS:bar DNS:baz] + + expect do + @ca.sign(@name, false, @request) + end.should_not raise_error(Puppet::SSL::CertificateAuthority::CertificateSigningError) + end + it "should save the resulting certificate" do Puppet::SSL::Certificate.indirection.expects(:save).with(@cert) @@ -345,9 +354,9 @@ end it "should use a certificate type of :server" do - Puppet::SSL::CertificateFactory.expects(:new).with do |*args| + Puppet::SSL::CertificateFactory.expects(:build).with do |*args| args[0] == :server - end.returns @factory + end.returns "my real cert" @ca.sign(@name) end @@ -364,17 +373,45 @@ lambda { @ca.sign(@name) }.should raise_error(ArgumentError) end + it "should fail if an unknown request extension is present" do + @request.stubs :request_extensions => [{ "oid" => "bananas", + "value" => "delicious" }] + expect { @ca.sign(@name) }. + should raise_error(/CSR has request extensions that are not permitted/) + end + + it "should fail if the CSR contains alt names and they are not expected" do + @request.stubs(:subject_alt_names).returns %w[DNS:foo DNS:bar DNS:baz] + + expect do + @ca.sign(@name, false) + end.to raise_error(Puppet::SSL::CertificateAuthority::CertificateSigningError, /CSR '#{@name}' contains subject alternative names \(.*?\), which are disallowed. Use `puppet cert --allow-dns-alt-names sign #{@name}` to sign this request./) + end + + it "should not fail if the CSR does not contain alt names and they are expected" do + @request.stubs(:subject_alt_names).returns [] + expect { @ca.sign(@name, true) }.should_not raise_error + end + + it "should reject alt names by default" do + @request.stubs(:subject_alt_names).returns %w[DNS:foo DNS:bar DNS:baz] + + expect do + @ca.sign(@name) + end.to raise_error(Puppet::SSL::CertificateAuthority::CertificateSigningError, /CSR '#{@name}' contains subject alternative names \(.*?\), which are disallowed. Use `puppet cert --allow-dns-alt-names sign #{@name}` to sign this request./) + end + it "should use the CA certificate as the issuer" do - Puppet::SSL::CertificateFactory.expects(:new).with do |*args| + Puppet::SSL::CertificateFactory.expects(:build).with do |*args| args[2] == @cacert.content - end.returns @factory + end.returns "my real cert" @ca.sign(@name) end it "should pass the next serial as the serial number" do - Puppet::SSL::CertificateFactory.expects(:new).with do |*args| + Puppet::SSL::CertificateFactory.expects(:build).with do |*args| args[3] == @serial - end.returns @factory + end.returns "my real cert" @ca.sign(@name) end @@ -399,6 +436,80 @@ @ca.sign(@name) end + + it "should check the internal signing policies" do + @ca.expects(:check_internal_signing_policies).returns true + @ca.sign(@name) + end + end + + context "#check_internal_signing_policies" do + before do + @serial = 10 + @ca.stubs(:next_serial).returns @serial + + Puppet::SSL::CertificateRequest.indirection.stubs(:find).with(@name).returns @request + @cert.stubs :save + end + + it "should reject a critical extension that isn't on the whitelist" do + @request.stubs(:request_extensions).returns [{ "oid" => "banana", + "value" => "yumm", + "critical" => true }] + expect { @ca.sign(@name) }.to raise_error( + Puppet::SSL::CertificateAuthority::CertificateSigningError, + /request extensions that are not permitted/ + ) + end + + it "should reject a non-critical extension that isn't on the whitelist" do + @request.stubs(:request_extensions).returns [{ "oid" => "peach", + "value" => "meh", + "critical" => false }] + expect { @ca.sign(@name) }.to raise_error( + Puppet::SSL::CertificateAuthority::CertificateSigningError, + /request extensions that are not permitted/ + ) + end + + it "should reject non-whitelist extensions even if a valid extension is present" do + @request.stubs(:request_extensions).returns [{ "oid" => "peach", + "value" => "meh", + "critical" => false }, + { "oid" => "subjectAltName", + "value" => "DNS:foo", + "critical" => true }] + expect { @ca.sign(@name) }.to raise_error( + Puppet::SSL::CertificateAuthority::CertificateSigningError, + /request extensions that are not permitted/ + ) + end + + it "should reject a subjectAltName for a non-DNS value" do + @request.stubs(:subject_alt_names).returns ['DNS:foo', 'email:bar@example.com'] + expect { @ca.sign(@name, true) }.to raise_error( + Puppet::SSL::CertificateAuthority::CertificateSigningError, + /subjectAltName outside the DNS label space/ + ) + end + + it "should reject a wildcard subject" do + @request.content.stubs(:subject). + returns(OpenSSL::X509::Name.new([["CN", "*.local"]])) + + expect { @ca.sign(@name) }.to raise_error( + Puppet::SSL::CertificateAuthority::CertificateSigningError, + /subject contains a wildcard/ + ) + end + + it "should reject a wildcard subjectAltName" do + @request.stubs(:subject_alt_names).returns ['DNS:foo', 'DNS:*.bar'] + expect { @ca.sign(@name, true) }.to raise_error( + Puppet::SSL::CertificateAuthority::CertificateSigningError, + /subjectAltName contains a wildcard/ + ) + end end it "should create a certificate instance with the content set to the newly signed x509 certificate" do @@ -763,8 +874,7 @@ end it "should sign the generated request" do - @ca.expects(:sign).with("him") - + @ca.expects(:sign).with("him", false) @ca.generate("him") end end --- puppet-2.7.1.orig/spec/unit/ssl/certificate_request_spec.rb +++ puppet-2.7.1/spec/unit/ssl/certificate_request_spec.rb @@ -125,7 +125,7 @@ it "should set the CN to the :ca_name setting when the CSR is for a CA" do subject = mock 'subject' - Puppet.settings.expects(:value).with(:ca_name).returns "mycertname" + Puppet[:ca_name] = "mycertname" OpenSSL::X509::Name.expects(:new).with { |subject| subject[0][1] == "mycertname" }.returns(subject) @request.expects(:subject=).with(subject) Puppet::SSL::CertificateRequest.new(Puppet::SSL::CA_NAME).generate(@key) @@ -144,6 +144,67 @@ @instance.generate(@key) end + context "without subjectAltName / dns_alt_names" do + before :each do + Puppet[:dns_alt_names] = "" + end + + ["extreq", "msExtReq"].each do |name| + it "should not add any #{name} attribute" do + @request.expects(:add_attribute).never + @request.expects(:attributes=).never + @instance.generate(@key) + end + + it "should return no subjectAltNames" do + @instance.generate(@key) + @instance.subject_alt_names.should be_empty + end + end + end + + context "with dns_alt_names" do + before :each do + Puppet[:dns_alt_names] = "one, two, three" + end + + ["extreq", "msExtReq"].each do |name| + it "should not add any #{name} attribute" do + @request.expects(:add_attribute).never + @request.expects(:attributes=).never + @instance.generate(@key) + end + + it "should return no subjectAltNames" do + @instance.generate(@key) + @instance.subject_alt_names.should be_empty + end + end + end + + context "with subjectAltName to generate request" do + before :each do + Puppet[:dns_alt_names] = "" + end + + it "should add an extreq attribute" do + @request.expects(:add_attribute).with do |arg| + arg.value.value.all? do |x| + x.value.all? do |y| + y.value[0].value == "subjectAltName" + end + end + end + + @instance.generate(@key, :dns_alt_names => 'one, two') + end + + it "should return the subjectAltName values" do + @instance.generate(@key, :dns_alt_names => 'one,two') + @instance.subject_alt_names.should =~ ["DNS:myname", "DNS:one", "DNS:two"] + end + end + it "should sign the csr with the provided key and a digest" do digest = mock 'digest' OpenSSL::Digest::MD5.expects(:new).returns(digest) @@ -194,6 +255,7 @@ csr = Puppet::SSL::CertificateRequest.new("me") terminus = mock 'terminus' Puppet::SSL::CertificateRequest.indirection.expects(:prepare).returns(terminus) + Puppet::SSL::CertificateRequest.indirection.stubs(:cache?).returns false terminus.expects(:save).with { |request| request.instance == csr && request.key == "me" } Puppet::SSL::CertificateRequest.indirection.save(csr) @@ -207,6 +269,7 @@ csr = Puppet::SSL::CertificateRequest.new("me") terminus = mock 'terminus' Puppet::SSL::CertificateRequest.indirection.expects(:prepare).returns(terminus) + Puppet::SSL::CertificateRequest.indirection.stubs(:cache?).returns false terminus.expects(:save).with { |request| request.instance == csr && request.key == "me" } Puppet::SSL::CertificateRequest.indirection.save(csr) --- puppet-2.7.1.orig/spec/unit/ssl/host_spec.rb +++ puppet-2.7.1/spec/unit/ssl/host_spec.rb @@ -2,10 +2,10 @@ require 'spec_helper' require 'puppet/ssl/host' -require 'puppet/sslcertificates' -require 'puppet/sslcertificates/ca' describe Puppet::SSL::Host do + include PuppetSpec::Files + before do Puppet::SSL::Host.indirection.terminus_class = :file @host = Puppet::SSL::Host.new("myname") @@ -66,6 +66,48 @@ Puppet::SSL::Host.localhost.should equal(host) end + it "should create a localhost cert if no cert is available and it is a CA with autosign and it is using DNS alt names" do + Puppet[:autosign] = true + Puppet[:confdir] = tmpdir('conf') + Puppet[:dns_alt_names] = "foo,bar,baz" + ca = Puppet::SSL::CertificateAuthority.new + Puppet::SSL::CertificateAuthority.stubs(:instance).returns ca + + localhost = Puppet::SSL::Host.localhost + cert = localhost.certificate + + cert.should be_a(Puppet::SSL::Certificate) + cert.subject_alt_names.should =~ %W[DNS:#{Puppet[:certname]} DNS:foo DNS:bar DNS:baz] + end + + context "with dns_alt_names" do + before :each do + Puppet[:dns_alt_names] = 'one, two' + + @key = stub('key content') + key = stub('key', :generate => true, :content => @key) + Puppet::SSL::Key.stubs(:new).returns key + Puppet::SSL::Key.indirection.stubs(:save).with(key) + + @cr = stub('certificate request') + Puppet::SSL::CertificateRequest.stubs(:new).returns @cr + Puppet::SSL::CertificateRequest.indirection.stubs(:save).with(@cr) + end + + it "should not include subjectAltName if not the local node" do + @cr.expects(:generate).with(@key, {}) + + Puppet::SSL::Host.new('not-the-' + Puppet[:certname]).generate + end + + it "should include subjectAltName if I am a CA" do + @cr.expects(:generate). + with(@key, { :dns_alt_names => Puppet[:dns_alt_names] }) + + Puppet::SSL::Host.localhost + end + end + it "should always read the key for the localhost instance in from disk" do host = stub 'host', :certificate => "eh" Puppet::SSL::Host.expects(:new).returns host @@ -382,7 +424,7 @@ key = stub 'key', :public_key => mock("public_key"), :content => "mycontent" @host.stubs(:key).returns(key) - @request.expects(:generate).with("mycontent") + @request.expects(:generate).with("mycontent", {}) Puppet::SSL::CertificateRequest.indirection.expects(:save).with(@request) @host.generate_certificate_request.should be_true @@ -574,7 +616,7 @@ it "should use the CA to sign its certificate request if it does not have a certificate" do @host.expects(:certificate).returns nil - @ca.expects(:sign).with(@host.name) + @ca.expects(:sign).with(@host.name, true) @host.generate end @@ -717,7 +759,6 @@ before do Puppet[:vardir] = tmpdir("ssl_test_vardir") Puppet[:ssldir] = tmpdir("ssl_test_ssldir") - Puppet::SSLCertificates::CA.new.mkrootcert # localcacert is where each client stores the CA certificate # cacert is where the master stores the CA certificate # Since we need to play the role of both for testing we need them to be the same and exist --- puppet-2.7.1.orig/spec/unit/ssl/certificate_spec.rb +++ puppet-2.7.1/spec/unit/ssl/certificate_spec.rb @@ -89,6 +89,37 @@ @certificate.should respond_to(:content) end + describe "#subject_alt_names" do + it "should list all alternate names when the extension is present" do + key = Puppet::SSL::Key.new('quux') + key.generate + + csr = Puppet::SSL::CertificateRequest.new('quux') + csr.generate(key, :dns_alt_names => 'foo, bar,baz') + + raw_csr = csr.content + + cert = Puppet::SSL::CertificateFactory.build('server', csr, raw_csr, 14) + certificate = @class.from_s(cert.to_pem) + certificate.subject_alt_names. + should =~ ['DNS:foo', 'DNS:bar', 'DNS:baz', 'DNS:quux'] + end + + it "should return an empty list of names if the extension is absent" do + key = Puppet::SSL::Key.new('quux') + key.generate + + csr = Puppet::SSL::CertificateRequest.new('quux') + csr.generate(key) + + raw_csr = csr.content + + cert = Puppet::SSL::CertificateFactory.build('client', csr, raw_csr, 14) + certificate = @class.from_s(cert.to_pem) + certificate.subject_alt_names.should be_empty + end + end + it "should return a nil expiration if there is no actual certificate" do @certificate.stubs(:content).returns nil --- puppet-2.7.1.orig/spec/unit/ssl/certificate_factory_spec.rb +++ puppet-2.7.1/spec/unit/ssl/certificate_factory_spec.rb @@ -4,103 +4,123 @@ require 'puppet/ssl/certificate_factory' describe Puppet::SSL::CertificateFactory do - before do - @cert_type = mock 'cert_type' - @name = mock 'name' - @csr = stub 'csr', :subject => @name - @issuer = mock 'issuer' - @serial = mock 'serial' - - @factory = Puppet::SSL::CertificateFactory.new(@cert_type, @csr, @issuer, @serial) + let :serial do OpenSSL::BN.new('12') end + let :name do "example.local" end + let :x509_name do OpenSSL::X509::Name.new([['CN', name]]) end + let :key do Puppet::SSL::Key.new(name).generate end + let :csr do + csr = Puppet::SSL::CertificateRequest.new(name) + csr.generate(key) + csr end - - describe "when initializing" do - it "should set its :cert_type to its first argument" do - @factory.cert_type.should equal(@cert_type) - end - - it "should set its :csr to its second argument" do - @factory.csr.should equal(@csr) - end - - it "should set its :issuer to its third argument" do - @factory.issuer.should equal(@issuer) - end - - it "should set its :serial to its fourth argument" do - @factory.serial.should equal(@serial) - end - - it "should set its name to the subject of the csr" do - @factory.name.should equal(@name) - end + let :issuer do + cert = OpenSSL::X509::Certificate.new + cert.subject = OpenSSL::X509::Name.new([["CN", 'issuer.local']]) + cert end describe "when generating the certificate" do - before do - @cert = mock 'cert' - - @cert.stub_everything - - @factory.stubs :build_extensions - - @factory.stubs :set_ttl - - @issuer_name = mock 'issuer_name' - @issuer.stubs(:subject).returns @issuer_name - - @public_key = mock 'public_key' - @csr.stubs(:public_key).returns @public_key - - OpenSSL::X509::Certificate.stubs(:new).returns @cert - end - it "should return a new X509 certificate" do - OpenSSL::X509::Certificate.expects(:new).returns @cert - @factory.result.should equal(@cert) + subject.build(:server, csr, issuer, serial).should_not == + subject.build(:server, csr, issuer, serial) end it "should set the certificate's version to 2" do - @cert.expects(:version=).with 2 - @factory.result + subject.build(:server, csr, issuer, serial).version.should == 2 end it "should set the certificate's subject to the CSR's subject" do - @cert.expects(:subject=).with @name - @factory.result + cert = subject.build(:server, csr, issuer, serial) + cert.subject.should eql x509_name end it "should set the certificate's issuer to the Issuer's subject" do - @cert.expects(:issuer=).with @issuer_name - @factory.result + cert = subject.build(:server, csr, issuer, serial) + cert.issuer.should eql issuer.subject end it "should set the certificate's public key to the CSR's public key" do - @cert.expects(:public_key=).with @public_key - @factory.result + cert = subject.build(:server, csr, issuer, serial) + cert.public_key.should be_public + cert.public_key.to_s.should == csr.content.public_key.to_s end it "should set the certificate's serial number to the provided serial number" do - @cert.expects(:serial=).with @serial - @factory.result + cert = subject.build(:server, csr, issuer, serial) + cert.serial.should == serial end - it "should build extensions for the certificate" do - @factory.expects(:build_extensions) - @factory.result + it "should have 24 hours grace on the start of the cert" do + cert = subject.build(:server, csr, issuer, serial) + cert.not_before.should be_within(1).of(Time.now - 24*60*60) end - it "should set the ttl of the certificate" do - @factory.expects(:set_ttl) - @factory.result + it "should set the default TTL of the certificate" do + ttl = Puppet::SSL::CertificateFactory.ttl + cert = subject.build(:server, csr, issuer, serial) + cert.not_after.should be_within(1).of(Time.now + ttl) end - end - describe "when building extensions" do - it "should have tests" - end + it "should respect a custom TTL for the CA" do + Puppet[:ca_ttl] = 12 + cert = subject.build(:server, csr, issuer, serial) + cert.not_after.should be_within(1).of(Time.now + 12) + end - describe "when setting the ttl" do - it "should have tests" + it "should build extensions for the certificate" do + cert = subject.build(:server, csr, issuer, serial) + cert.extensions.map {|x| x.to_h }.find {|x| x["oid"] == "nsComment" }.should == + { "oid" => "nsComment", + "value" => "Puppet Ruby/OpenSSL Internal Certificate", + "critical" => false } + end + + # See #2848 for why we are doing this: we need to make sure that + # subjectAltName is set if the CSR has it, but *not* if it is set when the + # certificate is built! + it "should not add subjectAltNames from dns_alt_names" do + Puppet[:dns_alt_names] = 'one, two' + # Verify the CSR still has no extReq, just in case... + csr.request_extensions.should == [] + cert = subject.build(:server, csr, issuer, serial) + + cert.extensions.find {|x| x.oid == 'subjectAltName' }.should be_nil + end + + it "should add subjectAltName when the CSR requests them" do + Puppet[:dns_alt_names] = '' + + expect = %w{one two} + [name] + + csr = Puppet::SSL::CertificateRequest.new(name) + csr.generate(key, :dns_alt_names => expect.join(', ')) + + csr.request_extensions.should_not be_nil + csr.subject_alt_names.should =~ expect.map{|x| "DNS:#{x}"} + + cert = subject.build(:server, csr, issuer, serial) + san = cert.extensions.find {|x| x.oid == 'subjectAltName' } + san.should_not be_nil + expect.each do |name| + san.value.should =~ /DNS:#{name}\b/i + end + end + + # Can't check the CA here, since that requires way more infrastructure + # that I want to build up at this time. We can verify the critical + # values, though, which are non-CA certs. --daniel 2011-10-11 + { :ca => 'CA:TRUE', + :terminalsubca => ['CA:TRUE', 'pathlen:0'], + :server => 'CA:FALSE', + :ocsp => 'CA:FALSE', + :client => 'CA:FALSE', + }.each do |name, value| + it "should set basicConstraints for #{name} #{value.inspect}" do + cert = subject.build(name, csr, issuer, serial) + bc = cert.extensions.find {|x| x.oid == 'basicConstraints' } + bc.should be + bc.value.split(/\s*,\s*/).should =~ Array(value) + end + end end end --- puppet-2.7.1.orig/spec/unit/ssl/certificate_authority/interface_spec.rb +++ puppet-2.7.1/spec/unit/ssl/certificate_authority/interface_spec.rb @@ -31,13 +31,13 @@ end describe "when initializing" do it "should set its method using its settor" do - @class.any_instance.expects(:method=).with(:generate) - @class.new(:generate, :to => :all) + instance = @class.new(:generate, :to => :all) + instance.method.should == :generate end it "should set its subjects using the settor" do - @class.any_instance.expects(:subjects=).with(:all) - @class.new(:generate, :to => :all) + instance = @class.new(:generate, :to => :all) + instance.subjects.should == :all end it "should set the digest if given" do @@ -53,23 +53,27 @@ describe "when setting the method" do it "should set the method" do - @class.new(:generate, :to => :all).method.should == :generate + instance = @class.new(:generate, :to => :all) + instance.method = :list + + instance.method.should == :list end it "should fail if the method isn't a member of the INTERFACE_METHODS array" do - Puppet::SSL::CertificateAuthority::Interface::INTERFACE_METHODS.expects(:include?).with(:thing).returns false - - lambda { @class.new(:thing, :to => :all) }.should raise_error(ArgumentError) + lambda { @class.new(:thing, :to => :all) }.should raise_error(ArgumentError, /Invalid method thing to apply/) end end describe "when setting the subjects" do it "should set the subjects" do - @class.new(:generate, :to => :all).subjects.should == :all + instance = @class.new(:generate, :to => :all) + instance.subjects = :signed + + instance.subjects.should == :signed end - it "should fail if the subjects setting isn't :all or an array", :'fails_on_ruby_1.9.2' => true do - lambda { @class.new(:generate, "other") }.should raise_error(ArgumentError) + it "should fail if the subjects setting isn't :all or an array" do + lambda { @class.new(:generate, :to => "other") }.should raise_error(ArgumentError, /Subjects must be an array or :all; not other/) end end @@ -117,8 +121,8 @@ it "should call :generate on the CA for each host specified" do @applier = @class.new(:generate, :to => %w{host1 host2}) - @ca.expects(:generate).with("host1") - @ca.expects(:generate).with("host2") + @ca.expects(:generate).with("host1", {}) + @ca.expects(:generate).with("host2", {}) @applier.apply(@ca) end @@ -149,15 +153,24 @@ describe ":sign" do describe "and an array of names was provided" do - before do - @applier = @class.new(:sign, :to => %w{host1 host2}) - end + let(:applier) { @class.new(:sign, @options.merge(:to => %w{host1 host2})) } it "should sign the specified waiting certificate requests" do - @ca.expects(:sign).with("host1") - @ca.expects(:sign).with("host2") + @options = {:allow_dns_alt_names => false} - @applier.apply(@ca) + @ca.expects(:sign).with("host1", false) + @ca.expects(:sign).with("host2", false) + + applier.apply(@ca) + end + + it "should sign the certificate requests with alt names if specified" do + @options = {:allow_dns_alt_names => true} + + @ca.expects(:sign).with("host1", true) + @ca.expects(:sign).with("host2", true) + + applier.apply(@ca) end end @@ -165,8 +178,8 @@ it "should sign all waiting certificate requests" do @ca.stubs(:waiting?).returns(%w{cert1 cert2}) - @ca.expects(:sign).with("cert1") - @ca.expects(:sign).with("cert2") + @ca.expects(:sign).with("cert1", nil) + @ca.expects(:sign).with("cert2", nil) @applier = @class.new(:sign, :to => :all) @applier.apply(@ca) @@ -182,63 +195,93 @@ end describe ":list" do - describe "and an empty array was provided" do - it "should print a string containing all certificate requests" do - @ca.expects(:waiting?).returns %w{host1 host2} - @ca.stubs(:verify) + before :each do + @cert = Puppet::SSL::Certificate.new 'foo' + @csr = Puppet::SSL::CertificateRequest.new 'bar' + + @cert.stubs(:subject_alt_names).returns [] + @csr.stubs(:subject_alt_names).returns [] + + Puppet::SSL::Certificate.indirection.stubs(:find).returns @cert + Puppet::SSL::CertificateRequest.indirection.stubs(:find).returns @csr + + @ca.expects(:waiting?).returns %w{host1 host2 host3} + @ca.expects(:list).returns %w{host4 host5 host6} + @ca.stubs(:fingerprint).returns "fingerprint" + @ca.stubs(:verify) + end - @applier = @class.new(:list, :to => []) + describe "and an empty array was provided" do + it "should print all certificate requests" do + applier = @class.new(:list, :to => []) - @applier.expects(:puts).with "host1\nhost2" + applier.expects(:puts).with(<<-OUTPUT.chomp) + host1 (fingerprint) + host2 (fingerprint) + host3 (fingerprint) + OUTPUT - @applier.apply(@ca) + applier.apply(@ca) end end describe "and :all was provided" do it "should print a string containing all certificate requests and certificates" do - @ca.expects(:waiting?).returns %w{host1 host2} - @ca.expects(:list).returns %w{host3 host4} - @ca.stubs(:verify) - @ca.stubs(:fingerprint).returns "fingerprint" - @ca.expects(:verify).with("host3").raises(Puppet::SSL::CertificateAuthority::CertificateVerificationError.new(23), "certificate revoked") - - @applier = @class.new(:list, :to => :all) - - @applier.expects(:puts).with "host1 (fingerprint)" - @applier.expects(:puts).with "host2 (fingerprint)" - @applier.expects(:puts).with "- host3 (fingerprint) (certificate revoked)" - @applier.expects(:puts).with "+ host4 (fingerprint)" + @ca.stubs(:verify).with("host4").raises(Puppet::SSL::CertificateAuthority::CertificateVerificationError.new(23), "certificate revoked") - @applier.apply(@ca) + applier = @class.new(:list, :to => :all) + + applier.expects(:puts).with(<<-OUTPUT.chomp) + host1 (fingerprint) + host2 (fingerprint) + host3 (fingerprint) ++ host5 (fingerprint) ++ host6 (fingerprint) +- host4 (fingerprint) (certificate revoked) + OUTPUT + + applier.apply(@ca) end end describe "and :signed was provided" do it "should print a string containing all signed certificate requests and certificates" do - @ca.expects(:list).returns %w{host1 host2} + applier = @class.new(:list, :to => :signed) - @applier = @class.new(:list, :to => :signed) + applier.expects(:puts).with(<<-OUTPUT.chomp) ++ host4 (fingerprint) ++ host5 (fingerprint) ++ host6 (fingerprint) + OUTPUT - @applier.apply(@ca) + applier.apply(@ca) + end + + it "should include subject alt names if they are on the certificate request" do + @csr.stubs(:subject_alt_names).returns ["DNS:foo", "DNS:bar"] + + applier = @class.new(:list, :to => ['host1']) + + applier.expects(:puts).with(<<-OUTPUT.chomp) + host1 (fingerprint) (alt names: DNS:foo, DNS:bar) + OUTPUT + + applier.apply(@ca) end end describe "and an array of names was provided" do - it "should print a string of all named hosts that have a waiting request" do - @ca.expects(:waiting?).returns %w{host1 host2} - @ca.expects(:list).returns %w{host3 host4} - @ca.stubs(:fingerprint).returns "fingerprint" - @ca.stubs(:verify) - - @applier = @class.new(:list, :to => %w{host1 host2 host3 host4}) - - @applier.expects(:puts).with "host1 (fingerprint)" - @applier.expects(:puts).with "host2 (fingerprint)" - @applier.expects(:puts).with "+ host3 (fingerprint)" - @applier.expects(:puts).with "+ host4 (fingerprint)" + it "should print all named hosts" do + applier = @class.new(:list, :to => %w{host1 host2 host4 host5}) - @applier.apply(@ca) + applier.expects(:puts).with(<<-OUTPUT.chomp) + host1 (fingerprint) + host2 (fingerprint) ++ host4 (fingerprint) ++ host5 (fingerprint) + OUTPUT + + applier.apply(@ca) end end end --- puppet-2.7.1.orig/spec/unit/indirector/yaml_spec.rb +++ puppet-2.7.1/spec/unit/indirector/yaml_spec.rb @@ -63,6 +63,20 @@ 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 --- puppet-2.7.1.orig/spec/unit/indirector/terminus_spec.rb +++ puppet-2.7.1/spec/unit/indirector/terminus_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' require 'puppet/defaults' require 'puppet/indirector' -require 'puppet/indirector/file' +require 'puppet/indirector/memory' describe Puppet::Indirector::Terminus, :'fails_on_ruby_1.9.2' => true do before :each do @@ -201,14 +201,14 @@ @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 --- puppet-2.7.1.orig/spec/unit/indirector/ssl_file_spec.rb +++ puppet-2.7.1/spec/unit/indirector/ssl_file_spec.rb @@ -88,6 +88,25 @@ 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 --- puppet-2.7.1.orig/spec/unit/indirector/certificate_request/ca_spec.rb +++ puppet-2.7.1/spec/unit/indirector/certificate_request/ca_spec.rb @@ -6,8 +6,6 @@ require 'spec_helper' require 'puppet/ssl/host' -require 'puppet/sslcertificates' -require 'puppet/sslcertificates/ca' require 'puppet/indirector/certificate_request/ca' describe Puppet::SSL::CertificateRequest::Ca do @@ -18,7 +16,6 @@ Puppet::SSL::Host.ca_location = :local Puppet[:localcacert] = Puppet[:cacert] - Puppet::SSLCertificates::CA.new.mkrootcert @ca = Puppet::SSL::CertificateAuthority.new end --- puppet-2.7.1.orig/spec/unit/network/handler/ca_spec.rb +++ puppet-2.7.1/spec/unit/network/handler/ca_spec.rb @@ -0,0 +1,86 @@ +require 'spec_helper' + +require 'puppet/network/handler/ca' + +describe Puppet::Network::Handler::CA do + include PuppetSpec::Files + + describe "#getcert" do + let(:host) { "testhost" } + let(:x509_name) { OpenSSL::X509::Name.new [['CN', host]] } + let(:key) { Puppet::SSL::Key.new(host).generate } + + let(:csr) do + csr = OpenSSL::X509::Request.new + csr.subject = x509_name + csr.public_key = key.public_key + csr + end + + let(:ca) { Puppet::SSL::CertificateAuthority.new } + let(:cacert) { ca.instance_variable_get(:@certificate) } + + before :each do + Puppet[:confdir] = tmpdir('conf') + + Puppet::SSL::CertificateAuthority.stubs(:ca?).returns true + Puppet::SSL::CertificateAuthority.stubs(:singleton_instance).returns ca + end + + it "should do nothing if the master is not a CA" do + Puppet::SSL::CertificateAuthority.stubs(:ca?).returns false + + csr = OpenSSL::X509::Request.new + subject.getcert(csr.to_pem).should == '' + end + + describe "when a certificate already exists for the host" do + let!(:cert) { ca.generate(host) } + + it "should return the existing cert if it matches the public key of the CSR" do + csr.public_key = cert.content.public_key + + subject.getcert(csr.to_pem).should == [cert.to_s, cacert.to_s] + end + + it "should fail if the public key of the CSR does not match the existing cert" do + expect do + subject.getcert(csr.to_pem) + end.to raise_error(Puppet::Error, /Certificate request does not match existing certificate/) + end + end + + describe "when autosign is enabled" do + before :each do + Puppet[:autosign] = true + end + + it "should return the new cert and the CA cert" do + cert_str, cacert_str = subject.getcert(csr.to_pem) + + returned_cert = Puppet::SSL::Certificate.from_s(cert_str) + returned_cacert = Puppet::SSL::Certificate.from_s(cacert_str) + + returned_cert.name.should == host + returned_cacert.content.subject.cmp(cacert.content.subject).should == 0 + end + end + + describe "when autosign is disabled" do + before :each do + Puppet[:autosign] = false + end + + it "should save the CSR without signing it" do + subject.getcert(csr.to_pem) + + Puppet::SSL::Certificate.indirection.find(host).should be_nil + Puppet::SSL::CertificateRequest.indirection.find(host).should be_a(Puppet::SSL::CertificateRequest) + end + + it "should not return a cert" do + subject.getcert(csr.to_pem).should be_nil + end + end + end +end --- puppet-2.7.1.orig/spec/unit/util/settings_spec.rb +++ puppet-2.7.1/spec/unit/util/settings_spec.rb @@ -128,6 +128,16 @@ @settings[:myval].should == "" end + it "should flag settings from the CLI" do + @settings.handlearg("--myval") + @settings.setting(:myval).setbycli.should be_true + end + + it "should not flag settings memory" do + @settings[:myval] = "12" + @settings.setting(:myval).setbycli.should be_false + end + it "should clear the cache when setting getopt-specific values" do @settings.setdefaults :mysection, :one => ["whah", "yay"], :two => ["$one yay", "bah"] @settings[:two].should == "whah yay" --- puppet-2.7.1.orig/spec/unit/face/certificate_spec.rb +++ puppet-2.7.1/spec/unit/face/certificate_spec.rb @@ -5,14 +5,33 @@ require 'puppet/ssl/host' describe Puppet::Face[:certificate, '0.0.1'] do + include PuppetSpec::Files + + let(:ca) { Puppet::SSL::CertificateAuthority.instance } + + before :each do + Puppet[:confdir] = tmpdir('conf') + Puppet::SSL::CertificateAuthority.stubs(:ca?).returns true + + Puppet::SSL::Host.ca_location = :local + + # We can't cache the CA between tests, because each one has its own SSL dir. + ca = Puppet::SSL::CertificateAuthority.new + Puppet::SSL::CertificateAuthority.stubs(:new).returns ca + Puppet::SSL::CertificateAuthority.stubs(:instance).returns ca + end + it "should have a ca-location option" do subject.should be_option :ca_location end it "should set the ca location when invoked" do - Puppet::SSL::Host.expects(:ca_location=).with(:foo) - Puppet::SSL::Host.indirection.expects(:save) - subject.sign "hello, friend", :ca_location => :foo + Puppet::SSL::Host.expects(:ca_location=).with(:local) + ca.expects(:sign).with do |name,options| + name == "hello, friend" + end + + subject.sign "hello, friend", :ca_location => :local end it "(#7059) should set the ca location when an inherited action is invoked" do @@ -20,4 +39,152 @@ subject.indirection.expects(:find) subject.find "hello, friend", :ca_location => :foo end + + describe "#generate" do + let(:options) { {:ca_location => 'local'} } + let(:host) { Puppet::SSL::Host.new(hostname) } + let(:csr) { host.certificate_request } + + describe "for the current host" do + let(:hostname) { Puppet[:certname] } + + it "should generate a CSR for this host" do + subject.generate(hostname, options) + + csr.content.subject.to_s.should == "/CN=#{Puppet[:certname]}" + csr.name.should == Puppet[:certname] + end + + it "should add dns_alt_names from the global config if not otherwise specified" do + Puppet[:dns_alt_names] = 'from,the,config' + + subject.generate(hostname, options) + + expected = %W[DNS:from DNS:the DNS:config DNS:#{hostname}] + + csr.subject_alt_names.should =~ expected + end + + it "should add the provided dns_alt_names if they are specified" do + Puppet[:dns_alt_names] = 'from,the,config' + + subject.generate(hostname, options.merge(:dns_alt_names => 'explicit,alt,names')) + + expected = %W[DNS:explicit DNS:alt DNS:names DNS:#{hostname}] + + csr.subject_alt_names.should =~ expected + end + end + + describe "for another host" do + let(:hostname) { Puppet[:certname] + 'different' } + + it "should generate a CSR for the specified host" do + subject.generate(hostname, options) + + csr.content.subject.to_s.should == "/CN=#{hostname}" + csr.name.should == hostname + end + + it "should fail if a CSR already exists for the host" do + subject.generate(hostname, options) + + expect do + subject.generate(hostname, options) + end.to raise_error(RuntimeError, /#{hostname} already has a requested certificate; ignoring certificate request/) + end + + it "should add not dns_alt_names from the config file" do + Puppet[:dns_alt_names] = 'from,the,config' + + subject.generate(hostname, options) + + csr.subject_alt_names.should be_empty + end + + it "should add the provided dns_alt_names if they are specified" do + Puppet[:dns_alt_names] = 'from,the,config' + + subject.generate(hostname, options.merge(:dns_alt_names => 'explicit,alt,names')) + + expected = %W[DNS:explicit DNS:alt DNS:names DNS:#{hostname}] + + csr.subject_alt_names.should =~ expected + end + end + end + + describe "#sign" do + let(:options) { {:ca_location => 'local'} } + let(:host) { Puppet::SSL::Host.new(hostname) } + let(:hostname) { "foobar" } + + it "should sign the certificate request if one is waiting" do + subject.generate(hostname, options) + + subject.sign(hostname, options) + + host.certificate_request.should be_nil + host.certificate.should be_a(Puppet::SSL::Certificate) + host.state.should == 'signed' + end + + it "should fail if there is no waiting certificate request" do + expect do + subject.sign(hostname, options) + end.to raise_error(ArgumentError, /Could not find certificate request for #{hostname}/) + end + + describe "when ca_location is local" do + describe "when the request has dns alt names" do + before :each do + subject.generate(hostname, options.merge(:dns_alt_names => 'some,alt,names')) + end + + it "should refuse to sign the request if allow_dns_alt_names is not set" do + expect do + subject.sign(hostname, options) + end.to raise_error(Puppet::SSL::CertificateAuthority::CertificateSigningError, + /CSR '#{hostname}' contains subject alternative names \(.*?\), which are disallowed. Use `puppet cert --allow-dns-alt-names sign #{hostname}` to sign this request./i) + + host.state.should == 'requested' + end + + it "should sign the request if allow_dns_alt_names is set" do + expect do + subject.sign(hostname, options.merge(:allow_dns_alt_names => true)) + end.not_to raise_error + + host.state.should == 'signed' + end + end + + describe "when the request has no dns alt names" do + before :each do + subject.generate(hostname, options) + end + + it "should sign the request if allow_dns_alt_names is set" do + expect { subject.sign(hostname, options.merge(:allow_dns_alt_names => true)) }.not_to raise_error + + host.state.should == 'signed' + end + + it "should sign the request if allow_dns_alt_names is not set" do + expect { subject.sign(hostname, options) }.not_to raise_error + + host.state.should == 'signed' + end + end + end + + describe "when ca_location is remote" do + let(:options) { {:ca_location => :remote} } + it "should fail if allow-dns-alt-names is specified" do + expect do + subject.sign(hostname, options.merge(:allow_dns_alt_names => true)) + end + end + end + end end --- puppet-2.7.1.orig/spec/unit/provider/ssh_authorized_key/parsed_spec.rb +++ puppet-2.7.1/spec/unit/provider/ssh_authorized_key/parsed_spec.rb @@ -114,15 +114,15 @@ @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 @@ -158,11 +158,11 @@ @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 @@ -173,9 +173,9 @@ @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 --- puppet-2.7.1.orig/spec/integration/defaults_spec.rb +++ puppet-2.7.1/spec/integration/defaults_spec.rb @@ -22,6 +22,17 @@ end end + describe "when :certdnsnames is set" do + it "should not fail" do + expect { Puppet[:certdnsnames] = 'fred:wilma' }.should_not raise_error + end + + it "should warn the value is ignored" do + Puppet.expects(:warning).with {|msg| msg =~ /CVE-2011-3872/ } + Puppet[:certdnsnames] = 'fred:wilma' + end + end + describe "when configuring the :crl" do it "should warn if :cacrl is set to false" do Puppet.expects(:warning) --- puppet-2.7.1.orig/spec/integration/network/handler_spec.rb +++ puppet-2.7.1/spec/integration/network/handler_spec.rb @@ -1,7 +1,7 @@ #!/usr/bin/env rspec require 'spec_helper' -require 'puppet/network/client' +require 'puppet/network/handler' describe Puppet::Network::Handler do %w{ca filebucket fileserver master report runner status}.each do |name| --- puppet-2.7.1.orig/lib/puppet/defaults.rb +++ puppet-2.7.1/lib/puppet/defaults.rb @@ -206,9 +206,58 @@ to the fully qualified domain name.", :call_on_define => true, # Call our hook with the default value, so we're always downcased :hook => proc { |value| raise(ArgumentError, "Certificate names must be lower case; see #1168") unless value == value.downcase }}, - :certdnsnames => ['', "The DNS names on the Server certificate as a colon-separated list. - If it's anything other than an empty string, it will be used as an alias in the created - certificate. By default, only the server gets an alias set up, and only for 'puppet'."], + :certdnsnames => { + :default => '', + :hook => proc do |value| + unless value.nil? or value == '' then + Puppet.warning < < { + :default => '', + :desc => < { :default => "$ssldir/certs", :owner => "service", @@ -437,9 +486,11 @@ authorization system for `puppet master`." ], :ca => [true, "Wether the master should function as a certificate authority."], - :modulepath => {:default => "$confdir/modules:/usr/share/puppet/modules", - :desc => "The search path for modules as a colon-separated list of - directories.", :type => :setting }, # We don't want this to be considered a file, since it's multiple files. + :modulepath => { + :default => "$confdir/modules#{File::PATH_SEPARATOR}/usr/share/puppet/modules", + :desc => "The search path for modules as a list of directories separated by the '#{File::PATH_SEPARATOR}' character.", + :type => :setting # We don't want this to be considered a file, since it's multiple files. + }, :ssl_client_header => ["HTTP_X_CLIENT_DN", "The header containing an authenticated client's SSL DN. Only used with Mongrel. This header must be set by the proxy to the authenticated client's SSL DN (e.g., `/CN=puppet.puppetlabs.com`). --- puppet-2.7.1.orig/lib/puppet/indirector.rb +++ puppet-2.7.1/lib/puppet/indirector.rb @@ -48,4 +48,11 @@ module ClassMethods attr_reader :indirection end + + + # Helper definition for indirections that handle filenames. + BadNameRegexp = Regexp.union(/^\.\./, + %r{[\\/]}, + "\0", + /(?i)^[a-z]:/) end --- puppet-2.7.1.orig/lib/puppet/application/resource.rb +++ puppet-2.7.1/lib/puppet/application/resource.rb @@ -187,18 +187,25 @@ 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 --- puppet-2.7.1.orig/lib/puppet/application/kick.rb +++ puppet-2.7.1/lib/puppet/application/kick.rb @@ -173,8 +173,6 @@ end def main - require 'puppet/network/client' - Puppet.warning "Failed to load ruby LDAP library. LDAP functionality will not be available" unless Puppet.features.ldap? require 'puppet/util/ldap/connection' --- puppet-2.7.1.orig/lib/puppet/application/cert.rb +++ puppet-2.7.1/lib/puppet/application/cert.rb @@ -10,6 +10,7 @@ def subcommand @subcommand end + def subcommand=(name) # Handle the nasty, legacy mapping of "clean" to "destroy". sub = name.to_sym @@ -38,11 +39,15 @@ require 'puppet/ssl/certificate_authority/interface' Puppet::SSL::CertificateAuthority::Interface::INTERFACE_METHODS.reject {|m| m == :destroy }.each do |method| - option("--#{method}", "-#{method.to_s[0,1]}") do + option("--#{method.to_s.gsub('_','-')}", "-#{method.to_s[0,1]}") do self.subcommand = method end end + option("--[no-]allow-dns-alt-names") do |value| + options[:allow_dns_alt_names] = value + end + option("--verbose", "-v") do Puppet::Util::Log.level = :info end @@ -181,8 +186,8 @@ hosts = command_line.args.collect { |h| h.downcase } end begin - @ca.apply(:revoke, :to => hosts) if subcommand == :destroy - @ca.apply(subcommand, :to => hosts, :digest => @digest) + @ca.apply(:revoke, options.merge(:to => hosts)) if subcommand == :destroy + @ca.apply(subcommand, options.merge(:to => hosts, :digest => @digest)) rescue => detail puts detail.backtrace if Puppet[:trace] puts detail.to_s @@ -202,6 +207,15 @@ Puppet::SSL::Host.ca_location = :only end + # If we are generating, and the option came from the CLI, it gets added to + # the data. This will do the right thing for non-local certificates, in + # that the command line but *NOT* the config file option will apply. + if subcommand == :generate + if Puppet.settings.setting(:dns_alt_names).setbycli + options[:dns_alt_names] = Puppet[:dns_alt_names] + end + end + begin @ca = Puppet::SSL::CertificateAuthority.new rescue => detail --- puppet-2.7.1.orig/lib/puppet/ssl/host.rb +++ puppet-2.7.1/lib/puppet/ssl/host.rb @@ -156,11 +156,24 @@ @certificate_request ||= CertificateRequest.indirection.find(name) end + def this_csr_is_for_the_current_host + name == Puppet[:certname].downcase + end + # Our certificate request requires the key but that's all. - def generate_certificate_request + def generate_certificate_request(options = {}) generate_key unless key + + # If this is for the current machine... + if this_csr_is_for_the_current_host + # ...add our configured dns_alt_names + if Puppet[:dns_alt_names] and Puppet[:dns_alt_names] != '' + options[:dns_alt_names] ||= Puppet[:dns_alt_names] + end + end + @certificate_request = CertificateRequest.new(name) - @certificate_request.generate(key.content) + @certificate_request.generate(key.content, options) begin CertificateRequest.indirection.save(@certificate_request) rescue @@ -203,7 +216,7 @@ # should use it to sign our request; else, just try to read # the cert. if ! certificate and ca = Puppet::SSL::CertificateAuthority.instance - ca.sign(self.name) + ca.sign(self.name, true) end end --- puppet-2.7.1.orig/lib/puppet/ssl/certificate_authority.rb +++ puppet-2.7.1/lib/puppet/ssl/certificate_authority.rb @@ -11,6 +11,15 @@ # it can also be seen as a general interface into all of the # SSL stuff. class Puppet::SSL::CertificateAuthority + # We will only sign extensions on this whitelist, ever. Any CSR with a + # requested extension that we don't recognize is rejected, against the risk + # that it will introduce some security issue through our ignorance of it. + # + # Adding an extension to this whitelist simply means we will consider it + # further, not that we will always accept a certificate with an extension + # requested on this list. + RequestExtensionWhitelist = %w{subjectAltName} + require 'puppet/ssl/certificate_factory' require 'puppet/ssl/inventory' require 'puppet/ssl/certificate_revocation_list' @@ -31,6 +40,14 @@ cached_attr(:singleton_instance) { new } end + class CertificateSigningError < RuntimeError + attr_accessor :host + + def initialize(host) + @host = host + end + end + def self.ca? return false unless Puppet[:ca] return false unless Puppet.run_mode.master? @@ -52,7 +69,6 @@ def apply(method, options) raise ArgumentError, "You must specify the hosts to apply to; valid values are an array or the symbol :all" unless options[:to] applier = Interface.new(method, options) - applier.apply(self) end @@ -108,13 +124,16 @@ end # Generate a new certificate. - def generate(name) + def generate(name, options = {}) raise ArgumentError, "A Certificate already exists for #{name}" if Puppet::SSL::Certificate.indirection.find(name) host = Puppet::SSL::Host.new(name) - host.generate_certificate_request + # Pass on any requested subjectAltName field. + san = options[:dns_alt_names] - sign(name) + host = Puppet::SSL::Host.new(name) + host.generate_certificate_request(:dns_alt_names => san) + sign(name, !!san) end # Generate our CA certificate. @@ -123,14 +142,16 @@ host.generate_key unless host.key - # Create a new cert request. We do this - # specially, because we don't want to actually - # save the request anywhere. + # Create a new cert request. We do this specially, because we don't want + # to actually save the request anywhere. request = Puppet::SSL::CertificateRequest.new(host.name) + + # We deliberately do not put any subjectAltName in here: the CA + # certificate absolutely does not need them. --daniel 2011-10-13 request.generate(host.key) # Create a self-signed certificate. - @certificate = sign(host.name, :ca, request) + @certificate = sign(host.name, false, request) # And make sure we initialize our CRL. crl @@ -223,20 +244,34 @@ end # Sign a given certificate request. - def sign(hostname, cert_type = :server, self_signing_csr = nil) + def sign(hostname, allow_dns_alt_names = false, self_signing_csr = nil) # This is a self-signed certificate if self_signing_csr + # # This is a self-signed certificate, which is for the CA. Since this + # # forces the certificate to be self-signed, anyone who manages to trick + # # the system into going through this path gets a certificate they could + # # generate anyway. There should be no security risk from that. csr = self_signing_csr + cert_type = :ca issuer = csr.content else + allow_dns_alt_names = true if hostname == Puppet[:certname].downcase unless csr = Puppet::SSL::CertificateRequest.indirection.find(hostname) raise ArgumentError, "Could not find certificate request for #{hostname}" end + + cert_type = :server issuer = host.certificate.content + + # Make sure that the CSR conforms to our internal signing policies. + # This will raise if the CSR doesn't conform, but just in case... + check_internal_signing_policies(hostname, csr, allow_dns_alt_names) or + raise CertificateSigningError.new(hostname), "CSR had an unknown failure checking internal signing policies, will not sign!" end cert = Puppet::SSL::Certificate.new(hostname) - cert.content = Puppet::SSL::CertificateFactory.new(cert_type, csr.content, issuer, next_serial).result + cert.content = Puppet::SSL::CertificateFactory. + build(cert_type, csr, issuer, next_serial) cert.content.sign(host.key.content, OpenSSL::Digest::SHA1.new) Puppet.notice "Signed certificate request for #{hostname}" @@ -256,6 +291,47 @@ cert end + def check_internal_signing_policies(hostname, csr, allow_dns_alt_names) + # Reject unknown request extensions. + unknown_req = csr.request_extensions. + reject {|x| RequestExtensionWhitelist.include? x["oid"] } + + if unknown_req and not unknown_req.empty? + names = unknown_req.map {|x| x["oid"] }.sort.uniq.join(", ") + raise CertificateSigningError.new(hostname), "CSR has request extensions that are not permitted: #{names}" + end + + # Wildcards: we don't allow 'em at any point. + # + # The stringification here makes the content visible, and saves us having + # to scrobble through the content of the CSR subject field to make sure it + # is what we expect where we expect it. + if csr.content.subject.to_s.include? '*' + raise CertificateSigningError.new(hostname), "CSR subject contains a wildcard, which is not allowed: #{csr.content.subject.to_s}" + end + + unless csr.subject_alt_names.empty? + # If you alt names are allowed, they are required. Otherwise they are + # disallowed. Self-signed certs are implicitly trusted, however. + unless allow_dns_alt_names + raise CertificateSigningError.new(hostname), "CSR '#{csr.name}' contains subject alternative names (#{csr.subject_alt_names.join(', ')}), which are disallowed. Use `puppet cert --allow-dns-alt-names sign #{csr.name}` to sign this request." + end + + # If subjectAltNames are present, validate that they are only for DNS + # labels, not any other kind. + unless csr.subject_alt_names.all? {|x| x =~ /^DNS:/ } + raise CertificateSigningError.new(hostname), "CSR '#{csr.name}' contains a subjectAltName outside the DNS label space: #{csr.subject_alt_names.join(', ')}. To continue, this CSR needs to be cleaned." + end + + # Check for wildcards in the subjectAltName fields too. + if csr.subject_alt_names.any? {|x| x.include? '*' } + raise CertificateSigningError.new(hostname), "CSR '#{csr.name}' subjectAltName contains a wildcard, which is not allowed: #{csr.subject_alt_names.join(', ')} To continue, this CSR needs to be cleaned." + end + end + + return true # good enough for us! + end + # Verify a given host's certificate. def verify(name) unless cert = Puppet::SSL::Certificate.indirection.find(name) --- puppet-2.7.1.orig/lib/puppet/ssl/certificate_request.rb +++ puppet-2.7.1/lib/puppet/ssl/certificate_request.rb @@ -35,8 +35,12 @@ [:s] end + def extension_factory + @ef ||= OpenSSL::X509::ExtensionFactory.new + end + # How to create a certificate request with our system defaults. - def generate(key) + def generate(key, options = {}) Puppet.info "Creating a new SSL certificate request for #{name}" # Support either an actual SSL key, or a Puppet key. @@ -51,6 +55,19 @@ csr.version = 0 csr.subject = OpenSSL::X509::Name.new([["CN", common_name]]) csr.public_key = key.public_key + + if options[:dns_alt_names] then + names = options[:dns_alt_names].split(/\s*,\s*/).map(&:strip) + [name] + names = names.sort.uniq.map {|name| "DNS:#{name}" }.join(", ") + names = extension_factory.create_extension("subjectAltName", names, false) + + extReq = OpenSSL::ASN1::Set([OpenSSL::ASN1::Sequence([names])]) + + # We only support the standard request extensions. If you really need + # msExtReq support, let us know and we can restore them. --daniel 2011-10-10 + csr.add_attribute(OpenSSL::X509::Attribute.new("extReq", extReq)) + end + csr.sign(key, OpenSSL::Digest::MD5.new) raise Puppet::Error, "CSR sign verification failed; you need to clean the certificate request for #{name} on the server" unless csr.verify(key.public_key) @@ -59,4 +76,74 @@ Puppet.info "Certificate Request fingerprint (md5): #{fingerprint}" @content end + + # Return the set of extensions requested on this CSR, in a form designed to + # be useful to Ruby: a hash. Which, not coincidentally, you can pass + # successfully to the OpenSSL constructor later, if you want. + def request_extensions + raise Puppet::Error, "CSR needs content to extract fields" unless @content + + # Prefer the standard extReq, but accept the Microsoft specific version as + # a fallback, if the standard version isn't found. + ext = @content.attributes.find {|x| x.oid == "extReq" } or + @content.attributes.find {|x| x.oid == "msExtReq" } + return [] unless ext + + # Assert the structure and extract the names into an array of arrays. + unless ext.value.is_a? OpenSSL::ASN1::Set + raise Puppet::Error, "In #{ext.oid}, expected Set but found #{ext.value.class}" + end + + unless ext.value.value.is_a? Array + raise Puppet::Error, "In #{ext.oid}, expected Set[Array] but found #{ext.value.value.class}" + end + + unless ext.value.value.length == 1 + raise Puppet::Error, "In #{ext.oid}, expected Set[Array[...]], but found #{ext.value.value.length} items in the array" + end + + san = ext.value.value.first + unless san.is_a? OpenSSL::ASN1::Sequence + raise Puppet::Error, "In #{ext.oid}, expected Set[Array[Sequence[...]]], but found #{san.class}" + end + san = san.value + + # OK, now san should be the array of items, validate that... + index = -1 + san.map do |name| + index += 1 + + unless name.is_a? OpenSSL::ASN1::Sequence + raise Puppet::Error, "In #{ext.oid}, expected request extension record #{index} to be a Sequence, but found #{name.class}" + end + name = name.value + + # OK, turn that into an extension, to unpack the content. Lovely that + # we have to swap the order of arguments to the underlying method, or + # perhaps that the ASN.1 representation chose to pack them in a + # strange order where the optional component comes *earlier* than the + # fixed component in the sequence. + case name.length + when 2 + ev = OpenSSL::X509::Extension.new(name[0].value, name[1].value) + { "oid" => ev.oid, "value" => ev.value } + + when 3 + ev = OpenSSL::X509::Extension.new(name[0].value, name[2].value, name[1].value) + { "oid" => ev.oid, "value" => ev.value, "critical" => ev.critical? } + + else + raise Puppet::Error, "In #{ext.oid}, expected extension record #{index} to have two or three items, but found #{name.length}" + end + end.flatten + end + + def subject_alt_names + @subject_alt_names ||= request_extensions. + select {|x| x["oid"] = "subjectAltName" }. + map {|x| x["value"].split(/\s*,\s*/) }. + flatten. + sort. + uniq + end end --- puppet-2.7.1.orig/lib/puppet/ssl/certificate_factory.rb +++ puppet-2.7.1/lib/puppet/ssl/certificate_factory.rb @@ -2,7 +2,7 @@ # The tedious class that does all the manipulations to the # certificate to correctly sign it. Yay. -class Puppet::SSL::CertificateFactory +module Puppet::SSL::CertificateFactory # How we convert from various units to the required seconds. UNITMAP = { "y" => 365 * 24 * 60 * 60, @@ -11,75 +11,84 @@ "s" => 1 } - attr_reader :name, :cert_type, :csr, :issuer, :serial + def self.build(cert_type, csr, issuer, serial) + # Work out if we can even build the requested type of certificate. + build_extensions = "build_#{cert_type.to_s}_extensions" + respond_to?(build_extensions) or + raise ArgumentError, "#{cert_type.to_s} is an invalid certificate type!" + + # set up the certificate, and start building the content. + cert = OpenSSL::X509::Certificate.new + + cert.version = 2 # X509v3 + cert.subject = csr.content.subject + cert.issuer = issuer.subject + cert.public_key = csr.content.public_key + cert.serial = serial + + # Make the certificate valid as of yesterday, because so many people's + # clocks are out of sync. This gives one more day of validity than people + # might expect, but is better than making every person who has a messed up + # clock fail, and better than having every cert we generate expire a day + # before the user expected it to when they asked for "one year". + cert.not_before = Time.now - (60*60*24) + cert.not_after = Time.now + ttl - def initialize(cert_type, csr, issuer, serial) - @cert_type, @csr, @issuer, @serial = cert_type, csr, issuer, serial + add_extensions_to(cert, csr, issuer, send(build_extensions)) - @name = @csr.subject - end - - # Actually generate our certificate. - def result - @cert = OpenSSL::X509::Certificate.new - - @cert.version = 2 # X509v3 - @cert.subject = @csr.subject - @cert.issuer = @issuer.subject - @cert.public_key = @csr.public_key - @cert.serial = @serial - - build_extensions - - set_ttl - - @cert + return cert end private - # This is pretty ugly, but I'm not really sure it's even possible to do - # it any other way. - def build_extensions - @ef = OpenSSL::X509::ExtensionFactory.new - - @ef.subject_certificate = @cert - - if @issuer.is_a?(OpenSSL::X509::Request) # It's a self-signed cert - @ef.issuer_certificate = @cert - else - @ef.issuer_certificate = @issuer + def self.add_extensions_to(cert, csr, issuer, extensions) + ef = OpenSSL::X509::ExtensionFactory. + new(cert, issuer.is_a?(OpenSSL::X509::Request) ? cert : issuer) + + # Extract the requested extensions from the CSR. + requested_exts = csr.request_extensions.inject({}) do |hash, re| + hash[re["oid"]] = [re["value"], re["critical"]] + hash end - @subject_alt_name = [] - @key_usage = nil - @ext_key_usage = nil - @extensions = [] - - method = "add_#{@cert_type.to_s}_extensions" - - begin - send(method) - rescue NoMethodError - raise ArgumentError, "#{@cert_type} is an invalid certificate type" + # Produce our final set of extensions. We deliberately order these to + # build the way we want: + # 1. "safe" default values, like the comment, that no one cares about. + # 2. request extensions, from the CSR + # 3. extensions based on the type we are generating + # 4. overrides, which we always want to have in their form + # + # This ordering *is* security-critical, but we want to allow the user + # enough rope to shoot themselves in the foot, if they want to ignore our + # advice and externally approve a CSR that sets the basicConstraints. + # + # Swapping the order of 2 and 3 would ensure that you couldn't slip a + # certificate through where the CA constraint was true, though, if + # something went wrong up there. --daniel 2011-10-11 + defaults = { "nsComment" => "Puppet Ruby/OpenSSL Internal Certificate" } + override = { "subjectKeyIdentifier" => "hash" } + + exts = [defaults, requested_exts, extensions, override]. + inject({}) {|ret, val| ret.merge(val) } + + cert.extensions = exts.map do |oid, val| + val, crit = *val + val = val.join(', ') unless val.is_a? String + + # Enforce the X509v3 rules about subjectAltName being critical: + # specifically, it SHOULD NOT be critical if we have a subject, which we + # always do. --daniel 2011-10-18 + crit = false if oid == "subjectAltName" + + # val can be either a string, or [string, critical], and this does the + # right thing regardless of what we get passed. + ef.create_ext(oid, val, crit) end - - @extensions << @ef.create_extension("nsComment", "Puppet Ruby/OpenSSL Generated Certificate") - @extensions << @ef.create_extension("basicConstraints", @basic_constraint, true) - @extensions << @ef.create_extension("subjectKeyIdentifier", "hash") - @extensions << @ef.create_extension("keyUsage", @key_usage.join(",")) if @key_usage - @extensions << @ef.create_extension("extendedKeyUsage", @ext_key_usage.join(",")) if @ext_key_usage - @extensions << @ef.create_extension("subjectAltName", @subject_alt_name.join(",")) if ! @subject_alt_name.empty? - - @cert.extensions = @extensions - - # for some reason this _must_ be the last extension added - @extensions << @ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always") if @cert_type == :ca end # TTL for new certificates in seconds. If config param :ca_ttl is set, # use that, otherwise use :ca_days for backwards compatibility - def ttl + def self.ttl ttl = Puppet.settings[:ca_ttl] return ttl unless ttl.is_a?(String) @@ -89,57 +98,69 @@ $1.to_i * UNITMAP[$2] end - def set_ttl - # Make the certificate valid as of yesterday, because - # so many people's clocks are out of sync. - from = Time.now - (60*60*24) - @cert.not_before = from - @cert.not_after = from + ttl - end - # Woot! We're a CA. - def add_ca_extensions - @basic_constraint = "CA:TRUE" - @key_usage = %w{cRLSign keyCertSign} + def self.build_ca_extensions + { + # This was accidentally omitted in the previous version of this code: an + # effort was made to add it last, but that actually managed to avoid + # adding it to the certificate at all. + # + # We have some sort of bug, which means that when we add it we get a + # complaint that the issuer keyid can't be fetched, which breaks all + # sorts of things in our test suite and, e.g., bootstrapping the CA. + # + # http://tools.ietf.org/html/rfc5280#section-4.2.1.1 says that, to be a + # conforming CA we MAY omit the field if we are self-signed, which I + # think gives us a pass in the specific case. + # + # It also notes that we MAY derive the ID from the subject and serial + # number of the issuer, or from the key ID, and we definitely have the + # former data, should we want to restore this... + # + # Anyway, preserving this bug means we don't risk breaking anything in + # the field, even though it would be nice to have. --daniel 2011-10-11 + # + # "authorityKeyIdentifier" => "keyid:always,issuer:always", + "keyUsage" => [%w{cRLSign keyCertSign}, true], + "basicConstraints" => ["CA:TRUE", true], + } end # We're a terminal CA, probably not self-signed. - def add_terminalsubca_extensions - @basic_constraint = "CA:TRUE,pathlen:0" - @key_usage = %w{cRLSign keyCertSign} + def self.build_terminalsubca_extensions + { + "keyUsage" => [%w{cRLSign keyCertSign}, true], + "basicConstraints" => ["CA:TRUE,pathlen:0", true], + } end # We're a normal server. - def add_server_extensions - @basic_constraint = "CA:FALSE" - dnsnames = Puppet[:certdnsnames] - name = @name.to_s.sub(%r{/CN=},'') - if dnsnames != "" - dnsnames.split(':').each { |d| @subject_alt_name << 'DNS:' + d } - @subject_alt_name << 'DNS:' + name # Add the fqdn as an alias - elsif name == Facter.value(:fqdn) # we're a CA server, and thus probably the server - @subject_alt_name << 'DNS:' + "puppet" # Add 'puppet' as an alias - @subject_alt_name << 'DNS:' + name # Add the fqdn as an alias - @subject_alt_name << 'DNS:' + name.sub(/^[^.]+./, "puppet.") # add puppet.domain as an alias - end - @key_usage = %w{digitalSignature keyEncipherment} - @ext_key_usage = %w{serverAuth clientAuth emailProtection} + def self.build_server_extensions + { + "keyUsage" => [%w{digitalSignature keyEncipherment}, true], + "extendedKeyUsage" => [%w{serverAuth clientAuth}, true], + "basicConstraints" => ["CA:FALSE", true], + } end # Um, no idea. - def add_ocsp_extensions - @basic_constraint = "CA:FALSE" - @key_usage = %w{nonRepudiation digitalSignature} - @ext_key_usage = %w{serverAuth OCSPSigning} + def self.build_ocsp_extensions + { + "keyUsage" => [%w{nonRepudiation digitalSignature}, true], + "extendedKeyUsage" => [%w{serverAuth OCSPSigning}, true], + "basicConstraints" => ["CA:FALSE", true], + } end # Normal client. - def add_client_extensions - @basic_constraint = "CA:FALSE" - @key_usage = %w{nonRepudiation digitalSignature keyEncipherment} - @ext_key_usage = %w{clientAuth emailProtection} - - @extensions << @ef.create_extension("nsCertType", "client,email") + def self.build_client_extensions + { + "keyUsage" => [%w{nonRepudiation digitalSignature keyEncipherment}, true], + # We don't seem to use this, but that seems much more reasonable here... + "extendedKeyUsage" => [%w{clientAuth emailProtection}, true], + "basicConstraints" => ["CA:FALSE", true], + "nsCertType" => "client,email", + } end end --- puppet-2.7.1.orig/lib/puppet/ssl/certificate.rb +++ puppet-2.7.1/lib/puppet/ssl/certificate.rb @@ -27,6 +27,12 @@ [:s] end + def subject_alt_names + alts = content.extensions.find{|ext| ext.oid == "subjectAltName"} + return [] unless alts + alts.value.split(/\s*,\s*/) + end + def expiration return nil unless content content.not_after --- puppet-2.7.1.orig/lib/puppet/ssl/certificate_authority/interface.rb +++ puppet-2.7.1/lib/puppet/ssl/certificate_authority/interface.rb @@ -9,7 +9,7 @@ class InterfaceError < ArgumentError; end - attr_reader :method, :subjects, :digest + attr_reader :method, :subjects, :digest, :options # Actually perform the work. def apply(ca) @@ -35,49 +35,94 @@ raise InterfaceError, "It makes no sense to generate all hosts; you must specify a list" if subjects == :all subjects.each do |host| - ca.generate(host) + ca.generate(host, options) end end def initialize(method, options) self.method = method - self.subjects = options[:to] - @digest = options[:digest] || :MD5 + self.subjects = options.delete(:to) + @digest = options.delete(:digest) || :MD5 + @options = options end # List the hosts. def list(ca) - unless subjects - puts ca.waiting?.join("\n") - return nil - end - signed = ca.list requests = ca.waiting? - if subjects == :all + case subjects + when :all hosts = [signed, requests].flatten - elsif subjects == :signed + when :signed hosts = signed.flatten + when nil + hosts = requests else hosts = subjects end + certs = {:signed => {}, :invalid => {}, :request => {}} + + return if hosts.empty? + hosts.uniq.sort.each do |host| - invalid = false begin ca.verify(host) unless requests.include?(host) rescue Puppet::SSL::CertificateAuthority::CertificateVerificationError => details - invalid = details.to_s + verify_error = details.to_s end - if not invalid and signed.include?(host) - puts "+ #{host} (#{ca.fingerprint(host, @digest)})" - elsif invalid - puts "- #{host} (#{ca.fingerprint(host, @digest)}) (#{invalid})" + + if verify_error + cert = Puppet::SSL::Certificate.indirection.find(host) + certs[:invalid][host] = [cert, verify_error] + elsif signed.include?(host) + cert = Puppet::SSL::Certificate.indirection.find(host) + certs[:signed][host] = cert else - puts "#{host} (#{ca.fingerprint(host, @digest)})" + req = Puppet::SSL::CertificateRequest.indirection.find(host) + certs[:request][host] = req end end + + names = certs.values.map(&:keys).flatten + + name_width = names.sort_by(&:length).last.length rescue 0 + + output = [:request, :signed, :invalid].map do |type| + next if certs[type].empty? + + certs[type].map do |host,info| + format_host(ca, host, type, info, name_width) + end + end.flatten.compact.sort.join("\n") + + puts output + end + + def format_host(ca, host, type, info, width) + certish, verify_error = info + alt_names = case type + when :signed + certish.subject_alt_names + when :request + certish.subject_alt_names + else + [] + end + + alt_names.delete(host) + + alt_str = "(alt names: #{alt_names.join(', ')})" unless alt_names.empty? + + glyph = {:signed => '+', :request => ' ', :invalid => '-'}[type] + + name = host.ljust(width) + fingerprint = "(#{ca.fingerprint(host, @digest)})" + + explanation = "(#{verify_error})" if verify_error + + [glyph, name, fingerprint, alt_str, explanation].compact.join(' ') end # Set the method to apply. @@ -113,7 +158,7 @@ list = subjects == :all ? ca.waiting? : subjects raise InterfaceError, "No waiting certificate requests to sign" if list.empty? list.each do |host| - ca.sign(host) + ca.sign(host, options[:allow_dns_alt_names]) end end --- puppet-2.7.1.orig/lib/puppet/indirector/ssl_file.rb +++ puppet-2.7.1/lib/puppet/indirector/ssl_file.rb @@ -52,8 +52,12 @@ (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 --- puppet-2.7.1.orig/lib/puppet/indirector/yaml.rb +++ puppet-2.7.1/lib/puppet/indirector/yaml.rb @@ -43,6 +43,11 @@ # 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 --- puppet-2.7.1.orig/lib/puppet/network/rest_authconfig.rb +++ puppet-2.7.1/lib/puppet/network/rest_authconfig.rb @@ -18,6 +18,7 @@ { :acl => "/certificate/", :method => :find, :authenticated => false }, { :acl => "/certificate_request", :method => [:find, :save], :authenticated => false }, { :acl => "/status", :method => [:find], :authenticated => true }, + { :acl => "/resource", :method => [:find, :save, :search], :authenticated => true }, ] def self.main --- puppet-2.7.1.orig/lib/puppet/network/handler/ca.rb +++ puppet-2.7.1/lib/puppet/network/handler/ca.rb @@ -1,10 +1,7 @@ require 'openssl' require 'puppet' -require 'puppet/sslcertificates' require 'xmlrpc/server' - -# Much of this was taken from QuickCert: -# http://segment7.net/projects/ruby/QuickCert/ +require 'puppet/network/handler' class Puppet::Network::Handler class CA < Handler @@ -18,73 +15,17 @@ iface.add_method("array getcert(csr)") } - def autosign - if defined?(@autosign) - @autosign - else - Puppet[:autosign] - end - end - - # FIXME autosign? should probably accept both hostnames and IP addresses - def autosign?(hostname) - # simple values are easy - if autosign == true or autosign == false - return autosign - end - - # we only otherwise know how to handle files - unless autosign =~ /^\// - raise Puppet::Error, "Invalid autosign value #{autosign.inspect}" - end - - unless FileTest.exists?(autosign) - unless defined?(@@warnedonautosign) - @@warnedonautosign = true - Puppet.info "Autosign is enabled but #{autosign} is missing" - end - return false - end - auth = Puppet::Network::AuthStore.new - File.open(autosign) { |f| - f.each { |line| - next if line =~ /^\s*#/ - next if line =~ /^\s*$/ - auth.allow(line.chomp) - } - } - - # for now, just cheat and pass a fake IP address to allowed? - auth.allowed?(hostname, "127.1.1.1") - end - def initialize(hash = {}) Puppet.settings.use(:main, :ssl, :ca) - @autosign = hash[:autosign] if hash.include? :autosign - @ca = Puppet::SSLCertificates::CA.new(hash) + @ca = Puppet::SSL::CertificateAuthority.instance end # our client sends us a csr, and we either store it for later signing, # or we sign it right away def getcert(csrtext, client = nil, clientip = nil) - csr = OpenSSL::X509::Request.new(csrtext) - - # Use the hostname from the CSR, not from the network. - subject = csr.subject - - nameary = subject.to_a.find { |ary| - ary[0] == "CN" - } - - if nameary.nil? - Puppet.err( - "Invalid certificate request: could not retrieve server name" - ) - return "invalid" - end - - hostname = nameary[1] + csr = Puppet::SSL::CertificateRequest.from_s(csrtext) + hostname = csr.name unless @ca Puppet.notice "Host #{hostname} asked for signing from non-CA master" @@ -93,57 +34,26 @@ # We used to save the public key, but it's basically unnecessary # and it mucks with the permissions requirements. - # save_pk(hostname, csr.public_key) - - certfile = File.join(Puppet[:certdir], [hostname, "pem"].join(".")) # first check to see if we already have a signed cert for the host - cert, cacert = ca.getclientcert(hostname) - if cert and cacert + cert = Puppet::SSL::Certificate.indirection.find(hostname) + cacert = Puppet::SSL::Certificate.indirection.find(@ca.host.name) + + if cert Puppet.info "Retrieving existing certificate for #{hostname}" - unless csr.public_key.to_s == cert.public_key.to_s + unless csr.content.public_key.to_s == cert.content.public_key.to_s raise Puppet::Error, "Certificate request does not match existing certificate; run 'puppetca --clean #{hostname}'." end - return [cert.to_pem, cacert.to_pem] - elsif @ca - if self.autosign?(hostname) or client.nil? - Puppet.info "Signing certificate for CA server" if client.nil? - # okay, we don't have a signed cert - # if we're a CA and autosign is turned on, then go ahead and sign - # the csr and return the results - Puppet.info "Signing certificate for #{hostname}" - cert, cacert = @ca.sign(csr) - #Puppet.info "Cert: #{cert.class}; Cacert: #{cacert.class}" - return [cert.to_pem, cacert.to_pem] - else # just write out the csr for later signing - if @ca.getclientcsr(hostname) - Puppet.info "Not replacing existing request from #{hostname}" - else - Puppet.notice "Host #{hostname} has a waiting certificate request" - @ca.storeclientcsr(csr) - end - return ["", ""] - end + [cert.to_s, cacert.to_s] else - raise "huh?" - end - end - - private + Puppet::SSL::CertificateRequest.indirection.save(csr) - # Save the public key. - def save_pk(hostname, public_key) - pkeyfile = File.join(Puppet[:publickeydir], [hostname, "pem"].join('.')) - - if FileTest.exists?(pkeyfile) - currentkey = File.open(pkeyfile) { |k| k.read } - unless currentkey == public_key.to_s - raise Puppet::Error, "public keys for #{hostname} differ" + # We determine whether we signed the csr by checking if there's a certificate for it + if cert = Puppet::SSL::Certificate.indirection.find(hostname) + [cert.to_s, cacert.to_s] + else + nil end - else - File.open(pkeyfile, "w", 0644) { |f| - f.print public_key.to_s - } end end end --- puppet-2.7.1.orig/lib/puppet/network/handler/runner.rb +++ puppet-2.7.1/lib/puppet/network/handler/runner.rb @@ -1,4 +1,5 @@ require 'puppet/run' +require 'puppet/network/handler' class Puppet::Network::Handler class MissingMasterError < RuntimeError; end # Cannot find the master client --- puppet-2.7.1.orig/lib/puppet/network/handler/master.rb +++ puppet-2.7.1/lib/puppet/network/handler/master.rb @@ -1,6 +1,5 @@ require 'openssl' require 'puppet' -require 'puppet/sslcertificates' require 'xmlrpc/server' require 'yaml' @@ -33,8 +32,6 @@ args[:Local] = true - @ca = (hash.include?(:CA) and hash[:CA]) ? Puppet::SSLCertificates::CA.new : nil - # This is only used by the cfengine module, or if --loadclasses was # specified in +puppet+. args[:Classes] = hash[:Classes] if hash.include?(:Classes) --- puppet-2.7.1.orig/lib/puppet/util/monkey_patches.rb +++ puppet-2.7.1/lib/puppet/util/monkey_patches.rb @@ -103,6 +103,8 @@ ret += tmp.combination(num - 1).map{|a| a.unshift(e) } end end unless method_defined? :combination + + alias :count :length unless method_defined? :count end @@ -111,3 +113,20 @@ Proc.new { |*args| args.shift.__send__(self, *args) } end unless method_defined? :to_proc end + + +class String + def lines(separator = $/) + lines = split(separator) + block_given? and lines.each {|line| yield line } + lines + end +end + +class IO + def lines(separator = $/) + lines = split(separator) + block_given? and lines.each {|line| yield line } + lines + end +end --- puppet-2.7.1.orig/lib/puppet/util/settings.rb +++ puppet-2.7.1/lib/puppet/util/settings.rb @@ -495,6 +495,11 @@ end type = legacy_to_mode(type, param) @sync.synchronize do # yay, thread-safe + # Allow later inspection to determine if the setting was set on the + # command line, or through some other code path. Used for the + # `dns_alt_names` option during cert generate. --daniel 2011-10-18 + setting.setbycli = true if type == :cli + @values[type][param] = value @cache.clear --- puppet-2.7.1.orig/lib/puppet/type/k5login.rb +++ puppet-2.7.1/lib/puppet/type/k5login.rb @@ -79,7 +79,9 @@ 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 --- puppet-2.7.1.orig/lib/puppet/type/file.rb +++ puppet-2.7.1/lib/puppet/type/file.rb @@ -6,7 +6,6 @@ require 'puppet/network/handler' require 'puppet/util/diff' require 'puppet/util/checksums' -require 'puppet/network/client' require 'puppet/util/backups' Puppet::Type.newtype(:file) do --- puppet-2.7.1.orig/lib/puppet/face/certificate.rb +++ puppet-2.7.1/lib/puppet/face/certificate.rb @@ -47,10 +47,21 @@ $ puppet certificate generate somenode.puppetlabs.lan --ca-location remote EOT + # Duplicate the option here explicitly to distinguish if it was passed arg + # us vs. set in the config file. + option "--dns-alt-names NAMES" do + summary "Additional DNS names to add to the certificate request" + description Puppet.settings.setting(:dns_alt_names).desc + end + when_invoked do |name, options| host = Puppet::SSL::Host.new(name) - host.generate_certificate_request - host.certificate_request.class.indirection.save(host.certificate_request) + + # If dns_alt_names are specified via the command line, we will always add + # them. Otherwise, they will default to the config file setting iff this + # cert is for the host we're running on. + + host.generate_certificate_request(:dns_alt_names => options[:dns_alt_names]) end end @@ -82,10 +93,28 @@ $ puppet certificate sign somenode.puppetlabs.lan --ca-location remote EOT + option("--[no-]allow-dns-alt-names") do + summary "Whether or not to accept DNS alt names in the certificate request" + end + when_invoked do |name, options| host = Puppet::SSL::Host.new(name) - host.desired_state = 'signed' - Puppet::SSL::Host.indirection.save(host) + if options[:ca_location] == :remote + if options[:allow_dns_alt_names] + raise ArgumentError, "--allow-dns-alt-names may not be specified with a remote CA" + end + + host.desired_state = 'signed' + Puppet::SSL::Host.indirection.save(host) + else + # We have to do this case manually because we need to specify + # allow_dns_alt_names. + unless ca = Puppet::SSL::CertificateAuthority.instance + raise ArgumentError, "This process is not configured as a certificate authority" + end + + ca.sign(name, options[:allow_dns_alt_names]) + end end end --- puppet-2.7.1.orig/lib/puppet/provider/service/init.rb +++ puppet-2.7.1/lib/puppet/provider/service/init.rb @@ -134,7 +134,15 @@ # we just return that; otherwise, we return false, which causes it to # fallback to other mechanisms. def statuscmd - (@resource[:hasstatus] == :true) && [initscript, :status] + if @resource[:hasstatus] == :true then + # Workaround the fact that initctl status command doesn't return + # proper exit codes. Can be removed once LP: #552786 is fixed. + if File.symlink?(initscript) && File.readlink(initscript) == "/lib/init/upstart-job" then + ['sh', '-c', "LANG=C invoke-rc.d #{File::basename(initscript)} status | grep -q '^#{File::basename(initscript)}.*running'" ] + else + [initscript, :status ] + end + end end end --- puppet-2.7.1.orig/lib/puppet/provider/ssh_authorized_key/parsed.rb +++ puppet-2.7.1/lib/puppet/provider/ssh_authorized_key/parsed.rb @@ -56,21 +56,22 @@ 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 --- puppet-2.7.1.orig/acceptance/tests/ticket_5477_master_not_dectect_sitepp.rb +++ puppet-2.7.1/acceptance/tests/ticket_5477_master_not_dectect_sitepp.rb @@ -10,37 +10,27 @@ step "Master: kill running Puppet Master" on master, "ps -U puppet | awk '/puppet/ { print \$1 }' | xargs kill" -# Run tests against Master first -step "Master: mv site.pp file to /tmp, if existing" -on master, "if [ -e /etc/puppet/manifests/site.pp ] ; then mv /etc/puppet/manifests/site.pp /tmp/site.pp-5477 ; fi" - -# Start Puppet Master -#step "Master: Run Puppet Master in verbose mode" -#on master, puppet_master("--verbose") -step "Master: Start Puppet Master" -on master, puppet_master("--certdnsnames=\"puppet:$(hostname -s):$(hostname -f)\" --verbose") - -# Allow puppet server to start accepting conections -sleep 10 - -# Run test on Agents -step "Agent: agent --test" -agents.each { |agent| - on agent, puppet_agent("--test") -} - -# Create a new site.pp -step "Master: create basic site.pp file" -on master, "echo 'notify{ticket_5477_notify:}' > /etc/puppet/manifests/site.pp" - -sleep 20 - -step "Agent: puppet agent --test" -agents.each { |agent| - on agent, "puppet agent -t", :acceptable_exit_codes => [2] - fail_test "Site.pp not detect at Master?" unless - stdout.include? 'ticket_5477_notify' -} +on master, "rm -f #{manifest_file}" +on hosts, "rm -rf /etc/puppet/ssl" + +with_master_running_on(master, "--manifest #{manifest_file} --dns_alt_names=\"puppet, $(hostname -s), $(hostname -f)\" --verbose --filetimeout 1 --autosign true") do + # Run test on Agents + step "Agent: agent --test" + on agents, puppet_agent("--test --server #{master}") + + # Create a new site.pp + step "Master: create basic site.pp file" + on master, "echo 'notify{ticket_5477_notify:}' > /etc/puppet/manifests/site.pp" + + sleep 20 + + step "Agent: puppet agent --test" + agents.each { |agent| + on agent, "puppet agent -t", :acceptable_exit_codes => [2] + fail_test "Site.pp not detect at Master?" unless + stdout.include? 'ticket_5477_notify' + } +end step "Clean-up site.pp" on master, "rm /etc/puppet/manifests/site.pp" --- puppet-2.7.1.orig/acceptance/tests/ticket_7117_broke_env_criteria_authconf.rb +++ puppet-2.7.1/acceptance/tests/ticket_7117_broke_env_criteria_authconf.rb @@ -18,25 +18,18 @@ on master, puppet_master("--certdnsnames=\"puppet:$(hostname -s):$(hostname -f)\" --verbose --noop") # allow Master to start and initialize environment -step "Verify Puppet Master is ready to accept connections" -host=agents.first -time1 = Time.new -until - on(host, "curl -k https://#{master}:8140") do - sleep 1 - end -time2 = Time.new -elapsed = time2 - time1 -Log.notify "Slept for #{elapsed} seconds waiting for Puppet Master to become ready" +on hosts, "rm -rf /etc/puppet/ssl" -# Run test on Agents -step "Agent: agent --test" -on agents, puppet_agent("--test") +with_master_running_on(master, "--dns_alt_names=\"puppet, $(hostname -s), $(hostname -f)\" --rest_authconfig /tmp/auth.conf-7117 --verbose --autosign true") do + # Run test on Agents + step "Run agent to upload facts" + on agents, puppet_agent("--test --server #{master}") -step "Fetch agent facts from Puppet Master" -agents.each do |host| - on(host, "curl -k -H \"Accept: yaml\" https://#{master}:8140/override/facts/\`hostname -f\`") do - assert_match(/--- !ruby\/object:Puppet::Node::Facts/, stdout, "Agent Facts not returned for #{host}") + step "Fetch agent facts from Puppet Master" + agents.each do |host| + on(host, "curl -k -H \"Accept: yaml\" https://#{master}:8140/override/facts/\`hostname -f\`") do + assert_match(/--- !ruby\/object:Puppet::Node::Facts/, stdout, "Agent Facts not returned for #{host}") + end end end --- puppet-2.7.1.orig/.pc/.quilt_patches +++ puppet-2.7.1/.pc/.quilt_patches @@ -0,0 +1 @@ +debian/patches --- puppet-2.7.1.orig/.pc/applied-patches +++ puppet-2.7.1/.pc/applied-patches @@ -0,0 +1,7 @@ +debian-changes +CVE-2011-3848.patch +CVE-2011-3869.patch +CVE-2011-3870.patch +CVE-2011-3871.patch +secure-indirector-file-backed-terminus-base-cla.patch +CVE-2011-3872.patch --- puppet-2.7.1.orig/.pc/.quilt_series +++ puppet-2.7.1/.pc/.quilt_series @@ -0,0 +1 @@ +series --- puppet-2.7.1.orig/.pc/.version +++ puppet-2.7.1/.pc/.version @@ -0,0 +1 @@ +2 --- puppet-2.7.1.orig/.pc/CVE-2011-3869.patch/lib/puppet/type/k5login.rb +++ puppet-2.7.1/.pc/CVE-2011-3869.patch/lib/puppet/type/k5login.rb @@ -0,0 +1,85 @@ +# Plug-in type for handling k5login files + +Puppet::Type.newtype(:k5login) do + @doc = "Manage the `.k5login` file for a user. Specify the full path to + the `.k5login` file as the name and an array of principals as the + property principals." + + ensurable + + # Principals that should exist in the file + newproperty(:principals, :array_matching => :all) do + desc "The principals present in the `.k5login` file." + end + + # The path/name of the k5login file + newparam(:path) do + isnamevar + desc "The path to the file to manage. Must be fully qualified." + + validate do |value| + unless value =~ /^#{File::SEPARATOR}/ + raise Puppet::Error, "File paths must be fully qualified" + end + end + end + + # To manage the mode of the file + newproperty(:mode) do + desc "Manage the k5login file's mode" + defaultto { "644" } + end + + provide(:k5login) do + desc "The k5login provider is the only provider for the k5login + type." + + # Does this file exist? + def exists? + File.exists?(@resource[:name]) + end + + # create the file + def create + write(@resource.should(:principals)) + should_mode = @resource.should(:mode) + unless self.mode == should_mode + self.mode = should_mode + end + end + + # remove the file + def destroy + File.unlink(@resource[:name]) + end + + # Return the principals + def principals(dummy_argument=:work_arround_for_ruby_GC_bug) + if File.exists?(@resource[:name]) + File.readlines(@resource[:name]).collect { |line| line.chomp } + else + :absent + end + end + + # Write the principals out to the k5login file + def principals=(value) + write(value) + end + + # Return the mode as an octal string, not as an integer + def mode + "%o" % (File.stat(@resource[:name]).mode & 007777) + end + + # Set the file mode, converting from a string to an integer. + def mode=(value) + File.chmod(Integer("0#{value}"), @resource[:name]) + end + + private + def write(value) + File.open(@resource[:name], "w") { |f| f.puts value.join("\n") } + end + end +end --- puppet-2.7.1.orig/.pc/CVE-2011-3870.patch/spec/unit/provider/ssh_authorized_key/parsed_spec.rb +++ puppet-2.7.1/.pc/CVE-2011-3870.patch/spec/unit/provider/ssh_authorized_key/parsed_spec.rb @@ -0,0 +1,211 @@ +#!/usr/bin/env rspec +require 'spec_helper' +require 'shared_behaviours/all_parsedfile_providers' +require 'puppet_spec/files' + +provider_class = Puppet::Type.type(:ssh_authorized_key).provider(:parsed) + +describe provider_class do + include PuppetSpec::Files + + before :each do + @sshauthkey_class = Puppet::Type.type(:ssh_authorized_key) + @provider = @sshauthkey_class.provider(:parsed) + @keyfile = tmpfile('authorized_keys') + @provider.any_instance.stubs(:target).returns @keyfile + @user = 'random_bob' + Puppet::Util.stubs(:uid).with(@user).returns 12345 + end + + after :each do + @provider.initvars + end + + def mkkey(args) + args[:target] = @keyfile + args[:user] = @user + resource = Puppet::Type.type(:ssh_authorized_key).new(args) + key = @provider.new(resource) + args.each do |p,v| + key.send(p.to_s + "=", v) + end + key + end + + def genkey(key) + @provider.stubs(:filetype).returns(Puppet::Util::FileType::FileTypeRam) + File.stubs(:chown) + File.stubs(:chmod) + Puppet::Util::SUIDManager.stubs(:asuser).yields + key.flush + @provider.target_object(@keyfile).read + end + + it_should_behave_like "all parsedfile providers", provider_class + + it "should be able to generate a basic authorized_keys file" do + + key = mkkey(:name => "Just Testing", + :key => "AAAAfsfddsjldjgksdflgkjsfdlgkj", + :type => "ssh-dss", + :ensure => :present, + :options => [:absent] + ) + + genkey(key).should == "ssh-dss AAAAfsfddsjldjgksdflgkjsfdlgkj Just Testing\n" + end + + it "should be able to generate a authorized_keys file with options" do + + key = mkkey(:name => "root@localhost", + :key => "AAAAfsfddsjldjgksdflgkjsfdlgkj", + :type => "ssh-rsa", + :ensure => :present, + :options => ['from="192.168.1.1"', "no-pty", "no-X11-forwarding"] + ) + + genkey(key).should == "from=\"192.168.1.1\",no-pty,no-X11-forwarding ssh-rsa AAAAfsfddsjldjgksdflgkjsfdlgkj root@localhost\n" + end + + it "should be able to parse options containing commas via its parse_options method" do + options = %w{from="host1.reductlivelabs.com,host.reductivelabs.com" command="/usr/local/bin/run" ssh-pty} + optionstr = options.join(", ") + + @provider.parse_options(optionstr).should == options + end + + it "should use '' as name for entries that lack a comment" do + line = "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAut8aOSxenjOqF527dlsdHWV4MNoAsX14l9M297+SQXaQ5Z3BedIxZaoQthkDALlV/25A1COELrg9J2MqJNQc8Xe9XQOIkBQWWinUlD/BXwoOTWEy8C8zSZPHZ3getMMNhGTBO+q/O+qiJx3y5cA4MTbw2zSxukfWC87qWwcZ64UUlegIM056vPsdZWFclS9hsROVEa57YUMrehQ1EGxT4Z5j6zIopufGFiAPjZigq/vqgcAqhAKP6yu4/gwO6S9tatBeEjZ8fafvj1pmvvIplZeMr96gHE7xS3pEEQqnB3nd4RY7AF6j9kFixnsytAUO7STPh/M3pLiVQBN89TvWPQ==" + + @provider.parse(line)[0][:name].should == "" + end +end + +describe provider_class do + before :each do + @resource = stub("resource", :name => "foo") + @resource.stubs(:[]).returns "foo" + @resource.class.stubs(:key_attributes).returns( [:name] ) + + @provider = provider_class.new(@resource) + provider_class.stubs(:filetype).returns(Puppet::Util::FileType::FileTypeRam) + Puppet::Util::SUIDManager.stubs(:asuser).yields + end + + describe "when flushing" do + before :each do + # Stub file and directory operations + Dir.stubs(:mkdir) + File.stubs(:chmod) + File.stubs(:chown) + end + + describe "and both a user and a target have been specified" do + before :each do + Puppet::Util.stubs(:uid).with("random_bob").returns 12345 + @resource.stubs(:should).with(:user).returns "random_bob" + target = "/tmp/.ssh_dir/place_to_put_authorized_keys" + @resource.stubs(:should).with(:target).returns target + end + + it "should create the directory" do + File.stubs(:exist?).with("/tmp/.ssh_dir").returns false + Dir.expects(:mkdir).with("/tmp/.ssh_dir", 0700) + @provider.flush + end + + it "should chown the directory to the user" do + uid = Puppet::Util.uid("random_bob") + File.expects(:chown).with(uid, nil, "/tmp/.ssh_dir") + @provider.flush + end + + it "should 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") + @provider.flush + end + + it "should chmod the key file to 0600" do + File.expects(:chmod).with(0600, "/tmp/.ssh_dir/place_to_put_authorized_keys") + @provider.flush + end + end + + describe "and a user has been specified with no target" do + before :each do + @resource.stubs(:should).with(:user).returns "nobody" + @resource.stubs(:should).with(:target).returns nil + # + # I'd like to use random_bob here and something like + # + # File.stubs(:expand_path).with("~random_bob/.ssh").returns "/users/r/random_bob/.ssh" + # + # but mocha objects strenuously to stubbing File.expand_path + # so I'm left with using nobody. + @dir = File.expand_path("~nobody/.ssh") + end + + it "should create the directory if it doesn't exist" do + File.stubs(:exist?).with(@dir).returns false + Dir.expects(:mkdir).with(@dir,0700) + @provider.flush + end + + it "should not create or chown the directory if it already exist" do + File.stubs(:exist?).with(@dir).returns false + Dir.expects(:mkdir).never + @provider.flush + end + + it "should 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) + @provider.flush + end + + it "should not create or chown the directory if it already exist" do + File.stubs(:exist?).with(@dir).returns false + Dir.expects(:mkdir).never + File.expects(:chown).never + @provider.flush + end + + it "should 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")) + @provider.flush + end + + it "should chmod the key file to 0600" do + File.expects(:chmod).with(0600, File.expand_path("~nobody/.ssh/authorized_keys")) + @provider.flush + end + end + + describe "and a target has been specified with no user" do + before :each do + @resource.stubs(:should).with(:user).returns nil + @resource.stubs(:should).with(:target).returns("/tmp/.ssh_dir/place_to_put_authorized_keys") + end + + it "should raise an error" do + proc { @provider.flush }.should raise_error + end + end + + describe "and a invalid user has been specified with no target" do + before :each do + @resource.stubs(:should).with(:user).returns "thisusershouldnotexist" + @resource.stubs(:should).with(:target).returns nil + end + + it "should catch an exception and raise a Puppet error" do + lambda { @provider.flush }.should raise_error(Puppet::Error) + end + end + + end +end --- puppet-2.7.1.orig/.pc/CVE-2011-3870.patch/lib/puppet/provider/ssh_authorized_key/parsed.rb +++ puppet-2.7.1/.pc/CVE-2011-3870.patch/lib/puppet/provider/ssh_authorized_key/parsed.rb @@ -0,0 +1,96 @@ +require 'puppet/provider/parsedfile' + + + Puppet::Type.type(:ssh_authorized_key).provide( + :parsed, + :parent => Puppet::Provider::ParsedFile, + :filetype => :flat, + + :default_target => '' +) do + desc "Parse and generate authorized_keys files for SSH." + + text_line :comment, :match => /^#/ + text_line :blank, :match => /^\s+/ + + record_line :parsed, + :fields => %w{options type key name}, + :optional => %w{options}, + :rts => /^\s+/, + :match => /^(?:(.+) )?(ssh-dss|ssh-rsa) ([^ ]+) ?(.*)$/, + :post_parse => proc { |h| + h[:name] = "" if h[:name] == :absent + h[:options] ||= [:absent] + h[:options] = Puppet::Type::Ssh_authorized_key::ProviderParsed.parse_options(h[:options]) if h[:options].is_a? String + }, + :pre_gen => proc { |h| + h[:options] = [] if h[:options].include?(:absent) + h[:options] = h[:options].join(',') + } + + record_line :key_v1, + :fields => %w{options bits exponent modulus name}, + :optional => %w{options}, + :rts => /^\s+/, + :match => /^(?:(.+) )?(\d+) (\d+) (\d+)(?: (.+))?$/ + + def dir_perm + 0700 + end + + def file_perm + 0600 + end + + def target + @resource.should(:target) || File.expand_path("~#{@resource.should(:user)}/.ssh/authorized_keys") + rescue + raise Puppet::Error, "Target not defined and/or specified user does not exist yet" + end + + def user + uid = File.stat(target).uid + Etc.getpwuid(uid).name + end + + 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) + end + + # parse sshv2 option strings, wich is a comma separated list of + # either key="values" elements or bare-word elements + def self.parse_options(options) + result = [] + scanner = StringScanner.new(options) + while !scanner.eos? + scanner.skip(/[ \t]*/) + # scan a long option + if out = scanner.scan(/[-a-z0-9A-Z_]+=\".*?\"/) or out = scanner.scan(/[-a-z0-9A-Z_]+/) + result << out + else + # found an unscannable token, let's abort + break + end + # eat a comma + scanner.skip(/[ \t]*,[ \t]*/) + end + result + end +end + --- puppet-2.7.1.orig/.pc/secure-indirector-file-backed-terminus-base-cla.patch/spec/unit/indirector/terminus_spec.rb +++ puppet-2.7.1/.pc/secure-indirector-file-backed-terminus-base-cla.patch/spec/unit/indirector/terminus_spec.rb @@ -0,0 +1,250 @@ +#!/usr/bin/env rspec +require 'spec_helper' +require 'puppet/defaults' +require 'puppet/indirector' +require 'puppet/indirector/file' + +describe Puppet::Indirector::Terminus, :'fails_on_ruby_1.9.2' => true do + before :each do + Puppet::Indirector::Terminus.stubs(:register_terminus_class) + @indirection = stub 'indirection', :name => :my_stuff, :register_terminus_type => nil + Puppet::Indirector::Indirection.stubs(:instance).with(:my_stuff).returns(@indirection) + @abstract_terminus = Class.new(Puppet::Indirector::Terminus) do + def self.to_s + "Testing::Abstract" + end + end + @terminus_class = Class.new(@abstract_terminus) do + def self.to_s + "MyStuff::TermType" + end + end + @terminus = @terminus_class.new + end + + describe Puppet::Indirector::Terminus do + + it "should provide a method for setting terminus class documentation" do + @terminus_class.should respond_to(:desc) + end + + it "should support a class-level name attribute" do + @terminus_class.should respond_to(:name) + end + + it "should support a class-level indirection attribute" do + @terminus_class.should respond_to(:indirection) + end + + it "should support a class-level terminus-type attribute" do + @terminus_class.should respond_to(:terminus_type) + end + + it "should support a class-level model attribute" do + @terminus_class.should respond_to(:model) + end + + it "should accept indirection instances as its indirection" do + indirection = stub 'indirection', :is_a? => true, :register_terminus_type => nil + proc { @terminus_class.indirection = indirection }.should_not raise_error + @terminus_class.indirection.should equal(indirection) + end + + it "should look up indirection instances when only a name has been provided" do + indirection = mock 'indirection' + Puppet::Indirector::Indirection.expects(:instance).with(:myind).returns(indirection) + @terminus_class.indirection = :myind + @terminus_class.indirection.should equal(indirection) + end + + it "should fail when provided a name that does not resolve to an indirection" do + Puppet::Indirector::Indirection.expects(:instance).with(:myind).returns(nil) + proc { @terminus_class.indirection = :myind }.should raise_error(ArgumentError) + + # It shouldn't overwrite our existing one (or, more normally, it shouldn't set + # anything). + @terminus_class.indirection.should equal(@indirection) + end + end + + describe Puppet::Indirector::Terminus, " when creating terminus classes" do + it "should associate the subclass with an indirection based on the subclass constant" do + @terminus.indirection.should equal(@indirection) + end + + it "should set the subclass's type to the abstract terminus name" do + @terminus.terminus_type.should == :abstract + end + + it "should set the subclass's name to the indirection name" do + @terminus.name.should == :term_type + end + + it "should set the subclass's model to the indirection model" do + @indirection.expects(:model).returns :yay + @terminus.model.should == :yay + end + end + + describe Puppet::Indirector::Terminus, " when a terminus instance" do + + it "should return the class's name as its name" do + @terminus.name.should == :term_type + end + + it "should return the class's indirection as its indirection" do + @terminus.indirection.should equal(@indirection) + end + + it "should set the instances's type to the abstract terminus type's name" do + @terminus.terminus_type.should == :abstract + end + + it "should set the instances's model to the indirection's model" do + @indirection.expects(:model).returns :yay + @terminus.model.should == :yay + end + end +end + +# LAK: This could reasonably be in the Indirection instances, too. It doesn't make +# a whole heckuva lot of difference, except that with the instance loading in +# the Terminus base class, we have to have a check to see if we're already +# instance-loading a given terminus class type. +describe Puppet::Indirector::Terminus, " when managing terminus classes" do + it "should provide a method for registering terminus classes" do + Puppet::Indirector::Terminus.should respond_to(:register_terminus_class) + end + + it "should provide a method for returning terminus classes by name and type" do + terminus = stub 'terminus_type', :name => :abstract, :indirection_name => :whatever + Puppet::Indirector::Terminus.register_terminus_class(terminus) + Puppet::Indirector::Terminus.terminus_class(:whatever, :abstract).should equal(terminus) + end + + it "should set up autoloading for any terminus class types requested" do + Puppet::Indirector::Terminus.expects(:instance_load).with(:test2, "puppet/indirector/test2") + Puppet::Indirector::Terminus.terminus_class(:test2, :whatever) + end + + it "should load terminus classes that are not found" do + # Set up instance loading; it would normally happen automatically + Puppet::Indirector::Terminus.instance_load :test1, "puppet/indirector/test1" + + Puppet::Indirector::Terminus.instance_loader(:test1).expects(:load).with(:yay) + Puppet::Indirector::Terminus.terminus_class(:test1, :yay) + end + + it "should fail when no indirection can be found", :'fails_on_ruby_1.9.2' => true do + Puppet::Indirector::Indirection.expects(:instance).with(:my_indirection).returns(nil) + + @abstract_terminus = Class.new(Puppet::Indirector::Terminus) do + def self.to_s + "Abstract" + end + end + proc { + @terminus = Class.new(@abstract_terminus) do + def self.to_s + "MyIndirection::TestType" + end + end + }.should raise_error(ArgumentError) + end + + it "should register the terminus class with the terminus base class", :'fails_on_ruby_1.9.2' => true do + Puppet::Indirector::Terminus.expects(:register_terminus_class).with do |type| + type.indirection_name == :my_indirection and type.name == :test_terminus + end + @indirection = stub 'indirection', :name => :my_indirection, :register_terminus_type => nil + Puppet::Indirector::Indirection.expects(:instance).with(:my_indirection).returns(@indirection) + + @abstract_terminus = Class.new(Puppet::Indirector::Terminus) do + def self.to_s + "Abstract" + end + end + + @terminus = Class.new(@abstract_terminus) do + def self.to_s + "MyIndirection::TestTerminus" + end + end + end +end + +describe Puppet::Indirector::Terminus, " when parsing class constants for indirection and terminus names" do + before do + @subclass = mock 'subclass' + @subclass.stubs(:to_s).returns("TestInd::OneTwo") + @subclass.stubs(:mark_as_abstract_terminus) + Puppet::Indirector::Terminus.stubs(:register_terminus_class) + end + + it "should fail when anonymous classes are used" do + proc { Puppet::Indirector::Terminus.inherited(Class.new) }.should raise_error(Puppet::DevError) + end + + it "should use the last term in the constant for the terminus class name" do + @subclass.expects(:name=).with(:one_two) + @subclass.stubs(:indirection=) + Puppet::Indirector::Terminus.inherited(@subclass) + end + + it "should convert the terminus name to a downcased symbol" do + @subclass.expects(:name=).with(:one_two) + @subclass.stubs(:indirection=) + Puppet::Indirector::Terminus.inherited(@subclass) + end + + it "should use the second to last term in the constant for the indirection name" do + @subclass.expects(:indirection=).with(:test_ind) + @subclass.stubs(:name=) + @subclass.stubs(:terminus_type=) + Puppet::Indirector::File.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) + end + + it "should convert camel case to lower case with underscores as word separators" do + @subclass.expects(:name=).with(:one_two) + @subclass.stubs(:indirection=) + + Puppet::Indirector::Terminus.inherited(@subclass) + end +end + +describe Puppet::Indirector::Terminus, " when creating terminus class types", :'fails_on_ruby_1.9.2' => true do + before do + Puppet::Indirector::Terminus.stubs(:register_terminus_class) + @subclass = Class.new(Puppet::Indirector::Terminus) do + def self.to_s + "Puppet::Indirector::Terminus::MyTermType" + end + end + end + + it "should set the name of the abstract subclass to be its class constant" do + @subclass.name.should equal(:my_term_type) + end + + it "should mark abstract terminus types as such" do + @subclass.should be_abstract_terminus + end + + it "should not allow instances of abstract subclasses to be created" do + proc { @subclass.new }.should raise_error(Puppet::DevError) + end +end + +describe Puppet::Indirector::Terminus, " when listing terminus classes" do + it "should list the terminus files available to load" do + Puppet::Util::Autoload.any_instance.stubs(:files_to_load).returns ["/foo/bar/baz", "/max/runs/marathon"] + Puppet::Indirector::Terminus.terminus_classes('my_stuff').should == [:baz, :marathon] + end +end --- puppet-2.7.1.orig/.pc/secure-indirector-file-backed-terminus-base-cla.patch/spec/unit/indirector/file_spec.rb +++ puppet-2.7.1/.pc/secure-indirector-file-backed-terminus-base-cla.patch/spec/unit/indirector/file_spec.rb @@ -0,0 +1,179 @@ +#!/usr/bin/env rspec +require 'spec_helper' +require 'puppet/indirector/file' + + +describe Puppet::Indirector::File do + before :all 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) + + module Testing; end + @file_class = class Testing::MyFile < Puppet::Indirector::File + self + 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 --- puppet-2.7.1.orig/.pc/secure-indirector-file-backed-terminus-base-cla.patch/lib/puppet/indirector/file.rb +++ puppet-2.7.1/.pc/secure-indirector-file-backed-terminus-base-cla.patch/lib/puppet/indirector/file.rb @@ -0,0 +1,79 @@ +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 --- puppet-2.7.1.orig/.pc/debian-changes/Rakefile +++ puppet-2.7.1/.pc/debian-changes/Rakefile @@ -0,0 +1,53 @@ +# Rakefile for Puppet -*- ruby -*- + +$LOAD_PATH << File.join(File.dirname(__FILE__), 'tasks') + +require 'rake' +require 'rake/packagetask' +require 'rake/gempackagetask' +require 'rspec' +require "rspec/core/rake_task" + +module Puppet + PUPPETVERSION = File.read('lib/puppet.rb')[/PUPPETVERSION *= *'(.*)'/,1] or fail "Couldn't find PUPPETVERSION" +end + +Dir['tasks/**/*.rake'].each { |t| load t } + +FILES = FileList[ + '[A-Z]*', + 'install.rb', + 'bin/**/*', + 'sbin/**/*', + 'lib/**/*', + 'conf/**/*', + 'man/**/*', + 'examples/**/*', + 'ext/**/*', + 'tasks/**/*', + 'test/**/*', + 'spec/**/*' +] + +Rake::PackageTask.new("puppet", Puppet::PUPPETVERSION) do |pkg| + pkg.package_dir = 'pkg' + pkg.need_tar_gz = true + pkg.package_files = FILES.to_a +end + +task :default do + sh %{rake -T} +end + +desc "Create the tarball and the gem - use when releasing" +task :puppetpackages => [:create_gem, :package] + +RSpec::Core::RakeTask.new do |t| + t.pattern ='spec/{unit,integration}/**/*.rb' + t.fail_on_error = true +end + +desc "Run the unit tests" +task :unit do + Dir.chdir("test") { sh "rake" } +end --- puppet-2.7.1.orig/.pc/debian-changes/test/lib/puppettest/fakes.rb +++ puppet-2.7.1/.pc/debian-changes/test/lib/puppettest/fakes.rb @@ -0,0 +1,199 @@ +require File.expand_path(File.join(File.dirname(__FILE__), '../../../lib/puppet/util')) + +module PuppetTest + # A baseclass for the faketypes. + class FakeModel + include Puppet::Util + class << self + attr_accessor :name, :realresource + @name = :fakeresource + end + + def self.key_attributes + @realresource.key_attributes + end + + def self.validproperties + Puppet::Type.type(@name).validproperties + end + + def self.validproperty?(name) + Puppet::Type.type(@name).validproperty?(name) + end + + def self.to_s + "Fake#{@name.to_s.capitalize}" + end + + def [](param) + if @realresource.attrtype(param) == :property + @is[param] + else + @params[param] + end + end + + def []=(param, value) + param = symbolize(param) + raise Puppet::DevError, "Invalid attribute #{param} for #{@realresource.name}" unless @realresource.valid_parameter?(param) + if @realresource.attrtype(param) == :property + @should[param] = value + else + @params[param] = value + end + end + + def initialize(name) + @realresource = Puppet::Type.type(self.class.name) + raise "Could not find type #{self.class.name}" unless @realresource + @is = {} + @should = {} + @params = {} + self[@realresource.key_attributes.first] = name + end + + def inspect + "#{self.class.to_s.sub(/.+::/, '')}(#{super()})" + end + + def is(param) + @is[param] + end + + def should(param) + @should[param] + end + + def to_hash + hash = @params.dup + [@is, @should].each do |h| + h.each do |p, v| + hash[p] = v + end + end + hash + end + + def name + self[:name] + end + end + + class FakeProvider + attr_accessor :resource + class << self + attr_accessor :name, :resource_type, :methods + end + + # A very low number, so these never show up as defaults via the standard + # algorithms. + def self.defaultnum + -50 + end + + # Set up methods to fake things + def self.apimethods(*ary) + @resource_type.validproperties.each do |property| + ary << property unless ary.include? property + end + attr_accessor(*ary) + + @methods = ary + end + + def self.default? + false + end + + def self.initvars + @calls = Hash.new do |hash, key| + hash[key] = 0 + end + end + + def self.source + self.name + end + + def self.supports_parameter?(param) + true + end + + def self.suitable? + true + end + + def clear + @resource = nil + end + + def initialize(resource) + @resource = resource + end + + def properties + self.class.resource_type.validproperties.inject({}) do |props, name| + props[name] = self.send(name) || :absent + props + end + end + end + + class FakeParsedProvider < FakeProvider + def hash + ret = {} + instance_variables.each do |v| + v = v.sub("@", '') + if val = self.send(v) + ret[v.intern] = val + end + end + + ret + end + + def store(hash) + hash.each do |n, v| + method = n.to_s + "=" + send(method, v) if respond_to? method + end + end + end + + @@fakeresources = {} + @@fakeproviders = {} + + def fakeresource(type, name, options = {}) + type = type.intern if type.is_a? String + unless @@fakeresources.include? type + @@fakeresources[type] = Class.new(FakeModel) + @@fakeresources[type].name = type + + resource = Puppet::Type.type(type) + raise("Could not find type #{type}") unless resource + @@fakeresources[type].realresource = resource + end + + obj = @@fakeresources[type].new(name) + options.each do |name, val| + obj[name] = val + end + obj + end + + module_function :fakeresource + + def fakeprovider(type, resource) + type = type.intern if type.is_a? String + unless @@fakeproviders.include? type + @@fakeproviders[type] = Class.new(FakeModel) do + @name = type + end + end + + @@fakeproviders[type].new(resource) + end + + module_function :fakeprovider +end + --- puppet-2.7.1.orig/.pc/debian-changes/lib/puppet/network/rest_authconfig.rb +++ puppet-2.7.1/.pc/debian-changes/lib/puppet/network/rest_authconfig.rb @@ -0,0 +1,89 @@ +require 'puppet/network/authconfig' + +module Puppet + class Network::RestAuthConfig < Network::AuthConfig + + extend MonitorMixin + attr_accessor :rights + + DEFAULT_ACL = [ + { :acl => "~ ^\/catalog\/([^\/]+)$", :method => :find, :allow => '$1', :authenticated => true }, + { :acl => "~ ^\/node\/([^\/]+)$", :method => :find, :allow => '$1', :authenticated => true }, + # this one will allow all file access, and thus delegate + # to fileserver.conf + { :acl => "/file" }, + { :acl => "/certificate_revocation_list/ca", :method => :find, :authenticated => true }, + { :acl => "/report", :method => :save, :authenticated => true }, + { :acl => "/certificate/ca", :method => :find, :authenticated => false }, + { :acl => "/certificate/", :method => :find, :authenticated => false }, + { :acl => "/certificate_request", :method => [:find, :save], :authenticated => false }, + { :acl => "/status", :method => [:find], :authenticated => true }, + ] + + def self.main + synchronize do + add_acl = @main.nil? + super + @main.insert_default_acl if add_acl and !@main.exists? + end + @main + end + + # check wether this request is allowed in our ACL + # raise an Puppet::Network::AuthorizedError if the request + # is denied. + def allowed?(indirection, method, key, params) + read + + # we're splitting the request in part because + # fail_on_deny could as well be called in the XMLRPC context + # with a ClientRequest. + + if authorization_failure_exception = @rights.is_request_forbidden_and_why?(indirection, method, key, params) + Puppet.warning("Denying access: #{authorization_failure_exception}") + raise authorization_failure_exception + end + end + + def initialize(file = nil, parsenow = true) + super(file || Puppet[:rest_authconfig], parsenow) + + # if we didn't read a file (ie it doesn't exist) + # make sure we can create some default rights + @rights ||= Puppet::Network::Rights.new + end + + def parse + super() + insert_default_acl + end + + # force regular ACLs to be present + def insert_default_acl + DEFAULT_ACL.each do |acl| + unless rights[acl[:acl]] + Puppet.info "Inserting default '#{acl[:acl]}'(#{acl[:authenticated] ? "auth" : "non-auth"}) ACL because #{( !exists? ? "#{Puppet[:rest_authconfig]} doesn't exist" : "none were found in '#{@file}'")}" + mk_acl(acl) + end + end + # queue an empty (ie deny all) right for every other path + # actually this is not strictly necessary as the rights system + # denies not explicitely allowed paths + unless rights["/"] + rights.newright("/") + rights.restrict_authenticated("/", :any) + end + end + + def mk_acl(acl) + @rights.newright(acl[:acl]) + @rights.allow(acl[:acl], acl[:allow] || "*") + + if method = acl[:method] + method = [method] unless method.is_a?(Array) + method.each { |m| @rights.restrict_method(acl[:acl], m) } + end + @rights.restrict_authenticated(acl[:acl], acl[:authenticated]) unless acl[:authenticated].nil? + end + end +end --- puppet-2.7.1.orig/.pc/debian-changes/lib/puppet/provider/service/init.rb +++ puppet-2.7.1/.pc/debian-changes/lib/puppet/provider/service/init.rb @@ -0,0 +1,141 @@ +# The standard init-based service type. Many other service types are +# customizations of this module. +Puppet::Type.type(:service).provide :init, :parent => :base do + desc "Standard init service management. + + This provider assumes that the init script has no `status` command, + because so few scripts do, so you need to either provide a status + command or specify via `hasstatus` that one already exists in the + init script. + +" + + class << self + attr_accessor :defpath + end + + case Facter["operatingsystem"].value + when "FreeBSD" + @defpath = ["/etc/rc.d", "/usr/local/etc/rc.d"] + when "HP-UX" + @defpath = "/sbin/init.d" + else + @defpath = "/etc/init.d" + end + + # We can't confine this here, because the init path can be overridden. + #confine :exists => @defpath + + # List all services of this type. + def self.instances + get_services(self.defpath) + end + + def self.get_services(defpath, exclude=[]) + defpath = [defpath] unless defpath.is_a? Array + instances = [] + defpath.each do |path| + unless FileTest.directory?(path) + Puppet.debug "Service path #{path} does not exist" + next + end + + check = [:ensure] + + check << :enable if public_method_defined? :enabled? + + Dir.entries(path).each do |name| + fullpath = File.join(path, name) + next if name =~ /^\./ + next if exclude.include? name + next if not FileTest.executable?(fullpath) + instances << new(:name => name, :path => path, :hasstatus => true) + end + end + instances + end + + # Mark that our init script supports 'status' commands. + def hasstatus=(value) + case value + when true, "true"; @parameters[:hasstatus] = true + when false, "false"; @parameters[:hasstatus] = false + else + raise Puppet::Error, "Invalid 'hasstatus' value #{value.inspect}" + end + end + + # Where is our init script? + def initscript + @initscript ||= self.search(@resource[:name]) + end + + def paths + @paths ||= @resource[:path].find_all do |path| + if File.directory?(path) + true + else + if File.exist?(path) and ! File.directory?(path) + self.debug "Search path #{path} is not a directory" + else + self.debug "Search path #{path} does not exist" + end + false + end + end + end + + def search(name) + paths.each { |path| + fqname = File.join(path,name) + begin + stat = File.stat(fqname) + rescue + # should probably rescue specific errors... + self.debug("Could not find #{name} in #{path}") + next + end + + # if we've gotten this far, we found a valid script + return fqname + } + + paths.each { |path| + fqname_sh = File.join(path,"#{name}.sh") + begin + stat = File.stat(fqname_sh) + rescue + # should probably rescue specific errors... + self.debug("Could not find #{name}.sh in #{path}") + next + end + + # if we've gotten this far, we found a valid script + return fqname_sh + } + raise Puppet::Error, "Could not find init script for '#{name}'" + end + + # The start command is just the init scriptwith 'start'. + def startcmd + [initscript, :start] + end + + # The stop command is just the init script with 'stop'. + def stopcmd + [initscript, :stop] + end + + def restartcmd + (@resource[:hasrestart] == :true) && [initscript, :restart] + end + + # If it was specified that the init script has a 'status' command, then + # we just return that; otherwise, we return false, which causes it to + # fallback to other mechanisms. + def statuscmd + (@resource[:hasstatus] == :true) && [initscript, :status] + end + +end + --- puppet-2.7.1.orig/.pc/debian-changes/ext/rack/files/apache2.conf +++ puppet-2.7.1/.pc/debian-changes/ext/rack/files/apache2.conf @@ -0,0 +1,38 @@ + +# you probably want to tune these settings +PassengerHighPerformance on +PassengerMaxPoolSize 12 +PassengerPoolIdleTime 1500 +# PassengerMaxRequests 1000 +PassengerStatThrottleRate 120 +RackAutoDetect Off +RailsAutoDetect Off + +Listen 8140 + + + SSLEngine on + SSLProtocol -ALL +SSLv3 +TLSv1 + SSLCipherSuite ALL:!ADH:RC4+RSA:+HIGH:+MEDIUM:-LOW:-SSLv2:-EXP + + SSLCertificateFile /etc/puppet/ssl/certs/squigley.namespace.at.pem + SSLCertificateKeyFile /etc/puppet/ssl/private_keys/squigley.namespace.at.pem + SSLCertificateChainFile /etc/puppet/ssl/ca/ca_crt.pem + SSLCACertificateFile /etc/puppet/ssl/ca/ca_crt.pem + # If Apache complains about invalid signatures on the CRL, you can try disabling + # CRL checking by commenting the next line, but this is not recommended. + SSLCARevocationFile /etc/puppet/ssl/ca/ca_crl.pem + SSLVerifyClient optional + SSLVerifyDepth 1 + SSLOptions +StdEnvVars + + DocumentRoot /etc/puppet/rack/public/ + RackBaseURI / + + Options None + AllowOverride None + Order allow,deny + allow from all + + + --- puppet-2.7.1.orig/.pc/CVE-2011-3871.patch/lib/puppet/application/resource.rb +++ puppet-2.7.1/.pc/CVE-2011-3871.patch/lib/puppet/application/resource.rb @@ -0,0 +1,220 @@ +require 'puppet/application' + +class Puppet::Application::Resource < Puppet::Application + + should_not_parse_config + + attr_accessor :host, :extra_params + + def preinit + @extra_params = [] + @host = nil + Facter.loadfacts + end + + option("--debug","-d") + option("--verbose","-v") + option("--edit","-e") + + option("--host HOST","-H") do |arg| + @host = arg + end + + option("--types", "-t") do |arg| + types = [] + Puppet::Type.loadall + Puppet::Type.eachtype do |t| + next if t.name == :component + types << t.name.to_s + end + puts types.sort + exit + end + + option("--param PARAM", "-p") do |arg| + @extra_params << arg.to_sym + end + + def help + <<-HELP + +puppet-resource(8) -- The resource abstraction layer shell +======== + +SYNOPSIS +-------- +Uses the Puppet RAL to directly interact with the system. + + +USAGE +----- +puppet resource [-h|--help] [-d|--debug] [-v|--verbose] [-e|--edit] + [-H|--host ] [-p|--param ] [-t|--types] + [] [= ...] + + +DESCRIPTION +----------- +This command provides simple facilities for converting current system +state into Puppet code, along with some ability to modify the current +state using Puppet's RAL. + +By default, you must at least provide a type to list, in which case +puppet resource will tell you everything it knows about all resources of +that type. You can optionally specify an instance name, and puppet +resource will only describe that single instance. + +If given a type, a name, and a series of = pairs, +puppet resource will modify the state of the specified resource. +Alternately, if given a type, a name, and the '--edit' flag, puppet +resource will write its output to a file, open that file in an editor, +and then apply the saved file as a Puppet transaction. + + +OPTIONS +------- +Note that any configuration parameter that's valid in the configuration +file is also a valid long argument. For example, 'ssldir' is a valid +configuration parameter, so you can specify '--ssldir ' as an +argument. + +See the configuration file documentation at +http://docs.puppetlabs.com/references/stable/configuration.html for the +full list of acceptable parameters. A commented list of all +configuration options can also be generated by running puppet with +'--genconfig'. + +* --debug: + Enable full debugging. + +* --edit: + Write the results of the query to a file, open the file in an editor, + and read the file back in as an executable Puppet manifest. + +* --host: + When specified, connect to the resource server on the named host + and retrieve the list of resouces of the type specified. + +* --help: + Print this help message. + +* --param: + Add more parameters to be outputted from queries. + +* --types: + List all available types. + +* --verbose: + Print extra information. + + +EXAMPLE +------- +This example uses `puppet resource` to return a Puppet configuration for +the user `luke`: + + $ puppet resource user luke + user { 'luke': + home => '/home/luke', + uid => '100', + ensure => 'present', + comment => 'Luke Kanies,,,', + gid => '1000', + shell => '/bin/bash', + groups => ['sysadmin','audio','video','puppet'] + } + + +AUTHOR +------ +Luke Kanies + + +COPYRIGHT +--------- +Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License + + HELP + end + + def main + args = command_line.args + type = args.shift or raise "You must specify the type to display" + typeobj = Puppet::Type.type(type) or raise "Could not find type #{type}" + name = args.shift + params = {} + args.each do |setting| + if setting =~ /^(\w+)=(.+)$/ + params[$1] = $2 + else + raise "Invalid parameter setting #{setting}" + end + end + + raise "You cannot edit a remote host" if options[:edit] and @host + + properties = typeobj.properties.collect { |s| s.name } + + format = proc {|trans| + trans.dup.collect do |param, value| + if value.nil? or value.to_s.empty? + trans.delete(param) + elsif value.to_s == "absent" and param.to_s != "ensure" + trans.delete(param) + end + + trans.delete(param) unless properties.include?(param) or @extra_params.include?(param) + end + trans.to_manifest + } + + if @host + Puppet::Resource.indirection.terminus_class = :rest + port = Puppet[:puppetport] + key = ["https://#{host}:#{port}", "production", "resources", type, name].join('/') + else + key = [type, name].join('/') + end + + text = if name + if params.empty? + [ Puppet::Resource.indirection.find( key ) ] + else + [ Puppet::Resource.indirection.save(Puppet::Resource.new( type, name, :parameters => params ), key) ] + end + else + Puppet::Resource.indirection.search( key, {} ) + end.map(&format).join("\n") + + if options[:edit] + file = "/tmp/x2puppet-#{Process.pid}.pp" + begin + File.open(file, "w") do |f| + f.puts text + end + ENV["EDITOR"] ||= "vi" + system(ENV["EDITOR"], file) + system("puppet -v #{file}") + ensure + #if FileTest.exists? file + # File.unlink(file) + #end + end + else + puts text + end + end + + def setup + Puppet::Util::Log.newdestination(:console) + + # Now parse the config + Puppet.parse_config + + if options[:debug] + Puppet::Util::Log.level = :debug + elsif options[:verbose] + Puppet::Util::Log.level = :info + end + end +end --- puppet-2.7.1.orig/.pc/CVE-2011-3848.patch/spec/unit/indirector/yaml_spec.rb +++ puppet-2.7.1/.pc/CVE-2011-3848.patch/spec/unit/indirector/yaml_spec.rb @@ -0,0 +1,158 @@ +#!/usr/bin/env rspec +require 'spec_helper' + +require 'puppet/indirector/yaml' + +describe Puppet::Indirector::Yaml, " when choosing file location" do + before :all do + @indirection = stub 'indirection', :name => :my_yaml, :register_terminus_type => nil + Puppet::Indirector::Indirection.expects(:instance).with(:my_yaml).returns(@indirection) + module MyYaml; end + @store_class = class MyYaml::MyType < Puppet::Indirector::Yaml + self + end + end + before :each do + @store = @store_class.new + + @subject = Object.new + @subject.singleton_class.send(:attr_accessor, :name) + @subject.name = :me + + @dir = "/what/ever" + Puppet.settings.stubs(:value).returns("fakesettingdata") + Puppet.settings.stubs(:value).with(:clientyamldir).returns(@dir) + Puppet.run_mode.stubs(:master?).returns false + + @request = stub 'request', :key => :me, :instance => @subject + end + + describe Puppet::Indirector::Yaml, " when choosing file location" do + it "should use the server_datadir if the run_mode is master" do + Puppet.run_mode.expects(:master?).returns true + Puppet.settings.expects(:value).with(:yamldir).returns "/server/yaml/dir" + @store.path(:me).should =~ %r{^/server/yaml/dir} + end + + it "should use the client yamldir if the run_mode is not master" do + Puppet.run_mode.expects(:master?).returns false + Puppet.settings.expects(:value).with(:clientyamldir).returns "/client/yaml/dir" + @store.path(:me).should =~ %r{^/client/yaml/dir} + end + + it "should use the extension if one is specified" do + Puppet.run_mode.expects(:master?).returns true + Puppet.settings.expects(:value).with(:yamldir).returns "/server/yaml/dir" + @store.path(:me,'.farfignewton').should =~ %r{\.farfignewton$} + end + + it "should assume an extension of .yaml if none is specified" do + Puppet.run_mode.expects(:master?).returns true + Puppet.settings.expects(:value).with(:yamldir).returns "/server/yaml/dir" + @store.path(:me).should =~ %r{\.yaml$} + end + + it "should store all files in a single file root set in the Puppet defaults" do + @store.path(:me).should =~ %r{^#{@dir}} + end + + it "should use the terminus name for choosing the subdirectory" do + @store.path(:me).should =~ %r{^#{@dir}/my_yaml} + end + + it "should use the object's name to determine the file name" do + @store.path(:me).should =~ %r{me.yaml$} + end + end + + describe Puppet::Indirector::Yaml, " when storing objects as YAML" do + it "should only store objects that respond to :name" do + @request.stubs(:instance).returns Object.new + proc { @store.save(@request) }.should raise_error(ArgumentError) + end + + it "should convert Ruby objects to YAML and write them to disk using a write lock" do + yaml = @subject.to_yaml + file = mock 'file' + path = @store.send(:path, @subject.name) + FileTest.expects(:exist?).with(File.dirname(path)).returns(true) + @store.expects(:writelock).with(path, 0660).yields(file) + file.expects(:print).with(yaml) + + @store.save(@request) + end + + it "should create the indirection subdirectory if it does not exist" do + yaml = @subject.to_yaml + file = mock 'file' + path = @store.send(:path, @subject.name) + dir = File.dirname(path) + + FileTest.expects(:exist?).with(dir).returns(false) + Dir.expects(:mkdir).with(dir) + + @store.expects(:writelock).yields(file) + file.expects(:print).with(yaml) + + @store.save(@request) + end + end + + describe Puppet::Indirector::Yaml, " when retrieving YAML" do + it "should read YAML in from disk using a read lock and convert it to Ruby objects" do + path = @store.send(:path, @subject.name) + + yaml = @subject.to_yaml + FileTest.expects(:exist?).with(path).returns(true) + + fh = mock 'filehandle' + @store.expects(:readlock).with(path).yields fh + fh.expects(:read).returns yaml + + @store.find(@request).instance_variable_get("@name").should == :me + end + + it "should fail coherently when the stored YAML is invalid" do + path = @store.send(:path, @subject.name) + FileTest.expects(:exist?).with(path).returns(true) + + # Something that will fail in yaml + yaml = "--- !ruby/object:Hash" + + fh = mock 'filehandle' + @store.expects(:readlock).yields fh + fh.expects(:read).returns yaml + + proc { @store.find(@request) }.should raise_error(Puppet::Error) + end + end + + describe Puppet::Indirector::Yaml, " when searching" do + it "should return an array of fact instances with one instance for each file when globbing *" do + @request = stub 'request', :key => "*", :instance => @subject + @one = mock 'one' + @two = mock 'two' + @store.expects(:path).with(@request.key,'').returns :glob + Dir.expects(:glob).with(:glob).returns(%w{one.yaml two.yaml}) + YAML.expects(:load_file).with("one.yaml").returns @one; + YAML.expects(:load_file).with("two.yaml").returns @two; + @store.search(@request).should == [@one, @two] + end + + it "should return an array containing a single instance of fact when globbing 'one*'" do + @request = stub 'request', :key => "one*", :instance => @subject + @one = mock 'one' + @store.expects(:path).with(@request.key,'').returns :glob + Dir.expects(:glob).with(:glob).returns(%w{one.yaml}) + YAML.expects(:load_file).with("one.yaml").returns @one; + @store.search(@request).should == [@one] + end + + it "should return an empty array when the glob doesn't match anything" do + @request = stub 'request', :key => "f*ilglobcanfail*", :instance => @subject + @store.expects(:path).with(@request.key,'').returns :glob + Dir.expects(:glob).with(:glob).returns [] + @store.search(@request).should == [] + end + end +end --- puppet-2.7.1.orig/.pc/CVE-2011-3848.patch/spec/unit/indirector/ssl_file_spec.rb +++ puppet-2.7.1/.pc/CVE-2011-3848.patch/spec/unit/indirector/ssl_file_spec.rb @@ -0,0 +1,282 @@ +#!/usr/bin/env rspec +# +# Created by Luke Kanies on 2008-3-10. +# Copyright (c) 2007. All rights reserved. + +require 'spec_helper' + +require 'puppet/indirector/ssl_file' + +describe Puppet::Indirector::SslFile do + before :all do + @indirection = stub 'indirection', :name => :testing, :model => @model + Puppet::Indirector::Indirection.expects(:instance).with(:testing).returns(@indirection) + module Testing; end + @file_class = class Testing::MyType < Puppet::Indirector::SslFile + self + end + end + before :each do + @model = mock 'model' + + @setting = :certdir + @file_class.store_in @setting + @path = "/tmp/my_directory" + Puppet[:noop] = false + Puppet[@setting] = @path + Puppet[:trace] = false + end + + it "should use :main and :ssl upon initialization" do + Puppet.settings.expects(:use).with(:main, :ssl) + @file_class.new + end + + it "should return a nil collection directory if no directory setting has been provided" do + @file_class.store_in nil + @file_class.collection_directory.should be_nil + end + + it "should return a nil file location if no location has been provided" do + @file_class.store_at nil + @file_class.file_location.should be_nil + end + + it "should fail if no store directory or file location has been set" do + @file_class.store_in nil + @file_class.store_at nil + lambda { @file_class.new }.should raise_error(Puppet::DevError) + end + + describe "when managing ssl files" do + before do + Puppet.settings.stubs(:use) + @searcher = @file_class.new + + @cert = stub 'certificate', :name => "myname" + @certpath = File.join(@path, "myname.pem") + + @request = stub 'request', :key => @cert.name, :instance => @cert + end + + it "should consider the file a ca file if the name is equal to what the SSL::Host class says is the CA name" do + Puppet::SSL::Host.expects(:ca_name).returns "amaca" + @searcher.should be_ca("amaca") + end + + describe "when choosing the location for certificates" do + it "should set them at the ca setting's path if a ca setting is available and the name resolves to the CA name" do + @file_class.store_in nil + @file_class.store_at :mysetting + @file_class.store_ca_at :casetting + + Puppet.settings.stubs(:value).with(:casetting).returns "/ca/file" + + @searcher.expects(:ca?).with(@cert.name).returns true + @searcher.path(@cert.name).should == "/ca/file" + end + + it "should set them at the file location if a file setting is available" do + @file_class.store_in nil + @file_class.store_at :mysetting + + Puppet.settings.stubs(:value).with(:mysetting).returns "/some/file" + + @searcher.path(@cert.name).should == "/some/file" + end + + 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 + end + + describe "when finding certificates on disk" do + describe "and no certificate is present" do + before do + # Stub things so the case management bits work. + FileTest.stubs(:exist?).with(File.dirname(@certpath)).returns false + FileTest.expects(:exist?).with(@certpath).returns false + end + + it "should return nil" do + @searcher.find(@request).should be_nil + end + end + + describe "and a certificate is present" do + before do + FileTest.expects(:exist?).with(@certpath).returns true + end + + it "should return an instance of the model, which it should use to read the certificate" do + cert = mock 'cert' + model = mock 'model' + @file_class.stubs(:model).returns model + + model.expects(:new).with("myname").returns cert + cert.expects(:read).with(@certpath) + @searcher.find(@request).should equal(cert) + end + end + + describe "and a certificate is present but has uppercase letters" do + before do + @request = stub 'request', :key => "myhost" + end + + # This is kind of more an integration test; it's for #1382, until + # the support for upper-case certs can be removed around mid-2009. + it "should rename the existing file to the lower-case path" do + @path = @searcher.path("myhost") + FileTest.expects(:exist?).with(@path).returns(false) + dir, file = File.split(@path) + FileTest.expects(:exist?).with(dir).returns true + Dir.expects(:entries).with(dir).returns [".", "..", "something.pem", file.upcase] + + File.expects(:rename).with(File.join(dir, file.upcase), @path) + + cert = mock 'cert' + model = mock 'model' + @searcher.stubs(:model).returns model + @searcher.model.expects(:new).with("myhost").returns cert + cert.expects(:read).with(@path) + + @searcher.find(@request) + end + end + end + + describe "when saving certificates to disk" do + before do + FileTest.stubs(:directory?).returns true + FileTest.stubs(:writable?).returns true + end + + it "should fail if the directory is absent" do + FileTest.expects(:directory?).with(File.dirname(@certpath)).returns false + lambda { @searcher.save(@request) }.should raise_error(Puppet::Error) + end + + it "should fail if the directory is not writeable" do + FileTest.stubs(:directory?).returns true + FileTest.expects(:writable?).with(File.dirname(@certpath)).returns false + lambda { @searcher.save(@request) }.should raise_error(Puppet::Error) + end + + it "should save to the path the output of converting the certificate to a string" do + fh = mock 'filehandle' + fh.expects(:print).with("mycert") + + @searcher.stubs(:write).yields fh + @cert.expects(:to_s).returns "mycert" + + @searcher.save(@request) + end + + describe "and a directory setting is set" do + it "should use the Settings class to write the file" do + @searcher.class.store_in @setting + fh = mock 'filehandle' + fh.stubs :print + Puppet.settings.expects(:writesub).with(@setting, @certpath).yields fh + + @searcher.save(@request) + end + end + + describe "and a file location is set" do + it "should use the filehandle provided by the Settings" do + @searcher.class.store_at @setting + + fh = mock 'filehandle' + fh.stubs :print + Puppet.settings.expects(:write).with(@setting).yields fh + @searcher.save(@request) + end + end + + describe "and the name is the CA name and a ca setting is set" do + it "should use the filehandle provided by the Settings" do + @searcher.class.store_at @setting + @searcher.class.store_ca_at :castuff + Puppet.settings.stubs(:value).with(:castuff).returns "castuff stub" + + fh = mock 'filehandle' + fh.stubs :print + Puppet.settings.expects(:write).with(:castuff).yields fh + @searcher.stubs(:ca?).returns true + @searcher.save(@request) + end + end + end + + describe "when destroying certificates" do + describe "that do not exist" do + before do + FileTest.expects(:exist?).with(@certpath).returns false + end + + it "should return false" do + @searcher.destroy(@request).should be_false + end + end + + describe "that exist" do + before do + FileTest.expects(:exist?).with(@certpath).returns true + end + + it "should unlink the certificate file" do + File.expects(:unlink).with(@certpath) + @searcher.destroy(@request) + end + + it "should log that is removing the file" do + File.stubs(:exist?).returns true + File.stubs(:unlink) + Puppet.expects(:notice) + @searcher.destroy(@request) + end + end + end + + describe "when searching for certificates" do + before do + @model = mock 'model' + @file_class.stubs(:model).returns @model + end + it "should return a certificate instance for all files that exist" do + Dir.expects(:entries).with(@path).returns %w{one.pem two.pem} + + one = stub 'one', :read => nil + two = stub 'two', :read => nil + + @model.expects(:new).with("one").returns one + @model.expects(:new).with("two").returns two + + @searcher.search(@request).should == [one, two] + end + + it "should read each certificate in using the model's :read method" do + Dir.expects(:entries).with(@path).returns %w{one.pem} + + one = stub 'one' + one.expects(:read).with(File.join(@path, "one.pem")) + + @model.expects(:new).with("one").returns one + + @searcher.search(@request) + end + + it "should skip any files that do not match /\.pem$/" do + Dir.expects(:entries).with(@path).returns %w{. .. one.pem} + + one = stub 'one', :read => nil + + @model.expects(:new).with("one").returns one + + @searcher.search(@request) + end + end + end +end --- puppet-2.7.1.orig/.pc/CVE-2011-3848.patch/lib/puppet/indirector.rb +++ puppet-2.7.1/.pc/CVE-2011-3848.patch/lib/puppet/indirector.rb @@ -0,0 +1,51 @@ +# Manage indirections to termini. They are organized in terms of indirections - +# - e.g., configuration, node, file, certificate -- and each indirection has one +# or more terminus types defined. The indirection is configured via the +# +indirects+ method, which will be called by the class extending itself +# with this module. +module Puppet::Indirector + # LAK:FIXME We need to figure out how to handle documentation for the + # different indirection types. + + require 'puppet/indirector/indirection' + require 'puppet/indirector/terminus' + require 'puppet/indirector/envelope' + require 'puppet/network/format_handler' + + def self.configure_routes(application_routes) + application_routes.each do |indirection_name, termini| + indirection_name = indirection_name.to_sym + terminus_name = termini["terminus"] + cache_name = termini["cache"] + + Puppet::Indirector::Terminus.terminus_class(indirection_name, terminus_name || cache_name) + + indirection = Puppet::Indirector::Indirection.instance(indirection_name) + raise "Indirection #{indirection_name} does not exist" unless indirection + + indirection.terminus_class = terminus_name if terminus_name + indirection.cache_class = cache_name if cache_name + end + end + + # Declare that the including class indirects its methods to + # this terminus. The terminus name must be the name of a Puppet + # default, not the value -- if it's the value, then it gets + # evaluated at parse time, which is before the user has had a chance + # to override it. + def indirects(indirection, options = {}) + raise(ArgumentError, "Already handling indirection for #{@indirection.name}; cannot also handle #{indirection}") if @indirection + # populate this class with the various new methods + extend ClassMethods + include Puppet::Indirector::Envelope + extend Puppet::Network::FormatHandler + + # instantiate the actual Terminus for that type and this name (:ldap, w/ args :node) + # & hook the instantiated Terminus into this class (Node: @indirection = terminus) + @indirection = Puppet::Indirector::Indirection.new(self, indirection, options) + end + + module ClassMethods + attr_reader :indirection + end +end --- puppet-2.7.1.orig/.pc/CVE-2011-3848.patch/lib/puppet/indirector/ssl_file.rb +++ puppet-2.7.1/.pc/CVE-2011-3848.patch/lib/puppet/indirector/ssl_file.rb @@ -0,0 +1,174 @@ +require 'puppet/ssl' + +class Puppet::Indirector::SslFile < Puppet::Indirector::Terminus + # Specify the directory in which multiple files are stored. + def self.store_in(setting) + @directory_setting = setting + end + + # Specify a single file location for storing just one file. + # This is used for things like the CRL. + def self.store_at(setting) + @file_setting = setting + end + + # Specify where a specific ca file should be stored. + def self.store_ca_at(setting) + @ca_setting = setting + end + + class << self + attr_reader :directory_setting, :file_setting, :ca_setting + end + + # The full path to where we should store our files. + def self.collection_directory + return nil unless directory_setting + Puppet.settings[directory_setting] + end + + # The full path to an individual file we would be managing. + def self.file_location + return nil unless file_setting + Puppet.settings[file_setting] + end + + # The full path to a ca file we would be managing. + def self.ca_location + return nil unless ca_setting + Puppet.settings[ca_setting] + end + + # We assume that all files named 'ca' are pointing to individual ca files, + # rather than normal host files. It's a bit hackish, but all the other + # solutions seemed even more hackish. + def ca?(name) + name == Puppet::SSL::Host.ca_name + end + + def initialize + Puppet.settings.use(:main, :ssl) + + (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 ca?(name) and ca_location + ca_location + elsif collection_directory + File.join(collection_directory, name.to_s + ".pem") + else + file_location + end + end + + # Remove our file. + def destroy(request) + path = path(request.key) + return false unless FileTest.exist?(path) + + Puppet.notice "Removing file #{model} #{request.key} at '#{path}'" + begin + File.unlink(path) + rescue => detail + raise Puppet::Error, "Could not remove #{request.key}: #{detail}" + end + end + + # Find the file on disk, returning an instance of the model. + def find(request) + path = path(request.key) + + return nil unless FileTest.exist?(path) or rename_files_with_uppercase(path) + + result = model.new(request.key) + result.read(path) + result + end + + # Save our file to disk. + def save(request) + path = path(request.key) + dir = File.dirname(path) + + raise Puppet::Error.new("Cannot save #{request.key}; parent directory #{dir} does not exist") unless FileTest.directory?(dir) + raise Puppet::Error.new("Cannot save #{request.key}; parent directory #{dir} is not writable") unless FileTest.writable?(dir) + + write(request.key, path) { |f| f.print request.instance.to_s } + end + + # Search for more than one file. At this point, it just returns + # an instance for every file in the directory. + def search(request) + dir = collection_directory + Dir.entries(dir).reject { |file| file !~ /\.pem$/ }.collect do |file| + name = file.sub(/\.pem$/, '') + result = model.new(name) + result.read(File.join(dir, file)) + result + end + end + + private + + # Demeterish pointers to class info. + def collection_directory + self.class.collection_directory + end + + def file_location + self.class.file_location + end + + def ca_location + self.class.ca_location + end + + # A hack method to deal with files that exist with a different case. + # Just renames it; doesn't read it in or anything. + # LAK:NOTE This is a copy of the method in sslcertificates/support.rb, + # which we'll be EOL'ing at some point. This method was added at 20080702 + # and should be removed at some point. + def rename_files_with_uppercase(file) + dir, short = File.split(file) + return nil unless FileTest.exist?(dir) + + raise ArgumentError, "Tried to fix SSL files to a file containing uppercase" unless short.downcase == short + real_file = Dir.entries(dir).reject { |f| f =~ /^\./ }.find do |other| + other.downcase == short + end + + return nil unless real_file + + full_file = File.join(dir, real_file) + + Puppet.notice "Fixing case in #{full_file}; renaming to #{file}" + File.rename(full_file, file) + + true + end + + # Yield a filehandle set up appropriately, either with our settings doing + # the work or opening a filehandle manually. + def write(name, path) + if ca?(name) and ca_location + Puppet.settings.write(self.class.ca_setting) { |f| yield f } + elsif file_location + Puppet.settings.write(self.class.file_setting) { |f| yield f } + elsif setting = self.class.directory_setting + begin + Puppet.settings.writesub(setting, path) { |f| yield f } + rescue => detail + raise Puppet::Error, "Could not write #{path} to #{setting}: #{detail}" + end + else + raise Puppet::DevError, "You must provide a setting to determine where the files are stored" + end + end +end + +# LAK:NOTE This has to be at the end, because classes like SSL::Key use this +# class, and this require statement loads those, which results in a load loop +# and lots of failures. +require 'puppet/ssl/host' --- puppet-2.7.1.orig/.pc/CVE-2011-3848.patch/lib/puppet/indirector/yaml.rb +++ puppet-2.7.1/.pc/CVE-2011-3848.patch/lib/puppet/indirector/yaml.rb @@ -0,0 +1,65 @@ +require 'puppet/indirector/terminus' +require 'puppet/util/file_locking' + +# The base class for YAML indirection termini. +class Puppet::Indirector::Yaml < Puppet::Indirector::Terminus + include Puppet::Util::FileLocking + + # Read a given name's file in and convert it from YAML. + def find(request) + file = path(request.key) + return nil unless FileTest.exist?(file) + + yaml = nil + begin + readlock(file) { |fh| yaml = fh.read } + rescue => detail + raise Puppet::Error, "Could not read YAML data for #{indirection.name} #{request.key}: #{detail}" + end + begin + return from_yaml(yaml) + rescue => detail + raise Puppet::Error, "Could not parse YAML data for #{indirection.name} #{request.key}: #{detail}" + end + end + + # Convert our object to YAML and store it to the disk. + def save(request) + raise ArgumentError.new("You can only save objects that respond to :name") unless request.instance.respond_to?(:name) + + file = path(request.key) + + basedir = File.dirname(file) + + # This is quite likely a bad idea, since we're not managing ownership or modes. + Dir.mkdir(basedir) unless FileTest.exist?(basedir) + + begin + writelock(file, 0660) { |f| f.print to_yaml(request.instance) } + rescue TypeError => detail + Puppet.err "Could not save #{self.name} #{request.key}: #{detail}" + end + end + + # Return the path to a given node's file. + def path(name,ext='.yaml') + base = Puppet.run_mode.master? ? Puppet[:yamldir] : Puppet[:clientyamldir] + File.join(base, self.class.indirection_name.to_s, name.to_s + ext) + end + + def search(request) + Dir.glob(path(request.key,'')).collect do |file| + YAML.load_file(file) + end + end + + private + + def from_yaml(text) + YAML.load(text) + end + + def to_yaml(object) + YAML.dump(object) + end +end --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/test/language/functions.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/test/language/functions.rb @@ -0,0 +1,541 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../lib/puppettest') + +require 'puppet' +require 'puppet/parser/parser' +require 'puppet/network/client' +require 'puppettest' +require 'puppettest/resourcetesting' + +class TestLangFunctions < Test::Unit::TestCase + include PuppetTest::ParserTesting + include PuppetTest::ResourceTesting + def test_functions + Puppet::Node::Environment.stubs(:current).returns nil + assert_nothing_raised do + + Puppet::Parser::AST::Function.new( + + :name => "fakefunction", + + :arguments => AST::ASTArray.new( + :children => [nameobj("avalue")] + ) + ) + end + + assert_raise(Puppet::ParseError) do + + func = Puppet::Parser::AST::Function.new( + + :name => "fakefunction", + + :arguments => AST::ASTArray.new( + :children => [nameobj("avalue")] + ) + ) + func.evaluate(mkscope) + end + + assert_nothing_raised do + Puppet::Parser::Functions.newfunction(:fakefunction, :type => :rvalue) do |input| + return "output #{input[0]}" + end + end + + func = nil + assert_nothing_raised do + + func = Puppet::Parser::AST::Function.new( + + :name => "fakefunction", + :ftype => :rvalue, + + :arguments => AST::ASTArray.new( + :children => [nameobj("avalue")] + ) + ) + end + + scope = mkscope + val = nil + assert_nothing_raised do + val = func.evaluate(scope) + end + + assert_equal("output avalue", val) + end + + def test_taggedfunction + scope = mkscope + scope.resource.tag("yayness") + + # Make sure the ast stuff does what it's supposed to + {"yayness" => true, "booness" => false}.each do |tag, retval| + func = taggedobj(tag, :rvalue) + + val = nil + assert_nothing_raised do + val = func.evaluate(scope) + end + + assert_equal(retval, val, "'tagged' returned #{val} for #{tag}") + end + + # Now make sure we correctly get tags. + scope.resource.tag("resourcetag") + assert(scope.function_tagged("resourcetag"), "tagged function did not catch resource tags") + scope.compiler.catalog.tag("configtag") + assert(scope.function_tagged("configtag"), "tagged function did not catch catalog tags") + end + + def test_failfunction + func = nil + assert_nothing_raised do + + func = Puppet::Parser::AST::Function.new( + + :name => "fail", + :ftype => :statement, + + :arguments => AST::ASTArray.new( + :children => [stringobj("this is a failure"), stringobj("and another")] + ) + ) + end + + scope = mkscope + val = nil + assert_raise(Puppet::ParseError) do + val = func.evaluate(scope) + end + end + + def test_multipletemplates + Dir.mkdir(Puppet[:templatedir]) + onep = File.join(Puppet[:templatedir], "one") + twop = File.join(Puppet[:templatedir], "two") + + File.open(onep, "w") do |f| + f.puts "<%- if @one.nil? then raise '@one undefined' end -%>template <%= @one %>" + end + + File.open(twop, "w") do |f| + f.puts "template <%= @two %>" + end + func = nil + assert_nothing_raised do + + func = Puppet::Parser::AST::Function.new( + + :name => "template", + :ftype => :rvalue, + + :arguments => AST::ASTArray.new( + :children => [stringobj("one"), stringobj("two")] + ) + ) + end + ast = varobj("output", func) + + scope = mkscope + + # Test that our manual exception throw fails the parse + assert_raise(Puppet::ParseError) do + ast.evaluate(scope) + end + + # Test that our use of an undefined instance variable does not throw + # an exception, but only safely continues. + scope.setvar("one", "One") + assert_nothing_raised do + ast.evaluate(scope) + end + + # Ensure that we got the output we expected from that evaluation. + assert_equal("template One\ntemplate \n", scope.lookupvar("output"), "Undefined template variables do not raise exceptions") + + # Now, fill in the last variable and make sure the whole thing + # evaluates correctly. + scope.setvar("two", "Two") + scope.unsetvar("output") + assert_nothing_raised do + ast.evaluate(scope) + end + + + assert_equal( + "template One\ntemplate Two\n", scope.lookupvar("output"), + + "Templates were not handled correctly") + end + + # Now make sure we can fully qualify files, and specify just one + def test_singletemplates + template = tempfile + + File.open(template, "w") do |f| + f.puts "template <%= @yay.nil?() ? raise('yay undefined') : @yay %>" + end + + func = nil + assert_nothing_raised do + + func = Puppet::Parser::AST::Function.new( + + :name => "template", + :ftype => :rvalue, + + :arguments => AST::ASTArray.new( + :children => [stringobj(template)] + ) + ) + end + ast = varobj("output", func) + + scope = mkscope + assert_raise(Puppet::ParseError) do + ast.evaluate(scope) + end + + scope.setvar("yay", "this is yay") + + assert_nothing_raised do + ast.evaluate(scope) + end + + + assert_equal( + "template this is yay\n", scope.lookupvar("output"), + + "Templates were not handled correctly") + + end + + # Make sure that legacy template variable access works as expected. + def test_legacyvariables + template = tempfile + + File.open(template, "w") do |f| + f.puts "template <%= deprecated %>" + end + + func = nil + assert_nothing_raised do + + func = Puppet::Parser::AST::Function.new( + + :name => "template", + :ftype => :rvalue, + + :arguments => AST::ASTArray.new( + :children => [stringobj(template)] + ) + ) + end + ast = varobj("output", func) + + # Verify that we get an exception using old-style accessors. + scope = mkscope + assert_raise(Puppet::ParseError) do + ast.evaluate(scope) + end + + # Verify that we evaluate and return their value correctly. + scope.setvar("deprecated", "deprecated value") + assert_nothing_raised do + ast.evaluate(scope) + end + + + assert_equal( + "template deprecated value\n", scope.lookupvar("output"), + + "Deprecated template variables were not handled correctly") + end + + # Make sure that problems with kernel method visibility still exist. + def test_kernel_module_shadows_deprecated_var_lookup + template = tempfile + File.open(template, "w").puts("<%= binding %>") + + func = nil + assert_nothing_raised do + + func = Puppet::Parser::AST::Function.new( + + :name => "template", + :ftype => :rvalue, + + :arguments => AST::ASTArray.new( + :children => [stringobj(template)] + ) + ) + end + ast = varobj("output", func) + + # Verify that Kernel methods still shadow deprecated variable lookups. + scope = mkscope + assert_nothing_raised("No exception for Kernel shadowed variable names") do + ast.evaluate(scope) + end + end + + def test_tempatefunction_cannot_see_scopes + template = tempfile + + File.open(template, "w") do |f| + f.puts "<%= lookupvar('myvar') %>" + end + + func = nil + assert_nothing_raised do + + func = Puppet::Parser::AST::Function.new( + + :name => "template", + :ftype => :rvalue, + + :arguments => AST::ASTArray.new( + :children => [stringobj(template)] + ) + ) + end + ast = varobj("output", func) + + scope = mkscope + scope.setvar("myvar", "this is yayness") + assert_raise(Puppet::ParseError) do + ast.evaluate(scope) + end + end + + def test_template_reparses + template = tempfile + + File.open(template, "w") do |f| + f.puts "original text" + end + + file = tempfile + + Puppet[:code] = %{file { "#{file}": content => template("#{template}") }} + Puppet[:environment] = "yay" + node = mknode + node.stubs(:environment).returns Puppet::Node::Environment.new + + Puppet[:environment] = "yay" + + catalog = Puppet::Parser::Compiler.new(node).compile + + version = catalog.version + + fileobj = catalog.vertices.find { |r| r.title == file } + assert(fileobj, "File was not in catalog") + + + assert_equal( + "original text\n", fileobj["content"], + + "Template did not work") + + Puppet[:filetimeout] = -5 + # Have to sleep because one second is the fs's time granularity. + sleep(1) + + # Now modify the template + File.open(template, "w") do |f| + f.puts "new text" + end + + newversion = Puppet::Parser::Compiler.new(node).compile.version + + assert(version != newversion, "Parse date did not change") + end + + def test_template_defined_vars + template = tempfile + + File.open(template, "w") do |f| + f.puts "template <%= @yayness %>" + end + + func = nil + assert_nothing_raised do + + func = Puppet::Parser::AST::Function.new( + + :name => "template", + :ftype => :rvalue, + + :arguments => AST::ASTArray.new( + :children => [stringobj(template)] + ) + ) + end + ast = varobj("output", func) + + { + "" => "", + false => "false", + }.each do |string, value| + scope = mkscope + scope.setvar("yayness", string) + assert_equal(string, scope.lookupvar("yayness")) + + assert_nothing_raised("An empty string was not a valid variable value") do + ast.evaluate(scope) + end + + assert_equal( + "template #{value}\n", scope.lookupvar("output"), + "#{string.inspect} did not get evaluated correctly") + end + end + + def test_autoloading_functions + #assert_equal(false, Puppet::Parser::Functions.function(:autofunc), + # "Got told autofunc already exists") + + dir = tempfile + $LOAD_PATH << dir + newpath = File.join(dir, "puppet", "parser", "functions") + FileUtils.mkdir_p(newpath) + + File.open(File.join(newpath, "autofunc.rb"), "w") { |f| + f.puts %{ + Puppet::Parser::Functions.newfunction(:autofunc, :type => :rvalue) do |vals| + Puppet.wanring vals.inspect + end + } + } + Puppet::Node::Environment.stubs(:current).returns nil + obj = nil + assert_nothing_raised { + obj = Puppet::Parser::Functions.function(:autofunc) + } + + assert(obj, "Did not autoload function") + assert(Puppet::Parser::Functions.environment_module.method_defined?(:function_autofunc), "Did not set function correctly") + end + + def test_search + scope = mkscope + + fun = scope.known_resource_types.add Puppet::Resource::Type.new(:definition, "yay::ness") + foo = scope.known_resource_types.add Puppet::Resource::Type.new(:definition, "foo::bar") + + search = Puppet::Parser::Functions.function(:search) + scope.function_search(["foo", "yay"]) + + ffun = ffoo = nil + assert_nothing_raised("Search path change did not work") do + ffun = scope.find_definition("ness") + ffoo = scope.find_definition('bar') + end + + assert(ffun, "Could not find definition in 'fun' namespace") + assert(ffoo, "Could not find definition in 'foo' namespace") + end + + def test_include + scope = mkscope + parser = mkparser + + include = Puppet::Parser::Functions.function(:include) + + assert_raise(Puppet::Error, "did not throw error on missing class") do + scope.function_include("nosuchclass") + end + + scope.known_resource_types.add Puppet::Resource::Type.new(:hostclass, "myclass", {}) + + scope.compiler.expects(:evaluate_classes).with(%w{myclass otherclass}, scope, false).returns(%w{myclass otherclass}) + + assert_nothing_raised do + scope.function_include(["myclass", "otherclass"]) + end + end + + def test_file + parser = mkparser + scope = mkscope(:parser => parser) + + file = Puppet::Parser::Functions.function(:file) + + file1 = tempfile + file2 = tempfile + file3 = tempfile + + File.open(file2, "w") { |f| f.puts "yaytest" } + + val = nil + assert_nothing_raised("Failed to call file with one arg") do + val = scope.function_file([file2]) + end + + assert_equal("yaytest\n", val, "file() failed") + + assert_nothing_raised("Failed to call file with two args") do + val = scope.function_file([file1, file2]) + end + + assert_equal("yaytest\n", val, "file() failed") + + assert_raise(Puppet::ParseError, "did not fail when files are missing") do + val = scope.function_file([file1, file3]) + end + end + + def test_generate + command = tempfile + sh = %x{which sh} + File.open(command, "w") do |f| + f.puts %{#!#{sh} + if [ -n "$1" ]; then + echo "yay-$1" + else + echo yay + fi + } + end + File.chmod(0755, command) + assert_equal("yay\n", %x{#{command}}, "command did not work") + assert_equal("yay-foo\n", %x{#{command} foo}, "command did not work") + + Puppet::Node::Environment.stubs(:current).returns nil + generate = Puppet::Parser::Functions.function(:generate) + + scope = mkscope + parser = mkparser + + val = nil + assert_nothing_raised("Could not call generator with no args") do + val = scope.function_generate([command]) + end + assert_equal("yay\n", val, "generator returned wrong results") + + assert_nothing_raised("Could not call generator with args") do + val = scope.function_generate([command, "foo"]) + end + assert_equal("yay-foo\n", val, "generator returned wrong results") + + assert_raise(Puppet::ParseError, "Did not fail with an unqualified path") do + val = scope.function_generate([File.basename(command), "foo"]) + end + + assert_raise(Puppet::ParseError, "Did not fail when command failed") do + val = scope.function_generate([%x{which touch}.chomp, "/this/dir/does/not/exist"]) + end + + fake = File.join(File.dirname(command), "..") + dir = File.dirname(command) + dirname = File.basename(dir) + bad = File.join(dir, "..", dirname, File.basename(command)) + assert_raise(Puppet::ParseError, "Did not fail when command failed") do + val = scope.function_generate([bad]) + end + end +end + --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/test/language/snippets.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/test/language/snippets.rb @@ -0,0 +1,522 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../lib/puppettest') + +require 'puppet' +require 'puppet/parser/parser' +require 'puppet/network/client' +require 'puppet/network/handler' +require 'puppettest' + +class TestSnippets < Test::Unit::TestCase + include PuppetTest + + def setup + super + @file = Puppet::Type.type(:file) + Facter.stubs(:to_hash).returns({}) + Facter.stubs(:value).returns("whatever") + end + + def self.snippetdir + PuppetTest.datadir "snippets" + end + + def assert_file(path, msg = nil) + unless file = @catalog.resource(:file, path) + msg ||= "Could not find file #{path}" + raise msg + end + end + + def assert_not_file(path, msg = nil) + if file = @catalog.resource(:file, path) + msg ||= "File #{path} exists!" + raise msg + end + end + + def assert_mode_equal(mode, path) + if mode.is_a? Integer + mode = mode.to_s(8) + end + unless file = @catalog.resource(:file, path) + raise "Could not find file #{path}" + end + + unless mode == file.should(:mode) + raise "Mode for %s is incorrect: %o vs %o" % [path, mode, file.should(:mode)] + end + end + + def snippet(name) + File.join(self.class.snippetdir, name) + end + + def file2ast(file) + parser = Puppet::Parser::Parser.new + parser.file = file + ast = parser.parse + + ast + end + + def snippet2ast(text) + parser = Puppet::Parser::Parser.new + parser.string = text + ast = parser.parse + + ast + end + + def client + args = { + :Listen => false + } + Puppet::Network::Client.new(args) + end + + def ast2scope(ast) + scope = Puppet::Parser::Scope.new + ast.evaluate(scope) + + scope + end + + def scope2objs(scope) + objs = scope.to_trans + end + + def snippet2scope(snippet) + ast = snippet2ast(snippet) + scope = ast2scope(ast) + end + + def snippet2objs(snippet) + ast = snippet2ast(snippet) + scope = ast2scope(ast) + objs = scope2objs(scope) + end + + def properties(type) + properties = type.validproperties + end + + def metaparams(type) + mparams = [] + Puppet::Type.eachmetaparam { |param| + mparams.push param + } + + mparams + end + + def params(type) + params = [] + type.parameters.each { |name,property| + params.push name + } + + params + end + + def randthing(thing,type) + list = self.send(thing,type) + list[rand(list.length)] + end + + def randeach(type) + [:properties, :metaparams, :parameters].collect { |thing| + randthing(thing,type) + } + end + + @@snippets = { + true => [ + %{File { mode => 755 }} + ], + } + + def disabled_test_defaults + Puppet::Type.eachtype { |type| + next if type.name == :puppet or type.name == :component + + rands = randeach(type) + + name = type.name.to_s.capitalize + + [0..1, 0..2].each { |range| + params = rands[range] + paramstr = params.collect { |param| + "#{param} => fake" + }.join(", ") + + str = "#{name} { #{paramstr} }" + + scope = nil + assert_nothing_raised { + scope = snippet2scope(str) + } + + defaults = nil + assert_nothing_raised { + defaults = scope.lookupdefaults(name) + } + + p defaults + + params.each { |param| + puts "#{name} => '#{param}'" + assert(defaults.include?(param)) + } + } + } + end + + # this is here in case no tests get defined; otherwise we get a warning + def test_nothing + end + + def snippet_filecreate + %w{a b c d}.each { |letter| + path = "/tmp/create#{letter}test" + assert_file(path) + assert_mode_equal(0755, path) if %w{a b}.include?(letter) + } + end + + def snippet_simpledefaults + path = "/tmp/defaulttest" + assert_file(path) + assert_mode_equal(0755, path) + end + + def snippet_simpleselector + files = %w{a b c d}.collect { |letter| + path = "/tmp/snippetselect#{letter}test" + assert_file(path) + assert_mode_equal(0755, path) + } + end + + def snippet_classpathtest + path = "/tmp/classtest" + + file = @catalog.resource(:file, path) + assert(file, "did not create file #{path}") + + assert_equal( "/Stage[main]/Testing/Mytype[componentname]/File[/tmp/classtest]", file.path) + end + + def snippet_argumentdefaults + path1 = "/tmp/argumenttest1" + path2 = "/tmp/argumenttest2" + + file1 = @catalog.resource(:file, path1) + file2 = @catalog.resource(:file, path2) + + assert_file(path1) + assert_mode_equal(0755, path1) + + assert_file(path2) + assert_mode_equal(0644, path2) + end + + def snippet_casestatement + paths = %w{ + /tmp/existsfile + /tmp/existsfile2 + /tmp/existsfile3 + /tmp/existsfile4 + /tmp/existsfile5 + /tmp/existsfile6 + } + + paths.each { |path| + file = @catalog.resource(:file, path) + assert(file, "File #{path} is missing") + assert_mode_equal(0755, path) + } + end + + def snippet_implicititeration + paths = %w{a b c d e f g h}.collect { |l| "/tmp/iteration#{l}test" } + + paths.each { |path| + file = @catalog.resource(:file, path) + assert_file(path) + assert_mode_equal(0755, path) + } + end + + def snippet_multipleinstances + paths = %w{a b c}.collect { |l| "/tmp/multipleinstances#{l}" } + + paths.each { |path| + assert_file(path) + assert_mode_equal(0755, path) + + } + end + + def snippet_namevartest + file = "/tmp/testfiletest" + dir = "/tmp/testdirtest" + assert_file(file) + assert_file(dir) + assert_equal(:directory, @catalog.resource(:file, dir).should(:ensure), "Directory is not set to be a directory") + end + + def snippet_scopetest + file = "/tmp/scopetest" + assert_file(file) + assert_mode_equal(0755, file) + end + + def snippet_selectorvalues + nums = %w{1 2 3 4 5 6 7} + files = nums.collect { |n| + "/tmp/selectorvalues#{n}" + } + + files.each { |f| + assert_file(f) + assert_mode_equal(0755, f) + } + end + + def snippet_singleselector + nums = %w{1 2 3} + files = nums.collect { |n| + "/tmp/singleselector#{n}" + } + + files.each { |f| + assert_file(f) + assert_mode_equal(0755, f) + } + end + + def snippet_falsevalues + file = "/tmp/falsevaluesfalse" + assert_file(file) + end + + def disabled_snippet_classargtest + [1,2].each { |num| + file = "/tmp/classargtest#{num}" + assert_file(file) + assert_mode_equal(0755, file) + } + end + + def snippet_classheirarchy + [1,2,3].each { |num| + file = "/tmp/classheir#{num}" + assert_file(file) + assert_mode_equal(0755, file) + } + end + + def snippet_singleary + [1,2,3,4].each { |num| + file = "/tmp/singleary#{num}" + assert_file(file) + } + end + + def snippet_classincludes + [1,2,3].each { |num| + file = "/tmp/classincludes#{num}" + assert_file(file) + assert_mode_equal(0755, file) + } + end + + def snippet_componentmetaparams + ["/tmp/component1", "/tmp/component2"].each { |file| + assert_file(file) + } + end + + def snippet_aliastest + %w{/tmp/aliastest /tmp/aliastest2 /tmp/aliastest3}.each { |file| + assert_file(file) + } + end + + def snippet_singlequote + { 1 => 'a $quote', + 2 => 'some "\yayness\"' + }.each { |count, str| + path = "/tmp/singlequote#{count}" + assert_file(path) + assert_equal(str, @catalog.resource(:file, path).parameter(:content).actual_content) + } + end + + # There's no way to actually retrieve the list of classes from the + # transaction. + def snippet_tag + end + + # Make sure that set tags are correctly in place, yo. + def snippet_tagged + tags = {"testing" => true, "yayness" => false, + "both" => false, "bothtrue" => true, "define" => true} + + tags.each do |tag, retval| + assert_file("/tmp/tagged#{tag}#{retval.to_s}") + end + end + + def snippet_defineoverrides + file = "/tmp/defineoverrides1" + assert_file(file) + assert_mode_equal(0755, file) + end + + def snippet_deepclassheirarchy + 5.times { |i| + i += 1 + file = "/tmp/deepclassheir#{i}" + assert_file(file) + } + end + + def snippet_emptyclass + # There's nothing to check other than that it works + end + + def snippet_emptyexec + assert(@catalog.resource(:exec, "touch /tmp/emptyexectest"), "Did not create exec") + end + + def snippet_multisubs + path = "/tmp/multisubtest" + assert_file(path) + file = @catalog.resource(:file, path) + assert_equal("{md5}5fbef65269a99bddc2106251dd89b1dc", file.should(:content), "sub2 did not override content") + assert_mode_equal(0755, path) + end + + def snippet_collection + assert_file("/tmp/colltest1") + assert_nil(@catalog.resource(:file, "/tmp/colltest2"), "Incorrectly collected file") + end + + def snippet_virtualresources + %w{1 2 3 4}.each do |num| + assert_file("/tmp/virtualtest#{num}") + end + end + + def snippet_componentrequire + %w{1 2}.each do |num| + + assert_file( + "/tmp/testing_component_requires#{num}", + + "#{num} does not exist") + end + end + + def snippet_realize_defined_types + assert_file("/tmp/realize_defined_test1") + assert_file("/tmp/realize_defined_test2") + end + + def snippet_collection_within_virtual_definitions + assert_file("/tmp/collection_within_virtual_definitions1_foo.txt") + assert_file("/tmp/collection_within_virtual_definitions2_foo2.txt") + end + + def snippet_fqparents + assert_file("/tmp/fqparent1", "Did not make file from parent class") + assert_file("/tmp/fqparent2", "Did not make file from subclass") + end + + def snippet_fqdefinition + assert_file("/tmp/fqdefinition", "Did not make file from fully-qualified definition") + end + + def snippet_subclass_name_duplication + assert_file("/tmp/subclass_name_duplication1", "Did not make first file from duplicate subclass names") + assert_file("/tmp/subclass_name_duplication2", "Did not make second file from duplicate subclass names") + end + + def snippet_funccomma + assert_file("/tmp/funccomma1", "Did not make first file from trailing function comma") + assert_file("/tmp/funccomma2", "Did not make second file from trailing function comma") + end + + def snippet_arraytrailingcomma + assert_file("/tmp/arraytrailingcomma1", "Did not make first file from array") + assert_file("/tmp/arraytrailingcomma2", "Did not make second file from array") + end + + def snippet_multipleclass + assert_file("/tmp/multipleclassone", "one") + assert_file("/tmp/multipleclasstwo", "two") + end + + def snippet_multilinecomments + assert_not_file("/tmp/multilinecomments","Did create a commented resource"); + end + + def snippet_collection_override + path = "/tmp/collection" + assert_file(path) + assert_mode_equal(0600, path) + end + + def snippet_ifexpression + assert_file("/tmp/testiftest","if test"); + end + + def snippet_hash + assert_file("/tmp/myhashfile1","hash test 1"); + assert_file("/tmp/myhashfile2","hash test 2"); + assert_file("/tmp/myhashfile3","hash test 3"); + assert_file("/tmp/myhashfile4","hash test 4"); + end + + # Iterate across each of the snippets and create a test. + Dir.entries(snippetdir).sort.each { |file| + next if file =~ /^\./ + + + mname = "snippet_" + file.sub(/\.pp$/, '') + if self.method_defined?(mname) + #eval("alias #{testname} #{mname}") + testname = ("test_#{mname}").intern + self.send(:define_method, testname) { + Puppet[:manifest] = snippet(file) + facts = { + "hostname" => "testhost", + "domain" => "domain.com", + "ipaddress" => "127.0.0.1", + "fqdn" => "testhost.domain.com" + } + + node = Puppet::Node.new("testhost") + node.merge(facts) + + catalog = nil + assert_nothing_raised("Could not compile catalog") { + catalog = Puppet::Resource::Catalog.indirection.find(node) + } + + assert_nothing_raised("Could not convert catalog") { + catalog = catalog.to_ral + } + + @catalog = catalog + assert_nothing_raised { + self.send(mname) + } + } + mname = mname.intern + end + } +end --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/test/rails/rails.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/test/rails/rails.rb @@ -0,0 +1,25 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../lib/puppettest') + +require 'puppet' +require 'puppet/rails' +require 'puppet/parser/parser' +require 'puppet/network/client' +require 'puppettest' +require 'puppettest/parsertesting' +require 'puppettest/resourcetesting' +require 'puppettest/railstesting' + +class TestRails < Test::Unit::TestCase + include PuppetTest::ParserTesting + include PuppetTest::ResourceTesting + include PuppetTest::RailsTesting + + def test_includerails + assert_nothing_raised { + require 'puppet/rails' + } + end +end + --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/test/ral/type/filesources.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/test/ral/type/filesources.rb @@ -0,0 +1,507 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../lib/puppettest') + +require 'puppettest' +require 'puppettest/support/utils' +require 'cgi' +require 'fileutils' +require 'mocha' + +class TestFileSources < Test::Unit::TestCase + include PuppetTest::Support::Utils + include PuppetTest::FileTesting + def setup + super + if defined?(@port) + @port += 1 + else + @port = 12345 + end + @file = Puppet::Type.type(:file) + Puppet[:filetimeout] = -1 + Puppet::Util::SUIDManager.stubs(:asuser).yields + Facter.stubs(:to_hash).returns({}) + end + + def teardown + super + Puppet::Network::HttpPool.clear_http_instances + end + + def use_storage + initstorage + rescue + system("rm -rf #{Puppet[:statefile]}") + end + + def initstorage + Puppet::Util::Storage.init + Puppet::Util::Storage.load + end + + # Make a simple recursive tree. + def mk_sourcetree + source = tempfile + sourcefile = File.join(source, "file") + Dir.mkdir source + File.open(sourcefile, "w") { |f| f.puts "yay" } + + dest = tempfile + destfile = File.join(dest, "file") + return source, dest, sourcefile, destfile + end + + def recursive_source_test(fromdir, todir) + initstorage + tofile = nil + trans = nil + + + tofile = Puppet::Type.type(:file).new( + + :path => todir, + :recurse => true, + :backup => false, + + :source => fromdir + ) + catalog = mk_catalog(tofile) + catalog.apply + + assert(FileTest.exists?(todir), "Created dir #{todir} does not exist") + end + + def run_complex_sources(networked = false) + path = tempfile + + # first create the source directory + FileUtils.mkdir_p path + + # okay, let's create a directory structure + fromdir = File.join(path,"fromdir") + Dir.mkdir(fromdir) + FileUtils.cd(fromdir) { + File.open("one", "w") { |f| f.puts "onefile"} + File.open("two", "w") { |f| f.puts "twofile"} + } + + todir = File.join(path, "todir") + source = fromdir + source = "puppet://localhost/#{networked}#{fromdir}" if networked + recursive_source_test(source, todir) + + [fromdir,todir, File.join(todir, "one"), File.join(todir, "two")] + end + + def test_complex_sources_twice + fromdir, todir, one, two = run_complex_sources + assert_trees_equal(fromdir,todir) + recursive_source_test(fromdir, todir) + assert_trees_equal(fromdir,todir) + # Now remove the whole tree and try it again. + [one, two].each do |f| File.unlink(f) end + Dir.rmdir(todir) + recursive_source_test(fromdir, todir) + assert_trees_equal(fromdir,todir) + end + + def test_sources_with_deleted_destfiles + fromdir, todir, one, two = run_complex_sources + assert(FileTest.exists?(todir)) + + # then delete a file + File.unlink(two) + + # and run + recursive_source_test(fromdir, todir) + + assert(FileTest.exists?(two), "Deleted file was not recopied") + + # and make sure they're still equal + assert_trees_equal(fromdir,todir) + end + + def test_sources_with_readonly_destfiles + fromdir, todir, one, two = run_complex_sources + assert(FileTest.exists?(todir)) + File.chmod(0600, one) + recursive_source_test(fromdir, todir) + + # and make sure they're still equal + assert_trees_equal(fromdir,todir) + + # Now try it with the directory being read-only + File.chmod(0111, todir) + recursive_source_test(fromdir, todir) + + # and make sure they're still equal + assert_trees_equal(fromdir,todir) + end + + def test_sources_with_modified_dest_files + fromdir, todir, one, two = run_complex_sources + + assert(FileTest.exists?(todir)) + + # Modify a dest file + File.open(two, "w") { |f| f.puts "something else" } + + recursive_source_test(fromdir, todir) + + # and make sure they're still equal + assert_trees_equal(fromdir,todir) + end + + def test_sources_with_added_destfiles + fromdir, todir = run_complex_sources + assert(FileTest.exists?(todir)) + # and finally, add some new files + add_random_files(todir) + + recursive_source_test(fromdir, todir) + + fromtree = file_list(fromdir) + totree = file_list(todir) + + assert(fromtree != totree, "Trees are incorrectly equal") + + # then remove our new files + FileUtils.cd(todir) { + %x{find . 2>/dev/null}.chomp.split(/\n/).each { |file| + if file =~ /file[0-9]+/ + FileUtils.rm_rf(file) + end + } + } + + # and make sure they're still equal + assert_trees_equal(fromdir,todir) + end + + # Make sure added files get correctly caught during recursion + def test_RecursionWithAddedFiles + basedir = tempfile + Dir.mkdir(basedir) + @@tmpfiles << basedir + file1 = File.join(basedir, "file1") + file2 = File.join(basedir, "file2") + subdir1 = File.join(basedir, "subdir1") + file3 = File.join(subdir1, "file") + File.open(file1, "w") { |f| f.puts "yay" } + rootobj = nil + assert_nothing_raised { + + rootobj = Puppet::Type.type(:file).new( + + :name => basedir, + :recurse => true, + :check => %w{type owner}, + + :mode => 0755 + ) + } + + assert_apply(rootobj) + assert_equal(0755, filemode(file1)) + + File.open(file2, "w") { |f| f.puts "rah" } + assert_apply(rootobj) + assert_equal(0755, filemode(file2)) + + Dir.mkdir(subdir1) + File.open(file3, "w") { |f| f.puts "foo" } + assert_apply(rootobj) + assert_equal(0755, filemode(file3)) + end + + def mkfileserverconf(mounts) + file = tempfile + File.open(file, "w") { |f| + mounts.each { |path, name| + f.puts "[#{name}]\n\tpath #{path}\n\tallow *\n" + } + } + + @@tmpfiles << file + file + end + + def test_unmountedNetworkSources + server = nil + mounts = { + "/" => "root", + "/noexistokay" => "noexist" + } + + fileserverconf = mkfileserverconf(mounts) + + Puppet[:autosign] = true + Puppet[:masterport] = @port + Puppet[:certdnsnames] = "localhost" + + serverpid = nil + assert_nothing_raised("Could not start on port #{@port}") { + + server = Puppet::Network::HTTPServer::WEBrick.new( + + :Port => @port, + + :Handlers => { + :CA => {}, # so that certs autogenerate + :FileServer => { + :Config => fileserverconf + } + } + ) + + } + + serverpid = fork { + assert_nothing_raised { + #trap(:INT) { server.shutdown; Kernel.exit! } + trap(:INT) { server.shutdown } + server.start + } + } + @@tmppids << serverpid + + sleep(1) + + name = File.join(tmpdir, "nosourcefile") + + file = Puppet::Type.type(:file).new( + + :source => "puppet://localhost/noexist/file", + + :name => name + ) + + assert_raise Puppet::Error do + file.retrieve + end + + comp = mk_catalog(file) + comp.apply + + assert(!FileTest.exists?(name), "File with no source exists anyway") + end + + def test_sourcepaths + files = [] + 3.times { + files << tempfile + } + + to = tempfile + + File.open(files[-1], "w") { |f| f.puts "yee-haw" } + + file = nil + assert_nothing_raised { + + file = Puppet::Type.type(:file).new( + + :name => to, + + :source => files + ) + } + + comp = mk_catalog(file) + assert_events([:file_created], comp) + + assert(File.exists?(to), "File does not exist") + + txt = nil + File.open(to) { |f| txt = f.read.chomp } + + assert_equal("yee-haw", txt, "Contents do not match") + end + + # Make sure that source-copying updates the checksum on the same run + def test_sourcebeatsensure + source = tempfile + dest = tempfile + File.open(source, "w") { |f| f.puts "yay" } + + file = nil + assert_nothing_raised { + file = Puppet::Type.type(:file).new( + :name => dest, + :ensure => "file", + :source => source + ) + } + + file.retrieve + + assert_events([:file_created], file) + file.retrieve + assert_events([], file) + assert_events([], file) + end + + def test_sourcewithlinks + source = tempfile + link = tempfile + dest = tempfile + + File.open(source, "w") { |f| f.puts "yay" } + File.symlink(source, link) + + file = Puppet::Type.type(:file).new(:name => dest, :source => link) + + catalog = mk_catalog(file) + + # Default to managing links + catalog.apply + assert(FileTest.symlink?(dest), "Did not create link") + + # Now follow the links + file[:links] = :follow + catalog.apply + assert(FileTest.file?(dest), "Destination is not a file") + end + + # Make sure files aren't replaced when replace is false, but otherwise + # are. + def test_replace + dest = tempfile + + file = Puppet::Type.newfile( + + :path => dest, + :content => "foobar", + + :recurse => true + ) + + + assert_apply(file) + + File.open(dest, "w") { |f| f.puts "yayness" } + + file[:replace] = false + + assert_apply(file) + + # Make sure it doesn't change. + assert_equal("yayness\n", File.read(dest), "File got replaced when :replace was false") + + file[:replace] = true + assert_apply(file) + + # Make sure it changes. + assert_equal("foobar", File.read(dest), "File was not replaced when :replace was true") + end + + def test_sourceselect + dest = tempfile + sources = [] + 2.times { |i| + i = i + 1 + source = tempfile + sources << source + file = File.join(source, "file#{i}") + Dir.mkdir(source) + File.open(file, "w") { |f| f.print "yay" } + } + file1 = File.join(dest, "file1") + file2 = File.join(dest, "file2") + file3 = File.join(dest, "file3") + + # Now make different files with the same name in each source dir + sources.each_with_index do |source, i| + File.open(File.join(source, "file3"), "w") { |f| + f.print i.to_s + } + end + + + obj = Puppet::Type.newfile( + :path => dest, :recurse => true, + + :source => sources) + + assert_equal(:first, obj[:sourceselect], "sourceselect has the wrong default") + # First, make sure we default to just copying file1 + assert_apply(obj) + + assert(FileTest.exists?(file1), "File from source 1 was not copied") + assert(! FileTest.exists?(file2), "File from source 2 was copied") + assert(FileTest.exists?(file3), "File from source 1 was not copied") + assert_equal("0", File.read(file3), "file3 got wrong contents") + + # Now reset sourceselect + assert_nothing_raised do + obj[:sourceselect] = :all + end + File.unlink(file1) + File.unlink(file3) + Puppet.err :yay + assert_apply(obj) + + assert(FileTest.exists?(file1), "File from source 1 was not copied") + assert(FileTest.exists?(file2), "File from source 2 was copied") + assert(FileTest.exists?(file3), "File from source 1 was not copied") + assert_equal("0", File.read(file3), "file3 got wrong contents") + end + + def test_recursive_sourceselect + dest = tempfile + source1 = tempfile + source2 = tempfile + files = [] + [source1, source2, File.join(source1, "subdir"), File.join(source2, "subdir")].each_with_index do |dir, i| + Dir.mkdir(dir) + # Make a single file in each directory + file = File.join(dir, "file#{i}") + File.open(file, "w") { |f| f.puts "yay#{i}"} + + # Now make a second one in each directory + file = File.join(dir, "second-file#{i}") + File.open(file, "w") { |f| f.puts "yaysecond-#{i}"} + files << file + end + + obj = Puppet::Type.newfile(:path => dest, :source => [source1, source2], :sourceselect => :all, :recurse => true) + + assert_apply(obj) + + ["file0", "file1", "second-file0", "second-file1", "subdir/file2", "subdir/second-file2", "subdir/file3", "subdir/second-file3"].each do |file| + path = File.join(dest, file) + assert(FileTest.exists?(path), "did not create #{file}") + + assert_equal("yay#{File.basename(file).sub("file", '')}\n", File.read(path), "file was not copied correctly") + end + end + + # #594 + def test_purging_missing_remote_files + source = tempfile + dest = tempfile + s1 = File.join(source, "file1") + s2 = File.join(source, "file2") + d1 = File.join(dest, "file1") + d2 = File.join(dest, "file2") + Dir.mkdir(source) + [s1, s2].each { |name| File.open(name, "w") { |file| file.puts "something" } } + + # We have to add a second parameter, because that's the only way to expose the "bug". + file = Puppet::Type.newfile(:path => dest, :source => source, :recurse => true, :purge => true, :mode => "755") + + assert_apply(file) + + assert(FileTest.exists?(d1), "File1 was not copied") + assert(FileTest.exists?(d2), "File2 was not copied") + + File.unlink(s2) + + assert_apply(file) + + assert(FileTest.exists?(d1), "File1 was not kept") + assert(! FileTest.exists?(d2), "File2 was not purged") + end +end + --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/test/lib/puppettest/exetest.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/test/lib/puppettest/exetest.rb @@ -0,0 +1,125 @@ +require 'puppettest/servertest' + +module PuppetTest::ExeTest + include PuppetTest::ServerTest + + def setup + super + setbindir + setlibdir + end + + def bindir + File.join(basedir, "bin") + end + + def sbindir + File.join(basedir, "sbin") + end + + def setbindir + ENV["PATH"] = [bindir, ENV["PATH"]].join(":") unless ENV["PATH"].split(":").include?(bindir) + ENV["PATH"] = [sbindir, ENV["PATH"]].join(":") unless ENV["PATH"].split(":").include?(sbindir) + end + + def setlibdir + ENV["RUBYLIB"] = $LOAD_PATH.find_all { |dir| + dir =~ /puppet/ or dir =~ /\.\./ + }.join(":") + end + + # Run a ruby command. This explicitly uses ruby to run stuff, since we + # don't necessarily know where our ruby binary is, dernit. + # Currently unused, because I couldn't get it to work. + def rundaemon(*cmd) + @ruby ||= %x{which ruby}.chomp + cmd = cmd.unshift(@ruby).join(" ") + + out = nil + Dir.chdir(bindir) { + out = %x{#{@ruby} #{cmd}} + } + out + end + + def startmasterd(args = "") + output = nil + + manifest = mktestmanifest + args += " --manifest #{manifest}" + args += " --confdir #{Puppet[:confdir]}" + args += " --rundir #{File.join(Puppet[:vardir], "run")}" + args += " --vardir #{Puppet[:vardir]}" + args += " --certdnsnames #{Puppet[:certdnsnames]}" + args += " --masterport #{@@port}" + args += " --user #{Puppet::Util::SUIDManager.uid}" + args += " --group #{Puppet::Util::SUIDManager.gid}" + args += " --autosign true" + + #if Puppet[:debug] + # args += " --debug" + #end + + cmd = "puppetmasterd #{args}" + + + assert_nothing_raised { + output = %x{#{cmd}}.chomp + } + assert_equal("", output, "Puppetmasterd produced output #{output}") + assert($CHILD_STATUS == 0, "Puppetmasterd exit status was #{$CHILD_STATUS}") + sleep(1) + + cleanup do + stopmasterd + sleep(1) + end + + manifest + end + + def stopmasterd(running = true) + ps = Facter["ps"].value || "ps -ef" + + pidfile = File.join(Puppet[:vardir], "run", "puppetmasterd.pid") + + pid = nil + if FileTest.exists?(pidfile) + pid = File.read(pidfile).chomp.to_i + File.unlink(pidfile) + end + + return unless running + if running or pid + runningpid = nil + %x{#{ps}}.chomp.split(/\n/).each { |line| + if line =~ /ruby.+puppetmasterd/ + next if line =~ /\.rb/ # skip the test script itself + next if line =~ /^puppet/ # skip masters running as 'puppet' + ary = line.sub(/^\s+/, '').split(/\s+/) + pid = ary[1].to_i + end + } + + end + + # we default to mandating that it's running, but teardown + # doesn't require that + if pid + if pid == $PID + raise Puppet::Error, "Tried to kill own pid" + end + begin + Process.kill(:INT, pid) + rescue + # ignore it + end + end + end + + def teardown + stopmasterd(false) + super + end +end + --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/test/lib/puppettest/servertest.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/test/lib/puppettest/servertest.rb @@ -0,0 +1,74 @@ +require 'puppettest' +require 'puppet/network/http_server/webrick' + +module PuppetTest::ServerTest + include PuppetTest + def setup + super + + if defined?(@@port) + @@port += 1 + else + @@port = 20000 + end + end + + # create a simple manifest that just creates a file + def mktestmanifest + file = File.join(Puppet[:confdir], "#{(self.class.to_s + "test")}site.pp") + #@createdfile = File.join(tmpdir, self.class.to_s + "manifesttesting" + + # "_#{@method_name}") + @createdfile = tempfile + + File.open(file, "w") { |f| + f.puts "file { \"%s\": ensure => file, mode => 755 }\n" % @createdfile + } + + @@tmpfiles << @createdfile + @@tmpfiles << file + + file + end + + # create a server, forked into the background + def mkserver(handlers = nil) + Puppet[:name] = "puppetmasterd" + # our default handlers + unless handlers + handlers = { + :CA => {}, # so that certs autogenerate + :Master => { + :Manifest => mktestmanifest, + :UseNodes => false + }, + } + end + + # then create the actual server + server = nil + assert_nothing_raised { + + server = Puppet::Network::HTTPServer::WEBrick.new( + + :Port => @@port, + + :Handlers => handlers + ) + } + + # fork it + spid = fork { + trap(:INT) { server.shutdown } + server.start + } + + # and store its pid for killing + @@tmppids << spid + + # give the server a chance to do its thing + sleep 1 + spid + end + +end + --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/test/network/client/ca.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/test/network/client/ca.rb @@ -0,0 +1,69 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../lib/puppettest') + +require 'mocha' +require 'puppettest' +require 'puppet/network/client/ca' +require 'puppet/sslcertificates/support' + +class TestClientCA < Test::Unit::TestCase + include PuppetTest::ServerTest + + def setup + Puppet::Util::SUIDManager.stubs(:asuser).yields + super + @ca = Puppet::Network::Handler.ca.new + @client = Puppet::Network::Client.ca.new :CA => @ca + end + + def test_request_cert + assert_nothing_raised("Could not request cert") do + @client.request_cert + end + + [:hostprivkey, :hostcert, :localcacert].each do |name| + assert(FileTest.exists?(Puppet.settings[name]), "Did not create cert #{name}") + end + end + + # Make sure the ca defaults to specific ports and names + def test_ca_server + Puppet.settings.stubs(:value).returns "eh" + Puppet.settings.expects(:value).with(:ca_server).returns("myca") + Puppet.settings.expects(:value).with(:ca_port).returns(321) + Puppet.settings.stubs(:value).with(:http_proxy_host).returns(nil) + Puppet.settings.stubs(:value).with(:http_proxy_port).returns(nil) + Puppet.settings.stubs(:value).with(:http_keepalive).returns(false) + Puppet.settings.stubs(:value).with(:configtimeout).returns(180) + + # Just throw an error; the important thing is the values, not what happens next. + Net::HTTP.stubs(:new).with("myca", 321, nil, nil).raises(ArgumentError) + assert_raise(ArgumentError) { Puppet::Network::Client.ca.new } + end + + # #578 + def test_invalid_certs_are_not_written + # Run the get once, which should be valid + + assert_nothing_raised("Could not get a certificate") do + @client.request_cert + end + + # Now remove the cert and keys, so we get a broken cert + File.unlink(Puppet[:hostcert]) + File.unlink(Puppet[:localcacert]) + File.unlink(Puppet[:hostprivkey]) + + @client = Puppet::Network::Client.ca.new :CA => @ca + @ca.expects(:getcert).returns("yay") # not a valid cert + # Now make sure it fails, since we'll get the old cert but have new keys + assert_raise(Puppet::Network::Client::CA::InvalidCertificate, "Did not fail on invalid cert") do + @client.request_cert + end + + # And then make sure the cert isn't written to disk + assert(! FileTest.exists?(Puppet[:hostcert]), "Invalid cert got written to disk") + end +end + --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/test/network/client/dipper.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/test/network/client/dipper.rb @@ -0,0 +1,34 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../lib/puppettest') + +require 'puppettest' +require 'puppet/file_bucket/dipper' + +class TestDipperClient < Test::Unit::TestCase + include PuppetTest::ServerTest + + def setup + super + @dipper = Puppet::FileBucket::Dipper.new(:Path => tempfile) + end + + # Make sure we can create a new file with 'restore'. + def test_restore_to_new_file + file = tempfile + text = "asdf;lkajseofiqwekj" + File.open(file, "w") { |f| f.puts text } + md5 = nil + assert_nothing_raised("Could not send file") do + md5 = @dipper.backup(file) + end + + newfile = tempfile + assert_nothing_raised("could not restore to new path") do + @dipper.restore(newfile, md5) + end + + assert_equal(File.read(file), File.read(newfile), "did not restore correctly") + end +end + --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/test/network/xmlrpc/client.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/test/network/xmlrpc/client.rb @@ -0,0 +1,45 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../lib/puppettest') + +require 'puppettest' +require 'puppet/network/xmlrpc/client' +require 'mocha' + +class TestXMLRPCClient < Test::Unit::TestCase + include PuppetTest + + def setup + Puppet::Util::SUIDManager.stubs(:asuser).yields + super + end + + def test_set_backtrace + error = Puppet::Network::XMLRPCClientError.new("An error") + assert_nothing_raised do + error.set_backtrace ["caller"] + end + assert_equal(["caller"], error.backtrace) + end + + # Make sure we correctly generate a netclient + def test_handler_class + # Create a test handler + klass = Puppet::Network::XMLRPCClient + yay = Class.new(Puppet::Network::Handler) do + @interface = XMLRPC::Service::Interface.new("yay") { |iface| + iface.add_method("array getcert(csr)") + } + + @name = :Yay + end + Object.const_set("Yay", yay) + + net = nil + assert_nothing_raised("Failed when retrieving client for handler") do + net = klass.handler_class(yay) + end + + assert(net, "did not get net client") + end +end --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/test/network/handler/ca.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/test/network/handler/ca.rb @@ -0,0 +1,273 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../lib/puppettest') + +require 'puppettest' +require 'puppet/network/handler/ca' +require 'mocha' + +$short = (ARGV.length > 0 and ARGV[0] == "short") + +class TestCA < Test::Unit::TestCase + include PuppetTest::ServerTest + + def setup + Puppet::Util::SUIDManager.stubs(:asuser).yields + super + end + + # Verify that we're autosigning. We have to autosign a "different" machine, + # since we always autosign the CA server's certificate. + def test_autocertgeneration + ca = nil + + # create our ca + assert_nothing_raised { + ca = Puppet::Network::Handler.ca.new(:autosign => true) + } + + # create a cert with a fake name + key = nil + csr = nil + cert = nil + hostname = "test.domain.com" + assert_nothing_raised { + cert = Puppet::SSLCertificates::Certificate.new( + :name => "test.domain.com" + ) + } + + # make the request + assert_nothing_raised { + cert.mkcsr + } + + # and get it signed + certtext = nil + cacerttext = nil + assert_nothing_raised { + certtext, cacerttext = ca.getcert(cert.csr.to_s) + } + + # they should both be strings + assert_instance_of(String, certtext) + assert_instance_of(String, cacerttext) + + # and they should both be valid certs + assert_nothing_raised { + OpenSSL::X509::Certificate.new(certtext) + } + assert_nothing_raised { + OpenSSL::X509::Certificate.new(cacerttext) + } + + # and pull it again, just to make sure we're getting the same thing + newtext = nil + assert_nothing_raised { + newtext, cacerttext = ca.getcert( + cert.csr.to_s, "test.reductivelabs.com", "127.0.0.1" + ) + } + + assert_equal(certtext,newtext) + end + + # this time don't use autosign + def test_storeAndSign + ca = nil + caserv = nil + + # make our CA server + assert_nothing_raised { + caserv = Puppet::Network::Handler.ca.new(:autosign => false) + } + + # retrieve the actual ca object + assert_nothing_raised { + ca = caserv.ca + } + + # make our test cert again + key = nil + csr = nil + cert = nil + hostname = "test.domain.com" + assert_nothing_raised { + cert = Puppet::SSLCertificates::Certificate.new( + :name => "anothertest.domain.com" + ) + } + # and the CSR + assert_nothing_raised { + cert.mkcsr + } + + # retrieve them + certtext = nil + assert_nothing_raised { + certtext, cacerttext = caserv.getcert( + cert.csr.to_s, "test.reductivelabs.com", "127.0.0.1" + ) + } + + # verify we got nothing back, since autosign is off + assert_equal("", certtext) + + # now sign it manually, with the CA object + x509 = nil + assert_nothing_raised { + x509, cacert = ca.sign(cert.csr) + } + + # and write it out + cert.cert = x509 + assert_nothing_raised { + cert.write + } + + assert(File.exists?(cert.certfile)) + + # now get them again, and verify that we actually get them + newtext = nil + assert_nothing_raised { + newtext, cacerttext = caserv.getcert(cert.csr.to_s) + } + + assert(newtext) + assert_nothing_raised { + OpenSSL::X509::Certificate.new(newtext) + } + + # Now verify that we can clean a given host's certs + assert_nothing_raised { + ca.clean("anothertest.domain.com") + } + + assert(!File.exists?(cert.certfile), "Cert still exists after clean") + end + + # and now test the autosign file + def test_autosign + autosign = File.join(tmpdir, "autosigntesting") + @@tmpfiles << autosign + File.open(autosign, "w") { |f| + f.puts "hostmatch.domain.com" + f.puts "*.other.com" + } + + caserv = nil + assert_nothing_raised { + caserv = Puppet::Network::Handler.ca.new(:autosign => autosign) + } + + # make sure we know what's going on + assert(caserv.autosign?("hostmatch.domain.com")) + assert(caserv.autosign?("fakehost.other.com")) + assert(!caserv.autosign?("kirby.reductivelabs.com")) + assert(!caserv.autosign?("culain.domain.com")) + end + + # verify that things aren't autosigned by default + def test_nodefaultautosign + caserv = nil + assert_nothing_raised { + caserv = Puppet::Network::Handler.ca.new + } + + # make sure we know what's going on + assert(!caserv.autosign?("hostmatch.domain.com")) + assert(!caserv.autosign?("fakehost.other.com")) + assert(!caserv.autosign?("kirby.reductivelabs.com")) + assert(!caserv.autosign?("culain.domain.com")) + end + + # We want the CA to autosign its own certificate, because otherwise + # the puppetmasterd CA does not autostart. + def test_caautosign + server = nil + Puppet.stubs(:master?).returns true + assert_nothing_raised { + + server = Puppet::Network::HTTPServer::WEBrick.new( + + :Port => @@port, + + :Handlers => { + :CA => {}, # so that certs autogenerate + :Status => nil + } + ) + } + end + + # Make sure true/false causes the file to be ignored. + def test_autosign_true_beats_file + caserv = nil + assert_nothing_raised { + caserv = Puppet::Network::Handler.ca.new + } + + host = "hostname.domain.com" + + # Create an autosign file + file = tempfile + Puppet[:autosign] = file + + File.open(file, "w") { |f| + f.puts host + } + + # Start with "false" + Puppet[:autosign] = false + + assert(! caserv.autosign?(host), "Host was incorrectly autosigned") + + # Then set it to true + Puppet[:autosign] = true + assert(caserv.autosign?(host), "Host was not autosigned") + # And try a different host + assert(caserv.autosign?("other.yay.com"), "Host was not autosigned") + + # And lastly the file + Puppet[:autosign] = file + assert(caserv.autosign?(host), "Host was not autosigned") + + # And try a different host + assert(! caserv.autosign?("other.yay.com"), "Host was autosigned") + end + + # Make sure that a CSR created with keys that don't match the existing + # cert throws an exception on the server. + def test_mismatched_public_keys_throws_exception + ca = Puppet::Network::Handler.ca.new + + # First initialize the server + client = Puppet::Network::Client.ca.new :CA => ca + client.request_cert + File.unlink(Puppet[:hostcsr]) + + # Now use a different cert name + Puppet[:certname] = "my.host.com" + client = Puppet::Network::Client.ca.new :CA => ca + firstcsr = client.csr + File.unlink(Puppet[:hostcsr]) if FileTest.exists?(Puppet[:hostcsr]) + + assert_nothing_raised("Could not get cert") do + ca.getcert(firstcsr.to_s) + end + + # Now get rid of the public key, forcing a new csr + File.unlink(Puppet[:hostprivkey]) + + client = Puppet::Network::Client.ca.new :CA => ca + + second_csr = client.csr + + assert(firstcsr.to_s != second_csr.to_s, "CSR did not change") + + assert_raise(Puppet::Error, "CA allowed mismatched keys") do + ca.getcert(second_csr.to_s) + end + end +end + --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/test/network/server/mongrel_test.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/test/network/server/mongrel_test.rb @@ -0,0 +1,99 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../lib/puppettest') + +require 'puppettest' +require 'mocha' + +class TestMongrelServer < PuppetTest::TestCase + confine "Missing mongrel" => Puppet.features.mongrel? + + include PuppetTest::ServerTest + + def mkserver(handlers = nil) + handlers ||= { :Status => nil } + mongrel = Puppet::Network::HTTPServer::Mongrel.new(handlers) + end + + # Make sure client info is correctly extracted. + def test_client_info + obj = Object.new + obj.singleton_class.send(:attr_accessor, :params) + params = {} + obj.params = params + + mongrel = mkserver + + ip = Facter.value(:ipaddress) + params["REMOTE_ADDR"] = ip + params[Puppet[:ssl_client_header]] = "" + params[Puppet[:ssl_client_verify_header]] = "failure" + info = nil + Resolv.expects(:getname).with(ip).returns("host.domain.com").times(4) + assert_nothing_raised("Could not call client_info") do + info = mongrel.send(:client_info, obj) + end + assert(! info.authenticated?, "Client info object was marked valid even though headers were missing") + assert_equal(ip, info.ip, "Did not copy over ip correctly") + + assert_equal("host.domain.com", info.name, "Did not copy over hostname correctly") + + # Now pass the X-Forwarded-For header and check it is preferred over REMOTE_ADDR + params["REMOTE_ADDR"] = '127.0.0.1' + params["HTTP_X_FORWARDED_FOR"] = ip + info = nil + assert_nothing_raised("Could not call client_info") do + info = mongrel.send(:client_info, obj) + end + assert(! info.authenticated?, "Client info object was marked valid even though headers were missing") + assert_equal(ip, info.ip, "Did not copy over ip correctly") + + assert_equal("host.domain.com", info.name, "Did not copy over hostname correctly") + + # Now add a valid auth header. + params["REMOTE_ADDR"] = ip + params["HTTP_X_FORWARDED_FOR"] = nil + params[Puppet[:ssl_client_header]] = "/CN=host.domain.com" + assert_nothing_raised("Could not call client_info") do + info = mongrel.send(:client_info, obj) + end + assert(! info.authenticated?, "Client info object was marked valid even though the verify header was fals") + assert_equal(ip, info.ip, "Did not copy over ip correctly") + assert_equal("host.domain.com", info.name, "Did not copy over hostname correctly") + + # Now change the verify header to be true + params[Puppet[:ssl_client_verify_header]] = "SUCCESS" + assert_nothing_raised("Could not call client_info") do + info = mongrel.send(:client_info, obj) + end + + assert(info.authenticated?, "Client info object was not marked valid even though all headers were correct") + assert_equal(ip, info.ip, "Did not copy over ip correctly") + assert_equal("host.domain.com", info.name, "Did not copy over hostname correctly") + + # Now try it with a different header name + params.delete(Puppet[:ssl_client_header]) + Puppet[:ssl_client_header] = "header_testing" + params["header_testing"] = "/CN=other.domain.com" + info = nil + assert_nothing_raised("Could not call client_info with other header") do + info = mongrel.send(:client_info, obj) + end + + assert(info.authenticated?, "Client info object was not marked valid even though the header was present") + assert_equal(ip, info.ip, "Did not copy over ip correctly") + assert_equal("other.domain.com", info.name, "Did not copy over hostname correctly") + + # Now make sure it's considered invalid without that header + params.delete("header_testing") + info = nil + assert_nothing_raised("Could not call client_info with no header") do + info = mongrel.send(:client_info, obj) + end + + assert(! info.authenticated?, "Client info object was marked valid without header") + assert_equal(ip, info.ip, "Did not copy over ip correctly") + assert_equal(Resolv.getname(ip), info.name, "Did not look up hostname correctly") + end +end + --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/test/network/server/webrick.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/test/network/server/webrick.rb @@ -0,0 +1,128 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../lib/puppettest') + +require 'puppettest' +require 'puppet/network/http_server/webrick' +require 'mocha' + +class TestWebrickServer < Test::Unit::TestCase + include PuppetTest::ServerTest + + def setup + Puppet::Util::SUIDManager.stubs(:asuser).yields + super + end + + def teardown + super + Puppet::Network::HttpPool.clear_http_instances + end + + # Make sure we can create a server, and that it knows how to create its + # certs by default. + def test_basics + server = nil + assert_raise(Puppet::Error, "server succeeded with no cert") do + + server = Puppet::Network::HTTPServer::WEBrick.new( + + :Port => @@port, + + :Handlers => { + :Status => nil + } + ) + end + + assert_nothing_raised("Could not create simple server") do + + server = Puppet::Network::HTTPServer::WEBrick.new( + + :Port => @@port, + + :Handlers => { + :CA => {}, # so that certs autogenerate + :Status => nil + } + ) + end + + assert(server, "did not create server") + + assert(server.cert, "did not retrieve cert") + end + + # test that we can connect to the server + # we have to use fork here, because we apparently can't use threads + # to talk to other threads + def test_connect_with_fork + Puppet[:autosign] = true + serverpid, server = mk_status_server + + # create a status client, and verify it can talk + client = mk_status_client + + assert(client.cert, "did not get cert for client") + + retval = nil + assert_nothing_raised("Could not connect to server") { + retval = client.status + } + assert_equal(1, retval) + end + + def mk_status_client + client = nil + + assert_nothing_raised { + + client = Puppet::Network::Client.status.new( + + :Server => "localhost", + + :Port => @@port + ) + } + client + end + + def mk_status_server + server = nil + Puppet[:certdnsnames] = "localhost" + assert_nothing_raised { + + server = Puppet::Network::HTTPServer::WEBrick.new( + + :Port => @@port, + + :Handlers => { + :CA => {}, # so that certs autogenerate + :Status => nil + } + ) + + } + + pid = fork { + Puppet.run_mode.stubs(:master?).returns true + assert_nothing_raised { + trap(:INT) { server.shutdown } + server.start + } + } + @@tmppids << pid + [pid, server] + end + + def kill_and_wait(pid, file) + %x{kill -INT #{pid} 2>/dev/null} + count = 0 + while count < 30 && File::exist?(file) + count += 1 + sleep(1) + end + assert(count < 30, "Killing server #{pid} failed") + end +end + --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/test/certmgr/inventory.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/test/certmgr/inventory.rb @@ -0,0 +1,69 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../lib/puppettest') + +require 'puppet' +require 'puppettest/certificates' +require 'puppet/sslcertificates/inventory.rb' +require 'mocha' + +class TestCertInventory < Test::Unit::TestCase + include PuppetTest::Certificates + + Inventory = Puppet::SSLCertificates::Inventory + + def setup + super + Puppet::Util::SUIDManager.stubs(:asuser).yields + end + + def test_format + cert = mksignedcert + + format = nil + assert_nothing_raised do + format = Inventory.format(cert) + end + + + assert( + format =~ /^0x0001 \S+ \S+ #{cert.subject}/, + + "Did not create correct format") + end + + def test_init + # First create a couple of certificates + ca = mkCA + + cert1 = mksignedcert(ca, "host1.madstop.com") + cert2 = mksignedcert(ca, "host2.madstop.com") + + init = nil + assert_nothing_raised do + init = Inventory.init + end + + [cert1, cert2].each do |cert| + assert(init.include?(cert.subject.to_s), "Did not catch #{cert.subject}") + end + end + + def test_add + ca = mkCA + cert = mksignedcert(ca, "host.domain.com") + + assert_nothing_raised do + file = mock + file.expects(:puts).with do |written| + written.include? cert.subject.to_s + end + Puppet::Util::Settings.any_instance.stubs(:write) + Puppet::Util::Settings.any_instance.expects(:write). + with(:cert_inventory, 'a').yields(file) + + Puppet::SSLCertificates::Inventory.add(cert) + end + end +end + --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/test/certmgr/support.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/test/certmgr/support.rb @@ -0,0 +1,105 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../lib/puppettest') + +require 'puppettest' +require 'puppet/sslcertificates/support' +require 'mocha' + +class TestCertSupport < Test::Unit::TestCase + include PuppetTest + MissingCertificate = Puppet::SSLCertificates::Support::MissingCertificate + + class CertUser + include Puppet::SSLCertificates::Support + end + + def setup + super + Puppet::Util::SUIDManager.stubs(:asuser).yields + @user = CertUser.new + @ca = Puppet::SSLCertificates::CA.new + @client = Puppet::Network::Client.ca.new(:CA => @ca) + end + + # Yay, metaprogramming + def test_keytype + [:key, :csr, :cert, :ca_cert].each do |name| + assert(Puppet::SSLCertificates::Support.method_defined?(name), "No retrieval method for #{name}") + maker = "mk_#{name}" + assert(Puppet::SSLCertificates::Support.method_defined?(maker), "No maker method for #{name}") + end + end + + def test_keys + keys = [:hostprivkey, :hostpubkey].each { |n| Puppet[n] = tempfile } + + key = nil + assert_nothing_raised do + key = @user.key + end + + assert_logged(:info, /Creating a new SSL/, "Did not log about new key") + keys.each do |file| + + assert( + FileTest.exists?(Puppet[file]), + + "Did not create #{file} key file") + end + + # Make sure it's a valid key + assert_nothing_raised("Created key is invalid") do + OpenSSL::PKey::RSA.new(File.read(Puppet[:hostprivkey])) + end + + # now make sure we can read it in + other = CertUser.new + assert_nothing_raised("Could not read key in") do + other.key + end + + assert_equal(@user.key.to_s, other.key.to_s, "Keys are not equal") + end + + def test_csr + csr = nil + assert_nothing_raised("Could not create csr") do + csr = @user.csr + end + + assert(FileTest.exists?(Puppet[:hostcsr]), "did not create csr file") + assert_instance_of(OpenSSL::X509::Request, csr) + end + + def test_cacert + @user = CertUser.new + + assert_raise(MissingCertificate, "Did not fail when missing cacert") do + @user.ca_cert + end + end + + # Fixing #1382. This test will always fail on Darwin, because its + # FS is case-insensitive. + unless Facter.value(:operatingsystem) == "Darwin" + def test_uppercase_files_are_renamed_and_read + # Write a key out to disk in a file containing upper-case. + key = OpenSSL::PKey::RSA.new(32) + should_path = Puppet[:hostprivkey] + + dir, file = File.split(should_path) + newfile = file.sub(/^([-a-z.0-9]+)\./) { $1.upcase + "."} + upper_path = File.join(dir, newfile) +p upper_path + File.open(upper_path, "w") { |f| f.print key.to_s } + + user = CertUser.new + + assert_equal(key.to_s, user.read_key.to_s, "Did not read key in from disk") + assert(! FileTest.exist?(upper_path), "Upper case file was not removed") + assert(FileTest.exist?(should_path), "File was not renamed to lower-case file") + assert_equal(key.to_s, user.read_key.to_s, "Did not read key in from disk") + end + end +end --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/test/certmgr/certmgr.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/test/certmgr/certmgr.rb @@ -0,0 +1,308 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../lib/puppettest') + +require 'puppet' +require 'puppet/sslcertificates.rb' +require 'puppettest' +require 'puppettest/certificates' +require 'mocha' + +class TestCertMgr < Test::Unit::TestCase + include PuppetTest::Certificates + def setup + super + #@dir = File.join(Puppet[:certdir], "testing") + @dir = File.join(@configpath, "certest") + system("mkdir -p #{@dir}") + + Puppet::Util::SUIDManager.stubs(:asuser).yields + end + + def testCreateSelfSignedCertificate + cert = nil + name = "testing" + newcert = proc { + + Puppet::SSLCertificates::Certificate.new( + + :name => name, + + :selfsign => true + ) + } + assert_nothing_raised { + cert = newcert.call + } + assert_nothing_raised { + cert.mkselfsigned + } + + assert_raise(Puppet::Error) { + cert.mkselfsigned + } + + assert_nothing_raised { + cert.write + } + + assert(FileTest.exists?(cert.certfile)) + + assert_nothing_raised { + cert.delete + } + + assert_nothing_raised { + cert = newcert.call + } + assert_nothing_raised { + cert.mkselfsigned + } + + assert_nothing_raised { + cert.delete + } + + end + + def disabled_testCreateEncryptedSelfSignedCertificate + cert = nil + name = "testing" + keyfile = mkPassFile + assert_nothing_raised { + + cert = Puppet::SSLCertificates::Certificate.new( + + :name => name, + :selfsign => true, + + :capass => keyfile + ) + } + assert_nothing_raised { + cert.mkselfsigned + } + assert_nothing_raised { + cert.mkhash + } + + assert_raise(Puppet::Error) { + cert.mkselfsigned + } + + assert(FileTest.exists?(cert.certfile)) + assert(FileTest.exists?(cert.hash)) + + assert_nothing_raised { + cert.delete + } + + assert_nothing_raised { + cert.mkselfsigned + } + + assert_nothing_raised { + cert.delete + } + + end + + def testCreateCA + ca = nil + assert_nothing_raised { + ca = Puppet::SSLCertificates::CA.new + } + + # make the CA again and verify it doesn't fail because everything + # still exists + assert_nothing_raised { + ca = Puppet::SSLCertificates::CA.new + } + + end + + def testSignCert + ca = mkCA() + + cert = nil + assert_nothing_raised { + + cert = Puppet::SSLCertificates::Certificate.new( + + :name => "signedcertest", + :property => "TN", + :city => "Nashville", + :country => "US", + :email => "luke@madstop.com", + :org => "Puppet", + :ou => "Development", + + :encrypt => mkPassFile() + ) + + } + + assert_nothing_raised { + cert.mkcsr + } + + signedcert = nil + cacert = nil + + assert_nothing_raised { + signedcert, cacert = ca.sign(cert.csr) + } + + assert_instance_of(OpenSSL::X509::Certificate, signedcert) + assert_instance_of(OpenSSL::X509::Certificate, cacert) + + assert_nothing_raised { + cert.cert = signedcert + cert.cacert = cacert + cert.write + } + #system("find #{Puppet[:ssldir]}") + #system("cp -R #{Puppet[:ssldir]} /tmp/ssltesting") + + output = nil + assert_nothing_raised { + output = %x{openssl verify -CAfile #{Puppet[:cacert]} -purpose sslserver #{cert.certfile}} + #output = %x{openssl verify -CApath #{Puppet[:certdir]} -purpose sslserver #{cert.certfile}} + } + + assert_equal($CHILD_STATUS,0) + assert_equal(File.join(Puppet[:certdir], "signedcertest.pem: OK\n"), output) + end + + + def test_interactiveca + ca = nil + + assert_nothing_raised { + ca = Puppet::SSLCertificates::CA.new + } + + # basic initialization + hostname = "test.hostname.com" + cert = mkcert(hostname) + + # create the csr + csr = nil + assert_nothing_raised { + csr = cert.mkcsr + } + + assert_nothing_raised { + ca.storeclientcsr(csr) + } + + # store it + pulledcsr = nil + assert_nothing_raised { + pulledcsr = ca.getclientcsr(hostname) + } + + assert_equal(csr.to_pem, pulledcsr.to_pem) + + signedcert = nil + assert_nothing_raised { + signedcert, cacert = ca.sign(csr) + } + + assert_instance_of(OpenSSL::X509::Certificate, signedcert) + newsignedcert = nil + assert_nothing_raised { + newsignedcert, cacert = ca.getclientcert(hostname) + } + + assert(newsignedcert) + + assert_equal(signedcert.to_pem, newsignedcert.to_pem) + end + + def test_cafailures + ca = mkCA() + cert = cacert = nil + assert_nothing_raised { + cert, cacert = ca.getclientcert("nohost") + } + assert_nil(cert) + end + + def test_crl + ca = mkCA() + h1 = mksignedcert(ca, "host1.example.com") + h2 = mksignedcert(ca, "host2.example.com") + + assert(ca.cert.verify(ca.cert.public_key)) + assert(h1.verify(ca.cert.public_key)) + assert(h2.verify(ca.cert.public_key)) + + crl = ca.crl + assert_not_nil(crl) + + store = mkStore(ca) + assert( store.verify(ca.cert)) + assert( store.verify(h1, [ca.cert])) + assert( store.verify(h2, [ca.cert])) + + ca.revoke(h1.serial) + + oldcert = File.read(Puppet.settings[:cacert]) + oldserial = File.read(Puppet.settings[:serial]) + + # Recreate the CA from disk + ca = mkCA() + newcert = File.read(Puppet.settings[:cacert]) + newserial = File.read(Puppet.settings[:serial]) + assert_equal(oldcert, newcert, "The certs are not equal after making a new CA.") + assert_equal(oldserial, newserial, "The serials are not equal after making a new CA.") + store = mkStore(ca) + assert( store.verify(ca.cert), "Could not verify CA certs after reloading certs.") + assert(!store.verify(h1, [ca.cert]), "Incorrectly verified revoked cert.") + assert( store.verify(h2, [ca.cert]), "Could not verify certs with reloaded CA.") + + ca.revoke(h2.serial) + assert_equal(1, ca.crl.extensions.size) + + # Recreate the CA from disk + ca = mkCA() + store = mkStore(ca) + assert( store.verify(ca.cert)) + assert(!store.verify(h1, [ca.cert]), "first revoked cert passed") + assert(!store.verify(h2, [ca.cert]), "second revoked cert passed") + end + + def test_ttl + cert = mksignedcert + assert_equal(5 * 365 * 24 * 60 * 60, cert.not_after - cert.not_before) + + Puppet[:ca_ttl] = 7 * 24 * 60 * 60 + cert = mksignedcert + assert_equal(7 * 24 * 60 * 60, cert.not_after - cert.not_before) + + Puppet[:ca_ttl] = "2y" + cert = mksignedcert + assert_equal(2 * 365 * 24 * 60 * 60, cert.not_after - cert.not_before) + + Puppet[:ca_ttl] = "2y" + cert = mksignedcert + assert_equal(2 * 365 * 24 * 60 * 60, cert.not_after - cert.not_before) + + Puppet[:ca_ttl] = "1h" + cert = mksignedcert + assert_equal(60 * 60, cert.not_after - cert.not_before) + + Puppet[:ca_ttl] = "900s" + cert = mksignedcert + assert_equal(900, cert.not_after - cert.not_before) + + # This needs to be last, to make sure that setting ca_days + # overrides setting ca_ttl + Puppet[:ca_days] = 3 + cert = mksignedcert + assert_equal(3 * 24 * 60 * 60, cert.not_after - cert.not_before) + + end +end + --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/spec/spec_helper.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/spec/spec_helper.rb @@ -0,0 +1,76 @@ +dir = File.expand_path(File.dirname(__FILE__)) +$LOAD_PATH.unshift File.join(dir, 'lib') + +# Don't want puppet getting the command line arguments for rake or autotest +ARGV.clear + +require 'puppet' +require 'mocha' +gem 'rspec', '>=2.0.0' +require 'rspec/expectations' + +# So everyone else doesn't have to include this base constant. +module PuppetSpec + FIXTURE_DIR = File.join(dir = File.expand_path(File.dirname(__FILE__)), "fixtures") unless defined?(FIXTURE_DIR) +end + +require 'pathname' +require 'tmpdir' + +require 'puppet_spec/verbose' +require 'puppet_spec/files' +require 'puppet_spec/fixtures' +require 'puppet_spec/matchers' +require 'monkey_patches/alias_should_to_must' +require 'monkey_patches/publicize_methods' + +Pathname.glob("#{dir}/shared_behaviours/**/*.rb") do |behaviour| + require behaviour.relative_path_from(Pathname.new(dir)) +end + +RSpec.configure do |config| + include PuppetSpec::Fixtures + + config.mock_with :mocha + + config.before :each do + GC.disable + + # these globals are set by Application + $puppet_application_mode = nil + $puppet_application_name = nil + + # REVISIT: I think this conceals other bad tests, but I don't have time to + # fully diagnose those right now. When you read this, please come tell me + # I suck for letting this float. --daniel 2011-04-21 + Signal.stubs(:trap) + + # Set the confdir and vardir to gibberish so that tests + # have to be correctly mocked. + Puppet[:confdir] = "/dev/null" + Puppet[:vardir] = "/dev/null" + + # Avoid opening ports to the outside world + Puppet.settings[:bindaddress] = "127.0.0.1" + + @logs = [] + Puppet::Util::Log.newdestination(Puppet::Test::LogCollector.new(@logs)) + + @log_level = Puppet::Util::Log.level + end + + config.after :each do + Puppet.settings.clear + Puppet::Node::Environment.clear + Puppet::Util::Storage.clear + Puppet::Util::ExecutionStub.reset + + PuppetSpec::Files.cleanup + + @logs.clear + Puppet::Util::Log.close_all + Puppet::Util::Log.level = @log_level + + GC.enable + end +end --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/spec/unit/ssl/certificate_authority_spec.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/spec/unit/ssl/certificate_authority_spec.rb @@ -0,0 +1,772 @@ +#!/usr/bin/env rspec +require 'spec_helper' + +require 'puppet/ssl/certificate_authority' + +describe Puppet::SSL::CertificateAuthority do + after do + Puppet::Util::Cacher.expire + Puppet.settings.clearused + end + + def stub_ca_host + @key = mock 'key' + @key.stubs(:content).returns "cakey" + @cacert = mock 'certificate' + @cacert.stubs(:content).returns "cacertificate" + + @host = stub 'ssl_host', :key => @key, :certificate => @cacert, :name => Puppet::SSL::Host.ca_name + end + + it "should have a class method for returning a singleton instance" do + Puppet::SSL::CertificateAuthority.should respond_to(:instance) + end + + describe "when finding an existing instance" do + describe "and the host is a CA host and the run_mode is master" do + before do + Puppet.settings.stubs(:value).with(:ca).returns true + Puppet.run_mode.stubs(:master?).returns true + + @ca = mock('ca') + Puppet::SSL::CertificateAuthority.stubs(:new).returns @ca + end + + it "should return an instance" do + Puppet::SSL::CertificateAuthority.instance.should equal(@ca) + end + + it "should always return the same instance" do + Puppet::SSL::CertificateAuthority.instance.should equal(Puppet::SSL::CertificateAuthority.instance) + end + end + + describe "and the host is not a CA host" do + it "should return nil" do + Puppet.settings.stubs(:value).with(:ca).returns false + Puppet.run_mode.stubs(:master?).returns true + + ca = mock('ca') + Puppet::SSL::CertificateAuthority.expects(:new).never + Puppet::SSL::CertificateAuthority.instance.should be_nil + end + end + + describe "and the run_mode is not master" do + it "should return nil" do + Puppet.settings.stubs(:value).with(:ca).returns true + Puppet.run_mode.stubs(:master?).returns false + + ca = mock('ca') + Puppet::SSL::CertificateAuthority.expects(:new).never + Puppet::SSL::CertificateAuthority.instance.should be_nil + end + end + end + + describe "when initializing" do + before do + Puppet.settings.stubs(:use) + Puppet.settings.stubs(:value).returns "ca_testing" + + Puppet::SSL::CertificateAuthority.any_instance.stubs(:setup) + end + + it "should always set its name to the value of :certname" do + Puppet.settings.expects(:value).with(:certname).returns "ca_testing" + + Puppet::SSL::CertificateAuthority.new.name.should == "ca_testing" + end + + it "should create an SSL::Host instance whose name is the 'ca_name'" do + Puppet::SSL::Host.expects(:ca_name).returns "caname" + + host = stub 'host' + Puppet::SSL::Host.expects(:new).with("caname").returns host + + Puppet::SSL::CertificateAuthority.new + end + + it "should use the :main, :ca, and :ssl settings sections" do + Puppet.settings.expects(:use).with(:main, :ssl, :ca) + Puppet::SSL::CertificateAuthority.new + end + + it "should create an inventory instance" do + Puppet::SSL::Inventory.expects(:new).returns "inventory" + + Puppet::SSL::CertificateAuthority.new.inventory.should == "inventory" + end + + it "should make sure the CA is set up" do + Puppet::SSL::CertificateAuthority.any_instance.expects(:setup) + + Puppet::SSL::CertificateAuthority.new + end + end + + describe "when setting itself up" do + it "should generate the CA certificate if it does not have one" do + Puppet.settings.stubs :use + + host = stub 'host' + Puppet::SSL::Host.stubs(:new).returns host + + host.expects(:certificate).returns nil + + Puppet::SSL::CertificateAuthority.any_instance.expects(:generate_ca_certificate) + Puppet::SSL::CertificateAuthority.new + end + end + + describe "when retrieving the certificate revocation list" do + before do + Puppet.settings.stubs(:use) + Puppet.settings.stubs(:value).returns "ca_testing" + Puppet.settings.stubs(:value).with(:cacrl).returns "/my/crl" + + cert = stub("certificate", :content => "real_cert") + key = stub("key", :content => "real_key") + @host = stub 'host', :certificate => cert, :name => "hostname", :key => key + + Puppet::SSL::CertificateAuthority.any_instance.stubs(:setup) + @ca = Puppet::SSL::CertificateAuthority.new + + @ca.stubs(:host).returns @host + end + + it "should return any found CRL instance" do + crl = mock 'crl' + Puppet::SSL::CertificateRevocationList.indirection.expects(:find).returns crl + @ca.crl.should equal(crl) + end + + it "should create, generate, and save a new CRL instance of no CRL can be found" do + crl = Puppet::SSL::CertificateRevocationList.new("fakename") + Puppet::SSL::CertificateRevocationList.indirection.expects(:find).returns nil + + Puppet::SSL::CertificateRevocationList.expects(:new).returns crl + + crl.expects(:generate).with(@ca.host.certificate.content, @ca.host.key.content) + Puppet::SSL::CertificateRevocationList.indirection.expects(:save).with(crl) + + @ca.crl.should equal(crl) + end + end + + describe "when generating a self-signed CA certificate" do + before do + Puppet.settings.stubs(:use) + Puppet.settings.stubs(:value).returns "ca_testing" + + Puppet::SSL::CertificateAuthority.any_instance.stubs(:setup) + Puppet::SSL::CertificateAuthority.any_instance.stubs(:crl) + @ca = Puppet::SSL::CertificateAuthority.new + + @host = stub 'host', :key => mock("key"), :name => "hostname", :certificate => mock('certificate') + + Puppet::SSL::CertificateRequest.any_instance.stubs(:generate) + + @ca.stubs(:host).returns @host + end + + it "should create and store a password at :capass" do + Puppet.settings.expects(:value).with(:capass).returns "/path/to/pass" + + FileTest.expects(:exist?).with("/path/to/pass").returns false + + fh = mock 'filehandle' + Puppet.settings.expects(:write).with(:capass).yields fh + + fh.expects(:print).with { |s| s.length > 18 } + + @ca.stubs(:sign) + + @ca.generate_ca_certificate + end + + it "should generate a key if one does not exist" do + @ca.stubs :generate_password + @ca.stubs :sign + + @ca.host.expects(:key).returns nil + @ca.host.expects(:generate_key) + + @ca.generate_ca_certificate + end + + it "should create and sign a self-signed cert using the CA name" do + request = mock 'request' + Puppet::SSL::CertificateRequest.expects(:new).with(@ca.host.name).returns request + request.expects(:generate).with(@ca.host.key) + + @ca.expects(:sign).with(@host.name, :ca, request) + + @ca.stubs :generate_password + + @ca.generate_ca_certificate + end + + it "should generate its CRL" do + @ca.stubs :generate_password + @ca.stubs :sign + + @ca.host.expects(:key).returns nil + @ca.host.expects(:generate_key) + + @ca.expects(:crl) + + @ca.generate_ca_certificate + end + end + + describe "when signing" do + before do + Puppet.settings.stubs(:use) + + Puppet::SSL::CertificateAuthority.any_instance.stubs(:password?).returns true + + stub_ca_host + + Puppet::SSL::Host.expects(:new).with(Puppet::SSL::Host.ca_name).returns @host + + @ca = Puppet::SSL::CertificateAuthority.new + + @name = "myhost" + @real_cert = stub 'realcert', :sign => nil + @cert = Puppet::SSL::Certificate.new(@name) + @cert.content = @real_cert + + Puppet::SSL::Certificate.stubs(:new).returns @cert + + @cert.stubs(:content=) + Puppet::SSL::Certificate.indirection.stubs(:save) + + # Stub out the factory + @factory = stub 'factory', :result => "my real cert" + Puppet::SSL::CertificateFactory.stubs(:new).returns @factory + + @request = stub 'request', :content => "myrequest", :name => @name + + # And the inventory + @inventory = stub 'inventory', :add => nil + @ca.stubs(:inventory).returns @inventory + + Puppet::SSL::CertificateRequest.indirection.stubs(:destroy) + end + + describe "and calculating the next certificate serial number" do + before do + @path = "/path/to/serial" + Puppet.settings.stubs(:value).with(:serial).returns @path + + @filehandle = stub 'filehandle', :<< => @filehandle + Puppet.settings.stubs(:readwritelock).with(:serial).yields @filehandle + end + + it "should default to 0x1 for the first serial number" do + @ca.next_serial.should == 0x1 + end + + it "should return the current content of the serial file" do + FileTest.stubs(:exist?).with(@path).returns true + File.expects(:read).with(@path).returns "0002" + + @ca.next_serial.should == 2 + end + + it "should write the next serial number to the serial file as hex" do + @filehandle.expects(:<<).with("0002") + + @ca.next_serial + end + + it "should lock the serial file while writing" do + Puppet.settings.expects(:readwritelock).with(:serial) + + @ca.next_serial + end + end + + describe "its own certificate" do + before do + @serial = 10 + @ca.stubs(:next_serial).returns @serial + end + + it "should not look up a certificate request for the host" do + Puppet::SSL::CertificateRequest.indirection.expects(:find).never + + @ca.sign(@name, :ca, @request) + end + + it "should use a certificate type of :ca" do + Puppet::SSL::CertificateFactory.expects(:new).with do |*args| + args[0] == :ca + end.returns @factory + @ca.sign(@name, :ca, @request) + end + + it "should pass the provided CSR as the CSR" do + Puppet::SSL::CertificateFactory.expects(:new).with do |*args| + args[1] == "myrequest" + end.returns @factory + @ca.sign(@name, :ca, @request) + end + + it "should use the provided CSR's content as the issuer" do + Puppet::SSL::CertificateFactory.expects(:new).with do |*args| + args[2] == "myrequest" + end.returns @factory + @ca.sign(@name, :ca, @request) + end + + it "should pass the next serial as the serial number" do + Puppet::SSL::CertificateFactory.expects(:new).with do |*args| + args[3] == @serial + end.returns @factory + @ca.sign(@name, :ca, @request) + end + + it "should save the resulting certificate" do + Puppet::SSL::Certificate.indirection.expects(:save).with(@cert) + + @ca.sign(@name, :ca, @request) + end + end + + describe "another host's certificate" do + before do + @serial = 10 + @ca.stubs(:next_serial).returns @serial + + Puppet::SSL::CertificateRequest.indirection.stubs(:find).with(@name).returns @request + Puppet::SSL::CertificateRequest.indirection.stubs :save + end + + it "should use a certificate type of :server" do + Puppet::SSL::CertificateFactory.expects(:new).with do |*args| + args[0] == :server + end.returns @factory + + @ca.sign(@name) + end + + it "should use look up a CSR for the host in the :ca_file terminus" do + Puppet::SSL::CertificateRequest.indirection.expects(:find).with(@name).returns @request + + @ca.sign(@name) + end + + it "should fail if no CSR can be found for the host" do + Puppet::SSL::CertificateRequest.indirection.expects(:find).with(@name).returns nil + + lambda { @ca.sign(@name) }.should raise_error(ArgumentError) + end + + it "should use the CA certificate as the issuer" do + Puppet::SSL::CertificateFactory.expects(:new).with do |*args| + args[2] == @cacert.content + end.returns @factory + @ca.sign(@name) + end + + it "should pass the next serial as the serial number" do + Puppet::SSL::CertificateFactory.expects(:new).with do |*args| + args[3] == @serial + end.returns @factory + @ca.sign(@name) + end + + it "should sign the resulting certificate using its real key and a digest" do + digest = mock 'digest' + OpenSSL::Digest::SHA1.expects(:new).returns digest + + key = stub 'key', :content => "real_key" + @ca.host.stubs(:key).returns key + + @cert.content.expects(:sign).with("real_key", digest) + @ca.sign(@name) + end + + it "should save the resulting certificate" do + Puppet::SSL::Certificate.indirection.stubs(:save).with(@cert) + @ca.sign(@name) + end + + it "should remove the host's certificate request" do + Puppet::SSL::CertificateRequest.indirection.expects(:destroy).with(@name) + + @ca.sign(@name) + end + end + + it "should create a certificate instance with the content set to the newly signed x509 certificate" do + @serial = 10 + @ca.stubs(:next_serial).returns @serial + + Puppet::SSL::CertificateRequest.indirection.stubs(:find).with(@name).returns @request + Puppet::SSL::Certificate.indirection.stubs :save + Puppet::SSL::Certificate.expects(:new).with(@name).returns @cert + + @ca.sign(@name) + end + + it "should return the certificate instance" do + @ca.stubs(:next_serial).returns @serial + Puppet::SSL::CertificateRequest.indirection.stubs(:find).with(@name).returns @request + Puppet::SSL::Certificate.indirection.stubs :save + @ca.sign(@name).should equal(@cert) + end + + it "should add the certificate to its inventory" do + @ca.stubs(:next_serial).returns @serial + @inventory.expects(:add).with(@cert) + + Puppet::SSL::CertificateRequest.indirection.stubs(:find).with(@name).returns @request + Puppet::SSL::Certificate.indirection.stubs :save + @ca.sign(@name) + end + + it "should have a method for triggering autosigning of available CSRs" do + @ca.should respond_to(:autosign) + end + + describe "when autosigning certificates" do + it "should do nothing if autosign is disabled" do + Puppet.settings.expects(:value).with(:autosign).returns 'false' + + Puppet::SSL::CertificateRequest.indirection.expects(:search).never + @ca.autosign + end + + it "should do nothing if no autosign.conf exists" do + Puppet.settings.expects(:value).with(:autosign).returns '/auto/sign' + FileTest.expects(:exist?).with("/auto/sign").returns false + + Puppet::SSL::CertificateRequest.indirection.expects(:search).never + @ca.autosign + end + + describe "and autosign is enabled and the autosign.conf file exists" do + before do + Puppet.settings.stubs(:value).with(:autosign).returns '/auto/sign' + FileTest.stubs(:exist?).with("/auto/sign").returns true + File.stubs(:readlines).with("/auto/sign").returns ["one\n", "two\n"] + + Puppet::SSL::CertificateRequest.indirection.stubs(:search).returns [] + + @store = stub 'store', :allow => nil + Puppet::Network::AuthStore.stubs(:new).returns @store + end + + describe "when creating the AuthStore instance to verify autosigning" do + it "should create an AuthStore with each line in the configuration file allowed to be autosigned" do + Puppet::Network::AuthStore.expects(:new).returns @store + + @store.expects(:allow).with("one") + @store.expects(:allow).with("two") + + @ca.autosign + end + + it "should reparse the autosign configuration on each call" do + Puppet::Network::AuthStore.expects(:new).times(2).returns @store + + @ca.autosign + @ca.autosign + end + + it "should ignore comments" do + File.stubs(:readlines).with("/auto/sign").returns ["one\n", "#two\n"] + + @store.expects(:allow).with("one") + @ca.autosign + end + + it "should ignore blank lines" do + File.stubs(:readlines).with("/auto/sign").returns ["one\n", "\n"] + + @store.expects(:allow).with("one") + @ca.autosign + end + end + + it "should sign all CSRs whose hostname matches the autosign configuration" do + csr1 = mock 'csr1' + csr2 = mock 'csr2' + Puppet::SSL::CertificateRequest.indirection.stubs(:search).returns [csr1, csr2] + end + + it "should not sign CSRs whose hostname does not match the autosign configuration" do + csr1 = mock 'csr1' + csr2 = mock 'csr2' + Puppet::SSL::CertificateRequest.indirection.stubs(:search).returns [csr1, csr2] + end + end + end + end + + describe "when managing certificate clients" do + before do + Puppet.settings.stubs(:use) + + Puppet::SSL::CertificateAuthority.any_instance.stubs(:password?).returns true + + stub_ca_host + + Puppet::SSL::Host.expects(:new).returns @host + Puppet::SSL::CertificateAuthority.any_instance.stubs(:host).returns @host + + @cacert = mock 'certificate' + @cacert.stubs(:content).returns "cacertificate" + @ca = Puppet::SSL::CertificateAuthority.new + end + + it "should have a method for acting on the SSL files" do + @ca.should respond_to(:apply) + end + + describe "when applying a method to a set of hosts" do + it "should fail if no subjects have been specified" do + lambda { @ca.apply(:generate) }.should raise_error(ArgumentError) + end + + it "should create an Interface instance with the specified method and the options" do + Puppet::SSL::CertificateAuthority::Interface.expects(:new).with(:generate, :to => :host).returns(stub('applier', :apply => nil)) + @ca.apply(:generate, :to => :host) + end + + it "should apply the Interface with itself as the argument" do + applier = stub('applier') + applier.expects(:apply).with(@ca) + Puppet::SSL::CertificateAuthority::Interface.expects(:new).returns applier + @ca.apply(:generate, :to => :ca_testing) + end + end + + it "should be able to list waiting certificate requests" do + req1 = stub 'req1', :name => "one" + req2 = stub 'req2', :name => "two" + Puppet::SSL::CertificateRequest.indirection.expects(:search).with("*").returns [req1, req2] + + @ca.waiting?.should == %w{one two} + end + + it "should delegate removing hosts to the Host class" do + Puppet::SSL::Host.expects(:destroy).with("myhost") + + @ca.destroy("myhost") + end + + it "should be able to verify certificates" do + @ca.should respond_to(:verify) + end + + it "should list certificates as the sorted list of all existing signed certificates" do + cert1 = stub 'cert1', :name => "cert1" + cert2 = stub 'cert2', :name => "cert2" + Puppet::SSL::Certificate.indirection.expects(:search).with("*").returns [cert1, cert2] + @ca.list.should == %w{cert1 cert2} + end + + describe "and printing certificates" do + it "should return nil if the certificate cannot be found" do + Puppet::SSL::Certificate.indirection.expects(:find).with("myhost").returns nil + @ca.print("myhost").should be_nil + end + + it "should print certificates by calling :to_text on the host's certificate" do + cert1 = stub 'cert1', :name => "cert1", :to_text => "mytext" + Puppet::SSL::Certificate.indirection.expects(:find).with("myhost").returns cert1 + @ca.print("myhost").should == "mytext" + end + end + + describe "and fingerprinting certificates" do + before :each do + @cert = stub 'cert', :name => "cert", :fingerprint => "DIGEST" + Puppet::SSL::Certificate.indirection.stubs(:find).with("myhost").returns @cert + Puppet::SSL::CertificateRequest.indirection.stubs(:find).with("myhost") + end + + it "should raise an error if the certificate or CSR cannot be found" do + Puppet::SSL::Certificate.indirection.expects(:find).with("myhost").returns nil + Puppet::SSL::CertificateRequest.indirection.expects(:find).with("myhost").returns nil + lambda { @ca.fingerprint("myhost") }.should raise_error + end + + it "should try to find a CSR if no certificate can be found" do + Puppet::SSL::Certificate.indirection.expects(:find).with("myhost").returns nil + Puppet::SSL::CertificateRequest.indirection.expects(:find).with("myhost").returns @cert + @cert.expects(:fingerprint) + @ca.fingerprint("myhost") + end + + it "should delegate to the certificate fingerprinting" do + @cert.expects(:fingerprint) + @ca.fingerprint("myhost") + end + + it "should propagate the digest algorithm to the certificate fingerprinting system" do + @cert.expects(:fingerprint).with(:digest) + @ca.fingerprint("myhost", :digest) + end + end + + describe "and verifying certificates" do + before do + @store = stub 'store', :verify => true, :add_file => nil, :purpose= => nil, :add_crl => true, :flags= => nil + + OpenSSL::X509::Store.stubs(:new).returns @store + + Puppet.settings.stubs(:value).returns "crtstuff" + + @cert = stub 'cert', :content => "mycert" + Puppet::SSL::Certificate.indirection.stubs(:find).returns @cert + + @crl = stub('crl', :content => "mycrl") + + @ca.stubs(:crl).returns @crl + end + + it "should fail if the host's certificate cannot be found" do + Puppet::SSL::Certificate.indirection.expects(:find).with("me").returns(nil) + + lambda { @ca.verify("me") }.should raise_error(ArgumentError) + end + + it "should create an SSL Store to verify" do + OpenSSL::X509::Store.expects(:new).returns @store + + @ca.verify("me") + end + + it "should add the CA Certificate to the store" do + Puppet.settings.stubs(:value).with(:cacert).returns "/ca/cert" + @store.expects(:add_file).with "/ca/cert" + + @ca.verify("me") + end + + it "should add the CRL to the store if the crl is enabled" do + @store.expects(:add_crl).with "mycrl" + + @ca.verify("me") + end + + it "should set the store purpose to OpenSSL::X509::PURPOSE_SSL_CLIENT" do + Puppet.settings.stubs(:value).with(:cacert).returns "/ca/cert" + @store.expects(:add_file).with "/ca/cert" + + @ca.verify("me") + end + + it "should set the store flags to check the crl" do + @store.expects(:flags=).with OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK + + @ca.verify("me") + end + + it "should use the store to verify the certificate" do + @cert.expects(:content).returns "mycert" + + @store.expects(:verify).with("mycert").returns true + + @ca.verify("me") + end + + it "should fail if the verification returns false" do + @cert.expects(:content).returns "mycert" + + @store.expects(:verify).with("mycert").returns false + + lambda { @ca.verify("me") }.should raise_error + end + end + + describe "and revoking certificates" do + before do + @crl = mock 'crl' + @ca.stubs(:crl).returns @crl + + @ca.stubs(:next_serial).returns 10 + + @real_cert = stub 'real_cert', :serial => 15 + @cert = stub 'cert', :content => @real_cert + Puppet::SSL::Certificate.indirection.stubs(:find).returns @cert + + end + + it "should fail if the certificate revocation list is disabled" do + @ca.stubs(:crl).returns false + + lambda { @ca.revoke('ca_testing') }.should raise_error(ArgumentError) + + end + + it "should delegate the revocation to its CRL" do + @ca.crl.expects(:revoke) + + @ca.revoke('host') + end + + it "should get the serial number from the local certificate if it exists" do + @ca.crl.expects(:revoke).with { |serial, key| serial == 15 } + + Puppet::SSL::Certificate.indirection.expects(:find).with("host").returns @cert + + @ca.revoke('host') + end + + it "should get the serial number from inventory if no local certificate exists" do + real_cert = stub 'real_cert', :serial => 15 + cert = stub 'cert', :content => real_cert + Puppet::SSL::Certificate.indirection.expects(:find).with("host").returns nil + + @ca.inventory.expects(:serial).with("host").returns 16 + + @ca.crl.expects(:revoke).with { |serial, key| serial == 16 } + @ca.revoke('host') + end + end + + it "should be able to generate a complete new SSL host" do + @ca.should respond_to(:generate) + end + + describe "and generating certificates" do + before do + @host = stub 'host', :generate_certificate_request => nil + Puppet::SSL::Host.stubs(:new).returns @host + Puppet::SSL::Certificate.indirection.stubs(:find).returns nil + + @ca.stubs(:sign) + end + + it "should fail if a certificate already exists for the host" do + Puppet::SSL::Certificate.indirection.expects(:find).with("him").returns "something" + + lambda { @ca.generate("him") }.should raise_error(ArgumentError) + end + + it "should create a new Host instance with the correct name" do + Puppet::SSL::Host.expects(:new).with("him").returns @host + + @ca.generate("him") + end + + it "should use the Host to generate the certificate request" do + @host.expects :generate_certificate_request + + @ca.generate("him") + end + + it "should sign the generated request" do + @ca.expects(:sign).with("him") + + @ca.generate("him") + end + end + end +end --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/spec/unit/ssl/certificate_request_spec.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/spec/unit/ssl/certificate_request_spec.rb @@ -0,0 +1,216 @@ +#!/usr/bin/env rspec +require 'spec_helper' + +require 'puppet/ssl/certificate_request' +require 'puppet/ssl/key' + +describe Puppet::SSL::CertificateRequest do + before do + @class = Puppet::SSL::CertificateRequest + end + + it "should be extended with the Indirector module" do + @class.singleton_class.should be_include(Puppet::Indirector) + end + + it "should indirect certificate_request" do + @class.indirection.name.should == :certificate_request + end + + it "should use any provided name as its name" do + @class.new("myname").name.should == "myname" + end + + it "should only support the text format" do + @class.supported_formats.should == [:s] + end + + describe "when converting from a string" do + it "should create a CSR instance with its name set to the CSR subject and its content set to the extracted CSR" do + csr = stub 'csr', :subject => "/CN=Foo.madstop.com" + OpenSSL::X509::Request.expects(:new).with("my csr").returns(csr) + + mycsr = stub 'sslcsr' + mycsr.expects(:content=).with(csr) + + @class.expects(:new).with("foo.madstop.com").returns mycsr + + @class.from_s("my csr") + end + end + + describe "when managing instances" do + before do + @request = @class.new("myname") + end + + it "should have a name attribute" do + @request.name.should == "myname" + end + + it "should downcase its name" do + @class.new("MyName").name.should == "myname" + end + + it "should have a content attribute" do + @request.should respond_to(:content) + end + + it "should be able to read requests from disk" do + path = "/my/path" + File.expects(:read).with(path).returns("my request") + request = mock 'request' + OpenSSL::X509::Request.expects(:new).with("my request").returns(request) + @request.read(path).should equal(request) + @request.content.should equal(request) + end + + it "should return an empty string when converted to a string with no request" do + @request.to_s.should == "" + end + + it "should convert the request to pem format when converted to a string" do + request = mock 'request', :to_pem => "pem" + @request.content = request + @request.to_s.should == "pem" + end + + it "should have a :to_text method that it delegates to the actual key" do + real_request = mock 'request' + real_request.expects(:to_text).returns "requesttext" + @request.content = real_request + @request.to_text.should == "requesttext" + end + end + + describe "when generating" do + before do + @instance = @class.new("myname") + + key = Puppet::SSL::Key.new("myname") + @key = key.generate + + @request = OpenSSL::X509::Request.new + OpenSSL::X509::Request.expects(:new).returns(@request) + + @request.stubs(:verify).returns(true) + end + + it "should use the content of the provided key if the key is a Puppet::SSL::Key instance" do + key = Puppet::SSL::Key.new("test") + key.expects(:content).returns @key + + @request.expects(:sign).with{ |key, digest| key == @key } + @instance.generate(key) + end + + it "should log that it is creating a new certificate request" do + Puppet.expects(:info).twice + @instance.generate(@key) + end + + it "should set the subject to [CN, name]" do + subject = mock 'subject' + OpenSSL::X509::Name.expects(:new).with([["CN", @instance.name]]).returns(subject) + @request.expects(:subject=).with(subject) + @instance.generate(@key) + end + + it "should set the CN to the CSR name when the CSR is not for a CA" do + subject = mock 'subject' + OpenSSL::X509::Name.expects(:new).with { |subject| subject[0][1] == @instance.name }.returns(subject) + @request.expects(:subject=).with(subject) + @instance.generate(@key) + end + + it "should set the CN to the :ca_name setting when the CSR is for a CA" do + subject = mock 'subject' + Puppet.settings.expects(:value).with(:ca_name).returns "mycertname" + OpenSSL::X509::Name.expects(:new).with { |subject| subject[0][1] == "mycertname" }.returns(subject) + @request.expects(:subject=).with(subject) + Puppet::SSL::CertificateRequest.new(Puppet::SSL::CA_NAME).generate(@key) + end + + it "should set the version to 0" do + @request.expects(:version=).with(0) + @instance.generate(@key) + end + + it "should set the public key to the provided key's public key" do + # Yay, the private key extracts a new key each time. + pubkey = @key.public_key + @key.stubs(:public_key).returns pubkey + @request.expects(:public_key=).with(@key.public_key) + @instance.generate(@key) + end + + it "should sign the csr with the provided key and a digest" do + digest = mock 'digest' + OpenSSL::Digest::MD5.expects(:new).returns(digest) + @request.expects(:sign).with(@key, digest) + @instance.generate(@key) + end + + it "should verify the generated request using the public key" do + # Stupid keys don't have a competent == method. + @request.expects(:verify).with { |public_key| public_key.to_s == @key.public_key.to_s }.returns true + @instance.generate(@key) + end + + it "should fail if verification fails" do + @request.expects(:verify).returns false + + lambda { @instance.generate(@key) }.should raise_error(Puppet::Error) + end + + it "should fingerprint the request" do + @instance.expects(:fingerprint) + @instance.generate(@key) + end + + it "should display the fingerprint" do + Puppet.stubs(:info) + @instance.stubs(:fingerprint).returns("FINGERPRINT") + Puppet.expects(:info).with { |s| s =~ /FINGERPRINT/ } + @instance.generate(@key) + end + + it "should return the generated request" do + @instance.generate(@key).should equal(@request) + end + + it "should set its content to the generated request" do + @instance.generate(@key) + @instance.content.should equal(@request) + end + end + + describe "when a CSR is saved" do + describe "and a CA is available" do + it "should save the CSR and trigger autosigning" do + ca = mock 'ca', :autosign + Puppet::SSL::CertificateAuthority.expects(:instance).returns ca + + csr = Puppet::SSL::CertificateRequest.new("me") + terminus = mock 'terminus' + Puppet::SSL::CertificateRequest.indirection.expects(:prepare).returns(terminus) + terminus.expects(:save).with { |request| request.instance == csr && request.key == "me" } + + Puppet::SSL::CertificateRequest.indirection.save(csr) + end + end + + describe "and a CA is not available" do + it "should save the CSR" do + Puppet::SSL::CertificateAuthority.expects(:instance).returns nil + + csr = Puppet::SSL::CertificateRequest.new("me") + terminus = mock 'terminus' + Puppet::SSL::CertificateRequest.indirection.expects(:prepare).returns(terminus) + terminus.expects(:save).with { |request| request.instance == csr && request.key == "me" } + + Puppet::SSL::CertificateRequest.indirection.save(csr) + end + end + end +end --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/spec/unit/ssl/host_spec.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/spec/unit/ssl/host_spec.rb @@ -0,0 +1,793 @@ +#!/usr/bin/env rspec +require 'spec_helper' + +require 'puppet/ssl/host' +require 'puppet/sslcertificates' +require 'puppet/sslcertificates/ca' + +describe Puppet::SSL::Host do + before do + Puppet::SSL::Host.indirection.terminus_class = :file + @host = Puppet::SSL::Host.new("myname") + end + + after do + # Cleaned out any cached localhost instance. + Puppet::Util::Cacher.expire + Puppet::SSL::Host.ca_location = :none + end + + it "should use any provided name as its name" do + @host.name.should == "myname" + end + + it "should retrieve its public key from its private key" do + realkey = mock 'realkey' + key = stub 'key', :content => realkey + Puppet::SSL::Key.indirection.stubs(:find).returns(key) + pubkey = mock 'public_key' + realkey.expects(:public_key).returns pubkey + + @host.public_key.should equal(pubkey) + end + + it "should default to being a non-ca host" do + @host.ca?.should be_false + end + + it "should be a ca host if its name matches the CA_NAME" do + Puppet::SSL::Host.stubs(:ca_name).returns "yayca" + Puppet::SSL::Host.new("yayca").should be_ca + end + + it "should have a method for determining the CA location" do + Puppet::SSL::Host.should respond_to(:ca_location) + end + + it "should have a method for specifying the CA location" do + Puppet::SSL::Host.should respond_to(:ca_location=) + end + + it "should have a method for retrieving the default ssl host" do + Puppet::SSL::Host.should respond_to(:ca_location=) + end + + it "should have a method for producing an instance to manage the local host's keys" do + Puppet::SSL::Host.should respond_to(:localhost) + end + + it "should generate the certificate for the localhost instance if no certificate is available" do + host = stub 'host', :key => nil + Puppet::SSL::Host.expects(:new).returns host + + host.expects(:certificate).returns nil + host.expects(:generate) + + Puppet::SSL::Host.localhost.should equal(host) + end + + it "should always read the key for the localhost instance in from disk" do + host = stub 'host', :certificate => "eh" + Puppet::SSL::Host.expects(:new).returns host + + host.expects(:key) + + Puppet::SSL::Host.localhost + end + + it "should cache the localhost instance" do + host = stub 'host', :certificate => "eh", :key => 'foo' + Puppet::SSL::Host.expects(:new).once.returns host + + Puppet::SSL::Host.localhost.should == Puppet::SSL::Host.localhost + end + + it "should be able to expire the cached instance" do + one = stub 'host1', :certificate => "eh", :key => 'foo' + two = stub 'host2', :certificate => "eh", :key => 'foo' + Puppet::SSL::Host.expects(:new).times(2).returns(one).then.returns(two) + + Puppet::SSL::Host.localhost.should equal(one) + Puppet::Util::Cacher.expire + Puppet::SSL::Host.localhost.should equal(two) + end + + it "should be able to verify its certificate matches its key" do + Puppet::SSL::Host.new("foo").should respond_to(:certificate_matches_key?) + end + + it "should consider the certificate invalid if it cannot find a key" do + host = Puppet::SSL::Host.new("foo") + host.expects(:key).returns nil + + host.should_not be_certificate_matches_key + end + + it "should consider the certificate invalid if it cannot find a certificate" do + host = Puppet::SSL::Host.new("foo") + host.expects(:key).returns mock("key") + host.expects(:certificate).returns nil + + host.should_not be_certificate_matches_key + end + + it "should consider the certificate invalid if the SSL certificate's key verification fails" do + host = Puppet::SSL::Host.new("foo") + + key = mock 'key', :content => "private_key" + sslcert = mock 'sslcert' + certificate = mock 'cert', :content => sslcert + + host.stubs(:key).returns key + host.stubs(:certificate).returns certificate + + sslcert.expects(:check_private_key).with("private_key").returns false + + host.should_not be_certificate_matches_key + end + + it "should consider the certificate valid if the SSL certificate's key verification succeeds" do + host = Puppet::SSL::Host.new("foo") + + key = mock 'key', :content => "private_key" + sslcert = mock 'sslcert' + certificate = mock 'cert', :content => sslcert + + host.stubs(:key).returns key + host.stubs(:certificate).returns certificate + + sslcert.expects(:check_private_key).with("private_key").returns true + + host.should be_certificate_matches_key + end + + describe "when specifying the CA location" do + it "should support the location ':local'" do + lambda { Puppet::SSL::Host.ca_location = :local }.should_not raise_error + end + + it "should support the location ':remote'" do + lambda { Puppet::SSL::Host.ca_location = :remote }.should_not raise_error + end + + it "should support the location ':none'" do + lambda { Puppet::SSL::Host.ca_location = :none }.should_not raise_error + end + + it "should support the location ':only'" do + lambda { Puppet::SSL::Host.ca_location = :only }.should_not raise_error + end + + it "should not support other modes" do + lambda { Puppet::SSL::Host.ca_location = :whatever }.should raise_error(ArgumentError) + end + + describe "as 'local'" do + before do + Puppet::SSL::Host.ca_location = :local + end + + it "should set the cache class for Certificate, CertificateRevocationList, and CertificateRequest as :file" do + Puppet::SSL::Certificate.indirection.cache_class.should == :file + Puppet::SSL::CertificateRequest.indirection.cache_class.should == :file + Puppet::SSL::CertificateRevocationList.indirection.cache_class.should == :file + end + + it "should set the terminus class for Key and Host as :file" do + Puppet::SSL::Key.indirection.terminus_class.should == :file + Puppet::SSL::Host.indirection.terminus_class.should == :file + end + + it "should set the terminus class for Certificate, CertificateRevocationList, and CertificateRequest as :ca" do + Puppet::SSL::Certificate.indirection.terminus_class.should == :ca + Puppet::SSL::CertificateRequest.indirection.terminus_class.should == :ca + Puppet::SSL::CertificateRevocationList.indirection.terminus_class.should == :ca + end + end + + describe "as 'remote'" do + before do + Puppet::SSL::Host.ca_location = :remote + end + + it "should set the cache class for Certificate, CertificateRevocationList, and CertificateRequest as :file" do + Puppet::SSL::Certificate.indirection.cache_class.should == :file + Puppet::SSL::CertificateRequest.indirection.cache_class.should == :file + Puppet::SSL::CertificateRevocationList.indirection.cache_class.should == :file + end + + it "should set the terminus class for Key as :file" do + Puppet::SSL::Key.indirection.terminus_class.should == :file + end + + it "should set the terminus class for Host, Certificate, CertificateRevocationList, and CertificateRequest as :rest" do + Puppet::SSL::Host.indirection.terminus_class.should == :rest + Puppet::SSL::Certificate.indirection.terminus_class.should == :rest + Puppet::SSL::CertificateRequest.indirection.terminus_class.should == :rest + Puppet::SSL::CertificateRevocationList.indirection.terminus_class.should == :rest + end + end + + describe "as 'only'" do + before do + Puppet::SSL::Host.ca_location = :only + end + + it "should set the terminus class for Key, Certificate, CertificateRevocationList, and CertificateRequest as :ca" do + Puppet::SSL::Key.indirection.terminus_class.should == :ca + Puppet::SSL::Certificate.indirection.terminus_class.should == :ca + Puppet::SSL::CertificateRequest.indirection.terminus_class.should == :ca + Puppet::SSL::CertificateRevocationList.indirection.terminus_class.should == :ca + end + + it "should set the cache class for Certificate, CertificateRevocationList, and CertificateRequest to nil" do + Puppet::SSL::Certificate.indirection.cache_class.should be_nil + Puppet::SSL::CertificateRequest.indirection.cache_class.should be_nil + Puppet::SSL::CertificateRevocationList.indirection.cache_class.should be_nil + end + + it "should set the terminus class for Host to :file" do + Puppet::SSL::Host.indirection.terminus_class.should == :file + end + end + + describe "as 'none'" do + before do + Puppet::SSL::Host.ca_location = :none + end + + it "should set the terminus class for Key, Certificate, CertificateRevocationList, and CertificateRequest as :file" do + Puppet::SSL::Key.indirection.terminus_class.should == :file + Puppet::SSL::Certificate.indirection.terminus_class.should == :file + Puppet::SSL::CertificateRequest.indirection.terminus_class.should == :file + Puppet::SSL::CertificateRevocationList.indirection.terminus_class.should == :file + end + + it "should set the terminus class for Host to 'none'" do + lambda { Puppet::SSL::Host.indirection.terminus_class }.should raise_error(Puppet::DevError) + end + end + end + + it "should have a class method for destroying all files related to a given host" do + Puppet::SSL::Host.should respond_to(:destroy) + end + + describe "when destroying a host's SSL files" do + before do + Puppet::SSL::Key.indirection.stubs(:destroy).returns false + Puppet::SSL::Certificate.indirection.stubs(:destroy).returns false + Puppet::SSL::CertificateRequest.indirection.stubs(:destroy).returns false + end + + it "should destroy its certificate, certificate request, and key" do + Puppet::SSL::Key.indirection.expects(:destroy).with("myhost") + Puppet::SSL::Certificate.indirection.expects(:destroy).with("myhost") + Puppet::SSL::CertificateRequest.indirection.expects(:destroy).with("myhost") + + Puppet::SSL::Host.destroy("myhost") + end + + it "should return true if any of the classes returned true" do + Puppet::SSL::Certificate.indirection.expects(:destroy).with("myhost").returns true + + Puppet::SSL::Host.destroy("myhost").should be_true + end + + it "should report that nothing was deleted if none of the classes returned true" do + Puppet::SSL::Host.destroy("myhost").should == "Nothing was deleted" + end + end + + describe "when initializing" do + it "should default its name to the :certname setting" do + Puppet.settings.expects(:value).with(:certname).returns "myname" + + Puppet::SSL::Host.new.name.should == "myname" + end + + it "should downcase a passed in name" do + Puppet::SSL::Host.new("Host.Domain.Com").name.should == "host.domain.com" + end + + it "should downcase the certname if it's used" do + Puppet.settings.expects(:value).with(:certname).returns "Host.Domain.Com" + Puppet::SSL::Host.new.name.should == "host.domain.com" + end + + it "should indicate that it is a CA host if its name matches the ca_name constant" do + Puppet::SSL::Host.stubs(:ca_name).returns "myca" + Puppet::SSL::Host.new("myca").should be_ca + end + end + + describe "when managing its private key" do + before do + @realkey = "mykey" + @key = Puppet::SSL::Key.new("mykey") + @key.content = @realkey + end + + it "should return nil if the key is not set and cannot be found" do + Puppet::SSL::Key.indirection.expects(:find).with("myname").returns(nil) + @host.key.should be_nil + end + + it "should find the key in the Key class and return the Puppet instance" do + Puppet::SSL::Key.indirection.expects(:find).with("myname").returns(@key) + @host.key.should equal(@key) + end + + it "should be able to generate and save a new key" do + Puppet::SSL::Key.expects(:new).with("myname").returns(@key) + + @key.expects(:generate) + Puppet::SSL::Key.indirection.expects(:save) + + @host.generate_key.should be_true + @host.key.should equal(@key) + end + + it "should not retain keys that could not be saved" do + Puppet::SSL::Key.expects(:new).with("myname").returns(@key) + + @key.stubs(:generate) + Puppet::SSL::Key.indirection.expects(:save).raises "eh" + + lambda { @host.generate_key }.should raise_error + @host.key.should be_nil + end + + it "should return any previously found key without requerying" do + Puppet::SSL::Key.indirection.expects(:find).with("myname").returns(@key).once + @host.key.should equal(@key) + @host.key.should equal(@key) + end + end + + describe "when managing its certificate request" do + before do + @realrequest = "real request" + @request = Puppet::SSL::CertificateRequest.new("myname") + @request.content = @realrequest + end + + it "should return nil if the key is not set and cannot be found" do + Puppet::SSL::CertificateRequest.indirection.expects(:find).with("myname").returns(nil) + @host.certificate_request.should be_nil + end + + it "should find the request in the Key class and return it and return the Puppet SSL request" do + Puppet::SSL::CertificateRequest.indirection.expects(:find).with("myname").returns @request + + @host.certificate_request.should equal(@request) + end + + it "should generate a new key when generating the cert request if no key exists" do + Puppet::SSL::CertificateRequest.expects(:new).with("myname").returns @request + + key = stub 'key', :public_key => mock("public_key"), :content => "mycontent" + + @host.expects(:key).times(2).returns(nil).then.returns(key) + @host.expects(:generate_key).returns(key) + + @request.stubs(:generate) + Puppet::SSL::CertificateRequest.indirection.stubs(:save) + + @host.generate_certificate_request + end + + it "should be able to generate and save a new request using the private key" do + Puppet::SSL::CertificateRequest.expects(:new).with("myname").returns @request + + key = stub 'key', :public_key => mock("public_key"), :content => "mycontent" + @host.stubs(:key).returns(key) + @request.expects(:generate).with("mycontent") + Puppet::SSL::CertificateRequest.indirection.expects(:save).with(@request) + + @host.generate_certificate_request.should be_true + @host.certificate_request.should equal(@request) + end + + it "should return any previously found request without requerying" do + Puppet::SSL::CertificateRequest.indirection.expects(:find).with("myname").returns(@request).once + + @host.certificate_request.should equal(@request) + @host.certificate_request.should equal(@request) + end + + it "should not keep its certificate request in memory if the request cannot be saved" do + Puppet::SSL::CertificateRequest.expects(:new).with("myname").returns @request + + key = stub 'key', :public_key => mock("public_key"), :content => "mycontent" + @host.stubs(:key).returns(key) + @request.stubs(:generate) + @request.stubs(:name).returns("myname") + terminus = stub 'terminus' + Puppet::SSL::CertificateRequest.indirection.expects(:prepare).returns(terminus) + terminus.expects(:save).with { |req| req.instance == @request && req.key == "myname" }.raises "eh" + + lambda { @host.generate_certificate_request }.should raise_error + + @host.instance_eval { @certificate_request }.should be_nil + end + end + + describe "when managing its certificate" do + before do + @realcert = mock 'certificate' + @cert = stub 'cert', :content => @realcert + + @host.stubs(:key).returns mock("key") + @host.stubs(:certificate_matches_key?).returns true + end + + it "should find the CA certificate if it does not have a certificate" do + Puppet::SSL::Certificate.indirection.expects(:find).with(Puppet::SSL::CA_NAME).returns mock("cacert") + Puppet::SSL::Certificate.indirection.stubs(:find).with("myname").returns @cert + + @host.certificate + end + + it "should not find the CA certificate if it is the CA host" do + @host.expects(:ca?).returns true + Puppet::SSL::Certificate.indirection.stubs(:find) + Puppet::SSL::Certificate.indirection.expects(:find).with(Puppet::SSL::CA_NAME).never + + @host.certificate + end + + it "should return nil if it cannot find a CA certificate" do + Puppet::SSL::Certificate.indirection.expects(:find).with(Puppet::SSL::CA_NAME).returns nil + Puppet::SSL::Certificate.indirection.expects(:find).with("myname").never + + @host.certificate.should be_nil + end + + it "should find the key if it does not have one" do + Puppet::SSL::Certificate.indirection.stubs(:find) + @host.expects(:key).returns mock("key") + + @host.certificate + end + + it "should generate the key if one cannot be found" do + Puppet::SSL::Certificate.indirection.stubs(:find) + + @host.expects(:key).returns nil + @host.expects(:generate_key) + + @host.certificate + end + + it "should find the certificate in the Certificate class and return the Puppet certificate instance" do + Puppet::SSL::Certificate.indirection.expects(:find).with(Puppet::SSL::CA_NAME).returns mock("cacert") + Puppet::SSL::Certificate.indirection.expects(:find).with("myname").returns @cert + + @host.certificate.should equal(@cert) + end + + it "should fail if the found certificate does not match the private key" do + @host.expects(:certificate_matches_key?).returns false + + Puppet::SSL::Certificate.indirection.stubs(:find).returns @cert + + lambda { @host.certificate }.should raise_error(Puppet::Error) + end + + it "should return any previously found certificate" do + Puppet::SSL::Certificate.indirection.expects(:find).with(Puppet::SSL::CA_NAME).returns mock("cacert") + Puppet::SSL::Certificate.indirection.expects(:find).with("myname").returns(@cert).once + + @host.certificate.should equal(@cert) + @host.certificate.should equal(@cert) + end + end + + it "should have a method for listing certificate hosts" do + Puppet::SSL::Host.should respond_to(:search) + end + + describe "when listing certificate hosts" do + it "should default to listing all clients with any file types" do + Puppet::SSL::Key.indirection.expects(:search).returns [] + Puppet::SSL::Certificate.indirection.expects(:search).returns [] + Puppet::SSL::CertificateRequest.indirection.expects(:search).returns [] + Puppet::SSL::Host.search + end + + it "should be able to list only clients with a key" do + Puppet::SSL::Key.indirection.expects(:search).returns [] + Puppet::SSL::Certificate.indirection.expects(:search).never + Puppet::SSL::CertificateRequest.indirection.expects(:search).never + Puppet::SSL::Host.search :for => Puppet::SSL::Key + end + + it "should be able to list only clients with a certificate" do + Puppet::SSL::Key.indirection.expects(:search).never + Puppet::SSL::Certificate.indirection.expects(:search).returns [] + Puppet::SSL::CertificateRequest.indirection.expects(:search).never + Puppet::SSL::Host.search :for => Puppet::SSL::Certificate + end + + it "should be able to list only clients with a certificate request" do + Puppet::SSL::Key.indirection.expects(:search).never + Puppet::SSL::Certificate.indirection.expects(:search).never + Puppet::SSL::CertificateRequest.indirection.expects(:search).returns [] + Puppet::SSL::Host.search :for => Puppet::SSL::CertificateRequest + end + + it "should return a Host instance created with the name of each found instance", :'fails_on_ruby_1.9.2' => true do + key = stub 'key', :name => "key" + cert = stub 'cert', :name => "cert" + csr = stub 'csr', :name => "csr" + + Puppet::SSL::Key.indirection.expects(:search).returns [key] + Puppet::SSL::Certificate.indirection.expects(:search).returns [cert] + Puppet::SSL::CertificateRequest.indirection.expects(:search).returns [csr] + + returned = [] + %w{key cert csr}.each do |name| + result = mock(name) + returned << result + Puppet::SSL::Host.expects(:new).with(name).returns result + end + + result = Puppet::SSL::Host.search + returned.each do |r| + result.should be_include(r) + end + end + end + + it "should have a method for generating all necessary files" do + Puppet::SSL::Host.new("me").should respond_to(:generate) + end + + describe "when generating files" do + before do + @host = Puppet::SSL::Host.new("me") + @host.stubs(:generate_key) + @host.stubs(:generate_certificate_request) + end + + it "should generate a key if one is not present" do + @host.stubs(:key).returns nil + @host.expects(:generate_key) + + @host.generate + end + + it "should generate a certificate request if one is not present" do + @host.expects(:certificate_request).returns nil + @host.expects(:generate_certificate_request) + + @host.generate + end + + describe "and it can create a certificate authority" do + before do + @ca = mock 'ca' + Puppet::SSL::CertificateAuthority.stubs(:instance).returns @ca + end + + it "should use the CA to sign its certificate request if it does not have a certificate" do + @host.expects(:certificate).returns nil + + @ca.expects(:sign).with(@host.name) + + @host.generate + end + end + + describe "and it cannot create a certificate authority" do + before do + Puppet::SSL::CertificateAuthority.stubs(:instance).returns nil + end + + it "should seek its certificate" do + @host.expects(:certificate) + + @host.generate + end + end + end + + it "should have a method for creating an SSL store" do + Puppet::SSL::Host.new("me").should respond_to(:ssl_store) + end + + it "should always return the same store" do + host = Puppet::SSL::Host.new("foo") + store = mock 'store' + store.stub_everything + OpenSSL::X509::Store.expects(:new).returns store + host.ssl_store.should equal(host.ssl_store) + end + + describe "when creating an SSL store" do + before do + @host = Puppet::SSL::Host.new("me") + @store = mock 'store' + @store.stub_everything + OpenSSL::X509::Store.stubs(:new).returns @store + + Puppet.settings.stubs(:value).with(:localcacert).returns "ssl_host_testing" + + Puppet::SSL::CertificateRevocationList.indirection.stubs(:find).returns(nil) + end + + it "should accept a purpose" do + @store.expects(:purpose=).with "my special purpose" + @host.ssl_store("my special purpose") + end + + it "should default to OpenSSL::X509::PURPOSE_ANY as the purpose" do + @store.expects(:purpose=).with OpenSSL::X509::PURPOSE_ANY + @host.ssl_store + end + + it "should add the local CA cert file" do + Puppet.settings.stubs(:value).with(:localcacert).returns "/ca/cert/file" + @store.expects(:add_file).with "/ca/cert/file" + @host.ssl_store + end + + describe "and a CRL is available" do + before do + @crl = stub 'crl', :content => "real_crl" + Puppet::SSL::CertificateRevocationList.indirection.stubs(:find).returns @crl + Puppet.settings.stubs(:value).with(:certificate_revocation).returns true + end + + it "should add the CRL" do + @store.expects(:add_crl).with "real_crl" + @host.ssl_store + end + + it "should set the flags to OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK" do + @store.expects(:flags=).with OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK + @host.ssl_store + end + end + end + + describe "when waiting for a cert" do + before do + @host = Puppet::SSL::Host.new("me") + end + + it "should generate its certificate request and attempt to read the certificate again if no certificate is found" do + @host.expects(:certificate).times(2).returns(nil).then.returns "foo" + @host.expects(:generate) + @host.wait_for_cert(1) + end + + it "should catch and log errors during CSR saving" do + @host.expects(:certificate).times(2).returns(nil).then.returns "foo" + @host.expects(:generate).raises(RuntimeError).then.returns nil + @host.stubs(:sleep) + @host.wait_for_cert(1) + end + + it "should sleep and retry after failures saving the CSR if waitforcert is enabled" do + @host.expects(:certificate).times(2).returns(nil).then.returns "foo" + @host.expects(:generate).raises(RuntimeError).then.returns nil + @host.expects(:sleep).with(1) + @host.wait_for_cert(1) + end + + it "should exit after failures saving the CSR of waitforcert is disabled" do + @host.expects(:certificate).returns(nil) + @host.expects(:generate).raises(RuntimeError) + @host.expects(:puts) + expect { @host.wait_for_cert(0) }.to exit_with 1 + end + + it "should exit if the wait time is 0 and it can neither find nor retrieve a certificate" do + @host.stubs(:certificate).returns nil + @host.expects(:generate) + @host.expects(:puts) + expect { @host.wait_for_cert(0) }.to exit_with 1 + end + + it "should sleep for the specified amount of time if no certificate is found after generating its certificate request" do + @host.expects(:certificate).times(3).returns(nil).then.returns(nil).then.returns "foo" + @host.expects(:generate) + + @host.expects(:sleep).with(1) + + @host.wait_for_cert(1) + end + + it "should catch and log exceptions during certificate retrieval" do + @host.expects(:certificate).times(3).returns(nil).then.raises(RuntimeError).then.returns("foo") + @host.stubs(:generate) + @host.stubs(:sleep) + + Puppet.expects(:err) + + @host.wait_for_cert(1) + end + end + + describe "when handling PSON" do + include PuppetSpec::Files + + before do + Puppet[:vardir] = tmpdir("ssl_test_vardir") + Puppet[:ssldir] = tmpdir("ssl_test_ssldir") + Puppet::SSLCertificates::CA.new.mkrootcert + # localcacert is where each client stores the CA certificate + # cacert is where the master stores the CA certificate + # Since we need to play the role of both for testing we need them to be the same and exist + Puppet[:cacert] = Puppet[:localcacert] + + @ca=Puppet::SSL::CertificateAuthority.new + end + + describe "when converting to PSON" do + it "should be able to identify a host with an unsigned certificate request" do + host = Puppet::SSL::Host.new("bazinga") + host.generate_certificate_request + pson_hash = { + "fingerprint" => host.certificate_request.fingerprint, + "desired_state" => 'requested', + "name" => host.name + } + + result = PSON.parse(Puppet::SSL::Host.new(host.name).to_pson) + result["fingerprint"].should == pson_hash["fingerprint"] + result["name"].should == pson_hash["name"] + result["state"].should == pson_hash["desired_state"] + end + + it "should be able to identify a host with a signed certificate" do + host = Puppet::SSL::Host.new("bazinga") + host.generate_certificate_request + @ca.sign(host.name) + pson_hash = { + "fingerprint" => Puppet::SSL::Certificate.indirection.find(host.name).fingerprint, + "desired_state" => 'signed', + "name" => host.name, + } + + result = PSON.parse(Puppet::SSL::Host.new(host.name).to_pson) + result["fingerprint"].should == pson_hash["fingerprint"] + result["name"].should == pson_hash["name"] + result["state"].should == pson_hash["desired_state"] + end + + it "should be able to identify a host with a revoked certificate" do + host = Puppet::SSL::Host.new("bazinga") + host.generate_certificate_request + @ca.sign(host.name) + @ca.revoke(host.name) + pson_hash = { + "fingerprint" => Puppet::SSL::Certificate.indirection.find(host.name).fingerprint, + "desired_state" => 'revoked', + "name" => host.name, + } + + result = PSON.parse(Puppet::SSL::Host.new(host.name).to_pson) + result["fingerprint"].should == pson_hash["fingerprint"] + result["name"].should == pson_hash["name"] + result["state"].should == pson_hash["desired_state"] + end + end + + describe "when converting from PSON" do + it "should return a Puppet::SSL::Host object with the specified desired state" do + host = Puppet::SSL::Host.new("bazinga") + host.desired_state="signed" + pson_hash = { + "name" => host.name, + "desired_state" => host.desired_state, + } + generated_host = Puppet::SSL::Host.from_pson(pson_hash) + generated_host.desired_state.should == host.desired_state + generated_host.name.should == host.name + end + end + end +end --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/spec/unit/ssl/certificate_spec.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/spec/unit/ssl/certificate_spec.rb @@ -0,0 +1,123 @@ +#!/usr/bin/env rspec +require 'spec_helper' + +require 'puppet/ssl/certificate' + +describe Puppet::SSL::Certificate do + before do + @class = Puppet::SSL::Certificate + end + + after do + @class.instance_variable_set("@ca_location", nil) + end + + it "should be extended with the Indirector module" do + @class.singleton_class.should be_include(Puppet::Indirector) + end + + it "should indirect certificate" do + @class.indirection.name.should == :certificate + end + + it "should only support the text format" do + @class.supported_formats.should == [:s] + end + + describe "when converting from a string" do + it "should create a certificate instance with its name set to the certificate subject and its content set to the extracted certificate" do + cert = stub 'certificate', :subject => "/CN=Foo.madstop.com" + OpenSSL::X509::Certificate.expects(:new).with("my certificate").returns(cert) + + mycert = stub 'sslcert' + mycert.expects(:content=).with(cert) + + @class.expects(:new).with("foo.madstop.com").returns mycert + + @class.from_s("my certificate") + end + + it "should create multiple certificate instances when asked" do + cert1 = stub 'cert1' + @class.expects(:from_s).with("cert1").returns cert1 + cert2 = stub 'cert2' + @class.expects(:from_s).with("cert2").returns cert2 + + @class.from_multiple_s("cert1\n---\ncert2").should == [cert1, cert2] + end + end + + describe "when converting to a string" do + before do + @certificate = @class.new("myname") + end + + it "should return an empty string when it has no certificate" do + @certificate.to_s.should == "" + end + + it "should convert the certificate to pem format" do + certificate = mock 'certificate', :to_pem => "pem" + @certificate.content = certificate + @certificate.to_s.should == "pem" + end + + it "should be able to convert multiple instances to a string" do + cert2 = @class.new("foo") + @certificate.expects(:to_s).returns "cert1" + cert2.expects(:to_s).returns "cert2" + + @class.to_multiple_s([@certificate, cert2]).should == "cert1\n---\ncert2" + + end + end + + describe "when managing instances" do + before do + @certificate = @class.new("myname") + end + + it "should have a name attribute" do + @certificate.name.should == "myname" + end + + it "should convert its name to a string and downcase it" do + @class.new(:MyName).name.should == "myname" + end + + it "should have a content attribute" do + @certificate.should respond_to(:content) + end + + it "should return a nil expiration if there is no actual certificate" do + @certificate.stubs(:content).returns nil + + @certificate.expiration.should be_nil + end + + it "should use the expiration of the certificate as its expiration date" do + cert = stub 'cert' + @certificate.stubs(:content).returns cert + + cert.expects(:not_after).returns "sometime" + + @certificate.expiration.should == "sometime" + end + + it "should be able to read certificates from disk" do + path = "/my/path" + File.expects(:read).with(path).returns("my certificate") + certificate = mock 'certificate' + OpenSSL::X509::Certificate.expects(:new).with("my certificate").returns(certificate) + @certificate.read(path).should equal(certificate) + @certificate.content.should equal(certificate) + end + + it "should have a :to_text method that it delegates to the actual key" do + real_certificate = mock 'certificate' + real_certificate.expects(:to_text).returns "certificatetext" + @certificate.content = real_certificate + @certificate.to_text.should == "certificatetext" + end + end +end --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/spec/unit/ssl/certificate_factory_spec.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/spec/unit/ssl/certificate_factory_spec.rb @@ -0,0 +1,106 @@ +#!/usr/bin/env rspec +require 'spec_helper' + +require 'puppet/ssl/certificate_factory' + +describe Puppet::SSL::CertificateFactory do + before do + @cert_type = mock 'cert_type' + @name = mock 'name' + @csr = stub 'csr', :subject => @name + @issuer = mock 'issuer' + @serial = mock 'serial' + + @factory = Puppet::SSL::CertificateFactory.new(@cert_type, @csr, @issuer, @serial) + end + + describe "when initializing" do + it "should set its :cert_type to its first argument" do + @factory.cert_type.should equal(@cert_type) + end + + it "should set its :csr to its second argument" do + @factory.csr.should equal(@csr) + end + + it "should set its :issuer to its third argument" do + @factory.issuer.should equal(@issuer) + end + + it "should set its :serial to its fourth argument" do + @factory.serial.should equal(@serial) + end + + it "should set its name to the subject of the csr" do + @factory.name.should equal(@name) + end + end + + describe "when generating the certificate" do + before do + @cert = mock 'cert' + + @cert.stub_everything + + @factory.stubs :build_extensions + + @factory.stubs :set_ttl + + @issuer_name = mock 'issuer_name' + @issuer.stubs(:subject).returns @issuer_name + + @public_key = mock 'public_key' + @csr.stubs(:public_key).returns @public_key + + OpenSSL::X509::Certificate.stubs(:new).returns @cert + end + + it "should return a new X509 certificate" do + OpenSSL::X509::Certificate.expects(:new).returns @cert + @factory.result.should equal(@cert) + end + + it "should set the certificate's version to 2" do + @cert.expects(:version=).with 2 + @factory.result + end + + it "should set the certificate's subject to the CSR's subject" do + @cert.expects(:subject=).with @name + @factory.result + end + + it "should set the certificate's issuer to the Issuer's subject" do + @cert.expects(:issuer=).with @issuer_name + @factory.result + end + + it "should set the certificate's public key to the CSR's public key" do + @cert.expects(:public_key=).with @public_key + @factory.result + end + + it "should set the certificate's serial number to the provided serial number" do + @cert.expects(:serial=).with @serial + @factory.result + end + + it "should build extensions for the certificate" do + @factory.expects(:build_extensions) + @factory.result + end + + it "should set the ttl of the certificate" do + @factory.expects(:set_ttl) + @factory.result + end + end + + describe "when building extensions" do + it "should have tests" + end + + describe "when setting the ttl" do + it "should have tests" + end +end --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/spec/unit/ssl/certificate_authority/interface_spec.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/spec/unit/ssl/certificate_authority/interface_spec.rb @@ -0,0 +1,332 @@ +#!/usr/bin/env rspec +require 'spec_helper' + +require 'puppet/ssl/certificate_authority' + +shared_examples_for "a normal interface method" do + it "should call the method on the CA for each host specified if an array was provided" do + @ca.expects(@method).with("host1") + @ca.expects(@method).with("host2") + + @applier = Puppet::SSL::CertificateAuthority::Interface.new(@method, :to => %w{host1 host2}) + + @applier.apply(@ca) + end + + it "should call the method on the CA for all existing certificates if :all was provided" do + @ca.expects(:list).returns %w{host1 host2} + + @ca.expects(@method).with("host1") + @ca.expects(@method).with("host2") + + @applier = Puppet::SSL::CertificateAuthority::Interface.new(@method, :to => :all) + + @applier.apply(@ca) + end +end + +describe Puppet::SSL::CertificateAuthority::Interface do + before do + @class = Puppet::SSL::CertificateAuthority::Interface + end + describe "when initializing" do + it "should set its method using its settor" do + @class.any_instance.expects(:method=).with(:generate) + @class.new(:generate, :to => :all) + end + + it "should set its subjects using the settor" do + @class.any_instance.expects(:subjects=).with(:all) + @class.new(:generate, :to => :all) + end + + it "should set the digest if given" do + interface = @class.new(:generate, :to => :all, :digest => :digest) + interface.digest.should == :digest + end + + it "should set the digest to md5 if none given" do + interface = @class.new(:generate, :to => :all) + interface.digest.should == :MD5 + end + end + + describe "when setting the method" do + it "should set the method" do + @class.new(:generate, :to => :all).method.should == :generate + end + + it "should fail if the method isn't a member of the INTERFACE_METHODS array" do + Puppet::SSL::CertificateAuthority::Interface::INTERFACE_METHODS.expects(:include?).with(:thing).returns false + + lambda { @class.new(:thing, :to => :all) }.should raise_error(ArgumentError) + end + end + + describe "when setting the subjects" do + it "should set the subjects" do + @class.new(:generate, :to => :all).subjects.should == :all + end + + it "should fail if the subjects setting isn't :all or an array", :'fails_on_ruby_1.9.2' => true do + lambda { @class.new(:generate, "other") }.should raise_error(ArgumentError) + end + end + + it "should have a method for triggering the application" do + @class.new(:generate, :to => :all).should respond_to(:apply) + end + + describe "when applying" do + before do + # We use a real object here, because :verify can't be stubbed, apparently. + @ca = Object.new + end + + it "should raise InterfaceErrors" do + @applier = @class.new(:revoke, :to => :all) + + @ca.expects(:list).raises Puppet::SSL::CertificateAuthority::Interface::InterfaceError + + lambda { @applier.apply(@ca) }.should raise_error(Puppet::SSL::CertificateAuthority::Interface::InterfaceError) + end + + it "should log non-Interface failures rather than failing" do + @applier = @class.new(:revoke, :to => :all) + + @ca.expects(:list).raises ArgumentError + + Puppet.expects(:err) + + lambda { @applier.apply(@ca) }.should_not raise_error + end + + describe "with an empty array specified and the method is not list" do + it "should fail" do + @applier = @class.new(:sign, :to => []) + lambda { @applier.apply(@ca) }.should raise_error(ArgumentError) + end + end + + describe ":generate" do + it "should fail if :all was specified" do + @applier = @class.new(:generate, :to => :all) + lambda { @applier.apply(@ca) }.should raise_error(ArgumentError) + end + + it "should call :generate on the CA for each host specified" do + @applier = @class.new(:generate, :to => %w{host1 host2}) + + @ca.expects(:generate).with("host1") + @ca.expects(:generate).with("host2") + + @applier.apply(@ca) + end + end + + describe ":verify" do + before { @method = :verify } + #it_should_behave_like "a normal interface method" + + it "should call the method on the CA for each host specified if an array was provided" do + # LAK:NOTE Mocha apparently doesn't allow you to mock :verify, but I'm confident this works in real life. + end + + it "should call the method on the CA for all existing certificates if :all was provided" do + # LAK:NOTE Mocha apparently doesn't allow you to mock :verify, but I'm confident this works in real life. + end + end + + describe ":destroy" do + before { @method = :destroy } + it_should_behave_like "a normal interface method" + end + + describe ":revoke" do + before { @method = :revoke } + it_should_behave_like "a normal interface method" + end + + describe ":sign" do + describe "and an array of names was provided" do + before do + @applier = @class.new(:sign, :to => %w{host1 host2}) + end + + it "should sign the specified waiting certificate requests" do + @ca.expects(:sign).with("host1") + @ca.expects(:sign).with("host2") + + @applier.apply(@ca) + end + end + + describe "and :all was provided" do + it "should sign all waiting certificate requests" do + @ca.stubs(:waiting?).returns(%w{cert1 cert2}) + + @ca.expects(:sign).with("cert1") + @ca.expects(:sign).with("cert2") + + @applier = @class.new(:sign, :to => :all) + @applier.apply(@ca) + end + + it "should fail if there are no waiting certificate requests" do + @ca.stubs(:waiting?).returns([]) + + @applier = @class.new(:sign, :to => :all) + lambda { @applier.apply(@ca) }.should raise_error(Puppet::SSL::CertificateAuthority::Interface::InterfaceError) + end + end + end + + describe ":list" do + describe "and an empty array was provided" do + it "should print a string containing all certificate requests" do + @ca.expects(:waiting?).returns %w{host1 host2} + @ca.stubs(:verify) + + @applier = @class.new(:list, :to => []) + + @applier.expects(:puts).with "host1\nhost2" + + @applier.apply(@ca) + end + end + + describe "and :all was provided" do + it "should print a string containing all certificate requests and certificates" do + @ca.expects(:waiting?).returns %w{host1 host2} + @ca.expects(:list).returns %w{host3 host4} + @ca.stubs(:verify) + @ca.stubs(:fingerprint).returns "fingerprint" + @ca.expects(:verify).with("host3").raises(Puppet::SSL::CertificateAuthority::CertificateVerificationError.new(23), "certificate revoked") + + @applier = @class.new(:list, :to => :all) + + @applier.expects(:puts).with "host1 (fingerprint)" + @applier.expects(:puts).with "host2 (fingerprint)" + @applier.expects(:puts).with "- host3 (fingerprint) (certificate revoked)" + @applier.expects(:puts).with "+ host4 (fingerprint)" + + @applier.apply(@ca) + end + end + + describe "and :signed was provided" do + it "should print a string containing all signed certificate requests and certificates" do + @ca.expects(:list).returns %w{host1 host2} + + @applier = @class.new(:list, :to => :signed) + + @applier.apply(@ca) + end + end + + describe "and an array of names was provided" do + it "should print a string of all named hosts that have a waiting request" do + @ca.expects(:waiting?).returns %w{host1 host2} + @ca.expects(:list).returns %w{host3 host4} + @ca.stubs(:fingerprint).returns "fingerprint" + @ca.stubs(:verify) + + @applier = @class.new(:list, :to => %w{host1 host2 host3 host4}) + + @applier.expects(:puts).with "host1 (fingerprint)" + @applier.expects(:puts).with "host2 (fingerprint)" + @applier.expects(:puts).with "+ host3 (fingerprint)" + @applier.expects(:puts).with "+ host4 (fingerprint)" + + @applier.apply(@ca) + end + end + end + + describe ":print" do + describe "and :all was provided" do + it "should print all certificates" do + @ca.expects(:list).returns %w{host1 host2} + + @applier = @class.new(:print, :to => :all) + + @ca.expects(:print).with("host1").returns "h1" + @applier.expects(:puts).with "h1" + + @ca.expects(:print).with("host2").returns "h2" + @applier.expects(:puts).with "h2" + + @applier.apply(@ca) + end + end + + describe "and an array of names was provided" do + it "should print each named certificate if found" do + @applier = @class.new(:print, :to => %w{host1 host2}) + + @ca.expects(:print).with("host1").returns "h1" + @applier.expects(:puts).with "h1" + + @ca.expects(:print).with("host2").returns "h2" + @applier.expects(:puts).with "h2" + + @applier.apply(@ca) + end + + it "should log any named but not found certificates" do + @applier = @class.new(:print, :to => %w{host1 host2}) + + @ca.expects(:print).with("host1").returns "h1" + @applier.expects(:puts).with "h1" + + @ca.expects(:print).with("host2").returns nil + Puppet.expects(:err).with { |msg| msg.include?("host2") } + + @applier.apply(@ca) + end + end + end + + describe ":fingerprint" do + it "should fingerprint with the set digest algorithm" do + @applier = @class.new(:fingerprint, :to => %w{host1}, :digest => :digest) + + @ca.expects(:fingerprint).with("host1", :digest).returns "fingerprint1" + @applier.expects(:puts).with "host1 fingerprint1" + + @applier.apply(@ca) + end + + describe "and :all was provided" do + it "should fingerprint all certificates (including waiting ones)" do + @ca.expects(:list).returns %w{host1} + @ca.expects(:waiting?).returns %w{host2} + + @applier = @class.new(:fingerprint, :to => :all) + + @ca.expects(:fingerprint).with("host1", :MD5).returns "fingerprint1" + @applier.expects(:puts).with "host1 fingerprint1" + + @ca.expects(:fingerprint).with("host2", :MD5).returns "fingerprint2" + @applier.expects(:puts).with "host2 fingerprint2" + + @applier.apply(@ca) + end + end + + describe "and an array of names was provided" do + it "should print each named certificate if found" do + @applier = @class.new(:fingerprint, :to => %w{host1 host2}) + + @ca.expects(:fingerprint).with("host1", :MD5).returns "fingerprint1" + @applier.expects(:puts).with "host1 fingerprint1" + + @ca.expects(:fingerprint).with("host2", :MD5).returns "fingerprint2" + @applier.expects(:puts).with "host2 fingerprint2" + + @applier.apply(@ca) + end + end + end + end +end --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/spec/unit/indirector/certificate_request/ca_spec.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/spec/unit/indirector/certificate_request/ca_spec.rb @@ -0,0 +1,64 @@ +#!/usr/bin/env rspec +# +# Created by Luke Kanies on 2008-3-7. +# Copyright (c) 2007. All rights reserved. + +require 'spec_helper' + +require 'puppet/ssl/host' +require 'puppet/sslcertificates' +require 'puppet/sslcertificates/ca' +require 'puppet/indirector/certificate_request/ca' + +describe Puppet::SSL::CertificateRequest::Ca do + include PuppetSpec::Files + + before :each do + Puppet[:ssldir] = tmpdir('ssl') + + Puppet::SSL::Host.ca_location = :local + Puppet[:localcacert] = Puppet[:cacert] + Puppet::SSLCertificates::CA.new.mkrootcert + + @ca = Puppet::SSL::CertificateAuthority.new + end + + after :all do + Puppet::SSL::Host.ca_location = :none + end + + it "should have documentation" do + Puppet::SSL::CertificateRequest::Ca.doc.should be_instance_of(String) + end + + it "should use the :csrdir as the collection directory" do + Puppet.settings.expects(:value).with(:csrdir).returns "/request/dir" + Puppet::SSL::CertificateRequest::Ca.collection_directory.should == "/request/dir" + end + + it "should overwrite the previous certificate request if allow_duplicate_certs is true" do + Puppet[:allow_duplicate_certs] = true + host = Puppet::SSL::Host.new("foo") + host.generate_certificate_request + @ca.sign(host.name) + + Puppet::SSL::Host.indirection.find("foo").generate_certificate_request + + Puppet::SSL::Certificate.indirection.find("foo").name.should == "foo" + Puppet::SSL::CertificateRequest.indirection.find("foo").name.should == "foo" + Puppet::SSL::Host.indirection.find("foo").state.should == "requested" + end + + it "should reject a new certificate request if allow_duplicate_certs is false" do + Puppet[:allow_duplicate_certs] = false + host = Puppet::SSL::Host.new("bar") + host.generate_certificate_request + @ca.sign(host.name) + + expect { Puppet::SSL::Host.indirection.find("bar").generate_certificate_request }.should raise_error(/ignoring certificate request/) + + Puppet::SSL::Certificate.indirection.find("bar").name.should == "bar" + Puppet::SSL::CertificateRequest.indirection.find("bar").should be_nil + Puppet::SSL::Host.indirection.find("bar").state.should == "signed" + end +end --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/spec/unit/network/client_spec.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/spec/unit/network/client_spec.rb @@ -0,0 +1,45 @@ +#!/usr/bin/env rspec +# +# Created by Luke Kanies on 2008-3-24. +# Copyright (c) 2008. All rights reserved. + +require 'spec_helper' + +require 'puppet/network/client' + +describe Puppet::Network::Client do + before do + Puppet.settings.stubs(:use).returns(true) + Puppet::Network::HttpPool.stubs(:cert_setup) + end + + describe "when keep-alive is enabled" do + before do + Puppet::Network::HttpPool.stubs(:keep_alive?).returns true + end + it "should start the http client up on creation" do + http = mock 'http' + http.stub_everything + http.expects(:start) + Net::HTTP.stubs(:new).returns http + + # Pick a random subclass... + Puppet::Network::Client.runner.new :Server => Puppet[:server] + end + end + + describe "when keep-alive is disabled" do + before do + Puppet::Network::HttpPool.stubs(:keep_alive?).returns false + end + it "should not start the http client up on creation" do + http = mock 'http' + http.stub_everything + http.expects(:start).never + Net::HTTP.stubs(:new).returns http + + # Pick a random subclass... + Puppet::Network::Client.runner.new :Server => Puppet[:server] + end + end +end --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/spec/unit/network/xmlrpc/client_spec.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/spec/unit/network/xmlrpc/client_spec.rb @@ -0,0 +1,172 @@ +#!/usr/bin/env rspec +require 'puppet/network/client' + +require 'spec_helper' + +describe Puppet::Network::XMLRPCClient do + describe "when performing the rpc call" do + before do + Puppet::SSL::Host.any_instance.stubs(:certificate_matches_key?).returns true + @client = Puppet::Network::Client.report.xmlrpc_client.new + @client.stubs(:call).returns "foo" + end + + it "should call the specified namespace and method, with the specified arguments" do + @client.expects(:call).with("puppetreports.report", "eh").returns "foo" + @client.report("eh") + end + + it "should return the results from the call" do + @client.expects(:call).returns "foo" + @client.report("eh").should == "foo" + end + + it "should always close the http connection if it is still open after the call" do + http = mock 'http' + @client.stubs(:http).returns http + + http.expects(:started?).returns true + http.expects(:finish) + + @client.report("eh").should == "foo" + end + + it "should always close the http connection if it is still open after a call that raises an exception" do + http = mock 'http' + @client.stubs(:http).returns http + + @client.expects(:call).raises RuntimeError + + http.expects(:started?).returns true + http.expects(:finish) + + lambda { @client.report("eh") }.should raise_error + end + + describe "when returning the http instance" do + it "should use the http pool to create the instance" do + @client.instance_variable_set("@http", nil) + @client.expects(:host).returns "myhost" + @client.expects(:port).returns "myport" + Puppet::Network::HttpPool.expects(:http_instance).with("myhost", "myport", true).returns "http" + + @client.http.should == "http" + end + + it "should reuse existing instances" do + @client.http.should equal(@client.http) + end + end + + describe "when recycling the connection" do + it "should close the existing instance if it's open" do + http = mock 'http' + @client.stubs(:http).returns http + + http.expects(:started?).returns true + http.expects(:finish) + + @client.recycle_connection + end + + it "should force creation of a new instance" do + Puppet::Network::HttpPool.expects(:http_instance).returns "second_http" + + @client.recycle_connection + + @client.http.should == "second_http" + end + end + + describe "and an exception is raised" do + it "should raise XMLRPCClientError if XMLRPC::FaultException is raised" do + error = XMLRPC::FaultException.new("foo", "bar") + + @client.expects(:call).raises(error) + + lambda { @client.report("eh") }.should raise_error(Puppet::Network::XMLRPCClientError) + end + + it "should raise XMLRPCClientError if Errno::ECONNREFUSED is raised" do + @client.expects(:call).raises(Errno::ECONNREFUSED) + + lambda { @client.report("eh") }.should raise_error(Puppet::Network::XMLRPCClientError) + end + + it "should log and raise XMLRPCClientError if Timeout::Error is raised" do + Puppet.expects(:err) + @client.expects(:call).raises(Timeout::Error) + + lambda { @client.report("eh") }.should raise_error(Puppet::Network::XMLRPCClientError) + end + + it "should log and raise XMLRPCClientError if SocketError is raised" do + Puppet.expects(:err) + @client.expects(:call).raises(SocketError) + + lambda { @client.report("eh") }.should raise_error(Puppet::Network::XMLRPCClientError) + end + + it "should log, recycle the connection, and retry if Errno::EPIPE is raised" do + @client.expects(:call).times(2).raises(Errno::EPIPE).then.returns "eh" + + Puppet.expects(:info) + @client.expects(:recycle_connection) + + @client.report("eh") + end + + it "should log, recycle the connection, and retry if EOFError is raised" do + @client.expects(:call).times(2).raises(EOFError).then.returns "eh" + + Puppet.expects(:info) + @client.expects(:recycle_connection) + + @client.report("eh") + end + + it "should log and retry if an exception containing 'Wrong size' is raised" do + error = RuntimeError.new("Wrong size. Was 15, should be 30") + @client.expects(:call).times(2).raises(error).then.returns "eh" + + Puppet.expects(:warning) + + @client.report("eh") + end + + it "should raise XMLRPCClientError if OpenSSL::SSL::SSLError is raised" do + @client.expects(:call).raises(OpenSSL::SSL::SSLError) + + lambda { @client.report("eh") }.should raise_error(Puppet::Network::XMLRPCClientError) + end + + it "should log and raise XMLRPCClientError if OpenSSL::SSL::SSLError is raised with certificate issues" do + error = OpenSSL::SSL::SSLError.new("hostname was not match") + @client.expects(:call).raises(error) + + Puppet.expects(:warning) + + lambda { @client.report("eh") }.should raise_error(Puppet::Network::XMLRPCClientError) + end + + it "should log, recycle the connection, and retry if OpenSSL::SSL::SSLError is raised containing 'bad write retry'" do + error = OpenSSL::SSL::SSLError.new("bad write retry") + @client.expects(:call).times(2).raises(error).then.returns "eh" + + @client.expects(:recycle_connection) + + Puppet.expects(:warning) + + @client.report("eh") + end + + it "should log and raise XMLRPCClientError if any other exception is raised" do + @client.expects(:call).raises(RuntimeError) + + Puppet.expects(:err) + + lambda { @client.report("eh") }.should raise_error(Puppet::Network::XMLRPCClientError) + end + end + end +end --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/spec/unit/util/settings_spec.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/spec/unit/util/settings_spec.rb @@ -0,0 +1,1107 @@ +#!/usr/bin/env rspec +require 'spec_helper' + +describe Puppet::Util::Settings do + describe "when specifying defaults" do + before do + @settings = Puppet::Util::Settings.new + end + + it "should start with no defined parameters" do + @settings.params.length.should == 0 + end + + it "should allow specification of default values associated with a section as an array" do + @settings.setdefaults(:section, :myvalue => ["defaultval", "my description"]) + end + + it "should not allow duplicate parameter specifications" do + @settings.setdefaults(:section, :myvalue => ["a", "b"]) + lambda { @settings.setdefaults(:section, :myvalue => ["c", "d"]) }.should raise_error(ArgumentError) + end + + it "should allow specification of default values associated with a section as a hash" do + @settings.setdefaults(:section, :myvalue => {:default => "defaultval", :desc => "my description"}) + end + + it "should consider defined parameters to be valid" do + @settings.setdefaults(:section, :myvalue => ["defaultval", "my description"]) + @settings.valid?(:myvalue).should be_true + end + + it "should require a description when defaults are specified with an array" do + lambda { @settings.setdefaults(:section, :myvalue => ["a value"]) }.should raise_error(ArgumentError) + end + + it "should require a description when defaults are specified with a hash" do + lambda { @settings.setdefaults(:section, :myvalue => {:default => "a value"}) }.should raise_error(ArgumentError) + end + + it "should raise an error if we can't guess the type" do + lambda { @settings.setdefaults(:section, :myvalue => {:default => Object.new, :desc => "An impossible object"}) }.should raise_error(ArgumentError) + end + + it "should support specifying owner, group, and mode when specifying files" do + @settings.setdefaults(:section, :myvalue => {:default => "/some/file", :owner => "service", :mode => "boo", :group => "service", :desc => "whatever"}) + end + + it "should support specifying a short name" do + @settings.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) + end + + it "should support specifying the setting type" do + @settings.setdefaults(:section, :myvalue => {:default => "/w", :desc => "b", :type => :setting}) + @settings.setting(:myvalue).should be_instance_of(Puppet::Util::Settings::Setting) + end + + it "should fail if an invalid setting type is specified" do + lambda { @settings.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :type => :foo}) }.should raise_error(ArgumentError) + end + + it "should fail when short names conflict" do + @settings.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) + lambda { @settings.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) }.should raise_error(ArgumentError) + end + end + + describe "when setting values" do + before do + @settings = Puppet::Util::Settings.new + @settings.setdefaults :main, :myval => ["val", "desc"] + @settings.setdefaults :main, :bool => [true, "desc"] + end + + it "should provide a method for setting values from other objects" do + @settings[:myval] = "something else" + @settings[:myval].should == "something else" + end + + it "should support a getopt-specific mechanism for setting values" do + @settings.handlearg("--myval", "newval") + @settings[:myval].should == "newval" + end + + it "should support a getopt-specific mechanism for turning booleans off" do + @settings[:bool] = true + @settings.handlearg("--no-bool", "") + @settings[:bool].should == false + end + + it "should support a getopt-specific mechanism for turning booleans on" do + # Turn it off first + @settings[:bool] = false + @settings.handlearg("--bool", "") + @settings[:bool].should == true + end + + it "should consider a cli setting with no argument to be a boolean" do + # Turn it off first + @settings[:bool] = false + @settings.handlearg("--bool") + @settings[:bool].should == true + end + + it "should consider a cli setting with an empty string as an argument to be a boolean, if the setting itself is a boolean" do + # Turn it off first + @settings[:bool] = false + @settings.handlearg("--bool", "") + @settings[:bool].should == true + end + + it "should consider a cli setting with an empty string as an argument to be an empty argument, if the setting itself is not a boolean" do + @settings[:myval] = "bob" + @settings.handlearg("--myval", "") + @settings[:myval].should == "" + end + + it "should consider a cli setting with a boolean as an argument to be a boolean" do + # Turn it off first + @settings[:bool] = false + @settings.handlearg("--bool", "true") + @settings[:bool].should == true + end + + it "should not consider a cli setting of a non boolean with a boolean as an argument to be a boolean" do + # Turn it off first + @settings[:myval] = "bob" + @settings.handlearg("--no-myval", "") + @settings[:myval].should == "" + end + + it "should clear the cache when setting getopt-specific values" do + @settings.setdefaults :mysection, :one => ["whah", "yay"], :two => ["$one yay", "bah"] + @settings[:two].should == "whah yay" + @settings.handlearg("--one", "else") + @settings[:two].should == "else yay" + end + + it "should not clear other values when setting getopt-specific values" do + @settings[:myval] = "yay" + @settings.handlearg("--no-bool", "") + @settings[:myval].should == "yay" + end + + it "should clear the list of used sections" do + @settings.expects(:clearused) + @settings[:myval] = "yay" + end + + it "should call passed blocks when values are set" do + values = [] + @settings.setdefaults(:section, :hooker => {:default => "yay", :desc => "boo", :hook => lambda { |v| values << v }}) + values.should == [] + + @settings[:hooker] = "something" + values.should == %w{something} + end + + it "should call passed blocks when values are set via the command line" do + values = [] + @settings.setdefaults(:section, :hooker => {:default => "yay", :desc => "boo", :hook => lambda { |v| values << v }}) + values.should == [] + + @settings.handlearg("--hooker", "yay") + + values.should == %w{yay} + end + + it "should provide an option to call passed blocks during definition" do + values = [] + @settings.setdefaults(:section, :hooker => {:default => "yay", :desc => "boo", :call_on_define => true, :hook => lambda { |v| values << v }}) + values.should == %w{yay} + end + + it "should pass the fully interpolated value to the hook when called on definition" do + values = [] + @settings.setdefaults(:section, :one => ["test", "a"]) + @settings.setdefaults(:section, :hooker => {:default => "$one/yay", :desc => "boo", :call_on_define => true, :hook => lambda { |v| values << v }}) + values.should == %w{test/yay} + end + + it "should munge values using the setting-specific methods" do + @settings[:bool] = "false" + @settings[:bool].should == false + end + + it "should prefer cli values to values set in Ruby code" do + @settings.handlearg("--myval", "cliarg") + @settings[:myval] = "memarg" + @settings[:myval].should == "cliarg" + end + + it "should clear the list of environments" do + Puppet::Node::Environment.expects(:clear).at_least(1) + @settings[:myval] = "memarg" + end + + it "should raise an error if we try to set 'name'" do + lambda{ @settings[:name] = "foo" }.should raise_error(ArgumentError) + end + + it "should raise an error if we try to set 'run_mode'" do + lambda{ @settings[:run_mode] = "foo" }.should raise_error(ArgumentError) + end + + it "should warn and use [master] if we ask for [puppetmasterd]" do + Puppet.expects(:warning) + @settings.set_value(:myval, "foo", :puppetmasterd) + + @settings.stubs(:run_mode).returns(:master) + @settings.value(:myval).should == "foo" + end + + it "should warn and use [agent] if we ask for [puppetd]" do + Puppet.expects(:warning) + @settings.set_value(:myval, "foo", :puppetd) + + @settings.stubs(:run_mode).returns(:agent) + @settings.value(:myval).should == "foo" + end + end + + describe "when returning values" do + before do + @settings = Puppet::Util::Settings.new + @settings.setdefaults :section, :config => ["/my/file", "eh"], :one => ["ONE", "a"], :two => ["$one TWO", "b"], :three => ["$one $two THREE", "c"], :four => ["$two $three FOUR", "d"] + FileTest.stubs(:exist?).returns true + end + + it "should provide a mechanism for returning set values" do + @settings[:one] = "other" + @settings[:one].should == "other" + end + + it "should interpolate default values for other parameters into returned parameter values" do + @settings[:one].should == "ONE" + @settings[:two].should == "ONE TWO" + @settings[:three].should == "ONE ONE TWO THREE" + end + + it "should interpolate default values that themselves need to be interpolated" do + @settings[:four].should == "ONE TWO ONE ONE TWO THREE FOUR" + end + + it "should provide a method for returning uninterpolated values" do + @settings[:two] = "$one tw0" + @settings.uninterpolated_value(:two).should == "$one tw0" + @settings.uninterpolated_value(:four).should == "$two $three FOUR" + end + + it "should interpolate set values for other parameters into returned parameter values" do + @settings[:one] = "on3" + @settings[:two] = "$one tw0" + @settings[:three] = "$one $two thr33" + @settings[:four] = "$one $two $three f0ur" + @settings[:one].should == "on3" + @settings[:two].should == "on3 tw0" + @settings[:three].should == "on3 on3 tw0 thr33" + @settings[:four].should == "on3 on3 tw0 on3 on3 tw0 thr33 f0ur" + end + + it "should not cache interpolated values such that stale information is returned" do + @settings[:two].should == "ONE TWO" + @settings[:one] = "one" + @settings[:two].should == "one TWO" + end + + it "should not cache values such that information from one environment is returned for another environment" do + text = "[env1]\none = oneval\n[env2]\none = twoval\n" + @settings.stubs(:read_file).returns(text) + @settings.parse + + @settings.value(:one, "env1").should == "oneval" + @settings.value(:one, "env2").should == "twoval" + end + + it "should have a run_mode that defaults to user" do + @settings.run_mode.should == :user + end + end + + describe "when choosing which value to return" do + before do + @settings = Puppet::Util::Settings.new + @settings.setdefaults :section, + :config => ["/my/file", "a"], + :one => ["ONE", "a"], + :two => ["TWO", "b"] + FileTest.stubs(:exist?).returns true + Puppet.stubs(:run_mode).returns stub('run_mode', :name => :mymode) + end + + it "should return default values if no values have been set" do + @settings[:one].should == "ONE" + end + + it "should return values set on the cli before values set in the configuration file" do + text = "[main]\none = fileval\n" + @settings.stubs(:read_file).returns(text) + @settings.handlearg("--one", "clival") + @settings.parse + + @settings[:one].should == "clival" + end + + it "should return values set on the cli before values set in Ruby" do + @settings[:one] = "rubyval" + @settings.handlearg("--one", "clival") + @settings[:one].should == "clival" + end + + it "should return values set in the mode-specific section before values set in the main section" do + text = "[main]\none = mainval\n[mymode]\none = modeval\n" + @settings.stubs(:read_file).returns(text) + @settings.parse + + @settings[:one].should == "modeval" + end + + it "should not return values outside of its search path" do + text = "[other]\none = oval\n" + file = "/some/file" + @settings.stubs(:read_file).returns(text) + @settings.parse + @settings[:one].should == "ONE" + end + + it "should return values in a specified environment" do + text = "[env]\none = envval\n" + @settings.stubs(:read_file).returns(text) + @settings.parse + @settings.value(:one, "env").should == "envval" + end + + it 'should use the current environment for $environment' do + @settings.setdefaults :main, :myval => ["$environment/foo", "mydocs"] + + @settings.value(:myval, "myenv").should == "myenv/foo" + end + + it "should interpolate found values using the current environment" do + text = "[main]\none = mainval\n[myname]\none = nameval\ntwo = $one/two\n" + @settings.stubs(:read_file).returns(text) + @settings.parse + + @settings.value(:two, "myname").should == "nameval/two" + end + + it "should return values in a specified environment before values in the main or name sections" do + text = "[env]\none = envval\n[main]\none = mainval\n[myname]\none = nameval\n" + @settings.stubs(:read_file).returns(text) + @settings.parse + @settings.value(:one, "env").should == "envval" + end + end + + describe "when parsing its configuration" do + before do + @settings = Puppet::Util::Settings.new + @settings.stubs(:service_user_available?).returns true + @file = "/some/file" + @settings.setdefaults :section, :user => ["suser", "doc"], :group => ["sgroup", "doc"] + @settings.setdefaults :section, :config => ["/some/file", "eh"], :one => ["ONE", "a"], :two => ["$one TWO", "b"], :three => ["$one $two THREE", "c"] + FileTest.stubs(:exist?).returns true + end + + it "should not ignore the report setting" do + @settings.setdefaults :section, :report => ["false", "a"] + myfile = stub "myfile" + @settings[:config] = myfile + text = <<-CONF + [puppetd] + report=true + CONF + @settings.expects(:read_file).returns(text) + @settings.parse + @settings[:report].should be_true + end + + it "should use its current ':config' value for the file to parse" do + myfile = Puppet.features.posix? ? "/my/file" : "C:/myfile" # do not stub expand_path here, as this leads to a stack overflow, when mocha tries to use it + @settings[:config] = myfile + + File.expects(:read).with(myfile).returns "[main]" + + @settings.parse + end + + it "should fail if no configuration setting is defined" do + @settings = Puppet::Util::Settings.new + lambda { @settings.parse }.should raise_error(RuntimeError) + end + + it "should not try to parse non-existent files" do + FileTest.expects(:exist?).with("/some/file").returns false + + File.expects(:read).with("/some/file").never + + @settings.parse + end + + it "should set a timer that triggers reparsing, even if the file does not exist" do + FileTest.expects(:exist?).returns false + @settings.expects(:set_filetimeout_timer) + + @settings.parse + end + + it "should return values set in the configuration file" do + text = "[main] + one = fileval + " + @settings.expects(:read_file).returns(text) + @settings.parse + @settings[:one].should == "fileval" + end + + #484 - this should probably be in the regression area + it "should not throw an exception on unknown parameters" do + text = "[main]\nnosuchparam = mval\n" + @settings.expects(:read_file).returns(text) + lambda { @settings.parse }.should_not raise_error + end + + it "should convert booleans in the configuration file into Ruby booleans" do + text = "[main] + one = true + two = false + " + @settings.expects(:read_file).returns(text) + @settings.parse + @settings[:one].should == true + @settings[:two].should == false + end + + it "should convert integers in the configuration file into Ruby Integers" do + text = "[main] + one = 65 + " + @settings.expects(:read_file).returns(text) + @settings.parse + @settings[:one].should == 65 + end + + it "should support specifying all metadata (owner, group, mode) in the configuration file" do + @settings.setdefaults :section, :myfile => ["/myfile", "a"] + + text = "[main] + myfile = /other/file {owner = service, group = service, mode = 644} + " + @settings.expects(:read_file).returns(text) + @settings.parse + @settings[:myfile].should == "/other/file" + @settings.metadata(:myfile).should == {:owner => "suser", :group => "sgroup", :mode => "644"} + end + + it "should support specifying a single piece of metadata (owner, group, or mode) in the configuration file" do + @settings.setdefaults :section, :myfile => ["/myfile", "a"] + + text = "[main] + myfile = /other/file {owner = service} + " + file = "/some/file" + @settings.expects(:read_file).returns(text) + @settings.parse + @settings[:myfile].should == "/other/file" + @settings.metadata(:myfile).should == {:owner => "suser"} + end + + it "should call hooks associated with values set in the configuration file" do + values = [] + @settings.setdefaults :section, :mysetting => {:default => "defval", :desc => "a", :hook => proc { |v| values << v }} + + text = "[main] + mysetting = setval + " + @settings.expects(:read_file).returns(text) + @settings.parse + values.should == ["setval"] + end + + it "should not call the same hook for values set multiple times in the configuration file" do + values = [] + @settings.setdefaults :section, :mysetting => {:default => "defval", :desc => "a", :hook => proc { |v| values << v }} + + text = "[user] + mysetting = setval + [main] + mysetting = other + " + @settings.expects(:read_file).returns(text) + @settings.parse + values.should == ["setval"] + end + + it "should pass the environment-specific value to the hook when one is available" do + values = [] + @settings.setdefaults :section, :mysetting => {:default => "defval", :desc => "a", :hook => proc { |v| values << v }} + @settings.setdefaults :section, :environment => ["yay", "a"] + @settings.setdefaults :section, :environments => ["yay,foo", "a"] + + text = "[main] + mysetting = setval + [yay] + mysetting = other + " + @settings.expects(:read_file).returns(text) + @settings.parse + values.should == ["other"] + end + + it "should pass the interpolated value to the hook when one is available" do + values = [] + @settings.setdefaults :section, :base => {:default => "yay", :desc => "a", :hook => proc { |v| values << v }} + @settings.setdefaults :section, :mysetting => {:default => "defval", :desc => "a", :hook => proc { |v| values << v }} + + text = "[main] + mysetting = $base/setval + " + @settings.expects(:read_file).returns(text) + @settings.parse + values.should == ["yay/setval"] + end + + it "should allow empty values" do + @settings.setdefaults :section, :myarg => ["myfile", "a"] + + text = "[main] + myarg = + " + @settings.stubs(:read_file).returns(text) + @settings.parse + @settings[:myarg].should == "" + end + + describe "and when reading a non-positive filetimeout value from the config file" do + before do + @settings.setdefaults :foo, :filetimeout => [5, "eh"] + + somefile = "/some/file" + text = "[main] + filetimeout = -1 + " + File.expects(:read).with(somefile).returns(text) + File.expects(:expand_path).with(somefile).returns somefile + @settings[:config] = somefile + end + + it "should not set a timer" do + EventLoop::Timer.expects(:new).never + + @settings.parse + end + end + end + + describe "when reparsing its configuration" do + before do + @settings = Puppet::Util::Settings.new + @settings.setdefaults :section, :config => ["/test/file", "a"], :one => ["ONE", "a"], :two => ["$one TWO", "b"], :three => ["$one $two THREE", "c"] + FileTest.stubs(:exist?).returns true + end + + it "should use a LoadedFile instance to determine if the file has changed" do + file = mock 'file' + Puppet::Util::LoadedFile.expects(:new).with("/test/file").returns file + + file.expects(:changed?) + + @settings.stubs(:parse) + @settings.reparse + end + + it "should not create the LoadedFile instance and should not parse if the file does not exist" do + FileTest.expects(:exist?).with("/test/file").returns false + Puppet::Util::LoadedFile.expects(:new).never + + @settings.expects(:parse).never + + @settings.reparse + end + + it "should not reparse if the file has not changed" do + file = mock 'file' + Puppet::Util::LoadedFile.expects(:new).with("/test/file").returns file + + file.expects(:changed?).returns false + + @settings.expects(:parse).never + + @settings.reparse + end + + it "should reparse if the file has changed" do + file = stub 'file', :file => "/test/file" + Puppet::Util::LoadedFile.expects(:new).with("/test/file").returns file + + file.expects(:changed?).returns true + + @settings.expects(:parse) + + @settings.reparse + end + + it "should use a cached LoadedFile instance" do + first = mock 'first' + second = mock 'second' + Puppet::Util::LoadedFile.expects(:new).times(2).with("/test/file").returns(first).then.returns(second) + + @settings.file.should equal(first) + Puppet::Util::Cacher.expire + @settings.file.should equal(second) + end + + it "should replace in-memory values with on-file values" do + # Init the value + text = "[main]\none = disk-init\n" + file = mock 'file' + file.stubs(:changed?).returns(true) + file.stubs(:file).returns("/test/file") + @settings[:one] = "init" + @settings.file = file + + # Now replace the value + text = "[main]\none = disk-replace\n" + + # This is kinda ridiculous - the reason it parses twice is that + # it goes to parse again when we ask for the value, because the + # mock always says it should get reparsed. + @settings.stubs(:read_file).returns(text) + @settings.reparse + @settings[:one].should == "disk-replace" + end + + it "should retain parameters set by cli when configuration files are reparsed" do + @settings.handlearg("--one", "clival") + + text = "[main]\none = on-disk\n" + @settings.stubs(:read_file).returns(text) + @settings.parse + + @settings[:one].should == "clival" + end + + it "should remove in-memory values that are no longer set in the file" do + # Init the value + text = "[main]\none = disk-init\n" + @settings.expects(:read_file).returns(text) + @settings.parse + @settings[:one].should == "disk-init" + + # Now replace the value + text = "[main]\ntwo = disk-replace\n" + @settings.expects(:read_file).returns(text) + @settings.parse + #@settings.reparse + + # The originally-overridden value should be replaced with the default + @settings[:one].should == "ONE" + + # and we should now have the new value in memory + @settings[:two].should == "disk-replace" + end + + it "should retain in-memory values if the file has a syntax error" do + # Init the value + text = "[main]\none = initial-value\n" + @settings.expects(:read_file).returns(text) + @settings.parse + @settings[:one].should == "initial-value" + + # Now replace the value with something bogus + text = "[main]\nkenny = killed-by-what-follows\n1 is 2, blah blah florp\n" + @settings.expects(:read_file).returns(text) + @settings.parse + + # The originally-overridden value should not be replaced with the default + @settings[:one].should == "initial-value" + + # and we should not have the new value in memory + @settings[:kenny].should be_nil + end + end + + it "should provide a method for creating a catalog of resources from its configuration" do + Puppet::Util::Settings.new.should respond_to(:to_catalog) + end + + describe "when creating a catalog" do + before do + @settings = Puppet::Util::Settings.new + @settings.stubs(:service_user_available?).returns true + @prefix = Puppet.features.posix? ? "" : "C:" + end + + it "should add all file resources to the catalog if no sections have been specified" do + @settings.setdefaults :main, :maindir => [@prefix+"/maindir", "a"], :seconddir => [@prefix+"/seconddir", "a"] + @settings.setdefaults :other, :otherdir => [@prefix+"/otherdir", "a"] + + catalog = @settings.to_catalog + + [@prefix+"/maindir", @prefix+"/seconddir", @prefix+"/otherdir"].each do |path| + catalog.resource(:file, path).should be_instance_of(Puppet::Resource) + end + end + + it "should add only files in the specified sections if section names are provided" do + @settings.setdefaults :main, :maindir => [@prefix+"/maindir", "a"] + @settings.setdefaults :other, :otherdir => [@prefix+"/otherdir", "a"] + catalog = @settings.to_catalog(:main) + catalog.resource(:file, @prefix+"/otherdir").should be_nil + catalog.resource(:file, @prefix+"/maindir").should be_instance_of(Puppet::Resource) + end + + it "should not try to add the same file twice" do + @settings.setdefaults :main, :maindir => [@prefix+"/maindir", "a"] + @settings.setdefaults :other, :otherdir => [@prefix+"/maindir", "a"] + lambda { @settings.to_catalog }.should_not raise_error + end + + it "should ignore files whose :to_resource method returns nil" do + @settings.setdefaults :main, :maindir => [@prefix+"/maindir", "a"] + @settings.setting(:maindir).expects(:to_resource).returns nil + + Puppet::Resource::Catalog.any_instance.expects(:add_resource).never + @settings.to_catalog + end + + describe "when adding users and groups to the catalog" do + before do + Puppet.features.stubs(:root?).returns true + @settings.setdefaults :foo, :mkusers => [true, "e"], :user => ["suser", "doc"], :group => ["sgroup", "doc"] + @settings.setdefaults :other, :otherdir => {:default => "/otherdir", :desc => "a", :owner => "service", :group => "service"} + + @catalog = @settings.to_catalog + end + + it "should add each specified user and group to the catalog if :mkusers is a valid setting, is enabled, and we're running as root" do + @catalog.resource(:user, "suser").should be_instance_of(Puppet::Resource) + @catalog.resource(:group, "sgroup").should be_instance_of(Puppet::Resource) + end + + it "should only add users and groups to the catalog from specified sections" do + @settings.setdefaults :yay, :yaydir => {:default => "/yaydir", :desc => "a", :owner => "service", :group => "service"} + catalog = @settings.to_catalog(:other) + catalog.resource(:user, "jane").should be_nil + catalog.resource(:group, "billy").should be_nil + end + + it "should not add users or groups to the catalog if :mkusers not running as root" do + Puppet.features.stubs(:root?).returns false + + catalog = @settings.to_catalog + catalog.resource(:user, "suser").should be_nil + catalog.resource(:group, "sgroup").should be_nil + end + + it "should not add users or groups to the catalog if :mkusers is not a valid setting" do + Puppet.features.stubs(:root?).returns true + settings = Puppet::Util::Settings.new + settings.setdefaults :other, :otherdir => {:default => "/otherdir", :desc => "a", :owner => "service", :group => "service"} + + catalog = settings.to_catalog + catalog.resource(:user, "suser").should be_nil + catalog.resource(:group, "sgroup").should be_nil + end + + it "should not add users or groups to the catalog if :mkusers is a valid setting but is disabled" do + @settings[:mkusers] = false + + catalog = @settings.to_catalog + catalog.resource(:user, "suser").should be_nil + catalog.resource(:group, "sgroup").should be_nil + end + + it "should not try to add users or groups to the catalog twice" do + @settings.setdefaults :yay, :yaydir => {:default => "/yaydir", :desc => "a", :owner => "service", :group => "service"} + + # This would fail if users/groups were added twice + lambda { @settings.to_catalog }.should_not raise_error + end + + it "should set :ensure to :present on each created user and group" do + @catalog.resource(:user, "suser")[:ensure].should == :present + @catalog.resource(:group, "sgroup")[:ensure].should == :present + end + + it "should set each created user's :gid to the service group" do + @settings.to_catalog.resource(:user, "suser")[:gid].should == "sgroup" + end + + it "should not attempt to manage the root user" do + Puppet.features.stubs(:root?).returns true + @settings.setdefaults :foo, :foodir => {:default => "/foodir", :desc => "a", :owner => "root", :group => "service"} + + @settings.to_catalog.resource(:user, "root").should be_nil + end + end + end + + it "should be able to be converted to a manifest" do + Puppet::Util::Settings.new.should respond_to(:to_manifest) + end + + describe "when being converted to a manifest" do + it "should produce a string with the code for each resource joined by two carriage returns" do + @settings = Puppet::Util::Settings.new + @settings.setdefaults :main, :maindir => ["/maindir", "a"], :seconddir => ["/seconddir", "a"] + + main = stub 'main_resource', :ref => "File[/maindir]" + main.expects(:to_manifest).returns "maindir" + second = stub 'second_resource', :ref => "File[/seconddir]" + second.expects(:to_manifest).returns "seconddir" + @settings.setting(:maindir).expects(:to_resource).returns main + @settings.setting(:seconddir).expects(:to_resource).returns second + + @settings.to_manifest.split("\n\n").sort.should == %w{maindir seconddir} + end + end + + describe "when using sections of the configuration to manage the local host" do + before do + @settings = Puppet::Util::Settings.new + @settings.stubs(:service_user_available?).returns true + @settings.setdefaults :main, :noop => [false, ""] + @settings.setdefaults :main, :maindir => ["/maindir", "a"], :seconddir => ["/seconddir", "a"] + @settings.setdefaults :main, :user => ["suser", "doc"], :group => ["sgroup", "doc"] + @settings.setdefaults :other, :otherdir => {:default => "/otherdir", :desc => "a", :owner => "service", :group => "service", :mode => 0755} + @settings.setdefaults :third, :thirddir => ["/thirddir", "b"] + @settings.setdefaults :files, :myfile => {:default => "/myfile", :desc => "a", :mode => 0755} + end + + it "should provide a method that writes files with the correct modes" do + @settings.should respond_to(:write) + end + + it "should provide a method that creates directories with the correct modes" do + Puppet::Util::SUIDManager.expects(:asuser).with("suser", "sgroup").yields + Dir.expects(:mkdir).with("/otherdir", 0755) + @settings.mkdir(:otherdir) + end + + it "should create a catalog with the specified sections" do + @settings.expects(:to_catalog).with(:main, :other).returns Puppet::Resource::Catalog.new("foo") + @settings.use(:main, :other) + end + + it "should canonicalize the sections" do + @settings.expects(:to_catalog).with(:main, :other).returns Puppet::Resource::Catalog.new("foo") + @settings.use("main", "other") + end + + it "should ignore sections that have already been used" do + @settings.expects(:to_catalog).with(:main).returns Puppet::Resource::Catalog.new("foo") + @settings.use(:main) + @settings.expects(:to_catalog).with(:other).returns Puppet::Resource::Catalog.new("foo") + @settings.use(:main, :other) + end + + it "should ignore tags and schedules when creating files and directories" + + it "should be able to provide all of its parameters in a format compatible with GetOpt::Long" do + pending "Not converted from test/unit yet" + end + + it "should convert the created catalog to a RAL catalog" do + @catalog = Puppet::Resource::Catalog.new("foo") + @settings.expects(:to_catalog).with(:main).returns @catalog + + @catalog.expects(:to_ral).returns @catalog + @settings.use(:main) + end + + it "should specify that it is not managing a host catalog" do + catalog = Puppet::Resource::Catalog.new("foo") + catalog.expects(:apply) + @settings.expects(:to_catalog).returns catalog + + catalog.stubs(:to_ral).returns catalog + + catalog.expects(:host_config=).with false + + @settings.use(:main) + end + + it "should support a method for re-using all currently used sections" do + @settings.expects(:to_catalog).with(:main, :third).times(2).returns Puppet::Resource::Catalog.new("foo") + + @settings.use(:main, :third) + @settings.reuse + end + + it "should fail with an appropriate message if any resources fail" do + @catalog = Puppet::Resource::Catalog.new("foo") + @catalog.stubs(:to_ral).returns @catalog + @settings.expects(:to_catalog).returns @catalog + + @trans = mock("transaction") + @catalog.expects(:apply).yields(@trans) + + @trans.expects(:any_failed?).returns(true) + + report = mock 'report' + @trans.expects(:report).returns report + + log = mock 'log', :to_s => "My failure", :level => :err + report.expects(:logs).returns [log] + + @settings.expects(:raise).with { |msg| msg.include?("My failure") } + @settings.use(:whatever) + end + end + + describe "when dealing with printing configs" do + before do + @settings = Puppet::Util::Settings.new + #these are the magic default values + @settings.stubs(:value).with(:configprint).returns("") + @settings.stubs(:value).with(:genconfig).returns(false) + @settings.stubs(:value).with(:genmanifest).returns(false) + @settings.stubs(:value).with(:environment).returns(nil) + end + + describe "when checking print_config?" do + it "should return false when the :configprint, :genconfig and :genmanifest are not set" do + @settings.print_configs?.should be_false + end + + it "should return true when :configprint has a value" do + @settings.stubs(:value).with(:configprint).returns("something") + @settings.print_configs?.should be_true + end + + it "should return true when :genconfig has a value" do + @settings.stubs(:value).with(:genconfig).returns(true) + @settings.print_configs?.should be_true + end + + it "should return true when :genmanifest has a value" do + @settings.stubs(:value).with(:genmanifest).returns(true) + @settings.print_configs?.should be_true + end + end + + describe "when printing configs" do + describe "when :configprint has a value" do + it "should call print_config_options" do + @settings.stubs(:value).with(:configprint).returns("something") + @settings.expects(:print_config_options) + @settings.print_configs + end + + it "should get the value of the option using the environment" do + @settings.stubs(:value).with(:configprint).returns("something") + @settings.stubs(:include?).with("something").returns(true) + @settings.expects(:value).with(:environment).returns("env") + @settings.expects(:value).with("something", "env").returns("foo") + @settings.stubs(:puts).with("foo") + @settings.print_configs + end + + it "should print the value of the option" do + @settings.stubs(:value).with(:configprint).returns("something") + @settings.stubs(:include?).with("something").returns(true) + @settings.stubs(:value).with("something", nil).returns("foo") + @settings.expects(:puts).with("foo") + @settings.print_configs + end + + it "should print the value pairs if there are multiple options" do + @settings.stubs(:value).with(:configprint).returns("bar,baz") + @settings.stubs(:include?).with("bar").returns(true) + @settings.stubs(:include?).with("baz").returns(true) + @settings.stubs(:value).with("bar", nil).returns("foo") + @settings.stubs(:value).with("baz", nil).returns("fud") + @settings.expects(:puts).with("bar = foo") + @settings.expects(:puts).with("baz = fud") + @settings.print_configs + end + + it "should print a whole bunch of stuff if :configprint = all" + + it "should return true after printing" do + @settings.stubs(:value).with(:configprint).returns("something") + @settings.stubs(:include?).with("something").returns(true) + @settings.stubs(:value).with("something", nil).returns("foo") + @settings.stubs(:puts).with("foo") + @settings.print_configs.should be_true + end + + it "should return false if a config param is not found" do + @settings.stubs :puts + @settings.stubs(:value).with(:configprint).returns("something") + @settings.stubs(:include?).with("something").returns(false) + @settings.print_configs.should be_false + end + end + + describe "when genconfig is true" do + before do + @settings.stubs :puts + end + + it "should call to_config" do + @settings.stubs(:value).with(:genconfig).returns(true) + @settings.expects(:to_config) + @settings.print_configs + end + + it "should return true from print_configs" do + @settings.stubs(:value).with(:genconfig).returns(true) + @settings.stubs(:to_config) + @settings.print_configs.should be_true + end + end + + describe "when genmanifest is true" do + before do + @settings.stubs :puts + end + + it "should call to_config" do + @settings.stubs(:value).with(:genmanifest).returns(true) + @settings.expects(:to_manifest) + @settings.print_configs + end + + it "should return true from print_configs" do + @settings.stubs(:value).with(:genmanifest).returns(true) + @settings.stubs(:to_manifest) + @settings.print_configs.should be_true + end + end + end + end + + describe "when setting a timer to trigger configuration file reparsing" do + before do + @settings = Puppet::Util::Settings.new + @settings.setdefaults :foo, :filetimeout => [5, "eh"] + end + + it "should do nothing if no filetimeout setting is available" do + @settings.expects(:value).with(:filetimeout).returns nil + EventLoop::Timer.expects(:new).never + @settings.set_filetimeout_timer + end + + it "should always convert the timer interval to an integer" do + @settings.expects(:value).with(:filetimeout).returns "10" + EventLoop::Timer.expects(:new).with(:interval => 10, :start? => true, :tolerance => 1) + @settings.set_filetimeout_timer + end + + it "should do nothing if the filetimeout setting is not greater than 0" do + @settings.expects(:value).with(:filetimeout).returns -2 + EventLoop::Timer.expects(:new).never + @settings.set_filetimeout_timer + end + + it "should create a timer with its interval set to the filetimeout, start? set to true, and a tolerance of 1" do + @settings.expects(:value).with(:filetimeout).returns 5 + EventLoop::Timer.expects(:new).with(:interval => 5, :start? => true, :tolerance => 1) + + @settings.set_filetimeout_timer + end + + it "should reparse when the timer goes off" do + EventLoop::Timer.expects(:new).with(:interval => 5, :start? => true, :tolerance => 1).yields + + @settings.expects(:reparse) + + @settings.set_filetimeout_timer + end + end + + describe "when determining if the service user is available" do + it "should return false if there is no user setting" do + Puppet::Util::Settings.new.should_not be_service_user_available + end + + it "should return false if the user provider says the user is missing" do + settings = Puppet::Util::Settings.new + settings.setdefaults :main, :user => ["foo", "doc"] + + user = mock 'user' + user.expects(:exists?).returns false + + Puppet::Type.type(:user).expects(:new).with { |args| args[:name] == "foo" }.returns user + + settings.should_not be_service_user_available + end + + it "should return true if the user provider says the user is present" do + settings = Puppet::Util::Settings.new + settings.setdefaults :main, :user => ["foo", "doc"] + + user = mock 'user' + user.expects(:exists?).returns true + + Puppet::Type.type(:user).expects(:new).with { |args| args[:name] == "foo" }.returns user + + settings.should be_service_user_available + end + + it "should cache the result" + end +end --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/spec/unit/sslcertificates/ca_spec.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/spec/unit/sslcertificates/ca_spec.rb @@ -0,0 +1,110 @@ +#!/usr/bin/env rspec +require 'spec_helper' + +require 'puppet' +require 'puppet/sslcertificates' +require 'puppet/sslcertificates/ca' + +describe Puppet::SSLCertificates::CA do + before :all do + @hosts = %w{host.domain.com Other.Testing.Com} + end + + before :each do + Puppet::Util::SUIDManager.stubs(:asuser).yields + file = Tempfile.new("ca_testing") + @dir = file.path + file.delete + + Puppet.settings[:confdir] = @dir + Puppet.settings[:vardir] = @dir + + @ca = Puppet::SSLCertificates::CA.new + end + + after :each do + system("rm -rf #{@dir}") + end + + describe 'when cleaning' do + it 'should remove associated files' do + dirs = [:csrdir, :signeddir, :publickeydir, :privatekeydir, :certdir] + + @hosts.each do |host| + files = [] + dirs.each do |dir| + dir = Puppet[dir] + + # Case insensitivity is handled through downcasing + file = File.join(dir, host.downcase + '.pem') + + File.open(file, "w") do |f| + f.puts "testing" + end + + files << file + end + + lambda { @ca.clean(host) }.should_not raise_error + + files.reject {|f| ! File.exists?(f)}.should be_empty + end + end + end + + describe 'when mapping hosts to files' do + it 'should correctly return the certfile' do + @hosts.each do |host| + value = nil + lambda { value = @ca.host2certfile host }.should_not raise_error + + File.join(Puppet[:signeddir], host.downcase + '.pem').should == value + end + end + + it 'should correctly return the csrfile' do + @hosts.each do |host| + value = nil + lambda { value = @ca.host2csrfile host }.should_not raise_error + + File.join(Puppet[:csrdir], host.downcase + '.pem').should == value + end + end + end + + describe 'when listing' do + it 'should find all csr' do + list = [] + + # Make some fake CSRs + @hosts.each do |host| + file = File.join(Puppet[:csrdir], host.downcase + '.pem') + File.open(file, 'w') { |f| f.puts "yay" } + list << host.downcase + end + + @ca.list.sort.should == list.sort + end + end + + describe 'when creating a root certificate' do + before :each do + lambda { @ca.mkrootcert }.should_not raise_exception + end + + it 'should store the public key' do + File.exists?(Puppet[:capub]).should be_true + end + + it 'should prepend "Puppet CA: " to the fqdn as the ca_name by default' do + host_mock_fact = mock() + host_mock_fact.expects(:value).returns('myhost') + domain_mock_fact = mock() + domain_mock_fact.expects(:value).returns('puppetlabs.lan') + Facter.stubs(:[]).with('hostname').returns(host_mock_fact) + Facter.stubs(:[]).with('domain').returns(domain_mock_fact) + + @ca.mkrootcert.name.should == 'Puppet CA: myhost.puppetlabs.lan' + end + end +end --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/spec/unit/face/certificate_spec.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/spec/unit/face/certificate_spec.rb @@ -0,0 +1,23 @@ +#!/usr/bin/env rspec +require 'spec_helper' +require 'puppet/face' + +require 'puppet/ssl/host' + +describe Puppet::Face[:certificate, '0.0.1'] do + it "should have a ca-location option" do + subject.should be_option :ca_location + end + + it "should set the ca location when invoked" do + Puppet::SSL::Host.expects(:ca_location=).with(:foo) + Puppet::SSL::Host.indirection.expects(:save) + subject.sign "hello, friend", :ca_location => :foo + end + + it "(#7059) should set the ca location when an inherited action is invoked" do + Puppet::SSL::Host.expects(:ca_location=).with(:foo) + subject.indirection.expects(:find) + subject.find "hello, friend", :ca_location => :foo + end +end --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/spec/integration/defaults_spec.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/spec/integration/defaults_spec.rb @@ -0,0 +1,258 @@ +#!/usr/bin/env rspec +require 'spec_helper' + +require 'puppet/defaults' +require 'puppet/rails' + +describe "Puppet defaults" do + include Puppet::Util::Execution + after { Puppet.settings.clear } + + describe "when setting the :factpath" do + it "should add the :factpath to Facter's search paths" do + Facter.expects(:search).with("/my/fact/path") + + Puppet.settings[:factpath] = "/my/fact/path" + end + end + + describe "when setting the :certname" do + it "should fail if the certname is not downcased" do + lambda { Puppet.settings[:certname] = "Host.Domain.Com" }.should raise_error(ArgumentError) + end + end + + describe "when configuring the :crl" do + it "should warn if :cacrl is set to false" do + Puppet.expects(:warning) + Puppet.settings[:cacrl] = 'false' + end + end + + describe "when setting the :catalog_format" do + it "should log a deprecation notice" do + Puppet.expects(:warning) + Puppet.settings[:catalog_format] = 'marshal' + end + it "should copy the value to :preferred_serialization_format" do + Puppet.settings[:catalog_format] = 'marshal' + Puppet.settings[:preferred_serialization_format].should == 'marshal' + end + end + + it "should have a clientyamldir setting" do + Puppet.settings[:clientyamldir].should_not be_nil + end + + it "should have different values for the yamldir and clientyamldir" do + Puppet.settings[:yamldir].should_not == Puppet.settings[:clientyamldir] + end + + it "should have a client_datadir setting" do + Puppet.settings[:client_datadir].should_not be_nil + end + + it "should have different values for the server_datadir and client_datadir" do + Puppet.settings[:server_datadir].should_not == Puppet.settings[:client_datadir] + end + + # See #1232 + it "should not specify a user or group for the clientyamldir" do + Puppet.settings.setting(:clientyamldir).owner.should be_nil + Puppet.settings.setting(:clientyamldir).group.should be_nil + end + + it "should use the service user and group for the yamldir" do + Puppet.settings.stubs(:service_user_available?).returns true + Puppet.settings.setting(:yamldir).owner.should == Puppet.settings[:user] + Puppet.settings.setting(:yamldir).group.should == Puppet.settings[:group] + end + + # See #1232 + it "should not specify a user or group for the rundir" do + Puppet.settings.setting(:rundir).owner.should be_nil + Puppet.settings.setting(:rundir).group.should be_nil + end + + it "should specify that the host private key should be owned by the service user" do + Puppet.settings.stubs(:service_user_available?).returns true + Puppet.settings.setting(:hostprivkey).owner.should == Puppet.settings[:user] + end + + it "should specify that the host certificate should be owned by the service user" do + Puppet.settings.stubs(:service_user_available?).returns true + Puppet.settings.setting(:hostcert).owner.should == Puppet.settings[:user] + end + + it "should use a bind address of ''" do + Puppet.settings.clear + Puppet.settings[:bindaddress].should == "" + end + + [:factdest].each do |setting| + it "should force the :factdest to be a directory" do + Puppet.settings[setting].should =~ /\/$/ + end + end + + [:modulepath, :factpath].each do |setting| + it "should configure '#{setting}' not to be a file setting, so multi-directory settings are acceptable" do + Puppet.settings.setting(setting).should be_instance_of(Puppet::Util::Settings::Setting) + end + end + + it "should add /usr/sbin and /sbin to the path if they're not there" do + withenv("PATH" => "/usr/bin:/usr/local/bin") do + Puppet.settings[:path] = "none" # this causes it to ignore the setting + ENV["PATH"].split(File::PATH_SEPARATOR).should be_include("/usr/sbin") + ENV["PATH"].split(File::PATH_SEPARATOR).should be_include("/sbin") + end + end + + it "should default to pson for the preferred serialization format" do + Puppet.settings.value(:preferred_serialization_format).should == "pson" + end + + describe "when enabling storeconfigs" do + before do + Puppet::Resource::Catalog.indirection.stubs(:cache_class=) + Puppet::Node::Facts.indirection.stubs(:cache_class=) + Puppet::Node.indirection.stubs(:cache_class=) + + Puppet.features.stubs(:rails?).returns true + end + + it "should set the Catalog cache class to :active_record" do + Puppet::Resource::Catalog.indirection.expects(:cache_class=).with(:active_record) + Puppet.settings[:storeconfigs] = true + end + + it "should not set the Catalog cache class to :active_record if asynchronous storeconfigs is enabled" do + Puppet::Resource::Catalog.indirection.expects(:cache_class=).with(:active_record).never + Puppet.settings.expects(:value).with(:async_storeconfigs).returns true + Puppet.settings[:storeconfigs] = true + end + + it "should set the Facts cache class to :active_record" do + Puppet::Node::Facts.indirection.expects(:cache_class=).with(:active_record) + Puppet.settings[:storeconfigs] = true + end + + it "should set the Node cache class to :active_record" do + Puppet::Node.indirection.expects(:cache_class=).with(:active_record) + Puppet.settings[:storeconfigs] = true + end + + it "should fail if rails is not available" do + Puppet.features.stubs(:rails?).returns false + lambda { Puppet.settings[:storeconfigs] = true }.should raise_error + end + end + + describe "when enabling asynchronous storeconfigs" do + before do + Puppet::Resource::Catalog.indirection.stubs(:cache_class=) + Puppet::Node::Facts.indirection.stubs(:cache_class=) + Puppet::Node.indirection.stubs(:cache_class=) + Puppet.features.stubs(:rails?).returns true + end + + it "should set storeconfigs to true" do + Puppet.settings[:async_storeconfigs] = true + Puppet.settings[:storeconfigs].should be_true + end + + it "should set the Catalog cache class to :queue" do + Puppet::Resource::Catalog.indirection.expects(:cache_class=).with(:queue) + Puppet.settings[:async_storeconfigs] = true + end + + it "should set the Facts cache class to :active_record" do + Puppet::Node::Facts.indirection.expects(:cache_class=).with(:active_record) + Puppet.settings[:storeconfigs] = true + end + + it "should set the Node cache class to :active_record" do + Puppet::Node.indirection.expects(:cache_class=).with(:active_record) + Puppet.settings[:storeconfigs] = true + end + end + + describe "when enabling thin storeconfigs" do + before do + Puppet::Resource::Catalog.indirection.stubs(:cache_class=) + Puppet::Node::Facts.indirection.stubs(:cache_class=) + Puppet::Node.indirection.stubs(:cache_class=) + Puppet.features.stubs(:rails?).returns true + end + + it "should set storeconfigs to true" do + Puppet.settings[:thin_storeconfigs] = true + Puppet.settings[:storeconfigs].should be_true + end + end + + it "should have a setting for determining the configuration version and should default to an empty string" do + Puppet.settings[:config_version].should == "" + end + + describe "when enabling reports" do + it "should use the default server value when report server is unspecified" do + Puppet.settings[:server] = "server" + Puppet.settings[:report_server].should == "server" + end + + it "should use the default masterport value when report port is unspecified" do + Puppet.settings[:masterport] = "1234" + Puppet.settings[:report_port].should == "1234" + end + + it "should set report_server when reportserver is set" do + Puppet.settings[:reportserver] = "reportserver" + Puppet.settings[:report_server].should == "reportserver" + end + + it "should use report_port when set" do + Puppet.settings[:masterport] = "1234" + Puppet.settings[:report_port] = "5678" + Puppet.settings[:report_port].should == "5678" + end + + it "should prefer report_server over reportserver" do + Puppet.settings[:reportserver] = "reportserver" + Puppet.settings[:report_server] = "report_server" + Puppet.settings[:report_server].should == "report_server" + end + end + + it "should have a :caname setting that defaults to the cert name" do + Puppet.settings[:certname] = "foo" + Puppet.settings[:ca_name].should == "Puppet CA: foo" + end + + it "should have a 'prerun_command' that defaults to the empty string" do + Puppet.settings[:prerun_command].should == "" + end + + it "should have a 'postrun_command' that defaults to the empty string" do + Puppet.settings[:postrun_command].should == "" + end + + it "should have a 'certificate_revocation' setting that defaults to true" do + Puppet.settings[:certificate_revocation].should be_true + end + + it "should have an http_compression setting that defaults to false" do + Puppet.settings[:http_compression].should be_false + end + + describe "reportdir" do + subject { Puppet.settings[:reportdir] } + it { should == "#{Puppet[:vardir]}/reports" } + end + + describe "reporturl" do + subject { Puppet.settings[:reporturl] } + it { should == "http://localhost:3000/reports" } + end +end --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/spec/integration/network/client_spec.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/spec/integration/network/client_spec.rb @@ -0,0 +1,18 @@ +#!/usr/bin/env rspec +require 'spec_helper' + +require 'puppet/network/client' + +describe Puppet::Network::Client do + %w{ca file report runner status}.each do |name| + it "should have a #{name} client" do + Puppet::Network::Client.client(name).should be_instance_of(Class) + end + + [:name, :handler, :drivername].each do |data| + it "should have a #{data} value for the #{name} client" do + Puppet::Network::Client.client(name).send(data).should_not be_nil + end + end + end +end --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/spec/integration/network/handler_spec.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/spec/integration/network/handler_spec.rb @@ -0,0 +1,24 @@ +#!/usr/bin/env rspec +require 'spec_helper' + +require 'puppet/network/client' + +describe Puppet::Network::Handler do + %w{ca filebucket fileserver master report runner status}.each do |name| + it "should have a #{name} client" do + Puppet::Network::Handler.handler(name).should be_instance_of(Class) + end + + it "should have a name" do + Puppet::Network::Handler.handler(name).name.to_s.downcase.should == name.to_s.downcase + end + + it "should have an interface" do + Puppet::Network::Handler.handler(name).interface.should_not be_nil + end + + it "should have a prefix for the interface" do + Puppet::Network::Handler.handler(name).interface.prefix.should_not be_nil + end + end +end --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/lib/puppet/defaults.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/lib/puppet/defaults.rb @@ -0,0 +1,837 @@ +# The majority of the system configuration parameters are set in this file. +module Puppet + setdefaults(:main, + :confdir => [Puppet.run_mode.conf_dir, "The main Puppet configuration directory. The default for this parameter is calculated based on the user. If the process + is running as root or the user that Puppet is supposed to run as, it defaults to a system directory, but if it's running as any other user, + it defaults to being in the user's home directory."], + :vardir => [Puppet.run_mode.var_dir, "Where Puppet stores dynamic and growing data. The default for this parameter is calculated specially, like `confdir`_."], + :name => [Puppet.application_name.to_s, "The name of the application, if we are running as one. The + default is essentially $0 without the path or `.rb`."], + :run_mode => [Puppet.run_mode.name.to_s, "The effective 'run mode' of the application: master, agent, or user."] + ) + + setdefaults(:main, :logdir => Puppet.run_mode.logopts) + + setdefaults(:main, + :trace => [false, "Whether to print stack traces on some errors"], + :autoflush => { + :default => false, + :desc => "Whether log files should always flush to disk.", + :hook => proc { |value| Log.autoflush = value } + }, + :syslogfacility => ["daemon", "What syslog facility to use when logging to + syslog. Syslog has a fixed list of valid facilities, and you must + choose one of those; you cannot just make one up."], + :statedir => { :default => "$vardir/state", + :mode => 01755, + :desc => "The directory where Puppet state is stored. Generally, + this directory can be removed without causing harm (although it + might result in spurious service restarts)." + }, + :rundir => { + :default => Puppet.run_mode.run_dir, + :mode => 01777, + :desc => "Where Puppet PID files are kept." + }, + :genconfig => [false, + "Whether to just print a configuration to stdout and exit. Only makes + sense when used interactively. Takes into account arguments specified + on the CLI."], + :genmanifest => [false, + "Whether to just print a manifest to stdout and exit. Only makes + sense when used interactively. Takes into account arguments specified + on the CLI."], + :configprint => ["", + "Print the value of a specific configuration parameter. If a + parameter is provided for this, then the value is printed and puppet + exits. Comma-separate multiple values. For a list of all values, + specify 'all'. This feature is only available in Puppet versions + higher than 0.18.4."], + :color => ["ansi", "Whether to use colors when logging to the console. + Valid values are `ansi` (equivalent to `true`), `html` (mostly + used during testing with TextMate), and `false`, which produces + no color."], + :mkusers => [false, + "Whether to create the necessary user and group that puppet agent will + run as."], + :manage_internal_file_permissions => [true, + "Whether Puppet should manage the owner, group, and mode of files + it uses internally" + ], + :onetime => {:default => false, + :desc => "Run the configuration once, rather than as a long-running + daemon. This is useful for interactively running puppetd.", + :short => 'o' + }, + :path => {:default => "none", + :desc => "The shell search path. Defaults to whatever is inherited + from the parent process.", + :call_on_define => true, # Call our hook with the default value, so we always get the libdir set. + :hook => proc do |value| + ENV["PATH"] = "" if ENV["PATH"].nil? + ENV["PATH"] = value unless value == "none" + paths = ENV["PATH"].split(File::PATH_SEPARATOR) + %w{/usr/sbin /sbin}.each do |path| + ENV["PATH"] += File::PATH_SEPARATOR + path unless paths.include?(path) + end + value + end + }, + :libdir => {:default => "$vardir/lib", + :desc => "An extra search path for Puppet. This is only useful + for those files that Puppet will load on demand, and is only + guaranteed to work for those cases. In fact, the autoload + mechanism is responsible for making sure this directory + is in Ruby's search path", + :call_on_define => true, # Call our hook with the default value, so we always get the libdir set. + :hook => proc do |value| + $LOAD_PATH.delete(@oldlibdir) if defined?(@oldlibdir) and $LOAD_PATH.include?(@oldlibdir) + @oldlibdir = value + $LOAD_PATH << value + end + }, + :ignoreimport => [false, "A parameter that can be used in commit + hooks, since it enables you to parse-check a single file rather + than requiring that all files exist."], + :authconfig => [ "$confdir/namespaceauth.conf", + "The configuration file that defines the rights to the different + namespaces and methods. This can be used as a coarse-grained + authorization system for both `puppet agent` and `puppet master`." + ], + :environment => {:default => "production", :desc => "The environment Puppet is running in. For clients + (e.g., `puppet agent`) this determines the environment itself, which + is used to find modules and much more. For servers (i.e., `puppet master`) this provides the default environment for nodes + we know nothing about." + }, + :diff_args => ["-u", "Which arguments to pass to the diff command when printing differences between files."], + :diff => ["diff", "Which diff command to use when printing differences between files."], + :show_diff => [false, "Whether to print a contextual diff when files are being replaced. The diff + is printed on stdout, so this option is meaningless unless you are running Puppet interactively. + This feature currently requires the `diff/lcs` Ruby library."], + :daemonize => { :default => true, + :desc => "Send the process into the background. This is the default.", + :short => "D" + }, + :maximum_uid => [4294967290, "The maximum allowed UID. Some platforms use negative UIDs + but then ship with tools that do not know how to handle signed ints, so the UIDs show up as + huge numbers that can then not be fed back into the system. This is a hackish way to fail in a + slightly more useful way when that happens."], + :route_file => ["$confdir/routes.yaml", "The YAML file containing indirector route configuration."], + :node_terminus => ["plain", "Where to find information about nodes."], + :catalog_terminus => ["compiler", "Where to get node catalogs. This is useful to change if, for instance, + you'd like to pre-compile catalogs and store them in memcached or some other easily-accessed store."], + :facts_terminus => { + :default => Puppet.application_name.to_s == "master" ? 'yaml' : 'facter', + :desc => "The node facts terminus.", + :hook => proc do |value| + require 'puppet/node/facts' + if value.to_s == "rest" + Puppet::Node::Facts.indirection.cache_class = :yaml + end + end + }, + :inventory_terminus => [ "$facts_terminus", "Should usually be the same as the facts terminus" ], + :httplog => { :default => "$logdir/http.log", + :owner => "root", + :mode => 0640, + :desc => "Where the puppet agent web server logs." + }, + :http_proxy_host => ["none", + "The HTTP proxy host to use for outgoing connections. Note: You + may need to use a FQDN for the server hostname when using a proxy."], + :http_proxy_port => [3128, "The HTTP proxy port to use for outgoing connections"], + :filetimeout => [ 15, + "The minimum time to wait (in seconds) between checking for updates in + configuration files. This timeout determines how quickly Puppet checks whether + a file (such as manifests or templates) has changed on disk." + ], + :queue_type => ["stomp", "Which type of queue to use for asynchronous processing."], + :queue_type => ["stomp", "Which type of queue to use for asynchronous processing."], + :queue_source => ["stomp://localhost:61613/", "Which type of queue to use for asynchronous processing. If your stomp server requires + authentication, you can include it in the URI as long as your stomp client library is at least 1.1.1"], + :async_storeconfigs => {:default => false, :desc => "Whether to use a queueing system to provide asynchronous database integration. + Requires that `puppetqd` be running and that 'PSON' support for ruby be installed.", + :hook => proc do |value| + if value + # This reconfigures the terminii for Node, Facts, and Catalog + Puppet.settings[:storeconfigs] = true + + # But then we modify the configuration + Puppet::Resource::Catalog.indirection.cache_class = :queue + else + raise "Cannot disable asynchronous storeconfigs in a running process" + end + end + }, + :thin_storeconfigs => {:default => false, :desc => + "Boolean; wether storeconfigs store in the database only the facts and exported resources. + If true, then storeconfigs performance will be higher and still allow exported/collected + resources, but other usage external to Puppet might not work", + :hook => proc do |value| + Puppet.settings[:storeconfigs] = true if value + end + }, + :config_version => ["", "How to determine the configuration version. By default, it will be the + time that the configuration is parsed, but you can provide a shell script to override how the + version is determined. The output of this script will be added to every log message in the + reports, allowing you to correlate changes on your hosts to the source version on the server."], + :zlib => [true, + "Boolean; whether to use the zlib library", + ], + :prerun_command => ["", "A command to run before every agent run. If this command returns a non-zero + return code, the entire Puppet run will fail."], + :postrun_command => ["", "A command to run after every agent run. If this command returns a non-zero + return code, the entire Puppet run will be considered to have failed, even though it might have + performed work during the normal run."], + :freeze_main => [false, "Freezes the 'main' class, disallowing any code to be added to it. This + essentially means that you can't have any code outside of a node, class, or definition other + than in the site manifest."] + ) + + hostname = Facter["hostname"].value + domain = Facter["domain"].value + if domain and domain != "" + fqdn = [hostname, domain].join(".") + else + fqdn = hostname + end + + + Puppet.setdefaults( + :main, + + # We have to downcase the fqdn, because the current ssl stuff (as oppsed to in master) doesn't have good facilities for + # manipulating naming. + :certname => {:default => fqdn.downcase, :desc => "The name to use when handling certificates. Defaults + to the fully qualified domain name.", + :call_on_define => true, # Call our hook with the default value, so we're always downcased + :hook => proc { |value| raise(ArgumentError, "Certificate names must be lower case; see #1168") unless value == value.downcase }}, + :certdnsnames => ['', "The DNS names on the Server certificate as a colon-separated list. + If it's anything other than an empty string, it will be used as an alias in the created + certificate. By default, only the server gets an alias set up, and only for 'puppet'."], + :certdir => { + :default => "$ssldir/certs", + :owner => "service", + :desc => "The certificate directory." + }, + :ssldir => { + :default => "$confdir/ssl", + :mode => 0771, + :owner => "service", + :desc => "Where SSL certificates are kept." + }, + :publickeydir => { + :default => "$ssldir/public_keys", + :owner => "service", + :desc => "The public key directory." + }, + :requestdir => { + :default => "$ssldir/certificate_requests", + :owner => "service", + :desc => "Where host certificate requests are stored." + }, + :privatekeydir => { :default => "$ssldir/private_keys", + :mode => 0750, + :owner => "service", + :desc => "The private key directory." + }, + :privatedir => { :default => "$ssldir/private", + :mode => 0750, + :owner => "service", + :desc => "Where the client stores private certificate information." + }, + :passfile => { :default => "$privatedir/password", + :mode => 0640, + :owner => "service", + :desc => "Where puppet agent stores the password for its private key. + Generally unused." + }, + :hostcsr => { :default => "$ssldir/csr_$certname.pem", + :mode => 0644, + :owner => "service", + :desc => "Where individual hosts store and look for their certificate requests." + }, + :hostcert => { :default => "$certdir/$certname.pem", + :mode => 0644, + :owner => "service", + :desc => "Where individual hosts store and look for their certificates." + }, + :hostprivkey => { :default => "$privatekeydir/$certname.pem", + :mode => 0600, + :owner => "service", + :desc => "Where individual hosts store and look for their private key." + }, + :hostpubkey => { :default => "$publickeydir/$certname.pem", + :mode => 0644, + :owner => "service", + :desc => "Where individual hosts store and look for their public key." + }, + :localcacert => { :default => "$certdir/ca.pem", + :mode => 0644, + :owner => "service", + :desc => "Where each client stores the CA certificate." + }, + :hostcrl => { :default => "$ssldir/crl.pem", + :mode => 0644, + :owner => "service", + :desc => "Where the host's certificate revocation list can be found. + This is distinct from the certificate authority's CRL." + }, + :certificate_revocation => [true, "Whether certificate revocation should be supported by downloading a Certificate Revocation List (CRL) + to all clients. If enabled, CA chaining will almost definitely not work."] + ) + + setdefaults( + :ca, + :ca_name => ["Puppet CA: $certname", "The name to use the Certificate Authority certificate."], + :cadir => { :default => "$ssldir/ca", + :owner => "service", + :group => "service", + :mode => 0770, + :desc => "The root directory for the certificate authority." + }, + :cacert => { :default => "$cadir/ca_crt.pem", + :owner => "service", + :group => "service", + :mode => 0660, + :desc => "The CA certificate." + }, + :cakey => { :default => "$cadir/ca_key.pem", + :owner => "service", + :group => "service", + :mode => 0660, + :desc => "The CA private key." + }, + :capub => { :default => "$cadir/ca_pub.pem", + :owner => "service", + :group => "service", + :desc => "The CA public key." + }, + :cacrl => { :default => "$cadir/ca_crl.pem", + :owner => "service", + :group => "service", + :mode => 0664, + + :desc => "The certificate revocation list (CRL) for the CA. Will be used if present but otherwise ignored.", + :hook => proc do |value| + if value == 'false' + Puppet.warning "Setting the :cacrl to 'false' is deprecated; Puppet will just ignore the crl if yours is missing" + end + end + }, + :caprivatedir => { :default => "$cadir/private", + :owner => "service", + :group => "service", + :mode => 0770, + :desc => "Where the CA stores private certificate information." + }, + :csrdir => { :default => "$cadir/requests", + :owner => "service", + :group => "service", + :desc => "Where the CA stores certificate requests" + }, + :signeddir => { :default => "$cadir/signed", + :owner => "service", + :group => "service", + :mode => 0770, + :desc => "Where the CA stores signed certificates." + }, + :capass => { :default => "$caprivatedir/ca.pass", + :owner => "service", + :group => "service", + :mode => 0660, + :desc => "Where the CA stores the password for the private key" + }, + :serial => { :default => "$cadir/serial", + :owner => "service", + :group => "service", + :mode => 0644, + :desc => "Where the serial number for certificates is stored." + }, + :autosign => { :default => "$confdir/autosign.conf", + :mode => 0644, + :desc => "Whether to enable autosign. Valid values are true (which + autosigns any key request, and is a very bad idea), false (which + never autosigns any key request), and the path to a file, which + uses that configuration file to determine which keys to sign."}, + :allow_duplicate_certs => [false, "Whether to allow a new certificate + request to overwrite an existing certificate."], + :ca_days => ["", "How long a certificate should be valid. + This parameter is deprecated, use ca_ttl instead"], + :ca_ttl => ["5y", "The default TTL for new certificates; valid values + must be an integer, optionally followed by one of the units + 'y' (years of 365 days), 'd' (days), 'h' (hours), or + 's' (seconds). The unit defaults to seconds. If this parameter + is set, ca_days is ignored. Examples are '3600' (one hour) + and '1825d', which is the same as '5y' (5 years) "], + :ca_md => ["md5", "The type of hash used in certificates."], + :req_bits => [2048, "The bit length of the certificates."], + :keylength => [1024, "The bit length of keys."], + :cert_inventory => { + :default => "$cadir/inventory.txt", + :mode => 0644, + :owner => "service", + :group => "service", + :desc => "A Complete listing of all certificates" + } + ) + + # Define the config default. + + setdefaults( + Puppet.settings[:name], + :config => ["$confdir/puppet.conf", + "The configuration file for #{Puppet[:name]}."], + :pidfile => ["$rundir/$name.pid", "The pid file"], + :bindaddress => ["", "The address a listening server should bind to. Mongrel servers + default to 127.0.0.1 and WEBrick defaults to 0.0.0.0."], + :servertype => {:default => "webrick", :desc => "The type of server to use. Currently supported + options are webrick and mongrel. If you use mongrel, you will need + a proxy in front of the process or processes, since Mongrel cannot + speak SSL.", + + :call_on_define => true, # Call our hook with the default value, so we always get the correct bind address set. + :hook => proc { |value| value == "webrick" ? Puppet.settings[:bindaddress] = "0.0.0.0" : Puppet.settings[:bindaddress] = "127.0.0.1" if Puppet.settings[:bindaddress] == "" } + } + ) + + setdefaults(:master, + :user => ["puppet", "The user puppet master should run as."], + :group => ["puppet", "The group puppet master should run as."], + :manifestdir => ["$confdir/manifests", "Where puppet master looks for its manifests."], + :manifest => ["$manifestdir/site.pp", "The entry-point manifest for puppet master."], + :code => ["", "Code to parse directly. This is essentially only used + by `puppet`, and should only be set if you're writing your own Puppet + executable"], + :masterlog => { :default => "$logdir/puppetmaster.log", + :owner => "service", + :group => "service", + :mode => 0660, + :desc => "Where puppet master logs. This is generally not used, + since syslog is the default log destination." + }, + :masterhttplog => { :default => "$logdir/masterhttp.log", + :owner => "service", + :group => "service", + :mode => 0660, + :create => true, + :desc => "Where the puppet master web server logs." + }, + :masterport => [8140, "Which port puppet master listens on."], + :node_name => ["cert", "How the puppet master determines the client's identity + and sets the 'hostname', 'fqdn' and 'domain' facts for use in the manifest, + in particular for determining which 'node' statement applies to the client. + Possible values are 'cert' (use the subject's CN in the client's + certificate) and 'facter' (use the hostname that the client + reported in its facts)"], + :bucketdir => { + :default => "$vardir/bucket", + :mode => 0750, + :owner => "service", + :group => "service", + :desc => "Where FileBucket files are stored." + }, + :rest_authconfig => [ "$confdir/auth.conf", + "The configuration file that defines the rights to the different + rest indirections. This can be used as a fine-grained + authorization system for `puppet master`." + ], + :ca => [true, "Wether the master should function as a certificate authority."], + :modulepath => {:default => "$confdir/modules:/usr/share/puppet/modules", + :desc => "The search path for modules as a colon-separated list of + directories.", :type => :setting }, # We don't want this to be considered a file, since it's multiple files. + :ssl_client_header => ["HTTP_X_CLIENT_DN", "The header containing an authenticated + client's SSL DN. Only used with Mongrel. This header must be set by the proxy + to the authenticated client's SSL DN (e.g., `/CN=puppet.puppetlabs.com`). + See http://projects.puppetlabs.com/projects/puppet/wiki/Using_Mongrel for more information."], + :ssl_client_verify_header => ["HTTP_X_CLIENT_VERIFY", "The header containing the status + message of the client verification. Only used with Mongrel. This header must be set by the proxy + to 'SUCCESS' if the client successfully authenticated, and anything else otherwise. + See http://projects.puppetlabs.com/projects/puppet/wiki/Using_Mongrel for more information."], + # To make sure this directory is created before we try to use it on the server, we need + # it to be in the server section (#1138). + :yamldir => {:default => "$vardir/yaml", :owner => "service", :group => "service", :mode => "750", + :desc => "The directory in which YAML data is stored, usually in a subdirectory."}, + :server_datadir => {:default => "$vardir/server_data", :owner => "service", :group => "service", :mode => "750", + :desc => "The directory in which serialized data is stored, usually in a subdirectory."}, + :reports => ["store", + "The list of reports to generate. All reports are looked for + in `puppet/reports/name.rb`, and multiple report names should be + comma-separated (whitespace is okay)." + ], + :reportdir => {:default => "$vardir/reports", + :mode => 0750, + :owner => "service", + :group => "service", + :desc => "The directory in which to store reports + received from the client. Each client gets a separate + subdirectory."}, + :reporturl => ["http://localhost:3000/reports", + "The URL used by the http reports processor to send reports"], + :fileserverconfig => ["$confdir/fileserver.conf", "Where the fileserver configuration is stored."], + :strict_hostname_checking => [false, "Whether to only search for the complete + hostname as it is in the certificate when searching for node information + in the catalogs."] + ) + + setdefaults(:metrics, + :rrddir => {:default => "$vardir/rrd", + :mode => 0750, + :owner => "service", + :group => "service", + :desc => "The directory where RRD database files are stored. + Directories for each reporting host will be created under + this directory." + }, + :rrdinterval => ["$runinterval", "How often RRD should expect data. + This should match how often the hosts report back to the server."] + ) + + setdefaults(:device, + :devicedir => {:default => "$vardir/devices", :mode => "750", :desc => "The root directory of devices' $vardir"}, + :deviceconfig => ["$confdir/device.conf","Path to the device config file for puppet device"] + ) + + setdefaults(:agent, + :localconfig => { :default => "$statedir/localconfig", + :owner => "root", + :mode => 0660, + :desc => "Where puppet agent caches the local configuration. An + extension indicating the cache format is added automatically."}, + :statefile => { :default => "$statedir/state.yaml", + :mode => 0660, + :desc => "Where puppet agent and puppet master store state associated + with the running configuration. In the case of puppet master, + this file reflects the state discovered through interacting + with clients." + }, + :clientyamldir => {:default => "$vardir/client_yaml", :mode => "750", :desc => "The directory in which client-side YAML data is stored."}, + :client_datadir => {:default => "$vardir/client_data", :mode => "750", :desc => "The directory in which serialized data is stored on the client."}, + :classfile => { :default => "$statedir/classes.txt", + :owner => "root", + :mode => 0644, + :desc => "The file in which puppet agent stores a list of the classes + associated with the retrieved configuration. Can be loaded in + the separate `puppet` executable using the `--loadclasses` + option."}, + :puppetdlog => { :default => "$logdir/puppetd.log", + :owner => "root", + :mode => 0640, + :desc => "The log file for puppet agent. This is generally not used." + }, + :server => ["puppet", "The server to which server puppet agent should connect"], + :ignoreschedules => [false, + "Boolean; whether puppet agent should ignore schedules. This is useful + for initial puppet agent runs."], + :puppetport => [8139, "Which port puppet agent listens on."], + :noop => [false, "Whether puppet agent should be run in noop mode."], + :runinterval => [1800, # 30 minutes + "How often puppet agent applies the client configuration; in seconds."], + :listen => [false, "Whether puppet agent should listen for + connections. If this is true, then puppet agent will accept incoming + REST API requests, subject to the default ACLs and the ACLs set in + the `rest_authconfig` file. Puppet agent can respond usefully to + requests on the `run`, `facts`, `certificate`, and `resource` endpoints."], + :ca_server => ["$server", "The server to use for certificate + authority requests. It's a separate server because it cannot + and does not need to horizontally scale."], + :ca_port => ["$masterport", "The port to use for the certificate authority."], + :catalog_format => { + :default => "", + :desc => "(Deprecated for 'preferred_serialization_format') What format to + use to dump the catalog. Only supports 'marshal' and 'yaml'. Only + matters on the client, since it asks the server for a specific format.", + :hook => proc { |value| + if value + Puppet.warning "Setting 'catalog_format' is deprecated; use 'preferred_serialization_format' instead." + Puppet.settings[:preferred_serialization_format] = value + end + } + }, + :preferred_serialization_format => ["pson", "The preferred means of serializing + ruby instances for passing over the wire. This won't guarantee that all + instances will be serialized using this method, since not all classes + can be guaranteed to support this format, but it will be used for all + classes that support it."], + :puppetdlockfile => [ "$statedir/puppetdlock", "A lock file to temporarily stop puppet agent from doing anything."], + :usecacheonfailure => [true, + "Whether to use the cached configuration when the remote + configuration will not compile. This option is useful for testing + new configurations, where you want to fix the broken configuration + rather than reverting to a known-good one." + ], + :use_cached_catalog => [false, + "Whether to only use the cached catalog rather than compiling a new catalog + on every run. Puppet can be run with this enabled by default and then selectively + disabled when a recompile is desired."], + :ignorecache => [false, + "Ignore cache and always recompile the configuration. This is + useful for testing new configurations, where the local cache may in + fact be stale even if the timestamps are up to date - if the facts + change or if the server changes." + ], + :downcasefacts => [false, "Whether facts should be made all lowercase when sent to the server."], + :dynamicfacts => ["memorysize,memoryfree,swapsize,swapfree", + "Facts that are dynamic; these facts will be ignored when deciding whether + changed facts should result in a recompile. Multiple facts should be + comma-separated."], + :splaylimit => ["$runinterval", + "The maximum time to delay before runs. Defaults to being the same as the + run interval."], + :splay => [false, + "Whether to sleep for a pseudo-random (but consistent) amount of time before + a run."], + :clientbucketdir => { + :default => "$vardir/clientbucket", + :mode => 0750, + :desc => "Where FileBucket files are stored locally." + }, + :configtimeout => [120, + "How long the client should wait for the configuration to be retrieved + before considering it a failure. This can help reduce flapping if too + many clients contact the server at one time." + ], + :reportserver => { + :default => "$server", + :call_on_define => false, + :desc => "(Deprecated for 'report_server') The server to which to send transaction reports.", + :hook => proc do |value| + Puppet.settings[:report_server] = value if value + end + }, + :report_server => ["$server", + "The server to send transaction reports to." + ], + :report_port => ["$masterport", + "The port to communicate with the report_server." + ], + :inventory_server => ["$server", + "The server to send facts to." + ], + :inventory_port => ["$masterport", + "The port to communicate with the inventory_server." + ], + :report => [true, + "Whether to send reports after every transaction." + ], + :lastrunfile => { :default => "$statedir/last_run_summary.yaml", + :mode => 0660, + :desc => "Where puppet agent stores the last run report summary in yaml format." + }, + :lastrunreport => { :default => "$statedir/last_run_report.yaml", + :mode => 0660, + :desc => "Where puppet agent stores the last run report in yaml format." + }, + :graph => [false, "Whether to create dot graph files for the different + configuration graphs. These dot files can be interpreted by tools + like OmniGraffle or dot (which is part of ImageMagick)."], + :graphdir => ["$statedir/graphs", "Where to store dot-outputted graphs."], + :http_compression => [false, "Allow http compression in REST communication with the master. + This setting might improve performance for agent -> master communications over slow WANs. + Your puppet master needs to support compression (usually by activating some settings in a reverse-proxy + in front of the puppet master, which rules out webrick). + It is harmless to activate this settings if your master doesn't support + compression, but if it supports it, this setting might reduce performance on high-speed LANs."] + ) + + setdefaults(:inspect, + :archive_files => [false, "During an inspect run, whether to archive files whose contents are audited to a file bucket."], + :archive_file_server => ["$server", "During an inspect run, the file bucket server to archive files to if archive_files is set."] + ) + + # Plugin information. + + setdefaults( + :main, + :plugindest => ["$libdir", + "Where Puppet should store plugins that it pulls down from the central + server."], + :pluginsource => ["puppet://$server/plugins", + "From where to retrieve plugins. The standard Puppet `file` type + is used for retrieval, so anything that is a valid file source can + be used here."], + :pluginsync => [false, "Whether plugins should be synced with the central server."], + + :pluginsignore => [".svn CVS .git", "What files to ignore when pulling down plugins."] + ) + + # Central fact information. + + setdefaults( + :main, + :factpath => {:default => "$vardir/lib/facter:$vardir/facts", + :desc => "Where Puppet should look for facts. Multiple directories should + be colon-separated, like normal PATH variables.", + + :call_on_define => true, # Call our hook with the default value, so we always get the value added to facter. + :type => :setting, # Don't consider it a file, because it could be multiple colon-separated files + :hook => proc { |value| Facter.search(value) if Facter.respond_to?(:search) }}, + :factdest => ["$vardir/facts/", + "Where Puppet should store facts that it pulls down from the central + server."], + :factsource => ["puppet://$server/facts/", + "From where to retrieve facts. The standard Puppet `file` type + is used for retrieval, so anything that is a valid file source can + be used here."], + :factsync => [false, "Whether facts should be synced with the central server."], + :factsignore => [".svn CVS", "What files to ignore when pulling down facts."] + ) + + + setdefaults( + :tagmail, + :tagmap => ["$confdir/tagmail.conf", "The mapping between reporting tags and email addresses."], + :sendmail => [which('sendmail') || '', "Where to find the sendmail binary with which to send email."], + + :reportfrom => ["report@" + [Facter["hostname"].value, Facter["domain"].value].join("."), "The 'from' email address for the reports."], + :smtpserver => ["none", "The server through which to send email reports."] + ) + + setdefaults( + :rails, + :dblocation => { :default => "$statedir/clientconfigs.sqlite3", + :mode => 0660, + :owner => "service", + :group => "service", + :desc => "The database cache for client configurations. Used for + querying within the language." + }, + :dbadapter => [ "sqlite3", "The type of database to use." ], + :dbmigrate => [ false, "Whether to automatically migrate the database." ], + :dbname => [ "puppet", "The name of the database to use." ], + :dbserver => [ "localhost", "The database server for caching. Only + used when networked databases are used."], + :dbport => [ "", "The database password for caching. Only + used when networked databases are used."], + :dbuser => [ "puppet", "The database user for caching. Only + used when networked databases are used."], + :dbpassword => [ "puppet", "The database password for caching. Only + used when networked databases are used."], + :dbconnections => [ '', "The number of database connections for networked + databases. Will be ignored unless the value is a positive integer."], + :dbsocket => [ "", "The database socket location. Only used when networked + databases are used. Will be ignored if the value is an empty string."], + :railslog => {:default => "$logdir/rails.log", + :mode => 0600, + :owner => "service", + :group => "service", + :desc => "Where Rails-specific logs are sent" + }, + + :rails_loglevel => ["info", "The log level for Rails connections. The value must be + a valid log level within Rails. Production environments normally use `info` + and other environments normally use `debug`."] + ) + + setdefaults( + :couchdb, + + :couchdb_url => ["http://127.0.0.1:5984/puppet", "The url where the puppet couchdb database will be created"] + ) + + setdefaults( + :transaction, + :tags => ["", "Tags to use to find resources. If this is set, then + only resources tagged with the specified tags will be applied. + Values must be comma-separated."], + :evaltrace => [false, "Whether each resource should log when it is + being evaluated. This allows you to interactively see exactly + what is being done."], + :summarize => [false, + + "Whether to print a transaction summary." + ] + ) + + setdefaults( + :main, + :external_nodes => ["none", + + "An external command that can produce node information. The output + must be a YAML dump of a hash, and that hash must have one or both of + `classes` and `parameters`, where `classes` is an array and + `parameters` is a hash. For unknown nodes, the commands should + exit with a non-zero exit code. + + This command makes it straightforward to store your node mapping + information in other data sources like databases."]) + + setdefaults( + :ldap, + :ldapnodes => [false, + "Whether to search for node configurations in LDAP. See + http://projects.puppetlabs.com/projects/puppet/wiki/LDAP_Nodes for more information."], + :ldapssl => [false, + "Whether SSL should be used when searching for nodes. + Defaults to false because SSL usually requires certificates + to be set up on the client side."], + :ldaptls => [false, + "Whether TLS should be used when searching for nodes. + Defaults to false because TLS usually requires certificates + to be set up on the client side."], + :ldapserver => ["ldap", + "The LDAP server. Only used if `ldapnodes` is enabled."], + :ldapport => [389, + "The LDAP port. Only used if `ldapnodes` is enabled."], + + :ldapstring => ["(&(objectclass=puppetClient)(cn=%s))", + "The search string used to find an LDAP node."], + :ldapclassattrs => ["puppetclass", + "The LDAP attributes to use to define Puppet classes. Values + should be comma-separated."], + :ldapstackedattrs => ["puppetvar", + "The LDAP attributes that should be stacked to arrays by adding + the values in all hierarchy elements of the tree. Values + should be comma-separated."], + :ldapattrs => ["all", + "The LDAP attributes to include when querying LDAP for nodes. All + returned attributes are set as variables in the top-level scope. + Multiple values should be comma-separated. The value 'all' returns + all attributes."], + :ldapparentattr => ["parentnode", + "The attribute to use to define the parent node."], + :ldapuser => ["", + "The user to use to connect to LDAP. Must be specified as a + full DN."], + :ldappassword => ["", "The password to use to connect to LDAP."], + :ldapbase => ["", + "The search base for LDAP searches. It's impossible to provide + a meaningful default here, although the LDAP libraries might + have one already set. Generally, it should be the 'ou=Hosts' + branch under your main directory."] + ) + + setdefaults(:master, + :storeconfigs => {:default => false, :desc => "Whether to store each client's configuration. This + requires ActiveRecord from Ruby on Rails.", + :call_on_define => true, # Call our hook with the default value, so we always get the libdir set. + :hook => proc do |value| + require 'puppet/node' + require 'puppet/node/facts' + if value + require 'puppet/rails' + raise "StoreConfigs not supported without ActiveRecord 2.1 or higher" unless Puppet.features.rails? + Puppet::Resource::Catalog.indirection.cache_class = :active_record unless Puppet.settings[:async_storeconfigs] + Puppet::Node::Facts.indirection.cache_class = :active_record + Puppet::Node.indirection.cache_class = :active_record + end + end + } + ) + + # This doesn't actually work right now. + + setdefaults( + :parser, + + :lexical => [false, "Whether to use lexical scoping (vs. dynamic)."], + :templatedir => ["$vardir/templates", + "Where Puppet looks for template files. Can be a list of colon-seperated + directories." + ] + ) + setdefaults( + :puppetdoc, + :document_all => [false, "Document all resources"] + ) +end --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/lib/puppet/sslcertificates.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/lib/puppet/sslcertificates.rb @@ -0,0 +1,146 @@ +# The library for manipulating SSL certs. + +require 'puppet' + +raise Puppet::Error, "You must have the Ruby openssl library installed" unless Puppet.features.openssl? + +module Puppet::SSLCertificates + #def self.mkcert(type, name, dnsnames, ttl, issuercert, issuername, serial, publickey) + def self.mkcert(hash) + [:type, :name, :ttl, :issuer, :serial, :publickey].each { |param| + raise ArgumentError, "mkcert called without #{param}" unless hash.include?(param) + } + + cert = OpenSSL::X509::Certificate.new + # Make the certificate valid as of yesterday, because + # so many people's clocks are out of sync. + from = Time.now - (60*60*24) + + cert.subject = hash[:name] + if hash[:issuer] + cert.issuer = hash[:issuer].subject + else + # we're a self-signed cert + cert.issuer = hash[:name] + end + cert.not_before = from + cert.not_after = from + hash[:ttl] + cert.version = 2 # X509v3 + + cert.public_key = hash[:publickey] + cert.serial = hash[:serial] + + basic_constraint = nil + key_usage = nil + ext_key_usage = nil + subject_alt_name = [] + + ef = OpenSSL::X509::ExtensionFactory.new + + ef.subject_certificate = cert + + if hash[:issuer] + ef.issuer_certificate = hash[:issuer] + else + ef.issuer_certificate = cert + end + + ex = [] + case hash[:type] + when :ca + basic_constraint = "CA:TRUE" + key_usage = %w{cRLSign keyCertSign} + when :terminalsubca + basic_constraint = "CA:TRUE,pathlen:0" + key_usage = %w{cRLSign keyCertSign} + when :server + basic_constraint = "CA:FALSE" + dnsnames = Puppet[:certdnsnames] + name = hash[:name].to_s.sub(%r{/CN=},'') + if dnsnames != "" + dnsnames.split(':').each { |d| subject_alt_name << 'DNS:' + d } + subject_alt_name << 'DNS:' + name # Add the fqdn as an alias + elsif name == Facter.value(:fqdn) # we're a CA server, and thus probably the server + subject_alt_name << 'DNS:' + "puppet" # Add 'puppet' as an alias + subject_alt_name << 'DNS:' + name # Add the fqdn as an alias + subject_alt_name << 'DNS:' + name.sub(/^[^.]+./, "puppet.") # add puppet.domain as an alias + end + key_usage = %w{digitalSignature keyEncipherment} + ext_key_usage = %w{serverAuth clientAuth emailProtection} + when :ocsp + basic_constraint = "CA:FALSE" + key_usage = %w{nonRepudiation digitalSignature} + ext_key_usage = %w{serverAuth OCSPSigning} + when :client + basic_constraint = "CA:FALSE" + key_usage = %w{nonRepudiation digitalSignature keyEncipherment} + ext_key_usage = %w{clientAuth emailProtection} + ex << ef.create_extension("nsCertType", "client,email") + else + raise Puppet::Error, "unknown cert type '#{hash[:type]}'" + end + + + ex << ef.create_extension( + "nsComment", + + "Puppet Ruby/OpenSSL Generated Certificate") + ex << ef.create_extension("basicConstraints", basic_constraint, true) + ex << ef.create_extension("subjectKeyIdentifier", "hash") + + ex << ef.create_extension("keyUsage", key_usage.join(",")) if key_usage + ex << ef.create_extension("extendedKeyUsage", ext_key_usage.join(",")) if ext_key_usage + ex << ef.create_extension("subjectAltName", subject_alt_name.join(",")) if ! subject_alt_name.empty? + + #if @ca_config[:cdp_location] then + # ex << ef.create_extension("crlDistributionPoints", + # @ca_config[:cdp_location]) + #end + + #if @ca_config[:ocsp_location] then + # ex << ef.create_extension("authorityInfoAccess", + # "OCSP;" << @ca_config[:ocsp_location]) + #end + cert.extensions = ex + + # for some reason this _must_ be the last extension added + ex << ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always") if hash[:type] == :ca + + cert + end + + def self.mkhash(dir, cert, certfile) + # Make sure the hash is zero-padded to 8 chars + hash = "%08x" % cert.issuer.hash + hashpath = nil + 10.times { |i| + path = File.join(dir, "#{hash}.#{i}") + if FileTest.exists?(path) + if FileTest.symlink?(path) + dest = File.readlink(path) + if dest == certfile + # the correct link already exists + hashpath = path + break + else + next + end + else + next + end + end + + File.symlink(certfile, path) + + hashpath = path + break + } + + + hashpath + end + require 'puppet/sslcertificates/certificate' + require 'puppet/sslcertificates/inventory' + require 'puppet/sslcertificates/ca' +end + --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/lib/puppet/application/kick.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/lib/puppet/application/kick.rb @@ -0,0 +1,337 @@ +require 'puppet/application' + +class Puppet::Application::Kick < Puppet::Application + + should_not_parse_config + + attr_accessor :hosts, :tags, :classes + + option("--all","-a") + option("--foreground","-f") + option("--debug","-d") + option("--ping","-P") + option("--test") + + option("--host HOST") do |arg| + @hosts << arg + end + + option("--tag TAG", "-t") do |arg| + @tags << arg + end + + option("--class CLASS", "-c") do |arg| + @classes << arg + end + + option("--no-fqdn", "-n") do |arg| + options[:fqdn] = false + end + + option("--parallel PARALLEL", "-p") do |arg| + begin + options[:parallel] = Integer(arg) + rescue + $stderr.puts "Could not convert #{arg.inspect} to an integer" + exit(23) + end + end + + def help + <<-HELP + +puppet-kick(8) -- Remotely control puppet agent +======== + +SYNOPSIS +-------- +Trigger a puppet agent run on a set of hosts. + + +USAGE +----- +puppet kick [-a|--all] [-c|--class ] [-d|--debug] [-f|--foreground] + [-h|--help] [--host ] [--no-fqdn] [--ignoreschedules] + [-t|--tag ] [--test] [-p|--ping] [ [...]] + + +DESCRIPTION +----------- +This script can be used to connect to a set of machines running 'puppet +agent' and trigger them to run their configurations. The most common +usage would be to specify a class of hosts and a set of tags, and +'puppet kick' would look up in LDAP all of the hosts matching that +class, then connect to each host and trigger a run of all of the objects +with the specified tags. + +If you are not storing your host configurations in LDAP, you can specify +hosts manually. + +You will most likely have to run 'puppet kick' as root to get access to +the SSL certificates. + +'puppet kick' reads 'puppet master''s configuration file, so that it can +copy things like LDAP settings. + + +USAGE NOTES +----------- +Puppet kick is useless unless puppet agent is listening for incoming +connections and allowing access to the `run` endpoint. This entails +starting the agent with `listen = true` in its puppet.conf file, and +allowing access to the `/run` path in its auth.conf file; see +`http://docs.puppetlabs.com/guides/rest_auth_conf.html` for more +details. + +Additionally, due to a known bug, you must make sure a +namespaceauth.conf file exists in puppet agent's $confdir. This file +will not be consulted, and may be left empty. + +OPTIONS +------- +Note that any configuration parameter that's valid in the configuration +file is also a valid long argument. For example, 'ssldir' is a valid +configuration parameter, so you can specify '--ssldir ' as an +argument. + +See the configuration file documentation at +http://docs.puppetlabs.com/references/latest/configuration.html for +the full list of acceptable parameters. A commented list of all +configuration options can also be generated by running puppet master +with '--genconfig'. + +* --all: + Connect to all available hosts. Requires LDAP support at this point. + +* --class: + Specify a class of machines to which to connect. This only works if + you have LDAP configured, at the moment. + +* --debug: + Enable full debugging. + +* --foreground: + Run each configuration in the foreground; that is, when connecting to + a host, do not return until the host has finished its run. The default + is false. + +* --help: + Print this help message + +* --host: + A specific host to which to connect. This flag can be specified more + than once. + +* --ignoreschedules: + Whether the client should ignore schedules when running its + configuration. This can be used to force the client to perform work it + would not normally perform so soon. The default is false. + +* --parallel: + How parallel to make the connections. Parallelization is provided by + forking for each client to which to connect. The default is 1, meaning + serial execution. + +* --tag: + Specify a tag for selecting the objects to apply. Does not work with + the --test option. + +* --test: + Print the hosts you would connect to but do not actually connect. This + option requires LDAP support at this point. + +* --ping: + Do a ICMP echo against the target host. Skip hosts that don't respond + to ping. + + +EXAMPLE +------- + $ sudo puppet kick -p 10 -t remotefile -t webserver host1 host2 + + +AUTHOR +------ +Luke Kanies + + +COPYRIGHT +--------- +Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License + + HELP + end + + def run_command + @hosts += command_line.args + options[:test] ? test : main + end + + def test + puts "Skipping execution in test mode" + exit(0) + end + + def main + require 'puppet/network/client' + + Puppet.warning "Failed to load ruby LDAP library. LDAP functionality will not be available" unless Puppet.features.ldap? + require 'puppet/util/ldap/connection' + + todo = @hosts.dup + + failures = [] + + # Now do the actual work + go = true + while go + # If we don't have enough children in process and we still have hosts left to + # do, then do the next host. + if @children.length < options[:parallel] and ! todo.empty? + host = todo.shift + pid = fork do + run_for_host(host) + end + @children[pid] = host + else + # Else, see if we can reap a process. + begin + pid = Process.wait + + if host = @children[pid] + # Remove our host from the list of children, so the parallelization + # continues working. + @children.delete(pid) + failures << host if $CHILD_STATUS.exitstatus != 0 + print "#{host} finished with exit code #{$CHILD_STATUS.exitstatus}\n" + else + $stderr.puts "Could not find host for PID #{pid} with status #{$CHILD_STATUS.exitstatus}" + end + rescue Errno::ECHILD + # There are no children left, so just exit unless there are still + # children left to do. + next unless todo.empty? + + if failures.empty? + puts "Finished" + exit(0) + else + puts "Failed: #{failures.join(", ")}" + exit(3) + end + end + end + end + end + + def run_for_host(host) + if options[:ping] + out = %x{ping -c 1 #{host}} + unless $CHILD_STATUS == 0 + $stderr.print "Could not contact #{host}\n" + exit($CHILD_STATUS) + end + end + + require 'puppet/run' + Puppet::Run.indirection.terminus_class = :rest + port = Puppet[:puppetport] + url = ["https://#{host}:#{port}", "production", "run", host].join('/') + + print "Triggering #{host}\n" + begin + run_options = { + :tags => @tags, + :background => ! options[:foreground], + :ignoreschedules => options[:ignoreschedules] + } + run = Puppet::Run.indirection.save(Puppet::Run.new( run_options ), url) + puts "Getting status" + result = run.status + puts "status is #{result}" + rescue => detail + puts detail.backtrace if Puppet[:trace] + $stderr.puts "Host #{host} failed: #{detail}\n" + exit(2) + end + + case result + when "success"; + exit(0) + when "running" + $stderr.puts "Host #{host} is already running" + exit(3) + else + $stderr.puts "Host #{host} returned unknown answer '#{result}'" + exit(12) + end + end + + def initialize(*args) + super + @hosts = [] + @classes = [] + @tags = [] + end + + def preinit + [:INT, :TERM].each do |signal| + Signal.trap(signal) do + $stderr.puts "Cancelling" + exit(1) + end + end + options[:parallel] = 1 + options[:verbose] = true + options[:fqdn] = true + options[:ignoreschedules] = false + options[:foreground] = false + end + + def setup + if options[:debug] + Puppet::Util::Log.level = :debug + else + Puppet::Util::Log.level = :info + end + + # Now parse the config + Puppet.parse_config + + if Puppet[:node_terminus] == "ldap" and (options[:all] or @classes) + if options[:all] + @hosts = Puppet::Node.indirection.search("whatever", :fqdn => options[:fqdn]).collect { |node| node.name } + puts "all: #{@hosts.join(", ")}" + else + @hosts = [] + @classes.each do |klass| + list = Puppet::Node.indirection.search("whatever", :fqdn => options[:fqdn], :class => klass).collect { |node| node.name } + puts "#{klass}: #{list.join(", ")}" + + @hosts += list + end + end + elsif ! @classes.empty? + $stderr.puts "You must be using LDAP to specify host classes" + exit(24) + end + + @children = {} + + # If we get a signal, then kill all of our children and get out. + [:INT, :TERM].each do |signal| + Signal.trap(signal) do + Puppet.notice "Caught #{signal}; shutting down" + @children.each do |pid, host| + Process.kill("INT", pid) + end + + waitall + + exit(1) + end + end + + end + +end --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/lib/puppet/application/cert.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/lib/puppet/application/cert.rb @@ -0,0 +1,226 @@ +require 'puppet/application' + +class Puppet::Application::Cert < Puppet::Application + + should_parse_config + run_mode :master + + attr_accessor :all, :ca, :digest, :signed + + def subcommand + @subcommand + end + def subcommand=(name) + # Handle the nasty, legacy mapping of "clean" to "destroy". + sub = name.to_sym + @subcommand = (sub == :clean ? :destroy : sub) + end + + option("--clean", "-c") do + self.subcommand = "destroy" + end + + option("--all", "-a") do + @all = true + end + + option("--digest DIGEST") do |arg| + @digest = arg + end + + option("--signed", "-s") do + @signed = true + end + + option("--debug", "-d") do |arg| + Puppet::Util::Log.level = :debug + end + + require 'puppet/ssl/certificate_authority/interface' + Puppet::SSL::CertificateAuthority::Interface::INTERFACE_METHODS.reject {|m| m == :destroy }.each do |method| + option("--#{method}", "-#{method.to_s[0,1]}") do + self.subcommand = method + end + end + + option("--verbose", "-v") do + Puppet::Util::Log.level = :info + end + + def help + <<-HELP + +puppet-cert(8) -- Manage certificates and requests +======== + +SYNOPSIS +-------- +Standalone certificate authority. Capable of generating certificates, +but mostly used for signing certificate requests from puppet clients. + + +USAGE +----- +puppet cert [-h|--help] [-V|--version] [-d|--debug] [-v|--verbose] + [--digest ] [] + + +DESCRIPTION +----------- +Because the puppet master service defaults to not signing client +certificate requests, this script is available for signing outstanding +requests. It can be used to list outstanding requests and then either +sign them individually or sign all of them. + +ACTIONS +------- + +Every action except 'list' and 'generate' requires a hostname to act on, +unless the '--all' option is set. + +* clean: + Revoke a host's certificate (if applicable) and remove all files + related to that host from puppet cert's storage. This is useful when + rebuilding hosts, since new certificate signing requests will only be + honored if puppet cert does not have a copy of a signed certificate + for that host. If '--all' is specified then all host certificates, + both signed and unsigned, will be removed. + +* fingerprint: + Print the DIGEST (defaults to md5) fingerprint of a host's + certificate. + +* generate: + Generate a certificate for a named client. A certificate/keypair will + be generated for each client named on the command line. + +* list: + List outstanding certificate requests. If '--all' is specified, signed + certificates are also listed, prefixed by '+', and revoked or invalid + certificates are prefixed by '-' (the verification outcome is printed + in parenthesis). + +* print: + Print the full-text version of a host's certificate. + +* revoke: + Revoke the certificate of a client. The certificate can be specified + either by its serial number (given as a decimal number or a + hexadecimal number prefixed by '0x') or by its hostname. The + certificate is revoked by adding it to the Certificate Revocation List + given by the 'cacrl' configuration option. Note that the puppet master + needs to be restarted after revoking certificates. + +* sign: + Sign an outstanding certificate request. + +* verify: + Verify the named certificate against the local CA certificate. + + +OPTIONS +------- +Note that any configuration parameter that's valid in the configuration +file is also a valid long argument. For example, 'ssldir' is a valid +configuration parameter, so you can specify '--ssldir ' as an +argument. + +See the configuration file documentation at +http://docs.puppetlabs.com/references/stable/configuration.html for the +full list of acceptable parameters. A commented list of all +configuration options can also be generated by running puppet cert with +'--genconfig'. + +* --all: + Operate on all items. Currently only makes sense with the 'sign', + 'clean', 'list', and 'fingerprint' actions. + +* --digest: + Set the digest for fingerprinting (defaults to md5). Valid values + depends on your openssl and openssl ruby extension version, but should + contain at least md5, sha1, md2, sha256. + +* --debug: + Enable full debugging. + +* --help: + Print this help message + +* --verbose: + Enable verbosity. + +* --version: + Print the puppet version number and exit. + + +EXAMPLE +------- + $ puppet cert list + culain.madstop.com + $ puppet cert sign culain.madstop.com + + +AUTHOR +------ +Luke Kanies + + +COPYRIGHT +--------- +Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License + + HELP + end + + def main + if @all + hosts = :all + elsif @signed + hosts = :signed + else + hosts = command_line.args.collect { |h| h.downcase } + end + begin + @ca.apply(:revoke, :to => hosts) if subcommand == :destroy + @ca.apply(subcommand, :to => hosts, :digest => @digest) + rescue => detail + puts detail.backtrace if Puppet[:trace] + puts detail.to_s + exit(24) + end + end + + def setup + require 'puppet/ssl/certificate_authority' + exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? + + Puppet::Util::Log.newdestination :console + + if [:generate, :destroy].include? subcommand + Puppet::SSL::Host.ca_location = :local + else + Puppet::SSL::Host.ca_location = :only + end + + begin + @ca = Puppet::SSL::CertificateAuthority.new + rescue => detail + puts detail.backtrace if Puppet[:trace] + puts detail.to_s + exit(23) + end + end + + def parse_options + # handle the bareword subcommand pattern. + result = super + unless self.subcommand then + if sub = self.command_line.args.shift then + self.subcommand = sub + else + help + end + end + result + end +end --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/lib/puppet/ssl/host.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/lib/puppet/ssl/host.rb @@ -0,0 +1,312 @@ +require 'puppet/indirector' +require 'puppet/ssl' +require 'puppet/ssl/key' +require 'puppet/ssl/certificate' +require 'puppet/ssl/certificate_request' +require 'puppet/ssl/certificate_revocation_list' +require 'puppet/util/cacher' + +# The class that manages all aspects of our SSL certificates -- +# private keys, public keys, requests, etc. +class Puppet::SSL::Host + # Yay, ruby's strange constant lookups. + Key = Puppet::SSL::Key + CA_NAME = Puppet::SSL::CA_NAME + Certificate = Puppet::SSL::Certificate + CertificateRequest = Puppet::SSL::CertificateRequest + CertificateRevocationList = Puppet::SSL::CertificateRevocationList + + extend Puppet::Indirector + indirects :certificate_status, :terminus_class => :file + + attr_reader :name + attr_accessor :ca + + attr_writer :key, :certificate, :certificate_request + + # This accessor is used in instances for indirector requests to hold desired state + attr_accessor :desired_state + + class << self + include Puppet::Util::Cacher + + cached_attr(:localhost) do + result = new + result.generate unless result.certificate + result.key # Make sure it's read in + result + end + end + + # This is the constant that people will use to mark that a given host is + # a certificate authority. + def self.ca_name + CA_NAME + end + + class << self + attr_reader :ca_location + end + + # Configure how our various classes interact with their various terminuses. + def self.configure_indirection(terminus, cache = nil) + Certificate.indirection.terminus_class = terminus + CertificateRequest.indirection.terminus_class = terminus + CertificateRevocationList.indirection.terminus_class = terminus + + host_map = {:ca => :file, :file => nil, :rest => :rest} + if term = host_map[terminus] + self.indirection.terminus_class = term + else + self.indirection.reset_terminus_class + end + + if cache + # This is weird; we don't actually cache our keys, we + # use what would otherwise be the cache as our normal + # terminus. + Key.indirection.terminus_class = cache + else + Key.indirection.terminus_class = terminus + end + + if cache + Certificate.indirection.cache_class = cache + CertificateRequest.indirection.cache_class = cache + CertificateRevocationList.indirection.cache_class = cache + else + # Make sure we have no cache configured. puppet master + # switches the configurations around a bit, so it's important + # that we specify the configs for absolutely everything, every + # time. + Certificate.indirection.cache_class = nil + CertificateRequest.indirection.cache_class = nil + CertificateRevocationList.indirection.cache_class = nil + end + end + + CA_MODES = { + # Our ca is local, so we use it as the ultimate source of information + # And we cache files locally. + :local => [:ca, :file], + # We're a remote CA client. + :remote => [:rest, :file], + # We are the CA, so we don't have read/write access to the normal certificates. + :only => [:ca], + # We have no CA, so we just look in the local file store. + :none => [:file] + } + + # Specify how we expect to interact with our certificate authority. + def self.ca_location=(mode) + modes = CA_MODES.collect { |m, vals| m.to_s }.join(", ") + raise ArgumentError, "CA Mode can only be one of: #{modes}" unless CA_MODES.include?(mode) + + @ca_location = mode + + configure_indirection(*CA_MODES[@ca_location]) + end + + # Puppet::SSL::Host is actually indirected now so the original implementation + # has been moved into the certificate_status indirector. This method is in-use + # in `puppet cert -c `. + def self.destroy(name) + indirection.destroy(name) + end + + def self.from_pson(pson) + instance = new(pson["name"]) + if pson["desired_state"] + instance.desired_state = pson["desired_state"] + end + instance + end + + # Puppet::SSL::Host is actually indirected now so the original implementation + # has been moved into the certificate_status indirector. This method does not + # appear to be in use in `puppet cert -l`. + def self.search(options = {}) + indirection.search("*", options) + end + + # Is this a ca host, meaning that all of its files go in the CA location? + def ca? + ca + end + + def key + @key ||= Key.indirection.find(name) + end + + # This is the private key; we can create it from scratch + # with no inputs. + def generate_key + @key = Key.new(name) + @key.generate + begin + Key.indirection.save(@key) + rescue + @key = nil + raise + end + true + end + + def certificate_request + @certificate_request ||= CertificateRequest.indirection.find(name) + end + + # Our certificate request requires the key but that's all. + def generate_certificate_request + generate_key unless key + @certificate_request = CertificateRequest.new(name) + @certificate_request.generate(key.content) + begin + CertificateRequest.indirection.save(@certificate_request) + rescue + @certificate_request = nil + raise + end + + true + end + + def certificate + unless @certificate + generate_key unless key + + # get the CA cert first, since it's required for the normal cert + # to be of any use. + return nil unless Certificate.indirection.find("ca") unless ca? + return nil unless @certificate = Certificate.indirection.find(name) + + unless certificate_matches_key? + raise Puppet::Error, "Retrieved certificate does not match private key; please remove certificate from server and regenerate it with the current key" + end + end + @certificate + end + + def certificate_matches_key? + return false unless key + return false unless certificate + + certificate.content.check_private_key(key.content) + end + + # Generate all necessary parts of our ssl host. + def generate + generate_key unless key + generate_certificate_request unless certificate_request + + # If we can get a CA instance, then we're a valid CA, and we + # should use it to sign our request; else, just try to read + # the cert. + if ! certificate and ca = Puppet::SSL::CertificateAuthority.instance + ca.sign(self.name) + end + end + + def initialize(name = nil) + @name = (name || Puppet[:certname]).downcase + @key = @certificate = @certificate_request = nil + @ca = (name == self.class.ca_name) + end + + # Extract the public key from the private key. + def public_key + key.content.public_key + end + + # Create/return a store that uses our SSL info to validate + # connections. + def ssl_store(purpose = OpenSSL::X509::PURPOSE_ANY) + unless @ssl_store + @ssl_store = OpenSSL::X509::Store.new + @ssl_store.purpose = purpose + + # Use the file path here, because we don't want to cause + # a lookup in the middle of setting our ssl connection. + @ssl_store.add_file(Puppet[:localcacert]) + + # If there's a CRL, add it to our store. + if crl = Puppet::SSL::CertificateRevocationList.indirection.find(CA_NAME) + @ssl_store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK if Puppet.settings[:certificate_revocation] + @ssl_store.add_crl(crl.content) + end + return @ssl_store + end + @ssl_store + end + + def to_pson(*args) + my_cert = Puppet::SSL::Certificate.indirection.find(name) + pson_hash = { :name => name } + + my_state = state + + pson_hash[:state] = my_state + pson_hash[:desired_state] = desired_state if desired_state + + if my_state == 'requested' + pson_hash[:fingerprint] = certificate_request.fingerprint + else + pson_hash[:fingerprint] = my_cert.fingerprint + end + + pson_hash.to_pson(*args) + end + + # Attempt to retrieve a cert, if we don't already have one. + def wait_for_cert(time) + begin + return if certificate + generate + return if certificate + rescue SystemExit,NoMemoryError + raise + rescue Exception => detail + puts detail.backtrace if Puppet[:trace] + Puppet.err "Could not request certificate: #{detail}" + if time < 1 + puts "Exiting; failed to retrieve certificate and waitforcert is disabled" + exit(1) + else + sleep(time) + end + retry + end + + if time < 1 + puts "Exiting; no certificate found and waitforcert is disabled" + exit(1) + end + + while true + sleep time + begin + break if certificate + Puppet.notice "Did not receive certificate" + rescue StandardError => detail + puts detail.backtrace if Puppet[:trace] + Puppet.err "Could not request certificate: #{detail}" + end + end + end + + def state + my_cert = Puppet::SSL::Certificate.indirection.find(name) + if certificate_request + return 'requested' + end + + begin + Puppet::SSL::CertificateAuthority.new.verify(my_cert) + return 'signed' + rescue Puppet::SSL::CertificateAuthority::CertificateVerificationError + return 'revoked' + end + end +end + +require 'puppet/ssl/certificate_authority' --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/lib/puppet/ssl/certificate_authority.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/lib/puppet/ssl/certificate_authority.rb @@ -0,0 +1,284 @@ +require 'puppet/ssl/host' +require 'puppet/ssl/certificate_request' +require 'puppet/util/cacher' + +# The class that knows how to sign certificates. It creates +# a 'special' SSL::Host whose name is 'ca', thus indicating +# that, well, it's the CA. There's some magic in the +# indirector/ssl_file terminus base class that does that +# for us. +# This class mostly just signs certs for us, but +# it can also be seen as a general interface into all of the +# SSL stuff. +class Puppet::SSL::CertificateAuthority + require 'puppet/ssl/certificate_factory' + require 'puppet/ssl/inventory' + require 'puppet/ssl/certificate_revocation_list' + require 'puppet/ssl/certificate_authority/interface' + require 'puppet/network/authstore' + + class CertificateVerificationError < RuntimeError + attr_accessor :error_code + + def initialize(code) + @error_code = code + end + end + + class << self + include Puppet::Util::Cacher + + cached_attr(:singleton_instance) { new } + end + + def self.ca? + return false unless Puppet[:ca] + return false unless Puppet.run_mode.master? + true + end + + # If this process can function as a CA, then return a singleton + # instance. + def self.instance + return nil unless ca? + + singleton_instance + end + + attr_reader :name, :host + + # Create and run an applicator. I wanted to build an interface where you could do + # something like 'ca.apply(:generate).to(:all) but I don't think it's really possible. + def apply(method, options) + raise ArgumentError, "You must specify the hosts to apply to; valid values are an array or the symbol :all" unless options[:to] + applier = Interface.new(method, options) + + applier.apply(self) + end + + # If autosign is configured, then autosign all CSRs that match our configuration. + def autosign + return unless auto = autosign? + + store = nil + store = autosign_store(auto) if auto != true + + Puppet::SSL::CertificateRequest.indirection.search("*").each do |csr| + sign(csr.name) if auto == true or store.allowed?(csr.name, "127.1.1.1") + end + end + + # Do we autosign? This returns true, false, or a filename. + def autosign? + auto = Puppet[:autosign] + return false if ['false', false].include?(auto) + return true if ['true', true].include?(auto) + + raise ArgumentError, "The autosign configuration '#{auto}' must be a fully qualified file" unless auto =~ /^\// + FileTest.exist?(auto) && auto + end + + # Create an AuthStore for autosigning. + def autosign_store(file) + auth = Puppet::Network::AuthStore.new + File.readlines(file).each do |line| + next if line =~ /^\s*#/ + next if line =~ /^\s*$/ + auth.allow(line.chomp) + end + + auth + end + + # Retrieve (or create, if necessary) the certificate revocation list. + def crl + unless defined?(@crl) + unless @crl = Puppet::SSL::CertificateRevocationList.indirection.find(Puppet::SSL::CA_NAME) + @crl = Puppet::SSL::CertificateRevocationList.new(Puppet::SSL::CA_NAME) + @crl.generate(host.certificate.content, host.key.content) + Puppet::SSL::CertificateRevocationList.indirection.save(@crl) + end + end + @crl + end + + # Delegate this to our Host class. + def destroy(name) + Puppet::SSL::Host.destroy(name) + end + + # Generate a new certificate. + def generate(name) + raise ArgumentError, "A Certificate already exists for #{name}" if Puppet::SSL::Certificate.indirection.find(name) + host = Puppet::SSL::Host.new(name) + + host.generate_certificate_request + + sign(name) + end + + # Generate our CA certificate. + def generate_ca_certificate + generate_password unless password? + + host.generate_key unless host.key + + # Create a new cert request. We do this + # specially, because we don't want to actually + # save the request anywhere. + request = Puppet::SSL::CertificateRequest.new(host.name) + request.generate(host.key) + + # Create a self-signed certificate. + @certificate = sign(host.name, :ca, request) + + # And make sure we initialize our CRL. + crl + end + + def initialize + Puppet.settings.use :main, :ssl, :ca + + @name = Puppet[:certname] + + @host = Puppet::SSL::Host.new(Puppet::SSL::Host.ca_name) + + setup + end + + # Retrieve (or create, if necessary) our inventory manager. + def inventory + @inventory ||= Puppet::SSL::Inventory.new + end + + # Generate a new password for the CA. + def generate_password + pass = "" + 20.times { pass += (rand(74) + 48).chr } + + begin + Puppet.settings.write(:capass) { |f| f.print pass } + rescue Errno::EACCES => detail + raise Puppet::Error, "Could not write CA password: #{detail}" + end + + @password = pass + + pass + end + + # List all signed certificates. + def list + Puppet::SSL::Certificate.indirection.search("*").collect { |c| c.name } + end + + # Read the next serial from the serial file, and increment the + # file so this one is considered used. + def next_serial + serial = nil + + # This is slightly odd. If the file doesn't exist, our readwritelock creates + # it, but with a mode we can't actually read in some cases. So, use + # a default before the lock. + serial = 0x1 unless FileTest.exist?(Puppet[:serial]) + + Puppet.settings.readwritelock(:serial) { |f| + serial ||= File.read(Puppet.settings[:serial]).chomp.hex if FileTest.exist?(Puppet[:serial]) + + # We store the next valid serial, not the one we just used. + f << "%04X" % (serial + 1) + } + + serial + end + + # Does the password file exist? + def password? + FileTest.exist? Puppet[:capass] + end + + # Print a given host's certificate as text. + def print(name) + (cert = Puppet::SSL::Certificate.indirection.find(name)) ? cert.to_text : nil + end + + # Revoke a given certificate. + def revoke(name) + raise ArgumentError, "Cannot revoke certificates when the CRL is disabled" unless crl + + if cert = Puppet::SSL::Certificate.indirection.find(name) + serial = cert.content.serial + elsif ! serial = inventory.serial(name) + raise ArgumentError, "Could not find a serial number for #{name}" + end + crl.revoke(serial, host.key.content) + end + + # This initializes our CA so it actually works. This should be a private + # method, except that you can't any-instance stub private methods, which is + # *awesome*. This method only really exists to provide a stub-point during + # testing. + def setup + generate_ca_certificate unless @host.certificate + end + + # Sign a given certificate request. + def sign(hostname, cert_type = :server, self_signing_csr = nil) + # This is a self-signed certificate + if self_signing_csr + csr = self_signing_csr + issuer = csr.content + else + unless csr = Puppet::SSL::CertificateRequest.indirection.find(hostname) + raise ArgumentError, "Could not find certificate request for #{hostname}" + end + issuer = host.certificate.content + end + + cert = Puppet::SSL::Certificate.new(hostname) + cert.content = Puppet::SSL::CertificateFactory.new(cert_type, csr.content, issuer, next_serial).result + cert.content.sign(host.key.content, OpenSSL::Digest::SHA1.new) + + Puppet.notice "Signed certificate request for #{hostname}" + + # Add the cert to the inventory before we save it, since + # otherwise we could end up with it being duplicated, if + # this is the first time we build the inventory file. + inventory.add(cert) + + # Save the now-signed cert. This should get routed correctly depending + # on the certificate type. + Puppet::SSL::Certificate.indirection.save(cert) + + # And remove the CSR if this wasn't self signed. + Puppet::SSL::CertificateRequest.indirection.destroy(csr.name) unless self_signing_csr + + cert + end + + # Verify a given host's certificate. + def verify(name) + unless cert = Puppet::SSL::Certificate.indirection.find(name) + raise ArgumentError, "Could not find a certificate for #{name}" + end + store = OpenSSL::X509::Store.new + store.add_file Puppet[:cacert] + store.add_crl crl.content if self.crl + store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT + store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK if Puppet.settings[:certificate_revocation] + + raise CertificateVerificationError.new(store.error), store.error_string unless store.verify(cert.content) + end + + def fingerprint(name, md = :MD5) + unless cert = Puppet::SSL::Certificate.indirection.find(name) || Puppet::SSL::CertificateRequest.indirection.find(name) + raise ArgumentError, "Could not find a certificate or csr for #{name}" + end + cert.fingerprint(md) + end + + # List the waiting certificate requests. + def waiting? + Puppet::SSL::CertificateRequest.indirection.search("*").collect { |r| r.name } + end +end --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/lib/puppet/ssl/certificate_request.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/lib/puppet/ssl/certificate_request.rb @@ -0,0 +1,62 @@ +require 'puppet/ssl/base' + +# Manage certificate requests. +class Puppet::SSL::CertificateRequest < Puppet::SSL::Base + wraps OpenSSL::X509::Request + + extend Puppet::Indirector + + # If auto-signing is on, sign any certificate requests as they are saved. + module AutoSigner + def save(instance, key = nil) + super + + # Try to autosign the CSR. + if ca = Puppet::SSL::CertificateAuthority.instance + ca.autosign + end + end + end + + indirects :certificate_request, :terminus_class => :file, :extend => AutoSigner + + # Convert a string into an instance. + def self.from_s(string) + instance = wrapped_class.new(string) + name = instance.subject.to_s.sub(/\/CN=/i, '').downcase + result = new(name) + result.content = instance + result + end + + # Because of how the format handler class is included, this + # can't be in the base class. + def self.supported_formats + [:s] + end + + # How to create a certificate request with our system defaults. + def generate(key) + Puppet.info "Creating a new SSL certificate request for #{name}" + + # Support either an actual SSL key, or a Puppet key. + key = key.content if key.is_a?(Puppet::SSL::Key) + + # If we're a CSR for the CA, then use the real ca_name, rather than the + # fake 'ca' name. This is mostly for backward compatibility with 0.24.x, + # but it's also just a good idea. + common_name = name == Puppet::SSL::CA_NAME ? Puppet.settings[:ca_name] : name + + csr = OpenSSL::X509::Request.new + csr.version = 0 + csr.subject = OpenSSL::X509::Name.new([["CN", common_name]]) + csr.public_key = key.public_key + csr.sign(key, OpenSSL::Digest::MD5.new) + + raise Puppet::Error, "CSR sign verification failed; you need to clean the certificate request for #{name} on the server" unless csr.verify(key.public_key) + + @content = csr + Puppet.info "Certificate Request fingerprint (md5): #{fingerprint}" + @content + end +end --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/lib/puppet/ssl/certificate_factory.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/lib/puppet/ssl/certificate_factory.rb @@ -0,0 +1,145 @@ +require 'puppet/ssl' + +# The tedious class that does all the manipulations to the +# certificate to correctly sign it. Yay. +class Puppet::SSL::CertificateFactory + # How we convert from various units to the required seconds. + UNITMAP = { + "y" => 365 * 24 * 60 * 60, + "d" => 24 * 60 * 60, + "h" => 60 * 60, + "s" => 1 + } + + attr_reader :name, :cert_type, :csr, :issuer, :serial + + def initialize(cert_type, csr, issuer, serial) + @cert_type, @csr, @issuer, @serial = cert_type, csr, issuer, serial + + @name = @csr.subject + end + + # Actually generate our certificate. + def result + @cert = OpenSSL::X509::Certificate.new + + @cert.version = 2 # X509v3 + @cert.subject = @csr.subject + @cert.issuer = @issuer.subject + @cert.public_key = @csr.public_key + @cert.serial = @serial + + build_extensions + + set_ttl + + @cert + end + + private + + # This is pretty ugly, but I'm not really sure it's even possible to do + # it any other way. + def build_extensions + @ef = OpenSSL::X509::ExtensionFactory.new + + @ef.subject_certificate = @cert + + if @issuer.is_a?(OpenSSL::X509::Request) # It's a self-signed cert + @ef.issuer_certificate = @cert + else + @ef.issuer_certificate = @issuer + end + + @subject_alt_name = [] + @key_usage = nil + @ext_key_usage = nil + @extensions = [] + + method = "add_#{@cert_type.to_s}_extensions" + + begin + send(method) + rescue NoMethodError + raise ArgumentError, "#{@cert_type} is an invalid certificate type" + end + + @extensions << @ef.create_extension("nsComment", "Puppet Ruby/OpenSSL Generated Certificate") + @extensions << @ef.create_extension("basicConstraints", @basic_constraint, true) + @extensions << @ef.create_extension("subjectKeyIdentifier", "hash") + @extensions << @ef.create_extension("keyUsage", @key_usage.join(",")) if @key_usage + @extensions << @ef.create_extension("extendedKeyUsage", @ext_key_usage.join(",")) if @ext_key_usage + @extensions << @ef.create_extension("subjectAltName", @subject_alt_name.join(",")) if ! @subject_alt_name.empty? + + @cert.extensions = @extensions + + # for some reason this _must_ be the last extension added + @extensions << @ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always") if @cert_type == :ca + end + + # TTL for new certificates in seconds. If config param :ca_ttl is set, + # use that, otherwise use :ca_days for backwards compatibility + def ttl + ttl = Puppet.settings[:ca_ttl] + + return ttl unless ttl.is_a?(String) + + raise ArgumentError, "Invalid ca_ttl #{ttl}" unless ttl =~ /^(\d+)(y|d|h|s)$/ + + $1.to_i * UNITMAP[$2] + end + + def set_ttl + # Make the certificate valid as of yesterday, because + # so many people's clocks are out of sync. + from = Time.now - (60*60*24) + @cert.not_before = from + @cert.not_after = from + ttl + end + + # Woot! We're a CA. + def add_ca_extensions + @basic_constraint = "CA:TRUE" + @key_usage = %w{cRLSign keyCertSign} + end + + # We're a terminal CA, probably not self-signed. + def add_terminalsubca_extensions + @basic_constraint = "CA:TRUE,pathlen:0" + @key_usage = %w{cRLSign keyCertSign} + end + + # We're a normal server. + def add_server_extensions + @basic_constraint = "CA:FALSE" + dnsnames = Puppet[:certdnsnames] + name = @name.to_s.sub(%r{/CN=},'') + if dnsnames != "" + dnsnames.split(':').each { |d| @subject_alt_name << 'DNS:' + d } + @subject_alt_name << 'DNS:' + name # Add the fqdn as an alias + elsif name == Facter.value(:fqdn) # we're a CA server, and thus probably the server + @subject_alt_name << 'DNS:' + "puppet" # Add 'puppet' as an alias + @subject_alt_name << 'DNS:' + name # Add the fqdn as an alias + @subject_alt_name << 'DNS:' + name.sub(/^[^.]+./, "puppet.") # add puppet.domain as an alias + end + @key_usage = %w{digitalSignature keyEncipherment} + @ext_key_usage = %w{serverAuth clientAuth emailProtection} + end + + # Um, no idea. + def add_ocsp_extensions + @basic_constraint = "CA:FALSE" + @key_usage = %w{nonRepudiation digitalSignature} + @ext_key_usage = %w{serverAuth OCSPSigning} + end + + # Normal client. + def add_client_extensions + @basic_constraint = "CA:FALSE" + @key_usage = %w{nonRepudiation digitalSignature keyEncipherment} + @ext_key_usage = %w{clientAuth emailProtection} + + @extensions << @ef.create_extension("nsCertType", "client,email") + end +end + --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/lib/puppet/ssl/certificate.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/lib/puppet/ssl/certificate.rb @@ -0,0 +1,34 @@ +require 'puppet/ssl/base' + +# Manage certificates themselves. This class has no +# 'generate' method because the CA is responsible +# for turning CSRs into certificates; we can only +# retrieve them from the CA (or not, as is often +# the case). +class Puppet::SSL::Certificate < Puppet::SSL::Base + # This is defined from the base class + wraps OpenSSL::X509::Certificate + + extend Puppet::Indirector + indirects :certificate, :terminus_class => :file + + # Convert a string into an instance. + def self.from_s(string) + instance = wrapped_class.new(string) + name = instance.subject.to_s.sub(/\/CN=/i, '').downcase + result = new(name) + result.content = instance + result + end + + # Because of how the format handler class is included, this + # can't be in the base class. + def self.supported_formats + [:s] + end + + def expiration + return nil unless content + content.not_after + end +end --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/lib/puppet/ssl/certificate_authority/interface.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/lib/puppet/ssl/certificate_authority/interface.rb @@ -0,0 +1,134 @@ +# This class is basically a hidden class that knows how to act +# on the CA. It's only used by the 'puppetca' executable, and its +# job is to provide a CLI-like interface to the CA class. +module Puppet + module SSL + class CertificateAuthority + class Interface + INTERFACE_METHODS = [:destroy, :list, :revoke, :generate, :sign, :print, :verify, :fingerprint] + + class InterfaceError < ArgumentError; end + + attr_reader :method, :subjects, :digest + + # Actually perform the work. + def apply(ca) + unless subjects or method == :list + raise ArgumentError, "You must provide hosts or :all when using #{method}" + end + + begin + return send(method, ca) if respond_to?(method) + + (subjects == :all ? ca.list : subjects).each do |host| + ca.send(method, host) + end + rescue InterfaceError + raise + rescue => detail + puts detail.backtrace if Puppet[:trace] + Puppet.err "Could not call #{method}: #{detail}" + end + end + + def generate(ca) + raise InterfaceError, "It makes no sense to generate all hosts; you must specify a list" if subjects == :all + + subjects.each do |host| + ca.generate(host) + end + end + + def initialize(method, options) + self.method = method + self.subjects = options[:to] + @digest = options[:digest] || :MD5 + end + + # List the hosts. + def list(ca) + unless subjects + puts ca.waiting?.join("\n") + return nil + end + + signed = ca.list + requests = ca.waiting? + + if subjects == :all + hosts = [signed, requests].flatten + elsif subjects == :signed + hosts = signed.flatten + else + hosts = subjects + end + + hosts.uniq.sort.each do |host| + invalid = false + begin + ca.verify(host) unless requests.include?(host) + rescue Puppet::SSL::CertificateAuthority::CertificateVerificationError => details + invalid = details.to_s + end + if not invalid and signed.include?(host) + puts "+ #{host} (#{ca.fingerprint(host, @digest)})" + elsif invalid + puts "- #{host} (#{ca.fingerprint(host, @digest)}) (#{invalid})" + else + puts "#{host} (#{ca.fingerprint(host, @digest)})" + end + end + end + + # Set the method to apply. + def method=(method) + raise ArgumentError, "Invalid method #{method} to apply" unless INTERFACE_METHODS.include?(method) + @method = method + end + + # Print certificate information. + def print(ca) + (subjects == :all ? ca.list : subjects).each do |host| + if value = ca.print(host) + puts value + else + Puppet.err "Could not find certificate for #{host}" + end + end + end + + # Print certificate information. + def fingerprint(ca) + (subjects == :all ? ca.list + ca.waiting?: subjects).each do |host| + if value = ca.fingerprint(host, @digest) + puts "#{host} #{value}" + else + Puppet.err "Could not find certificate for #{host}" + end + end + end + + # Sign a given certificate. + def sign(ca) + list = subjects == :all ? ca.waiting? : subjects + raise InterfaceError, "No waiting certificate requests to sign" if list.empty? + list.each do |host| + ca.sign(host) + end + end + + # Set the list of hosts we're operating on. Also supports keywords. + def subjects=(value) + unless value == :all or value == :signed or value.is_a?(Array) + raise ArgumentError, "Subjects must be an array or :all; not #{value}" + end + + value = nil if value.is_a?(Array) and value.empty? + + @subjects = value + end + end + end + end +end + --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/lib/puppet/network/client.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/lib/puppet/network/client.rb @@ -0,0 +1,179 @@ +# the available clients + +require 'puppet' +require 'puppet/network/xmlrpc/client' +require 'puppet/util/subclass_loader' +require 'puppet/util/methodhelper' +require 'puppet/sslcertificates/support' + +require 'puppet/network/handler' + +require 'net/http' + +# Some versions of ruby don't have this method defined, which basically causes +# us to never use ssl. Yay. +class Net::HTTP + def use_ssl? + if defined?(@use_ssl) + @use_ssl + else + false + end + end + + # JJM: This is a "backport" of sorts to older ruby versions which + # do not have this accessor. See #896 for more information. + attr_accessor :enable_post_connection_check unless Net::HTTP.method_defined? "enable_post_connection_check" +end + +# The base class for all of the clients. Many clients just directly +# call methods, but some of them need to do some extra work or +# provide a different interface. +class Puppet::Network::Client + Client = self + include Puppet::Util + extend Puppet::Util::SubclassLoader + include Puppet::Util::MethodHelper + + # This handles reading in the key and such-like. + include Puppet::SSLCertificates::Support + + attr_accessor :schedule, :lastrun, :local, :stopping + + attr_reader :driver + + # Set up subclass loading + handle_subclasses :client, "puppet/network/client" + + # Determine what clients look for when being passed an object for local + # client/server stuff. E.g., you could call Client::CA.new(:CA => ca). + def self.drivername + @drivername ||= self.name + end + + # Figure out the handler for our client. + def self.handler + @handler ||= Puppet::Network::Handler.handler(self.name) + end + + # The class that handles xmlrpc interaction for us. + def self.xmlrpc_client + @xmlrpc_client ||= Puppet::Network::XMLRPCClient.handler_class(self.handler) + end + + # Create our client. + def initialize(hash) + # to whom do we connect? + @server = nil + + if hash.include?(:Cache) + @cache = hash[:Cache] + else + @cache = true + end + + driverparam = self.class.drivername + if hash.include?(:Server) + args = {:Server => hash[:Server]} + @server = hash[:Server] + args[:Port] = hash[:Port] || Puppet[:masterport] + + @driver = self.class.xmlrpc_client.new(args) + + self.read_cert + + # We have to start the HTTP connection manually before we start + # sending it requests or keep-alive won't work. Note that with #1010, + # we don't currently actually want keep-alive. + @driver.start if @driver.respond_to? :start and Puppet::Network::HttpPool.keep_alive? + + @local = false + elsif hash.include?(driverparam) + @driver = hash[driverparam] + if @driver == true + @driver = self.class.handler.new + end + @local = true + else + raise Puppet::Network::ClientError, "#{self.class} must be passed a Server or #{driverparam}" + end + end + + # Are we a local client? + def local? + if @local + true + else + false + end + end + + # Make sure we set the driver up when we read the cert in. + def recycle_connection + @driver.recycle_connection if @driver.respond_to?(:recycle_connection) + end + + # A wrapper method to run and then store the last run time + def runnow + if self.stopping + Puppet.notice "In shutdown progress; skipping run" + return + end + begin + self.run + self.lastrun = Time.now.to_i + rescue => detail + puts detail.backtrace if Puppet[:trace] + Puppet.err "Could not run #{self.class}: #{detail}" + end + end + + def run + raise Puppet::DevError, "Client type #{self.class} did not override run" + end + + def scheduled? + if sched = self.schedule + return sched.match?(self.lastrun) + else + return true + end + end + + def shutdown + if self.stopping + Puppet.notice "Already in shutdown" + else + self.stopping = true + Puppet::Util::Storage.store if self.respond_to? :running? and self.running? + rmpidfile + end + end + + # Start listening for events. We're pretty much just listening for + # timer events here. + def start + # Create our timer. Puppet will handle observing it and such. + + timer = Puppet.newtimer( + + :interval => Puppet[:runinterval], + :tolerance => 1, + + :start? => true + ) do + begin + self.runnow if self.scheduled? + rescue => detail + puts detail.backtrace if Puppet[:trace] + Puppet.err "Could not run client; got otherwise uncaught exception: #{detail}" + end + end + + # Run once before we start following the timer + self.runnow + end + + require 'puppet/network/client/proxy' +end + --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/lib/puppet/network/client/ca.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/lib/puppet/network/client/ca.rb @@ -0,0 +1,56 @@ +require 'puppet/network/client' + +# Request a certificate from the remote system. +class Puppet::Network::Client::CA < Puppet::Network::Client + class InvalidCertificate < Puppet::Error; end + + def initialize(options = {}) + options = symbolize_options(options) + unless options.include?(:Server) or options.include?(:CA) + options[:Server] = Puppet[:ca_server] + options[:Port] = Puppet[:ca_port] + end + super(options) + end + + # This client is really only able to request certificates for the + # current host. It uses the Puppet.settings settings to figure everything out. + def request_cert + Puppet.settings.use(:main, :ssl) + + if cert = read_cert + return cert + end + + begin + cert, cacert = @driver.getcert(csr.to_pem) + rescue => detail + puts detail.backtrace if Puppet[:trace] + raise Puppet::Error.new("Certificate retrieval failed: #{detail}") + end + + if cert.nil? or cert == "" + return nil + end + + begin + @cert = OpenSSL::X509::Certificate.new(cert) + @cacert = OpenSSL::X509::Certificate.new(cacert) + rescue => detail + raise InvalidCertificate.new( + "Invalid certificate: #{detail}" + ) + end + + unless @cert.check_private_key(key) + raise InvalidCertificate, "Certificate does not match private key. Try 'puppetca --clean #{Puppet[:certname]}' on the server." + end + + # Only write the cert out if it passes validating. + Puppet.settings.write(:hostcert) do |f| f.print cert end + Puppet.settings.write(:localcacert) do |f| f.print cacert end + + @cert + end +end + --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/lib/puppet/network/client/proxy.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/lib/puppet/network/client/proxy.rb @@ -0,0 +1,27 @@ +# unlike the other client classes (again, this design sucks) this class +# is basically just a proxy class -- it calls its methods on the driver +# and that's about it +class Puppet::Network::Client::ProxyClient < Puppet::Network::Client + def self.mkmethods + interface = self.handler.interface + namespace = interface.prefix + + + interface.methods.each { |ary| + method = ary[0] + Puppet.debug "#{self}: defining #{namespace}.#{method}" + define_method(method) { |*args| + begin + @driver.send(method, *args) + rescue XMLRPC::FaultException => detail + #Puppet.err "Could not call %s.%s: %s" % + # [namespace, method, detail.faultString] + #raise NetworkClientError, + # "XMLRPC Error: #{detail.faultString}" + raise NetworkClientError, detail.faultString + end + } + } + end +end + --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/lib/puppet/network/client/report.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/lib/puppet/network/client/report.rb @@ -0,0 +1,26 @@ +class Puppet::Network::Client::Report < Puppet::Network::Client + @handler = Puppet::Network::Handler.handler(:report) + + def initialize(hash = {}) + hash[:Report] = self.class.handler.new if hash.include?(:Report) + + super(hash) + end + + # Send our report. We get the transaction report and convert it to YAML + # as appropriate. + def report(transreport) + report = YAML.dump(transreport) + + report = CGI.escape(report) unless self.local + + # Now send the report + file = nil + benchmark(:info, "Sent transaction report") do + file = @driver.report(report) + end + + file + end +end + --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/lib/puppet/network/client/status.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/lib/puppet/network/client/status.rb @@ -0,0 +1,4 @@ +class Puppet::Network::Client::Status < Puppet::Network::Client::ProxyClient + self.mkmethods +end + --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/lib/puppet/network/client/file.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/lib/puppet/network/client/file.rb @@ -0,0 +1,6 @@ +class Puppet::Network::Client::File < Puppet::Network::Client::ProxyClient + @handler = Puppet::Network::Handler.handler(:fileserver) + @drivername = :FileServer + self.mkmethods +end + --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/lib/puppet/network/client/runner.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/lib/puppet/network/client/runner.rb @@ -0,0 +1,10 @@ +class Puppet::Network::Client::Runner < Puppet::Network::Client::ProxyClient + self.mkmethods + + def initialize(hash = {}) + hash[:Runner] = self.class.handler.new if hash.include?(:Runner) + + super(hash) + end +end + --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/lib/puppet/network/xmlrpc/client.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/lib/puppet/network/xmlrpc/client.rb @@ -0,0 +1,211 @@ +require 'puppet/sslcertificates' +require 'puppet/network/http_pool' +require 'openssl' +require 'puppet/external/base64' + +require 'xmlrpc/client' +require 'net/https' +require 'yaml' + +module Puppet::Network + class ClientError < Puppet::Error; end + class XMLRPCClientError < Puppet::Error; end + class XMLRPCClient < ::XMLRPC::Client + + attr_accessor :puppet_server, :puppet_port + @clients = {} + + class << self + include Puppet::Util + include Puppet::Util::ClassGen + end + + # Create a netclient for each handler + def self.mkclient(handler) + interface = handler.interface + namespace = interface.prefix + + # Create a subclass for every client type. This is + # so that all of the methods are on their own class, + # so that their namespaces can define the same methods if + # they want. + constant = handler.name.to_s.capitalize + name = namespace.downcase + newclient = genclass(name, :hash => @clients, :constant => constant) + + interface.methods.each { |ary| + method = ary[0] + newclient.send(:define_method,method) { |*args| + make_rpc_call(namespace, method, *args) + } + } + + newclient + end + + def self.handler_class(handler) + @clients[handler] || self.mkclient(handler) + end + + class ErrorHandler + def initialize(&block) + singleton_class.define_method(:execute, &block) + end + end + + # Use a class variable so all subclasses have access to it. + @@error_handlers = {} + + def self.error_handler(exception) + if handler = @@error_handlers[exception.class] + return handler + else + return @@error_handlers[:default] + end + end + + def self.handle_error(*exceptions, &block) + handler = ErrorHandler.new(&block) + + exceptions.each do |exception| + @@error_handlers[exception] = handler + end + end + + handle_error(OpenSSL::SSL::SSLError) do |client, detail, namespace, method| + if detail.message =~ /bad write retry/ + Puppet.warning "Transient SSL write error; restarting connection and retrying" + client.recycle_connection + return :retry + end + ["certificate verify failed", "hostname was not match", "hostname not match"].each do |str| + Puppet.warning "Certificate validation failed; consider using the certname configuration option" if detail.message.include?(str) + end + raise XMLRPCClientError, "Certificates were not trusted: #{detail}" + end + + handle_error(:default) do |client, detail, namespace, method| + if detail.message.to_s =~ /^Wrong size\. Was \d+, should be \d+$/ + Puppet.warning "XMLRPC returned wrong size. Retrying." + return :retry + end + Puppet.err "Could not call #{namespace}.#{method}: #{detail.inspect}" + error = XMLRPCClientError.new(detail.to_s) + error.set_backtrace detail.backtrace + raise error + end + + handle_error(OpenSSL::SSL::SSLError) do |client, detail, namespace, method| + if detail.message =~ /bad write retry/ + Puppet.warning "Transient SSL write error; restarting connection and retrying" + client.recycle_connection + return :retry + end + ["certificate verify failed", "hostname was not match", "hostname not match"].each do |str| + Puppet.warning "Certificate validation failed; consider using the certname configuration option" if detail.message.include?(str) + end + raise XMLRPCClientError, "Certificates were not trusted: #{detail}" + end + + handle_error(::XMLRPC::FaultException) do |client, detail, namespace, method| + raise XMLRPCClientError, detail.faultString + end + + handle_error(Errno::ECONNREFUSED) do |client, detail, namespace, method| + msg = "Could not connect to #{client.host} on port #{client.port}" + raise XMLRPCClientError, msg + end + + handle_error(SocketError) do |client, detail, namespace, method| + Puppet.err "Could not find server #{@host}: #{detail}" + error = XMLRPCClientError.new("Could not find server #{client.host}") + error.set_backtrace detail.backtrace + raise error + end + + handle_error(Errno::EPIPE, EOFError) do |client, detail, namespace, method| + Puppet.info "Other end went away; restarting connection and retrying" + client.recycle_connection + return :retry + end + + handle_error(Timeout::Error) do |client, detail, namespace, method| + Puppet.err "Connection timeout calling #{namespace}.#{method}: #{detail}" + error = XMLRPCClientError.new("Connection Timeout") + error.set_backtrace(detail.backtrace) + raise error + end + + def make_rpc_call(namespace, method, *args) + Puppet.debug "Calling #{namespace}.#{method}" + begin + call("#{namespace}.#{method}",*args) + rescue SystemExit,NoMemoryError + raise + rescue Exception => detail + retry if self.class.error_handler(detail).execute(self, detail, namespace, method) == :retry + end + ensure + http.finish if http.started? + end + + def http + @http ||= Puppet::Network::HttpPool.http_instance(host, port, true) + end + + attr_reader :host, :port + + def initialize(hash = {}) + hash[:Path] ||= "/RPC2" + hash[:Server] ||= Puppet[:server] + hash[:Port] ||= Puppet[:masterport] + hash[:HTTPProxyHost] ||= Puppet[:http_proxy_host] + hash[:HTTPProxyPort] ||= Puppet[:http_proxy_port] + + if "none" == hash[:HTTPProxyHost] + hash[:HTTPProxyHost] = nil + hash[:HTTPProxyPort] = nil + end + + + super( + + hash[:Server], + hash[:Path], + hash[:Port], + hash[:HTTPProxyHost], + hash[:HTTPProxyPort], + + nil, # user + nil, # password + true, # use_ssl + Puppet[:configtimeout] # use configured timeout (#1176) + ) + @http = Puppet::Network::HttpPool.http_instance(@host, @port) + end + + # Get rid of our existing connection, replacing it with a new one. + # This should only happen if we lose our connection somehow (e.g., an EPIPE) + # or we've just downloaded certs and we need to create new http instances + # with the certs added. + def recycle_connection + http.finish if http.started? + @http = nil + self.http # force a new one + end + + def start + @http.start unless @http.started? + rescue => detail + Puppet.err "Could not connect to server: #{detail}" + end + + def local + false + end + + def local? + false + end + end +end --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/lib/puppet/network/handler/ca.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/lib/puppet/network/handler/ca.rb @@ -0,0 +1,151 @@ +require 'openssl' +require 'puppet' +require 'puppet/sslcertificates' +require 'xmlrpc/server' + +# Much of this was taken from QuickCert: +# http://segment7.net/projects/ruby/QuickCert/ + +class Puppet::Network::Handler + class CA < Handler + attr_reader :ca + + desc "Provides an interface for signing CSRs. Accepts a CSR and returns + the CA certificate and the signed certificate, or returns nil if + the cert is not signed." + + @interface = XMLRPC::Service::Interface.new("puppetca") { |iface| + iface.add_method("array getcert(csr)") + } + + def autosign + if defined?(@autosign) + @autosign + else + Puppet[:autosign] + end + end + + # FIXME autosign? should probably accept both hostnames and IP addresses + def autosign?(hostname) + # simple values are easy + if autosign == true or autosign == false + return autosign + end + + # we only otherwise know how to handle files + unless autosign =~ /^\// + raise Puppet::Error, "Invalid autosign value #{autosign.inspect}" + end + + unless FileTest.exists?(autosign) + unless defined?(@@warnedonautosign) + @@warnedonautosign = true + Puppet.info "Autosign is enabled but #{autosign} is missing" + end + return false + end + auth = Puppet::Network::AuthStore.new + File.open(autosign) { |f| + f.each { |line| + next if line =~ /^\s*#/ + next if line =~ /^\s*$/ + auth.allow(line.chomp) + } + } + + # for now, just cheat and pass a fake IP address to allowed? + auth.allowed?(hostname, "127.1.1.1") + end + + def initialize(hash = {}) + Puppet.settings.use(:main, :ssl, :ca) + @autosign = hash[:autosign] if hash.include? :autosign + + @ca = Puppet::SSLCertificates::CA.new(hash) + end + + # our client sends us a csr, and we either store it for later signing, + # or we sign it right away + def getcert(csrtext, client = nil, clientip = nil) + csr = OpenSSL::X509::Request.new(csrtext) + + # Use the hostname from the CSR, not from the network. + subject = csr.subject + + nameary = subject.to_a.find { |ary| + ary[0] == "CN" + } + + if nameary.nil? + Puppet.err( + "Invalid certificate request: could not retrieve server name" + ) + return "invalid" + end + + hostname = nameary[1] + + unless @ca + Puppet.notice "Host #{hostname} asked for signing from non-CA master" + return "" + end + + # We used to save the public key, but it's basically unnecessary + # and it mucks with the permissions requirements. + # save_pk(hostname, csr.public_key) + + certfile = File.join(Puppet[:certdir], [hostname, "pem"].join(".")) + + # first check to see if we already have a signed cert for the host + cert, cacert = ca.getclientcert(hostname) + if cert and cacert + Puppet.info "Retrieving existing certificate for #{hostname}" + unless csr.public_key.to_s == cert.public_key.to_s + raise Puppet::Error, "Certificate request does not match existing certificate; run 'puppetca --clean #{hostname}'." + end + return [cert.to_pem, cacert.to_pem] + elsif @ca + if self.autosign?(hostname) or client.nil? + Puppet.info "Signing certificate for CA server" if client.nil? + # okay, we don't have a signed cert + # if we're a CA and autosign is turned on, then go ahead and sign + # the csr and return the results + Puppet.info "Signing certificate for #{hostname}" + cert, cacert = @ca.sign(csr) + #Puppet.info "Cert: #{cert.class}; Cacert: #{cacert.class}" + return [cert.to_pem, cacert.to_pem] + else # just write out the csr for later signing + if @ca.getclientcsr(hostname) + Puppet.info "Not replacing existing request from #{hostname}" + else + Puppet.notice "Host #{hostname} has a waiting certificate request" + @ca.storeclientcsr(csr) + end + return ["", ""] + end + else + raise "huh?" + end + end + + private + + # Save the public key. + def save_pk(hostname, public_key) + pkeyfile = File.join(Puppet[:publickeydir], [hostname, "pem"].join('.')) + + if FileTest.exists?(pkeyfile) + currentkey = File.open(pkeyfile) { |k| k.read } + unless currentkey == public_key.to_s + raise Puppet::Error, "public keys for #{hostname} differ" + end + else + File.open(pkeyfile, "w", 0644) { |f| + f.print public_key.to_s + } + end + end + end +end + --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/lib/puppet/network/handler/runner.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/lib/puppet/network/handler/runner.rb @@ -0,0 +1,31 @@ +require 'puppet/run' + +class Puppet::Network::Handler + class MissingMasterError < RuntimeError; end # Cannot find the master client + # A simple server for triggering a new run on a Puppet client. + class Runner < Handler + desc "An interface for triggering client configuration runs." + + @interface = XMLRPC::Service::Interface.new("puppetrunner") { |iface| + iface.add_method("string run(string, string)") + } + + side :client + + # Run the client configuration right now, optionally specifying + # tags and whether to ignore schedules + def run(tags = nil, ignoreschedules = false, fg = true, client = nil, clientip = nil) + options = {} + options[:tags] = tags if tags + options[:ignoreschedules] = ignoreschedules if ignoreschedules + options[:background] = !fg + + runner = Puppet::Run.new(options) + + runner.run + + runner.status + end + end +end + --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/lib/puppet/network/handler/master.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/lib/puppet/network/handler/master.rb @@ -0,0 +1,89 @@ +require 'openssl' +require 'puppet' +require 'puppet/sslcertificates' +require 'xmlrpc/server' +require 'yaml' + +class Puppet::Network::Handler + class MasterError < Puppet::Error; end + class Master < Handler + desc "Puppet's configuration interface. Used for all interactions related to + generating client configurations." + + include Puppet::Util + + attr_accessor :ast + attr_reader :ca + + @interface = XMLRPC::Service::Interface.new("puppetmaster") { |iface| + iface.add_method("string getconfig(string)") + iface.add_method("int freshness()") + } + + # Tell a client whether there's a fresh config for it + def freshness(client = nil, clientip = nil) + # Always force a recompile. Newer clients shouldn't do this (as of April 2008). + Time.now.to_i + end + + def initialize(hash = {}) + args = {} + + @local = hash[:Local] + + args[:Local] = true + + @ca = (hash.include?(:CA) and hash[:CA]) ? Puppet::SSLCertificates::CA.new : nil + + # This is only used by the cfengine module, or if --loadclasses was + # specified in +puppet+. + args[:Classes] = hash[:Classes] if hash.include?(:Classes) + end + + # Call our various handlers; this handler is getting deprecated. + def getconfig(facts, format = "marshal", client = nil, clientip = nil) + facts = decode_facts(facts) + + client ||= facts["hostname"] + + # Pass the facts to the fact handler + Puppet::Node::Facts.indirection.save(Puppet::Node::Facts.new(client, facts)) unless local? + + catalog = Puppet::Resource::Catalog.indirection.find(client) + + case format + when "yaml" + return CGI.escape(catalog.extract.to_yaml) + when "marshal" + return CGI.escape(Marshal.dump(catalog.extract)) + else + raise "Invalid markup format '#{format}'" + end + end + + # + def decode_facts(facts) + if @local + # we don't need to do anything, since we should already + # have raw objects + Puppet.debug "Our client is local" + else + Puppet.debug "Our client is remote" + + begin + facts = YAML.load(CGI.unescape(facts)) + rescue => detail + raise XMLRPC::FaultException.new( + 1, "Could not rebuild facts" + ) + end + end + + facts + end + + # Translate our configuration appropriately for sending back to a client. + def translate(config) + end + end +end --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/lib/puppet/network/http_server/webrick.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/lib/puppet/network/http_server/webrick.rb @@ -0,0 +1,155 @@ +require 'puppet' +require 'webrick' +require 'webrick/https' +require 'fcntl' + +require 'puppet/sslcertificates/support' +require 'puppet/network/xmlrpc/webrick_servlet' +require 'puppet/network/http_server' +require 'puppet/network/client' +require 'puppet/network/handler' + +module Puppet + class ServerError < RuntimeError; end + module Network + # The old-school, pure ruby webrick server, which is the default serving + # mechanism. + class HTTPServer::WEBrick < WEBrick::HTTPServer + include Puppet::SSLCertificates::Support + + # Read the CA cert and CRL and populate an OpenSSL::X509::Store + # with them, with flags appropriate for checking client + # certificates for revocation + def x509store + unless File.exist?(Puppet[:cacrl]) + # No CRL, no store needed + return nil + end + crl = OpenSSL::X509::CRL.new(File.read(Puppet[:cacrl])) + store = OpenSSL::X509::Store.new + store.purpose = OpenSSL::X509::PURPOSE_ANY + store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK if Puppet.settings[:certificate_revocation] + raise Puppet::Error, "Could not find CA certificate" unless self.ca_cert + + store.add_file(Puppet[:localcacert]) + store.add_crl(crl) + store + end + + # Set up the http log. + def httplog + args = [] + + # yuck; separate http logs + file = nil + Puppet.settings.use(:main, :ssl, Puppet[:name]) + if Puppet.run_mode.master? + file = Puppet[:masterhttplog] + else + file = Puppet[:httplog] + end + + # open the log manually to prevent file descriptor leak + file_io = open(file, "a+") + file_io.sync + file_io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) + + args << file_io + args << WEBrick::Log::DEBUG if Puppet[:debug] + + log = WEBrick::Log.new(*args) + + + log + end + + # Create our server, yo. + def initialize(hash = {}) + Puppet.info "Starting server for Puppet version #{Puppet.version}" + + if handlers = hash[:Handlers] + handler_instances = setup_handlers(handlers) + else + raise ServerError, "A server must have handlers" + end + + unless self.read_cert + if ca = handler_instances.find { |handler| handler.is_a?(Puppet::Network::Handler.ca) } + request_cert(ca) + else + raise Puppet::Error, "No certificate and no CA; cannot get cert" + end + end + + setup_webrick(hash) + + begin + super(hash) + rescue => detail + puts detail.backtrace if Puppet[:trace] + raise Puppet::Error, "Could not start WEBrick: #{detail}" + end + + # make sure children don't inherit the sockets + listeners.each { |sock| + sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) + } + + Puppet.info "Listening on port #{hash[:Port]}" + + # this creates a new servlet for every connection, + # but all servlets have the same list of handlers + # thus, the servlets can have their own state -- passing + # around the requests and such -- but the handlers + # have a global state + + # mount has to be called after the server is initialized + servlet = Puppet::Network::XMLRPC::WEBrickServlet.new( handler_instances) + self.mount("/RPC2", servlet) + end + + # Create a ca client to set up our cert for us. + def request_cert(ca) + client = Puppet::Network::Client.ca.new(:CA => ca) + raise Puppet::Error, "Could get certificate" unless client.request_cert + end + + # Create all of our handler instances. + def setup_handlers(handlers) + raise ServerError, "Handlers must have arguments" unless handlers.is_a?(Hash) + + handlers.collect { |handler, args| + hclass = nil + unless hclass = Puppet::Network::Handler.handler(handler) + raise ServerError, "Invalid handler #{handler}" + end + hclass.new(args) + } + end + + # Handle all of the many webrick arguments. + def setup_webrick(hash) + hash[:Port] ||= Puppet[:masterport] + hash[:Logger] ||= self.httplog + hash[:AccessLog] ||= [ + [ self.httplog, WEBrick::AccessLog::COMMON_LOG_FORMAT ], + [ self.httplog, WEBrick::AccessLog::REFERER_LOG_FORMAT ] + ] + + hash[:SSLCertificateStore] = x509store + hash[:SSLCertificate] = self.cert + hash[:SSLPrivateKey] = self.key + hash[:SSLStartImmediately] = true + hash[:SSLEnable] = true + hash[:SSLCACertificateFile] = Puppet[:localcacert] + hash[:SSLVerifyClient] = OpenSSL::SSL::VERIFY_PEER + hash[:SSLCertName] = nil + + if addr = Puppet[:bindaddress] and addr != "" + hash[:BindAddress] = addr + end + end + end + end +end + --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/lib/puppet/util/monkey_patches.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/lib/puppet/util/monkey_patches.rb @@ -0,0 +1,113 @@ + +unless defined? JRUBY_VERSION + Process.maxgroups = 1024 +end + +module RDoc + def self.caller(skip=nil) + in_gem_wrapper = false + Kernel.caller.reject { |call| + in_gem_wrapper ||= call =~ /#{Regexp.escape $0}:\d+:in `load'/ + } + end +end + + +require "yaml" +require "puppet/util/zaml.rb" + +class Symbol + def to_zaml(z) + z.emit("!ruby/sym ") + to_s.to_zaml(z) + end + def <=> (other) + self.to_s <=> other.to_s + end +end + +[Object, Exception, Integer, Struct, Date, Time, Range, Regexp, Hash, Array, Float, String, FalseClass, TrueClass, Symbol, NilClass, Class].each { |cls| + cls.class_eval do + def to_yaml(ignored=nil) + ZAML.dump(self) + end + end +} + +def YAML.dump(*args) + ZAML.dump(*args) +end + +# +# Workaround for bug in MRI 1.8.7, see +# http://redmine.ruby-lang.org/issues/show/2708 +# for details +# +if RUBY_VERSION == '1.8.7' + class NilClass + def closed? + true + end + end +end + +class Object + # ActiveSupport 2.3.x mixes in a dangerous method + # that can cause rspec to fork bomb + # and other strange things like that. + def daemonize + raise NotImplementedError, "Kernel.daemonize is too dangerous, please don't try to use it." + end + + # The following code allows callers to make assertions that are only + # checked when the environment variable PUPPET_ENABLE_ASSERTIONS is + # set to a non-empty string. For example: + # + # assert_that { condition } + # assert_that(message) { condition } + if ENV["PUPPET_ENABLE_ASSERTIONS"].to_s != '' + def assert_that(message = nil) + unless yield + raise Exception.new("Assertion failure: #{message}") + end + end + else + def assert_that(message = nil) + end + end +end + +# Workaround for yaml_initialize, which isn't supported before Ruby +# 1.8.3. +if RUBY_VERSION == '1.8.1' || RUBY_VERSION == '1.8.2' + YAML.add_ruby_type( /^object/ ) { |tag, val| + type, obj_class = YAML.read_type_class( tag, Object ) + r = YAML.object_maker( obj_class, val ) + if r.respond_to? :yaml_initialize + r.instance_eval { instance_variables.each { |name| remove_instance_variable name } } + r.yaml_initialize(tag, val) + end + r + } +end + +class Array + # Ruby < 1.8.7 doesn't have this method but we use it in tests + def combination(num) + return [] if num < 0 || num > size + return [[]] if num == 0 + return map{|e| [e] } if num == 1 + tmp = self.dup + self[0, size - (num - 1)].inject([]) do |ret, e| + tmp.shift + ret += tmp.combination(num - 1).map{|a| a.unshift(e) } + end + end unless method_defined? :combination +end + + +class Symbol + def to_proc + Proc.new { |*args| args.shift.__send__(self, *args) } + end unless method_defined? :to_proc +end --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/lib/puppet/util/settings.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/lib/puppet/util/settings.rb @@ -0,0 +1,932 @@ +require 'puppet' +require 'sync' +require 'getoptlong' +require 'puppet/external/event-loop' +require 'puppet/util/cacher' +require 'puppet/util/loadedfile' + +# The class for handling configuration files. +class Puppet::Util::Settings + include Enumerable + include Puppet::Util::Cacher + + require 'puppet/util/settings/setting' + require 'puppet/util/settings/file_setting' + require 'puppet/util/settings/boolean_setting' + + attr_accessor :file + attr_reader :timer + + ReadOnly = [:run_mode, :name] + + # Retrieve a config value + def [](param) + value(param) + end + + # Set a config value. This doesn't set the defaults, it sets the value itself. + def []=(param, value) + set_value(param, value, :memory) + end + + # Generate the list of valid arguments, in a format that GetoptLong can + # understand, and add them to the passed option list. + def addargs(options) + # Add all of the config parameters as valid options. + self.each { |name, setting| + setting.getopt_args.each { |args| options << args } + } + + options + end + + # Generate the list of valid arguments, in a format that OptionParser can + # understand, and add them to the passed option list. + def optparse_addargs(options) + # Add all of the config parameters as valid options. + self.each { |name, setting| + options << setting.optparse_args + } + + options + end + + # Is our parameter a boolean parameter? + def boolean?(param) + param = param.to_sym + !!(@config.include?(param) and @config[param].kind_of? BooleanSetting) + end + + # Remove all set values, potentially skipping cli values. + def clear(exceptcli = false) + @sync.synchronize do + unsafe_clear(exceptcli) + end + end + + # Remove all set values, potentially skipping cli values. + def unsafe_clear(exceptcli = false) + @values.each do |name, values| + @values.delete(name) unless exceptcli and name == :cli + end + + # Don't clear the 'used' in this case, since it's a config file reparse, + # and we want to retain this info. + @used = [] unless exceptcli + + @cache.clear + end + + # This is mostly just used for testing. + def clearused + @cache.clear + @used = [] + end + + # Do variable interpolation on the value. + def convert(value, environment = nil) + return value unless value + return value unless value.is_a? String + newval = value.gsub(/\$(\w+)|\$\{(\w+)\}/) do |value| + varname = $2 || $1 + if varname == "environment" and environment + environment + elsif pval = self.value(varname, environment) + pval + else + raise Puppet::DevError, "Could not find value for #{value}" + end + end + + newval + end + + # Return a value's description. + def description(name) + if obj = @config[name.to_sym] + obj.desc + else + nil + end + end + + def each + @config.each { |name, object| + yield name, object + } + end + + # Iterate over each section name. + def eachsection + yielded = [] + @config.each do |name, object| + section = object.section + unless yielded.include? section + yield section + yielded << section + end + end + end + + # Return an object by name. + def setting(param) + param = param.to_sym + @config[param] + end + + # Handle a command-line argument. + def handlearg(opt, value = nil) + @cache.clear + value &&= munge_value(value) + str = opt.sub(/^--/,'') + + bool = true + newstr = str.sub(/^no-/, '') + if newstr != str + str = newstr + bool = false + end + str = str.intern + + if @config[str].is_a?(Puppet::Util::Settings::BooleanSetting) + if value == "" or value.nil? + value = bool + end + end + + set_value(str, value, :cli) + end + + def include?(name) + name = name.intern if name.is_a? String + @config.include?(name) + end + + # check to see if a short name is already defined + def shortinclude?(short) + short = short.intern if name.is_a? String + @shortnames.include?(short) + end + + # Create a new collection of config settings. + def initialize + @config = {} + @shortnames = {} + + @created = [] + @searchpath = nil + + # Mutex-like thing to protect @values + @sync = Sync.new + + # Keep track of set values. + @values = Hash.new { |hash, key| hash[key] = {} } + + # And keep a per-environment cache + @cache = Hash.new { |hash, key| hash[key] = {} } + + # The list of sections we've used. + @used = [] + end + + # NOTE: ACS ahh the util classes. . .sigh + # as part of a fix for 1183, I pulled the logic for the following 5 methods out of the executables and puppet.rb + # They probably deserve their own class, but I don't want to do that until I can refactor environments + # its a little better than where they were + + # Prints the contents of a config file with the available config settings, or it + # prints a single value of a config setting. + def print_config_options + env = value(:environment) + val = value(:configprint) + if val == "all" + hash = {} + each do |name, obj| + val = value(name,env) + val = val.inspect if val == "" + hash[name] = val + end + hash.sort { |a,b| a[0].to_s <=> b[0].to_s }.each do |name, val| + puts "#{name} = #{val}" + end + else + val.split(/\s*,\s*/).sort.each do |v| + if include?(v) + #if there is only one value, just print it for back compatibility + if v == val + puts value(val,env) + break + end + puts "#{v} = #{value(v,env)}" + else + puts "invalid parameter: #{v}" + return false + end + end + end + true + end + + def generate_config + puts to_config + true + end + + def generate_manifest + puts to_manifest + true + end + + def print_configs + return print_config_options if value(:configprint) != "" + return generate_config if value(:genconfig) + generate_manifest if value(:genmanifest) + end + + def print_configs? + (value(:configprint) != "" || value(:genconfig) || value(:genmanifest)) && true + end + + # Return a given object's file metadata. + def metadata(param) + if obj = @config[param.to_sym] and obj.is_a?(FileSetting) + return [:owner, :group, :mode].inject({}) do |meta, p| + if v = obj.send(p) + meta[p] = v + end + meta + end + else + nil + end + end + + # Make a directory with the appropriate user, group, and mode + def mkdir(default) + obj = get_config_file_default(default) + + Puppet::Util::SUIDManager.asuser(obj.owner, obj.group) do + mode = obj.mode || 0750 + Dir.mkdir(obj.value, mode) + end + end + + # Figure out the section name for the run_mode. + def run_mode + Puppet.run_mode.name + end + + # Return all of the parameters associated with a given section. + def params(section = nil) + if section + section = section.intern if section.is_a? String + @config.find_all { |name, obj| + obj.section == section + }.collect { |name, obj| + name + } + else + @config.keys + end + end + + # Parse the configuration file. Just provides + # thread safety. + def parse + raise "No :config setting defined; cannot parse unknown config file" unless self[:config] + + @sync.synchronize do + unsafe_parse(self[:config]) + end + + # Create a timer so that this file will get checked automatically + # and reparsed if necessary. + set_filetimeout_timer + end + + # Unsafely parse the file -- this isn't thread-safe and causes plenty of problems if used directly. + def unsafe_parse(file) + return unless FileTest.exist?(file) + begin + data = parse_file(file) + rescue => details + puts details.backtrace if Puppet[:trace] + Puppet.err "Could not parse #{file}: #{details}" + return + end + + unsafe_clear(true) + + metas = {} + data.each do |area, values| + metas[area] = values.delete(:_meta) + values.each do |key,value| + set_value(key, value, area, :dont_trigger_handles => true, :ignore_bad_settings => true ) + end + end + + # Determine our environment, if we have one. + if @config[:environment] + env = self.value(:environment).to_sym + else + env = "none" + end + + # Call any hooks we should be calling. + settings_with_hooks.each do |setting| + each_source(env) do |source| + if value = @values[source][setting.name] + # We still have to use value to retrieve the value, since + # we want the fully interpolated value, not $vardir/lib or whatever. + # This results in extra work, but so few of the settings + # will have associated hooks that it ends up being less work this + # way overall. + setting.handle(self.value(setting.name, env)) + break + end + end + end + + # We have to do it in the reverse of the search path, + # because multiple sections could set the same value + # and I'm too lazy to only set the metadata once. + searchpath.reverse.each do |source| + source = run_mode if source == :run_mode + source = @name if (@name && source == :name) + if meta = metas[source] + set_metadata(meta) + end + end + end + + # Create a new setting. The value is passed in because it's used to determine + # what kind of setting we're creating, but the value itself might be either + # a default or a value, so we can't actually assign it. + def newsetting(hash) + klass = nil + hash[:section] = hash[:section].to_sym if hash[:section] + if type = hash[:type] + unless klass = {:setting => Setting, :file => FileSetting, :boolean => BooleanSetting}[type] + raise ArgumentError, "Invalid setting type '#{type}'" + end + hash.delete(:type) + else + case hash[:default] + when true, false, "true", "false" + klass = BooleanSetting + when /^\$\w+\//, /^\//, /^\w:\// + klass = FileSetting + when String, Integer, Float # nothing + klass = Setting + else + raise ArgumentError, "Invalid value '#{hash[:default].inspect}' for #{hash[:name]}" + end + end + hash[:settings] = self + setting = klass.new(hash) + + setting + end + + # This has to be private, because it doesn't add the settings to @config + private :newsetting + + # Iterate across all of the objects in a given section. + def persection(section) + section = section.to_sym + self.each { |name, obj| + if obj.section == section + yield obj + end + } + end + + # Cache this in an easily clearable way, since we were + # having trouble cleaning it up after tests. + cached_attr(:file) do + if path = self[:config] and FileTest.exist?(path) + Puppet::Util::LoadedFile.new(path) + end + end + + # Reparse our config file, if necessary. + def reparse + if file and file.changed? + Puppet.notice "Reparsing #{file.file}" + parse + reuse + end + end + + def reuse + return unless defined?(@used) + @sync.synchronize do # yay, thread-safe + new = @used + @used = [] + self.use(*new) + end + end + + # The order in which to search for values. + def searchpath(environment = nil) + if environment + [:cli, :memory, environment, :run_mode, :main, :mutable_defaults] + else + [:cli, :memory, :run_mode, :main, :mutable_defaults] + end + end + + # Get a list of objects per section + def sectionlist + sectionlist = [] + self.each { |name, obj| + section = obj.section || "puppet" + sections[section] ||= [] + sectionlist << section unless sectionlist.include?(section) + sections[section] << obj + } + + return sectionlist, sections + end + + def service_user_available? + return @service_user_available if defined?(@service_user_available) + + return @service_user_available = false unless user_name = self[:user] + + user = Puppet::Type.type(:user).new :name => self[:user], :audit => :ensure + + @service_user_available = user.exists? + end + + def legacy_to_mode(type, param) + if not defined?(@app_names) + require 'puppet/util/command_line' + command_line = Puppet::Util::CommandLine.new + @app_names = Puppet::Util::CommandLine::LegacyName.inject({}) do |hash, pair| + app, legacy = pair + command_line.require_application app + hash[legacy.to_sym] = Puppet::Application.find(app).run_mode.name + hash + end + end + if new_type = @app_names[type] + Puppet.warning "You have configuration parameter $#{param} specified in [#{type}], which is a deprecated section. I'm assuming you meant [#{new_type}]" + return new_type + end + type + end + + def set_value(param, value, type, options = {}) + param = param.to_sym + unless setting = @config[param] + if options[:ignore_bad_settings] + return + else + raise ArgumentError, + "Attempt to assign a value to unknown configuration parameter #{param.inspect}" + end + end + value = setting.munge(value) if setting.respond_to?(:munge) + setting.handle(value) if setting.respond_to?(:handle) and not options[:dont_trigger_handles] + if ReadOnly.include? param and type != :mutable_defaults + raise ArgumentError, + "You're attempting to set configuration parameter $#{param}, which is read-only." + end + type = legacy_to_mode(type, param) + @sync.synchronize do # yay, thread-safe + @values[type][param] = value + @cache.clear + + clearused + + # Clear the list of environments, because they cache, at least, the module path. + # We *could* preferentially just clear them if the modulepath is changed, + # but we don't really know if, say, the vardir is changed and the modulepath + # is defined relative to it. We need the defined?(stuff) because of loading + # order issues. + Puppet::Node::Environment.clear if defined?(Puppet::Node) and defined?(Puppet::Node::Environment) + end + + value + end + + # Set a bunch of defaults in a given section. The sections are actually pretty + # pointless, but they help break things up a bit, anyway. + def setdefaults(section, defs) + section = section.to_sym + call = [] + defs.each { |name, hash| + if hash.is_a? Array + unless hash.length == 2 + raise ArgumentError, "Defaults specified as an array must contain only the default value and the decription" + end + tmp = hash + hash = {} + [:default, :desc].zip(tmp).each { |p,v| hash[p] = v } + end + name = name.to_sym + hash[:name] = name + hash[:section] = section + raise ArgumentError, "Parameter #{name} is already defined" if @config.include?(name) + tryconfig = newsetting(hash) + if short = tryconfig.short + if other = @shortnames[short] + raise ArgumentError, "Parameter #{other.name} is already using short name '#{short}'" + end + @shortnames[short] = tryconfig + end + @config[name] = tryconfig + + # Collect the settings that need to have their hooks called immediately. + # We have to collect them so that we can be sure we're fully initialized before + # the hook is called. + call << tryconfig if tryconfig.call_on_define + } + + call.each { |setting| setting.handle(self.value(setting.name)) } + end + + # Create a timer to check whether the file should be reparsed. + def set_filetimeout_timer + return unless timeout = self[:filetimeout] and timeout = Integer(timeout) and timeout > 0 + timer = EventLoop::Timer.new(:interval => timeout, :tolerance => 1, :start? => true) { self.reparse } + end + + # Convert the settings we manage into a catalog full of resources that model those settings. + def to_catalog(*sections) + sections = nil if sections.empty? + + catalog = Puppet::Resource::Catalog.new("Settings") + + @config.values.find_all { |value| value.is_a?(FileSetting) }.each do |file| + next unless (sections.nil? or sections.include?(file.section)) + next unless resource = file.to_resource + next if catalog.resource(resource.ref) + + catalog.add_resource(resource) + end + + add_user_resources(catalog, sections) + + catalog + end + + # Convert our list of config settings into a configuration file. + def to_config + str = %{The configuration file for #{Puppet[:name]}. Note that this file +is likely to have unused configuration parameters in it; any parameter that's +valid anywhere in Puppet can be in any config file, even if it's not used. + +Every section can specify three special parameters: owner, group, and mode. +These parameters affect the required permissions of any files specified after +their specification. Puppet will sometimes use these parameters to check its +own configured state, so they can be used to make Puppet a bit more self-managing. + +Generated on #{Time.now}. + +}.gsub(/^/, "# ") + +# Add a section heading that matches our name. +if @config.include?(:run_mode) + str += "[#{self[:run_mode]}]\n" + end + eachsection do |section| + persection(section) do |obj| + str += obj.to_config + "\n" unless ReadOnly.include? obj.name or obj.name == :genconfig + end + end + + return str + end + + # Convert to a parseable manifest + def to_manifest + catalog = to_catalog + catalog.resource_refs.collect do |ref| + catalog.resource(ref).to_manifest + end.join("\n\n") + end + + # Create the necessary objects to use a section. This is idempotent; + # you can 'use' a section as many times as you want. + def use(*sections) + sections = sections.collect { |s| s.to_sym } + @sync.synchronize do # yay, thread-safe + sections = sections.reject { |s| @used.include?(s) } + + return if sections.empty? + + begin + catalog = to_catalog(*sections).to_ral + rescue => detail + puts detail.backtrace if Puppet[:trace] + Puppet.err "Could not create resources for managing Puppet's files and directories in sections #{sections.inspect}: #{detail}" + + # We need some way to get rid of any resources created during the catalog creation + # but not cleaned up. + return + end + + catalog.host_config = false + catalog.apply do |transaction| + if transaction.any_failed? + report = transaction.report + failures = report.logs.find_all { |log| log.level == :err } + raise "Got #{failures.length} failure(s) while initializing: #{failures.collect { |l| l.to_s }.join("; ")}" + end + end + + sections.each { |s| @used << s } + @used.uniq! + end + end + + def valid?(param) + param = param.to_sym + @config.has_key?(param) + end + + def uninterpolated_value(param, environment = nil) + param = param.to_sym + environment &&= environment.to_sym + + # See if we can find it within our searchable list of values + val = catch :foundval do + each_source(environment) do |source| + # Look for the value. We have to test the hash for whether + # it exists, because the value might be false. + @sync.synchronize do + throw :foundval, @values[source][param] if @values[source].include?(param) + end + end + throw :foundval, nil + end + + # If we didn't get a value, use the default + val = @config[param].default if val.nil? + + val + end + + # Find the correct value using our search path. Optionally accept an environment + # in which to search before the other configuration sections. + def value(param, environment = nil) + param = param.to_sym + environment &&= environment.to_sym + + # Short circuit to nil for undefined parameters. + return nil unless @config.include?(param) + + # Yay, recursion. + #self.reparse unless [:config, :filetimeout].include?(param) + + # Check the cache first. It needs to be a per-environment + # cache so that we don't spread values from one env + # to another. + if cached = @cache[environment||"none"][param] + return cached + end + + val = uninterpolated_value(param, environment) + + if param == :code + # if we interpolate code, all hell breaks loose. + return val + end + + # Convert it if necessary + val = convert(val, environment) + + # And cache it + @cache[environment||"none"][param] = val + val + end + + # Open a file with the appropriate user, group, and mode + def write(default, *args, &bloc) + obj = get_config_file_default(default) + writesub(default, value(obj.name), *args, &bloc) + end + + # Open a non-default file under a default dir with the appropriate user, + # group, and mode + def writesub(default, file, *args, &bloc) + obj = get_config_file_default(default) + chown = nil + if Puppet.features.root? + chown = [obj.owner, obj.group] + else + chown = [nil, nil] + end + + Puppet::Util::SUIDManager.asuser(*chown) do + mode = obj.mode || 0640 + args << "w" if args.empty? + + args << mode + + # Update the umask to make non-executable files + Puppet::Util.withumask(File.umask ^ 0111) do + File.open(file, *args) do |file| + yield file + end + end + end + end + + def readwritelock(default, *args, &bloc) + file = value(get_config_file_default(default).name) + tmpfile = file + ".tmp" + sync = Sync.new + raise Puppet::DevError, "Cannot create #{file}; directory #{File.dirname(file)} does not exist" unless FileTest.directory?(File.dirname(tmpfile)) + + sync.synchronize(Sync::EX) do + File.open(file, ::File::CREAT|::File::RDWR, 0600) do |rf| + rf.lock_exclusive do + if File.exist?(tmpfile) + raise Puppet::Error, ".tmp file already exists for #{file}; Aborting locked write. Check the .tmp file and delete if appropriate" + end + + # If there's a failure, remove our tmpfile + begin + writesub(default, tmpfile, *args, &bloc) + rescue + File.unlink(tmpfile) if FileTest.exist?(tmpfile) + raise + end + + begin + File.rename(tmpfile, file) + rescue => detail + Puppet.err "Could not rename #{file} to #{tmpfile}: #{detail}" + File.unlink(tmpfile) if FileTest.exist?(tmpfile) + end + end + end + end + end + + private + + def get_config_file_default(default) + obj = nil + unless obj = @config[default] + raise ArgumentError, "Unknown default #{default}" + end + + raise ArgumentError, "Default #{default} is not a file" unless obj.is_a? FileSetting + + obj + end + + # Create the transportable objects for users and groups. + def add_user_resources(catalog, sections) + return unless Puppet.features.root? + return unless self[:mkusers] + + @config.each do |name, setting| + next unless setting.respond_to?(:owner) + next unless sections.nil? or sections.include?(setting.section) + + if user = setting.owner and user != "root" and catalog.resource(:user, user).nil? + resource = Puppet::Resource.new(:user, user, :parameters => {:ensure => :present}) + resource[:gid] = self[:group] if self[:group] + catalog.add_resource resource + end + if group = setting.group and ! %w{root wheel}.include?(group) and catalog.resource(:group, group).nil? + catalog.add_resource Puppet::Resource.new(:group, group, :parameters => {:ensure => :present}) + end + end + end + + # Yield each search source in turn. + def each_source(environment) + searchpath(environment).each do |source| + # Modify the source as necessary. + source = self.run_mode if source == :run_mode + yield source + end + end + + # Return all settings that have associated hooks; this is so + # we can call them after parsing the configuration file. + def settings_with_hooks + @config.values.find_all { |setting| setting.respond_to?(:handle) } + end + + # Extract extra setting information for files. + def extract_fileinfo(string) + result = {} + value = string.sub(/\{\s*([^}]+)\s*\}/) do + params = $1 + params.split(/\s*,\s*/).each do |str| + if str =~ /^\s*(\w+)\s*=\s*([\w\d]+)\s*$/ + param, value = $1.intern, $2 + result[param] = value + raise ArgumentError, "Invalid file option '#{param}'" unless [:owner, :mode, :group].include?(param) + + if param == :mode and value !~ /^\d+$/ + raise ArgumentError, "File modes must be numbers" + end + else + raise ArgumentError, "Could not parse '#{string}'" + end + end + '' + end + result[:value] = value.sub(/\s*$/, '') + result + end + + # Convert arguments into booleans, integers, or whatever. + def munge_value(value) + # Handle different data types correctly + return case value + when /^false$/i; false + when /^true$/i; true + when /^\d+$/i; Integer(value) + when true; true + when false; false + else + value.gsub(/^["']|["']$/,'').sub(/\s+$/, '') + end + end + + # This method just turns a file in to a hash of hashes. + def parse_file(file) + text = read_file(file) + + result = Hash.new { |names, name| + names[name] = {} + } + + count = 0 + + # Default to 'main' for the section. + section = :main + result[section][:_meta] = {} + text.split(/\n/).each { |line| + count += 1 + case line + when /^\s*\[(\w+)\]\s*$/ + section = $1.intern # Section names + # Add a meta section + result[section][:_meta] ||= {} + when /^\s*#/; next # Skip comments + when /^\s*$/; next # Skip blanks + when /^\s*(\w+)\s*=\s*(.*?)\s*$/ # settings + var = $1.intern + + # We don't want to munge modes, because they're specified in octal, so we'll + # just leave them as a String, since Puppet handles that case correctly. + if var == :mode + value = $2 + else + value = munge_value($2) + end + + # Check to see if this is a file argument and it has extra options + begin + if value.is_a?(String) and options = extract_fileinfo(value) + value = options[:value] + options.delete(:value) + result[section][:_meta][var] = options + end + result[section][var] = value + rescue Puppet::Error => detail + detail.file = file + detail.line = line + raise + end + else + error = Puppet::Error.new("Could not match line #{line}") + error.file = file + error.line = line + raise error + end + } + + result + end + + # Read the file in. + def read_file(file) + begin + return File.read(file) + rescue Errno::ENOENT + raise ArgumentError, "No such file #{file}" + rescue Errno::EACCES + raise ArgumentError, "Permission denied to file #{file}" + end + end + + # Set file metadata. + def set_metadata(meta) + meta.each do |var, values| + values.each do |param, value| + @config[var].send(param.to_s + "=", value) + end + end + end +end --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/lib/puppet/type/file.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/lib/puppet/type/file.rb @@ -0,0 +1,794 @@ +require 'digest/md5' +require 'cgi' +require 'etc' +require 'uri' +require 'fileutils' +require 'puppet/network/handler' +require 'puppet/util/diff' +require 'puppet/util/checksums' +require 'puppet/network/client' +require 'puppet/util/backups' + +Puppet::Type.newtype(:file) do + include Puppet::Util::MethodHelper + include Puppet::Util::Checksums + include Puppet::Util::Backups + @doc = "Manages local files, including setting ownership and + permissions, creation of both files and directories, and + retrieving entire files from remote servers. As Puppet matures, it + expected that the `file` resource will be used less and less to + manage content, and instead native resources will be used to do so. + + If you find that you are often copying files in from a central + location, rather than using native resources, please contact + Puppet Labs and we can hopefully work with you to develop a + native resource to support what you are doing. + + **Autorequires:** If Puppet is managing the user or group that owns a file, the file resource will autorequire them. If Puppet is managing any parent directories of a file, the file resource will autorequire them." + + def self.title_patterns + [ [ /^(.*?)\/*\Z/m, [ [ :path, lambda{|x| x} ] ] ] ] + end + + newparam(:path) do + desc "The path to the file to manage. Must be fully qualified." + isnamevar + + validate do |value| + # accept various path syntaxes: lone slash, posix, win32, unc + unless (Puppet.features.posix? and value =~ /^\//) or (Puppet.features.microsoft_windows? and (value =~ /^.:\// or value =~ /^\/\/[^\/]+\/[^\/]+/)) + fail Puppet::Error, "File paths must be fully qualified, not '#{value}'" + end + end + + # convert the current path in an index into the collection and the last + # path name. The aim is to use less storage for all common paths in a hierarchy + munge do |value| + path, name = ::File.split(value.gsub(/\/+/,'/')) + { :index => Puppet::FileCollection.collection.index(path), :name => name } + end + + # and the reverse + unmunge do |value| + basedir = Puppet::FileCollection.collection.path(value[:index]) + # a lone slash as :name indicates a root dir on windows + if value[:name] == '/' + basedir + else + ::File.join( basedir, value[:name] ) + end + end + end + + newparam(:backup) do + desc "Whether files should be backed up before + being replaced. The preferred method of backing files up is via + a `filebucket`, which stores files by their MD5 sums and allows + easy retrieval without littering directories with backups. You + can specify a local filebucket or a network-accessible + server-based filebucket by setting `backup => bucket-name`. + Alternatively, if you specify any value that begins with a `.` + (e.g., `.puppet-bak`), then Puppet will use copy the file in + the same directory with that value as the extension of the + backup. Setting `backup => false` disables all backups of the + file in question. + + Puppet automatically creates a local filebucket named `puppet` and + defaults to backing up there. To use a server-based filebucket, + you must specify one in your configuration + + filebucket { main: + server => puppet + } + + The `puppet master` daemon creates a filebucket by default, + so you can usually back up to your main server with this + configuration. Once you've described the bucket in your + configuration, you can use it in any file + + file { \"/my/file\": + source => \"/path/in/nfs/or/something\", + backup => main + } + + This will back the file up to the central server. + + At this point, the benefits of using a filebucket are that you do not + have backup files lying around on each of your machines, a given + version of a file is only backed up once, and you can restore + any given file manually, no matter how old. Eventually, + transactional support will be able to automatically restore + filebucketed files. + " + + defaultto "puppet" + + munge do |value| + # I don't really know how this is happening. + value = value.shift if value.is_a?(Array) + + case value + when false, "false", :false + false + when true, "true", ".puppet-bak", :true + ".puppet-bak" + when String + value + else + self.fail "Invalid backup type #{value.inspect}" + end + end + end + + newparam(:recurse) do + desc "Whether and how deeply to do recursive + management. Options are: + + * `inf,true` --- Regular style recursion on both remote and local + directory structure. + * `remote` --- Descends recursively into the remote directory + but not the local directory. Allows copying of + a few files into a directory containing many + unmanaged files without scanning all the local files. + * `false` --- Default of no recursion. + * `[0-9]+` --- Same as true, but limit recursion. Warning: this syntax + has been deprecated in favor of the `recurselimit` attribute. + " + + newvalues(:true, :false, :inf, :remote, /^[0-9]+$/) + + # Replace the validation so that we allow numbers in + # addition to string representations of them. + validate { |arg| } + munge do |value| + newval = super(value) + case newval + when :true, :inf; true + when :false; false + when :remote; :remote + when Integer, Fixnum, Bignum + self.warning "Setting recursion depth with the recurse parameter is now deprecated, please use recurselimit" + + # recurse == 0 means no recursion + return false if value == 0 + + resource[:recurselimit] = value + true + when /^\d+$/ + self.warning "Setting recursion depth with the recurse parameter is now deprecated, please use recurselimit" + value = Integer(value) + + # recurse == 0 means no recursion + return false if value == 0 + + resource[:recurselimit] = value + true + else + self.fail "Invalid recurse value #{value.inspect}" + end + end + end + + newparam(:recurselimit) do + desc "How deeply to do recursive management." + + newvalues(/^[0-9]+$/) + + munge do |value| + newval = super(value) + case newval + when Integer, Fixnum, Bignum; value + when /^\d+$/; Integer(value) + else + self.fail "Invalid recurselimit value #{value.inspect}" + end + end + end + + newparam(:replace, :boolean => true) do + desc "Whether or not to replace a file that is + sourced but exists. This is useful for using file sources + purely for initialization." + newvalues(:true, :false) + aliasvalue(:yes, :true) + aliasvalue(:no, :false) + defaultto :true + end + + newparam(:force, :boolean => true) do + desc "Force the file operation. Currently only used when replacing + directories with links." + newvalues(:true, :false) + defaultto false + end + + newparam(:ignore) do + desc "A parameter which omits action on files matching + specified patterns during recursion. Uses Ruby's builtin globbing + engine, so shell metacharacters are fully supported, e.g. `[a-z]*`. + Matches that would descend into the directory structure are ignored, + e.g., `*/*`." + + validate do |value| + unless value.is_a?(Array) or value.is_a?(String) or value == false + self.devfail "Ignore must be a string or an Array" + end + end + end + + newparam(:links) do + desc "How to handle links during file actions. During file copying, + `follow` will copy the target file instead of the link, `manage` + will copy the link itself, and `ignore` will just pass it by. + When not copying, `manage` and `ignore` behave equivalently + (because you cannot really ignore links entirely during local recursion), and `follow` will manage the file to which the + link points." + + newvalues(:follow, :manage) + + defaultto :manage + end + + newparam(:purge, :boolean => true) do + desc "Whether unmanaged files should be purged. If you have a filebucket + configured the purged files will be uploaded, but if you do not, + this will destroy data. Only use this option for generated + files unless you really know what you are doing. This option only + makes sense when recursively managing directories. + + Note that when using `purge` with `source`, Puppet will purge any files + that are not on the remote system." + + defaultto :false + + newvalues(:true, :false) + end + + newparam(:sourceselect) do + desc "Whether to copy all valid sources, or just the first one. This parameter + is only used in recursive copies; by default, the first valid source is the + only one used as a recursive source, but if this parameter is set to `all`, + then all valid sources will have all of their contents copied to the local host, + and for sources that have the same file, the source earlier in the list will + be used." + + defaultto :first + + newvalues(:first, :all) + end + + # Autorequire any parent directories. + autorequire(:file) do + basedir = ::File.dirname(self[:path]) + if basedir != self[:path] + basedir + else + nil + end + end + + # Autorequire the owner and group of the file. + {:user => :owner, :group => :group}.each do |type, property| + autorequire(type) do + if @parameters.include?(property) + # The user/group property automatically converts to IDs + next unless should = @parameters[property].shouldorig + val = should[0] + if val.is_a?(Integer) or val =~ /^\d+$/ + nil + else + val + end + end + end + end + + CREATORS = [:content, :source, :target] + SOURCE_ONLY_CHECKSUMS = [:none, :ctime, :mtime] + + validate do + creator_count = 0 + CREATORS.each do |param| + creator_count += 1 if self.should(param) + end + creator_count += 1 if @parameters.include?(:source) + self.fail "You cannot specify more than one of #{CREATORS.collect { |p| p.to_s}.join(", ")}" if creator_count > 1 + + self.fail "You cannot specify a remote recursion without a source" if !self[:source] and self[:recurse] == :remote + + self.fail "You cannot specify source when using checksum 'none'" if self[:checksum] == :none && !self[:source].nil? + + SOURCE_ONLY_CHECKSUMS.each do |checksum_type| + self.fail "You cannot specify content when using checksum '#{checksum_type}'" if self[:checksum] == checksum_type && !self[:content].nil? + end + + self.warning "Possible error: recurselimit is set but not recurse, no recursion will happen" if !self[:recurse] and self[:recurselimit] + end + + def self.[](path) + return nil unless path + super(path.gsub(/\/+/, '/').sub(/\/$/, '')) + end + + def self.instances(base = '/') + return self.new(:name => base, :recurse => true, :recurselimit => 1, :audit => :all).recurse_local.values + end + + # Determine the user to write files as. + def asuser + if self.should(:owner) and ! self.should(:owner).is_a?(Symbol) + writeable = Puppet::Util::SUIDManager.asuser(self.should(:owner)) { + FileTest.writable?(::File.dirname(self[:path])) + } + + # If the parent directory is writeable, then we execute + # as the user in question. Otherwise we'll rely on + # the 'owner' property to do things. + asuser = self.should(:owner) if writeable + end + + asuser + end + + def bucket + return @bucket if @bucket + + backup = self[:backup] + return nil unless backup + return nil if backup =~ /^\./ + + unless catalog or backup == "puppet" + fail "Can not find filebucket for backups without a catalog" + end + + unless catalog and filebucket = catalog.resource(:filebucket, backup) or backup == "puppet" + fail "Could not find filebucket #{backup} specified in backup" + end + + return default_bucket unless filebucket + + @bucket = filebucket.bucket + + @bucket + end + + def default_bucket + Puppet::Type.type(:filebucket).mkdefaultbucket.bucket + end + + # Does the file currently exist? Just checks for whether + # we have a stat + def exist? + stat ? true : false + end + + # We have to do some extra finishing, to retrieve our bucket if + # there is one. + def finish + # Look up our bucket, if there is one + bucket + super + end + + # Create any children via recursion or whatever. + def eval_generate + return [] unless self.recurse? + + recurse + #recurse.reject do |resource| + # catalog.resource(:file, resource[:path]) + #end.each do |child| + # catalog.add_resource child + # catalog.relationship_graph.add_edge self, child + #end + end + + def flush + # We want to make sure we retrieve metadata anew on each transaction. + @parameters.each do |name, param| + param.flush if param.respond_to?(:flush) + end + @stat = nil + end + + def initialize(hash) + # Used for caching clients + @clients = {} + + super + + # If they've specified a source, we get our 'should' values + # from it. + unless self[:ensure] + if self[:target] + self[:ensure] = :symlink + elsif self[:content] + self[:ensure] = :file + end + end + + @stat = nil + end + + # Configure discovered resources to be purged. + def mark_children_for_purging(children) + children.each do |name, child| + next if child[:source] + child[:ensure] = :absent + end + end + + # Create a new file or directory object as a child to the current + # object. + def newchild(path) + full_path = ::File.join(self[:path], path) + + # Add some new values to our original arguments -- these are the ones + # set at initialization. We specifically want to exclude any param + # values set by the :source property or any default values. + # LAK:NOTE This is kind of silly, because the whole point here is that + # the values set at initialization should live as long as the resource + # but values set by default or by :source should only live for the transaction + # or so. Unfortunately, we don't have a straightforward way to manage + # the different lifetimes of this data, so we kludge it like this. + # The right-side hash wins in the merge. + options = @original_parameters.merge(:path => full_path).reject { |param, value| value.nil? } + + # These should never be passed to our children. + [:parent, :ensure, :recurse, :recurselimit, :target, :alias, :source].each do |param| + options.delete(param) if options.include?(param) + end + + self.class.new(options) + end + + # Files handle paths specially, because they just lengthen their + # path names, rather than including the full parent's title each + # time. + def pathbuilder + # We specifically need to call the method here, so it looks + # up our parent in the catalog graph. + if parent = parent() + # We only need to behave specially when our parent is also + # a file + if parent.is_a?(self.class) + # Remove the parent file name + list = parent.pathbuilder + list.pop # remove the parent's path info + return list << self.ref + else + return super + end + else + return [self.ref] + end + end + + # Should we be purging? + def purge? + @parameters.include?(:purge) and (self[:purge] == :true or self[:purge] == "true") + end + + # Recursively generate a list of file resources, which will + # be used to copy remote files, manage local files, and/or make links + # to map to another directory. + def recurse + children = (self[:recurse] == :remote) ? {} : recurse_local + + if self[:target] + recurse_link(children) + elsif self[:source] + recurse_remote(children) + end + + # If we're purging resources, then delete any resource that isn't on the + # remote system. + mark_children_for_purging(children) if self.purge? + + result = children.values.sort { |a, b| a[:path] <=> b[:path] } + remove_less_specific_files(result) + end + + # This is to fix bug #2296, where two files recurse over the same + # set of files. It's a rare case, and when it does happen you're + # not likely to have many actual conflicts, which is good, because + # this is a pretty inefficient implementation. + def remove_less_specific_files(files) + mypath = self[:path].split(::File::Separator) + other_paths = catalog.vertices. + select { |r| r.is_a?(self.class) and r[:path] != self[:path] }. + collect { |r| r[:path].split(::File::Separator) }. + select { |p| p[0,mypath.length] == mypath } + + return files if other_paths.empty? + + files.reject { |file| + path = file[:path].split(::File::Separator) + other_paths.any? { |p| path[0,p.length] == p } + } + end + + # A simple method for determining whether we should be recursing. + def recurse? + self[:recurse] == true or self[:recurse] == :remote + end + + # Recurse the target of the link. + def recurse_link(children) + perform_recursion(self[:target]).each do |meta| + if meta.relative_path == "." + self[:ensure] = :directory + next + end + + children[meta.relative_path] ||= newchild(meta.relative_path) + if meta.ftype == "directory" + children[meta.relative_path][:ensure] = :directory + else + children[meta.relative_path][:ensure] = :link + children[meta.relative_path][:target] = meta.full_path + end + end + children + end + + # Recurse the file itself, returning a Metadata instance for every found file. + def recurse_local + result = perform_recursion(self[:path]) + return {} unless result + result.inject({}) do |hash, meta| + next hash if meta.relative_path == "." + + hash[meta.relative_path] = newchild(meta.relative_path) + hash + end + end + + # Recurse against our remote file. + def recurse_remote(children) + sourceselect = self[:sourceselect] + + total = self[:source].collect do |source| + next unless result = perform_recursion(source) + return if top = result.find { |r| r.relative_path == "." } and top.ftype != "directory" + result.each { |data| data.source = "#{source}/#{data.relative_path}" } + break result if result and ! result.empty? and sourceselect == :first + result + end.flatten + + # This only happens if we have sourceselect == :all + unless sourceselect == :first + found = [] + total.reject! do |data| + result = found.include?(data.relative_path) + found << data.relative_path unless found.include?(data.relative_path) + result + end + end + + total.each do |meta| + if meta.relative_path == "." + parameter(:source).metadata = meta + next + end + children[meta.relative_path] ||= newchild(meta.relative_path) + children[meta.relative_path][:source] = meta.source + children[meta.relative_path][:checksum] = :md5 if meta.ftype == "file" + + children[meta.relative_path].parameter(:source).metadata = meta + end + + children + end + + def perform_recursion(path) + Puppet::FileServing::Metadata.indirection.search( + path, + :links => self[:links], + :recurse => (self[:recurse] == :remote ? true : self[:recurse]), + :recurselimit => self[:recurselimit], + :ignore => self[:ignore], + :checksum_type => (self[:source] || self[:content]) ? self[:checksum] : :none + ) + end + + # Remove any existing data. This is only used when dealing with + # links or directories. + def remove_existing(should) + return unless s = stat + + self.fail "Could not back up; will not replace" unless perform_backup + + unless should.to_s == "link" + return if s.ftype.to_s == should.to_s + end + + case s.ftype + when "directory" + if self[:force] == :true + debug "Removing existing directory for replacement with #{should}" + FileUtils.rmtree(self[:path]) + else + notice "Not removing directory; use 'force' to override" + end + when "link", "file" + debug "Removing existing #{s.ftype} for replacement with #{should}" + ::File.unlink(self[:path]) + else + self.fail "Could not back up files of type #{s.ftype}" + end + expire + end + + def retrieve + if source = parameter(:source) + source.copy_source_values + end + super + end + + # Set the checksum, from another property. There are multiple + # properties that modify the contents of a file, and they need the + # ability to make sure that the checksum value is in sync. + def setchecksum(sum = nil) + if @parameters.include? :checksum + if sum + @parameters[:checksum].checksum = sum + else + # If they didn't pass in a sum, then tell checksum to + # figure it out. + currentvalue = @parameters[:checksum].retrieve + @parameters[:checksum].checksum = currentvalue + end + end + end + + # Should this thing be a normal file? This is a relatively complex + # way of determining whether we're trying to create a normal file, + # and it's here so that the logic isn't visible in the content property. + def should_be_file? + return true if self[:ensure] == :file + + # I.e., it's set to something like "directory" + return false if e = self[:ensure] and e != :present + + # The user doesn't really care, apparently + if self[:ensure] == :present + return true unless s = stat + return(s.ftype == "file" ? true : false) + end + + # If we've gotten here, then :ensure isn't set + return true if self[:content] + return true if stat and stat.ftype == "file" + false + end + + # Stat our file. Depending on the value of the 'links' attribute, we + # use either 'stat' or 'lstat', and we expect the properties to use the + # resulting stat object accordingly (mostly by testing the 'ftype' + # value). + cached_attr(:stat) do + method = :stat + + # Files are the only types that support links + if (self.class.name == :file and self[:links] != :follow) or self.class.name == :tidy + method = :lstat + end + path = self[:path] + + begin + ::File.send(method, self[:path]) + rescue Errno::ENOENT => error + return nil + rescue Errno::EACCES => error + warning "Could not stat; permission denied" + return nil + end + end + + # We have to hack this just a little bit, because otherwise we'll get + # an error when the target and the contents are created as properties on + # the far side. + def to_trans(retrieve = true) + obj = super + obj.delete(:target) if obj[:target] == :notlink + obj + end + + # Write out the file. Requires the property name for logging. + # Write will be done by the content property, along with checksum computation + def write(property) + remove_existing(:file) + + use_temporary_file = write_temporary_file? + if use_temporary_file + path = "#{self[:path]}.puppettmp_#{rand(10000)}" + path = "#{self[:path]}.puppettmp_#{rand(10000)}" while ::File.exists?(path) or ::File.symlink?(path) + else + path = self[:path] + end + + mode = self.should(:mode) # might be nil + umask = mode ? 000 : 022 + mode_int = mode ? mode.to_i(8) : nil + + content_checksum = Puppet::Util.withumask(umask) { ::File.open(path, 'w', mode_int ) { |f| write_content(f) } } + + # And put our new file in place + if use_temporary_file # This is only not true when our file is empty. + begin + fail_if_checksum_is_wrong(path, content_checksum) if validate_checksum? + ::File.rename(path, self[:path]) + rescue => detail + fail "Could not rename temporary file #{path} to #{self[:path]}: #{detail}" + ensure + # Make sure the created file gets removed + ::File.unlink(path) if FileTest.exists?(path) + end + end + + # make sure all of the modes are actually correct + property_fix + + end + + private + + # Should we validate the checksum of the file we're writing? + def validate_checksum? + self[:checksum] !~ /time/ + end + + # Make sure the file we wrote out is what we think it is. + def fail_if_checksum_is_wrong(path, content_checksum) + newsum = parameter(:checksum).sum_file(path) + return if [:absent, nil, content_checksum].include?(newsum) + + self.fail "File written to disk did not match checksum; discarding changes (#{content_checksum} vs #{newsum})" + end + + # write the current content. Note that if there is no content property + # simply opening the file with 'w' as done in write is enough to truncate + # or write an empty length file. + def write_content(file) + (content = property(:content)) && content.write(file) + end + + private + + def write_temporary_file? + # unfortunately we don't know the source file size before fetching it + # so let's assume the file won't be empty + (c = property(:content) and c.length) || (s = @parameters[:source] and 1) + end + + # There are some cases where all of the work does not get done on + # file creation/modification, so we have to do some extra checking. + def property_fix + properties.each do |thing| + next unless [:mode, :owner, :group, :seluser, :selrole, :seltype, :selrange].include?(thing.name) + + # Make sure we get a new stat objct + expire + currentvalue = thing.retrieve + thing.sync unless thing.safe_insync?(currentvalue) + end + end +end + +# We put all of the properties in separate files, because there are so many +# of them. The order these are loaded is important, because it determines +# the order they are in the property lit. +require 'puppet/type/file/checksum' +require 'puppet/type/file/content' # can create the file +require 'puppet/type/file/source' # can create the file +require 'puppet/type/file/target' # creates a different type of file +require 'puppet/type/file/ensure' # can create the file +require 'puppet/type/file/owner' +require 'puppet/type/file/group' +require 'puppet/type/file/mode' +require 'puppet/type/file/type' +require 'puppet/type/file/selcontext' # SELinux file context +require 'puppet/type/file/ctime' +require 'puppet/type/file/mtime' --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/lib/puppet/sslcertificates/ca.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/lib/puppet/sslcertificates/ca.rb @@ -0,0 +1,375 @@ +require 'sync' + +class Puppet::SSLCertificates::CA + include Puppet::Util::Warnings + + Certificate = Puppet::SSLCertificates::Certificate + attr_accessor :keyfile, :file, :config, :dir, :cert, :crl + + def certfile + @config[:cacert] + end + + # Remove all traces of a given host. This is kind of hackish, but, eh. + def clean(host) + host = host.downcase + [:csrdir, :signeddir, :publickeydir, :privatekeydir, :certdir].each do |name| + dir = Puppet[name] + + file = File.join(dir, host + ".pem") + + if FileTest.exists?(file) + begin + if Puppet[:name] == "cert" + puts "Removing #{file}" + else + Puppet.info "Removing #{file}" + end + File.unlink(file) + rescue => detail + raise Puppet::Error, "Could not delete #{file}: #{detail}" + end + end + + end + end + + def host2csrfile(hostname) + File.join(Puppet[:csrdir], [hostname.downcase, "pem"].join(".")) + end + + # this stores signed certs in a directory unrelated to + # normal client certs + def host2certfile(hostname) + File.join(Puppet[:signeddir], [hostname.downcase, "pem"].join(".")) + end + + # Turn our hostname into a Name object + def thing2name(thing) + thing.subject.to_a.find { |ary| + ary[0] == "CN" + }[1] + end + + def initialize(hash = {}) + Puppet.settings.use(:main, :ca, :ssl) + self.setconfig(hash) + + if Puppet[:capass] + if FileTest.exists?(Puppet[:capass]) + #puts "Reading #{Puppet[:capass]}" + #system "ls -al #{Puppet[:capass]}" + #File.read Puppet[:capass] + @config[:password] = self.getpass + else + # Don't create a password if the cert already exists + @config[:password] = self.genpass unless FileTest.exists?(@config[:cacert]) + end + end + + self.getcert + init_crl + unless FileTest.exists?(@config[:serial]) + Puppet.settings.write(:serial) do |f| + f << "%04X" % 1 + end + end + end + + # Generate a new password for the CA. + def genpass + pass = "" + 20.times { pass += (rand(74) + 48).chr } + + begin + Puppet.settings.write(:capass) { |f| f.print pass } + rescue Errno::EACCES => detail + raise Puppet::Error, detail.to_s + end + pass + end + + # Get the CA password. + def getpass + if @config[:capass] and File.readable?(@config[:capass]) + return File.read(@config[:capass]) + else + raise Puppet::Error, "Could not decrypt CA key with password: #{detail}" + end + end + + # Get the CA cert. + def getcert + if FileTest.exists?(@config[:cacert]) + @cert = OpenSSL::X509::Certificate.new( + File.read(@config[:cacert]) + ) + else + self.mkrootcert + end + end + + # Retrieve a client's CSR. + def getclientcsr(host) + csrfile = host2csrfile(host) + return nil unless File.exists?(csrfile) + + OpenSSL::X509::Request.new(File.read(csrfile)) + end + + # Retrieve a client's certificate. + def getclientcert(host) + certfile = host2certfile(host) + return [nil, nil] unless File.exists?(certfile) + + [OpenSSL::X509::Certificate.new(File.read(certfile)), @cert] + end + + # List certificates waiting to be signed. This returns a list of hostnames, not actual + # files -- the names can be converted to full paths with host2csrfile. + def list(dummy_argument=:work_arround_for_ruby_GC_bug) + return Dir.entries(Puppet[:csrdir]).find_all { |file| + file =~ /\.pem$/ + }.collect { |file| + file.sub(/\.pem$/, '') + } + end + + # List signed certificates. This returns a list of hostnames, not actual + # files -- the names can be converted to full paths with host2csrfile. + def list_signed(dummy_argument=:work_arround_for_ruby_GC_bug) + return Dir.entries(Puppet[:signeddir]).find_all { |file| + file =~ /\.pem$/ + }.collect { |file| + file.sub(/\.pem$/, '') + } + end + + # Create the root certificate. + def mkrootcert + # Make the root cert's name "Puppet CA: " plus the FQDN of the host running the CA. + name = "Puppet CA: #{Facter["hostname"].value}" + if domain = Facter["domain"].value + name += ".#{domain}" + end + + cert = Certificate.new( + :name => name, + :cert => @config[:cacert], + :encrypt => @config[:capass], + :key => @config[:cakey], + :selfsign => true, + :ttl => ttl, + :type => :ca + ) + + # This creates the cakey file + Puppet::Util::SUIDManager.asuser(Puppet[:user], Puppet[:group]) do + @cert = cert.mkselfsigned + end + Puppet.settings.write(:cacert) do |f| + f.puts @cert.to_pem + end + Puppet.settings.write(:capub) do |f| + f.puts @cert.public_key + end + cert + end + + def removeclientcsr(host) + csrfile = host2csrfile(host) + raise Puppet::Error, "No certificate request for #{host}" unless File.exists?(csrfile) + + File.unlink(csrfile) + end + + # Revoke the certificate with serial number SERIAL issued by this + # CA. The REASON must be one of the OpenSSL::OCSP::REVOKED_* reasons + def revoke(serial, reason = OpenSSL::OCSP::REVOKED_STATUS_KEYCOMPROMISE) + time = Time.now + revoked = OpenSSL::X509::Revoked.new + revoked.serial = serial + revoked.time = time + enum = OpenSSL::ASN1::Enumerated(reason) + ext = OpenSSL::X509::Extension.new("CRLReason", enum) + revoked.add_extension(ext) + @crl.add_revoked(revoked) + store_crl + end + + # Take the Puppet config and store it locally. + def setconfig(hash) + @config = {} + Puppet.settings.params("ca").each { |param| + param = param.intern if param.is_a? String + if hash.include?(param) + @config[param] = hash[param] + Puppet[param] = hash[param] + hash.delete(param) + else + @config[param] = Puppet[param] + end + } + + if hash.include?(:password) + @config[:password] = hash[:password] + hash.delete(:password) + end + + raise ArgumentError, "Unknown parameters #{hash.keys.join(",")}" if hash.length > 0 + + [:cadir, :csrdir, :signeddir].each { |dir| + raise Puppet::DevError, "#{dir} is undefined" unless @config[dir] + } + end + + # Sign a given certificate request. + def sign(csr) + unless csr.is_a?(OpenSSL::X509::Request) + raise Puppet::Error, + "CA#sign only accepts OpenSSL::X509::Request objects, not #{csr.class}" + end + + raise Puppet::Error, "CSR sign verification failed" unless csr.verify(csr.public_key) + + serial = nil + Puppet.settings.readwritelock(:serial) { |f| + serial = File.read(@config[:serial]).chomp.hex + # increment the serial + f << "%04X" % (serial + 1) + } + + newcert = Puppet::SSLCertificates.mkcert( + :type => :server, + :name => csr.subject, + :ttl => ttl, + :issuer => @cert, + :serial => serial, + :publickey => csr.public_key + ) + + sign_with_key(newcert) + + self.storeclientcert(newcert) + + [newcert, @cert] + end + + # Store the client's CSR for later signing. This is called from + # server/ca.rb, and the CSRs are deleted once the certificate is actually + # signed. + def storeclientcsr(csr) + host = thing2name(csr) + + csrfile = host2csrfile(host) + raise Puppet::Error, "Certificate request for #{host} already exists" if File.exists?(csrfile) + + Puppet.settings.writesub(:csrdir, csrfile) do |f| + f.print csr.to_pem + end + end + + # Store the certificate that we generate. + def storeclientcert(cert) + host = thing2name(cert) + + certfile = host2certfile(host) + Puppet.notice "Overwriting signed certificate #{certfile} for #{host}" if File.exists?(certfile) + + Puppet::SSLCertificates::Inventory::add(cert) + Puppet.settings.writesub(:signeddir, certfile) do |f| + f.print cert.to_pem + end + end + + # TTL for new certificates in seconds. If config param :ca_ttl is set, + # use that, otherwise use :ca_days for backwards compatibility + def ttl + days = @config[:ca_days] + if days && days.size > 0 + warnonce "Parameter ca_ttl is not set. Using depecated ca_days instead." + return @config[:ca_days] * 24 * 60 * 60 + else + ttl = @config[:ca_ttl] + if ttl.is_a?(String) + unless ttl =~ /^(\d+)(y|d|h|s)$/ + raise ArgumentError, "Invalid ca_ttl #{ttl}" + end + case $2 + when 'y' + unit = 365 * 24 * 60 * 60 + when 'd' + unit = 24 * 60 * 60 + when 'h' + unit = 60 * 60 + when 's' + unit = 1 + else + raise ArgumentError, "Invalid unit for ca_ttl #{ttl}" + end + return $1.to_i * unit + else + return ttl + end + end + end + + private + def init_crl + if FileTest.exists?(@config[:cacrl]) + @crl = OpenSSL::X509::CRL.new( + File.read(@config[:cacrl]) + ) + else + # Create new CRL + @crl = OpenSSL::X509::CRL.new + @crl.issuer = @cert.subject + @crl.version = 1 + store_crl + @crl + end + end + + def store_crl + # Increment the crlNumber + e = @crl.extensions.find { |e| e.oid == 'crlNumber' } + ext = @crl.extensions.reject { |e| e.oid == 'crlNumber' } + crlNum = OpenSSL::ASN1::Integer(e ? e.value.to_i + 1 : 0) + ext << OpenSSL::X509::Extension.new("crlNumber", crlNum) + @crl.extensions = ext + + # Set last/next update + now = Time.now + @crl.last_update = now + # Keep CRL valid for 5 years + @crl.next_update = now + 5 * 365*24*60*60 + + sign_with_key(@crl) + Puppet.settings.write(:cacrl) do |f| + f.puts @crl.to_pem + end + end + + def sign_with_key(signable, digest = OpenSSL::Digest::SHA1.new) + cakey = nil + if @config[:password] + begin + cakey = OpenSSL::PKey::RSA.new( + File.read(@config[:cakey]), @config[:password] + ) + rescue + raise Puppet::Error, + "Decrypt of CA private key with password stored in @config[:capass] not possible" + end + else + cakey = OpenSSL::PKey::RSA.new( + File.read(@config[:cakey]) + ) + end + + raise Puppet::Error, "CA Certificate is invalid" unless @cert.check_private_key(cakey) + + signable.sign(cakey, digest) + end +end + --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/lib/puppet/sslcertificates/inventory.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/lib/puppet/sslcertificates/inventory.rb @@ -0,0 +1,38 @@ +# A module for keeping track of all the certificates issued by the CA, ever +# Maintains the file "$cadir/inventory.txt" +module Puppet::SSLCertificates + module Inventory + + # Add CERT to the inventory of issued certs in '$cadir/inventory.txt' + # If no inventory exists yet, build an inventory and list all the + # certificates that have been signed so far + def self.add(cert) + inited = false + inited = true if FileTest.exists?(Puppet[:cert_inventory]) + + Puppet.settings.write(:cert_inventory, "a") do |f| + f.puts((inited ? nil : self.init).to_s + format(cert)) + end + end + + private + + def self.init + inv = "# Inventory of signed certificates\n" + inv += "# SERIAL NOT_BEFORE NOT_AFTER SUBJECT\n" + Dir.glob(File::join(Puppet[:signeddir], "*.pem")) do |f| + inv += format(OpenSSL::X509::Certificate.new(File::read(f))) + "\n" + end + inv + end + + def self.format(cert) + iso = '%Y-%m-%dT%H:%M:%S%Z' + return "0x%04x %s %s %s" % [cert.serial, + cert.not_before.strftime(iso), + cert.not_after.strftime(iso), + cert.subject] + end + end +end + --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/lib/puppet/sslcertificates/support.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/lib/puppet/sslcertificates/support.rb @@ -0,0 +1,146 @@ +require 'puppet/sslcertificates' + +# A module to handle reading of certificates. +module Puppet::SSLCertificates::Support + class MissingCertificate < Puppet::Error; end + class InvalidCertificate < Puppet::Error; end + + attr_reader :cacert + + # Some metaprogramming to create methods for retrieving and creating keys. + # This probably isn't fewer lines than defining each separately... + def self.keytype(name, options, &block) + var = "@#{name}" + + maker = "mk_#{name}" + reader = "read_#{name}" + + unless param = options[:param] + raise ArgumentError, "You must specify the parameter for the key" + end + + unless klass = options[:class] + raise ArgumentError, "You must specify the class for the key" + end + + # Define the method that creates it. + define_method(maker, &block) + + # Define the reading method. + define_method(reader) do + return nil unless FileTest.exists?(Puppet[param]) or rename_files_with_uppercase(Puppet[param]) + + begin + instance_variable_set(var, klass.new(File.read(Puppet[param]))) + rescue => detail + raise InvalidCertificate, "Could not read #{param}: #{detail}" + end + end + + # Define the overall method, which just calls the reader and maker + # as appropriate. + define_method(name) do + unless cert = instance_variable_get(var) + unless cert = send(reader) + cert = send(maker) + Puppet.settings.write(param) { |f| f.puts cert.to_pem } + end + instance_variable_set(var, cert) + end + cert + end + end + + # The key pair. + keytype :key, :param => :hostprivkey, :class => OpenSSL::PKey::RSA do + Puppet.info "Creating a new SSL key at #{Puppet[:hostprivkey]}" + key = OpenSSL::PKey::RSA.new(Puppet[:keylength]) + + # Our key meta programming can only handle one file, so we have + # to separately write out the public key. + Puppet.settings.write(:hostpubkey) do |f| + f.print key.public_key.to_pem + end + return key + end + + # Our certificate request + keytype :csr, :param => :hostcsr, :class => OpenSSL::X509::Request do + Puppet.info "Creating a new certificate request for #{Puppet[:certname]}" + + csr = OpenSSL::X509::Request.new + csr.version = 0 + csr.subject = OpenSSL::X509::Name.new([["CN", Puppet[:certname]]]) + csr.public_key = key.public_key + csr.sign(key, OpenSSL::Digest::MD5.new) + + return csr + end + + keytype :cert, :param => :hostcert, :class => OpenSSL::X509::Certificate do + raise MissingCertificate, "No host certificate" + end + + keytype :ca_cert, :param => :localcacert, :class => OpenSSL::X509::Certificate do + raise MissingCertificate, "No CA certificate" + end + + # Request a certificate from the remote system. This does all of the work + # of creating the cert request, contacting the remote system, and + # storing the cert locally. + def requestcert + begin + cert, cacert = caclient.getcert(@csr.to_pem) + rescue => detail + puts detail.backtrace if Puppet[:trace] + raise Puppet::Error.new("Certificate retrieval failed: #{detail}") + end + + if cert.nil? or cert == "" + return nil + end + Puppet.settings.write(:hostcert) do |f| f.print cert end + Puppet.settings.write(:localcacert) do |f| f.print cacert end + #File.open(@certfile, "w", 0644) { |f| f.print cert } + #File.open(@cacertfile, "w", 0644) { |f| f.print cacert } + begin + @cert = OpenSSL::X509::Certificate.new(cert) + @cacert = OpenSSL::X509::Certificate.new(cacert) + retrieved = true + rescue => detail + raise Puppet::Error.new( + "Invalid certificate: #{detail}" + ) + end + + raise Puppet::DevError, "Received invalid certificate" unless @cert.check_private_key(@key) + retrieved + end + + # A hack method to deal with files that exist with a different case. + # Just renames it; doesn't read it in or anything. + def rename_files_with_uppercase(file) + dir = File.dirname(file) + short = File.basename(file) + + # If the dir isn't present, we clearly don't have the file. + #return nil unless FileTest.directory?(dir) + + raise ArgumentError, "Tried to fix SSL files to a file containing uppercase" unless short.downcase == short + + return false unless File.directory?(dir) + + real_file = Dir.entries(dir).reject { |f| f =~ /^\./ }.find do |other| + other.downcase == short + end + + return nil unless real_file + + full_file = File.join(dir, real_file) + + Puppet.notice "Fixing case in #{full_file}; renaming to #{file}" + File.rename(full_file, file) + + true + end +end --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/lib/puppet/sslcertificates/certificate.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/lib/puppet/sslcertificates/certificate.rb @@ -0,0 +1,255 @@ +class Puppet::SSLCertificates::Certificate + SSLCertificates = Puppet::SSLCertificates + + attr_accessor :certfile, :keyfile, :name, :dir, :hash, :type + attr_accessor :key, :cert, :csr, :cacert + + @@params2names = { + :name => "CN", + :state => "ST", + :country => "C", + :email => "emailAddress", + :org => "O", + :city => "L", + :ou => "OU" + } + + def certname + OpenSSL::X509::Name.new self.subject + end + + def delete + [@certfile,@keyfile].each { |file| + File.unlink(file) if FileTest.exists?(file) + } + + if @hash + File.unlink(@hash) if FileTest.symlink?(@hash) + end + end + + def exists? + FileTest.exists?(@certfile) + end + + def getkey + self.mkkey unless FileTest.exists?(@keyfile) + if @password + + @key = OpenSSL::PKey::RSA.new( + + File.read(@keyfile), + + @password + ) + else + @key = OpenSSL::PKey::RSA.new( + File.read(@keyfile) + ) + end + end + + def initialize(hash) + raise Puppet::Error, "You must specify the common name for the certificate" unless hash.include?(:name) + @name = hash[:name] + + # init a few variables + @cert = @key = @csr = nil + + if hash.include?(:cert) + @certfile = hash[:cert] + @dir = File.dirname(@certfile) + else + @dir = hash[:dir] || Puppet[:certdir] + @certfile = File.join(@dir, @name) + end + + @cacertfile ||= File.join(Puppet[:certdir], "ca.pem") + + Puppet.recmkdir(@dir) unless FileTest.directory?(@dir) + + unless @certfile =~ /\.pem$/ + @certfile += ".pem" + end + @keyfile = hash[:key] || File.join( + Puppet[:privatekeydir], [@name,"pem"].join(".") + ) + Puppet.recmkdir(@dir) unless FileTest.directory?(@dir) + + [@keyfile].each { |file| + dir = File.dirname(file) + + Puppet.recmkdir(dir) unless FileTest.directory?(dir) + } + + @ttl = hash[:ttl] || 365 * 24 * 60 * 60 + @selfsign = hash[:selfsign] || false + @encrypt = hash[:encrypt] || false + @replace = hash[:replace] || false + @issuer = hash[:issuer] || nil + + if hash.include?(:type) + case hash[:type] + when :ca, :client, :server; @type = hash[:type] + else + raise "Invalid Cert type #{hash[:type]}" + end + else + @type = :client + end + + @params = {:name => @name} + [:state, :country, :email, :org, :ou].each { |param| + @params[param] = hash[param] if hash.include?(param) + } + + if @encrypt + if @encrypt =~ /^\// + File.open(@encrypt) { |f| + @password = f.read.chomp + } + else + raise Puppet::Error, ":encrypt must be a path to a pass phrase file" + end + else + @password = nil + end + + @selfsign = hash.include?(:selfsign) && hash[:selfsign] + end + + # this only works for servers, not for users + def mkcsr + self.getkey unless @key + + name = OpenSSL::X509::Name.new self.subject + + @csr = OpenSSL::X509::Request.new + @csr.version = 0 + @csr.subject = name + @csr.public_key = @key.public_key + @csr.sign(@key, OpenSSL::Digest::SHA1.new) + + #File.open(@csrfile, "w") { |f| + # f << @csr.to_pem + #} + + raise Puppet::Error, "CSR sign verification failed" unless @csr.verify(@key.public_key) + + @csr + end + + def mkkey + # @key is the file + + @key = OpenSSL::PKey::RSA.new(1024) +# { |p,n| +# case p +# when 0; Puppet.info "key info: ." # BN_generate_prime +# when 1; Puppet.info "key info: +" # BN_generate_prime +# when 2; Puppet.info "key info: *" # searching good prime, +# # n = #of try, +# # but also data from BN_generate_prime +# when 3; Puppet.info "key info: \n" # found good prime, n==0 - p, n==1 - q, +# # but also data from BN_generate_prime +# else; Puppet.info "key info: *" # BN_generate_prime +# end +# } + + if @password + # passwdproc = proc { @password } + + keytext = @key.export( + + OpenSSL::Cipher::DES.new(:EDE3, :CBC), + + @password + ) + File.open(@keyfile, "w", 0400) { |f| + f << keytext + } + else + File.open(@keyfile, "w", 0400) { |f| + f << @key.to_pem + } + end + + #cmd = "#{ossl} genrsa -out #{@key} 1024" + end + + def mkselfsigned + self.getkey unless @key + + raise Puppet::Error, "Cannot replace existing certificate" if @cert + + args = { + :name => self.certname, + :ttl => @ttl, + :issuer => nil, + :serial => 0x0, + :publickey => @key.public_key + } + if @type + args[:type] = @type + else + args[:type] = :server + end + @cert = SSLCertificates.mkcert(args) + + @cert.sign(@key, OpenSSL::Digest::SHA1.new) if @selfsign + + @cert + end + + def subject(string = false) + subj = @@params2names.collect { |param, name| + [name, @params[param]] if @params.include?(param) + }.reject { |ary| ary.nil? } + + if string + return "/" + subj.collect { |ary| + "%s=%s" % ary + }.join("/") + "/" + else + return subj + end + end + + # verify that we can track down the cert chain or whatever + def verify + "openssl verify -verbose -CAfile /home/luke/.puppet/ssl/certs/ca.pem -purpose sslserver culain.madstop.com.pem" + end + + def write + files = { + @certfile => @cert, + @keyfile => @key, + } + files[@cacertfile] = @cacert if defined?(@cacert) + + files.each { |file,thing| + if thing + next if FileTest.exists?(file) + + text = nil + + if thing.is_a?(OpenSSL::PKey::RSA) and @password + + text = thing.export( + + OpenSSL::Cipher::DES.new(:EDE3, :CBC), + + @password + ) + else + text = thing.to_pem + end + + File.open(file, "w", 0660) { |f| f.print text } + end + } + + SSLCertificates.mkhash(Puppet[:certdir], @cacert, @cacertfile) if defined?(@cacert) + end +end + --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/lib/puppet/face/certificate.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/lib/puppet/face/certificate.rb @@ -0,0 +1,115 @@ +require 'puppet/indirector/face' +require 'puppet/ssl/host' + +Puppet::Indirector::Face.define(:certificate, '0.0.1') do + copyright "Puppet Labs", 2011 + license "Apache 2 license; see COPYING" + + summary "Provide access to the CA for certificate management." + description <<-'EOT' + This subcommand interacts with a local or remote Puppet certificate + authority. Currently, its behavior is not a full superset of `puppet + cert`; specifically, it is unable to mimic puppet cert's "clean" option, + and its "generate" action submits a CSR rather than creating a + signed certificate. + EOT + + option "--ca-location LOCATION" do + summary "Which certificate authority to use (local or remote)." + description <<-'EOT' + Whether to act on the local certificate authority or one provided by a + remote puppet master. Allowed values are 'local' and 'remote.' + + This option is required. + EOT + + before_action do |action, args, options| + Puppet::SSL::Host.ca_location = options[:ca_location].to_sym + end + end + + action :generate do + summary "Generate a new certificate signing request." + arguments "" + returns "Nothing." + description <<-'EOT' + Generates and submits a certificate signing request (CSR) for the + specified host. This CSR will then have to be signed by a user + with the proper authorization on the certificate authority. + + Puppet agent usually handles CSR submission automatically. This action is + primarily useful for requesting certificates for individual users and + external applications. + EOT + examples <<-'EOT' + Request a certificate for "somenode" from the site's CA: + + $ puppet certificate generate somenode.puppetlabs.lan --ca-location remote + EOT + + when_invoked do |name, options| + host = Puppet::SSL::Host.new(name) + host.generate_certificate_request + host.certificate_request.class.indirection.save(host.certificate_request) + end + end + + action :list do + summary "List all certificate signing requests." + returns <<-'EOT' + An array of #inspect output from CSR objects. This output is + currently messy, but does contain the names of nodes requesting + certificates. This action returns #inspect strings even when used + from the Ruby API. + EOT + + when_invoked do |options| + Puppet::SSL::Host.indirection.search("*", { + :for => :certificate_request, + }).map { |h| h.inspect } + end + end + + action :sign do + summary "Sign a certificate signing request for HOST." + arguments "" + returns <<-'EOT' + A string that appears to be (but isn't) an x509 certificate. + EOT + examples <<-'EOT' + Sign somenode.puppetlabs.lan's certificate: + + $ puppet certificate sign somenode.puppetlabs.lan --ca-location remote + EOT + + when_invoked do |name, options| + host = Puppet::SSL::Host.new(name) + host.desired_state = 'signed' + Puppet::SSL::Host.indirection.save(host) + end + end + + # Indirector action doc overrides + find = get_action(:find) + find.summary "Retrieve a certificate." + find.arguments "" + find.returns <<-'EOT' + An x509 SSL certificate. You will usually want to render this as a + string (--render-as s). + + Note that this action has a side effect of caching a copy of the + certificate in Puppet's `ssldir`. + EOT + + destroy = get_action(:destroy) + destroy.summary "Delete a certificate." + destroy.arguments "" + destroy.returns "Nothing." + destroy.description <<-'EOT' + Deletes a certificate. This action currently only works on the local CA. + EOT + + get_action(:search).summary "Invalid for this subcommand." + get_action(:save).summary "Invalid for this subcommand." + get_action(:save).description "Invalid for this subcommand." +end --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/acceptance/tests/ticket_5477_master_not_dectect_sitepp.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/acceptance/tests/ticket_5477_master_not_dectect_sitepp.rb @@ -0,0 +1,46 @@ +# In 2.6, compile does not fail when site.pp does not exist. +# +# However, if a catalog is compiled when site.pp does not exist, +# puppetmaster does not detect when site.pp is created. This requires a restart +# + +test_name "Ticket 5477, Puppet Master does not detect newly created site.pp file" + +# Kill running Puppet Master +step "Master: kill running Puppet Master" +on master, "ps -U puppet | awk '/puppet/ { print \$1 }' | xargs kill" + +# Run tests against Master first +step "Master: mv site.pp file to /tmp, if existing" +on master, "if [ -e /etc/puppet/manifests/site.pp ] ; then mv /etc/puppet/manifests/site.pp /tmp/site.pp-5477 ; fi" + +# Start Puppet Master +#step "Master: Run Puppet Master in verbose mode" +#on master, puppet_master("--verbose") +step "Master: Start Puppet Master" +on master, puppet_master("--certdnsnames=\"puppet:$(hostname -s):$(hostname -f)\" --verbose") + +# Allow puppet server to start accepting conections +sleep 10 + +# Run test on Agents +step "Agent: agent --test" +agents.each { |agent| + on agent, puppet_agent("--test") +} + +# Create a new site.pp +step "Master: create basic site.pp file" +on master, "echo 'notify{ticket_5477_notify:}' > /etc/puppet/manifests/site.pp" + +sleep 20 + +step "Agent: puppet agent --test" +agents.each { |agent| + on agent, "puppet agent -t", :acceptable_exit_codes => [2] + fail_test "Site.pp not detect at Master?" unless + stdout.include? 'ticket_5477_notify' +} + +step "Clean-up site.pp" +on master, "rm /etc/puppet/manifests/site.pp" --- puppet-2.7.1.orig/.pc/CVE-2011-3872.patch/acceptance/tests/ticket_7117_broke_env_criteria_authconf.rb +++ puppet-2.7.1/.pc/CVE-2011-3872.patch/acceptance/tests/ticket_7117_broke_env_criteria_authconf.rb @@ -0,0 +1,44 @@ +test_name "#7117 Broke the environment criteria in auth.conf" + +# add to auth.conf +add_2_authconf = %q{ +path / +environment override +auth any +allow * +} + +step "Save original auth.conf file and create a temp auth.conf" +on master, "cp #{config['puppetpath']}/auth.conf /tmp/auth.conf-7117; echo '#{add_2_authconf}' > #{config['puppetpath']}/auth.conf" + +# Kill running Puppet Master -- should not be running at this point +step "Master: kill running Puppet Master" +on master, "ps -U puppet | awk '/puppet/ { print \$1 }' | xargs kill || echo \"Puppet Master not running\"" +step "Master: Start Puppet Master" +on master, puppet_master("--certdnsnames=\"puppet:$(hostname -s):$(hostname -f)\" --verbose --noop") +# allow Master to start and initialize environment + +step "Verify Puppet Master is ready to accept connections" +host=agents.first +time1 = Time.new +until + on(host, "curl -k https://#{master}:8140") do + sleep 1 + end +time2 = Time.new +elapsed = time2 - time1 +Log.notify "Slept for #{elapsed} seconds waiting for Puppet Master to become ready" + +# Run test on Agents +step "Agent: agent --test" +on agents, puppet_agent("--test") + +step "Fetch agent facts from Puppet Master" +agents.each do |host| + on(host, "curl -k -H \"Accept: yaml\" https://#{master}:8140/override/facts/\`hostname -f\`") do + assert_match(/--- !ruby\/object:Puppet::Node::Facts/, stdout, "Agent Facts not returned for #{host}") + end +end + +step "Restore original auth.conf file" +on master, "cp -f /tmp/auth.conf-7117 #{config['puppetpath']}/auth.conf" --- puppet-2.7.1.orig/debian/puppet-el.dirs +++ puppet-2.7.1/debian/puppet-el.dirs @@ -0,0 +1 @@ +usr/share/emacs/site-lisp --- puppet-2.7.1.orig/debian/puppet.postinst +++ puppet-2.7.1/debian/puppet.postinst @@ -0,0 +1,20 @@ +#!/bin/sh + +set -e + +# Remove renamed configuration files which are now handled by other +# packages +if dpkg-maintscript-helper supports rm_conffile 2>/dev/null; then + + dpkg-maintscript-helper rm_conffile \ + /etc/logrotate.d/puppet 2.6.4-2 puppet -- "$@" + + dpkg-maintscript-helper rm_conffile \ + /etc/logcheck/ignore.d.server/puppet 2.6.4-2 puppet -- "$@" + + dpkg-maintscript-helper rm_conffile \ + /etc/emacs/site-start.d/50puppet-mode-init.el 2.6.4-2 puppet -- "$@" + +fi + +#DEBHELPER# --- puppet-2.7.1.orig/debian/puppet-common.NEWS +++ puppet-2.7.1/debian/puppet-common.NEWS @@ -0,0 +1,23 @@ +puppet (0.25.4-3) unstable; urgency=low + + The pluginsync=true option is no longer set in the default puppet.conf + that is distributed with the package. There is a spurious error that + is thrown when this is enabled, and you have no plugins. Most people + will eventually want this enabled, however for a new puppet installation + it tends to scare people. Given the security implications of pluginsync + (it can append to anything in RUBYLIB, not just puppet/facter related + things), it is better to have it off by default, and let you decide + when you want it enabled. + + -- Micah Anderson Mon, 15 Mar 2010 18:01:15 -0400 + +puppet (0.25.4-2) unstable; urgency=low + + The default location of the puppet template directory has been moved to + /etc/puppet/templates from /var/lib/puppet/templates. + + If you use templates in your manifests, please either set "templatedir" in + /etc/puppet/puppet.conf to the old location, or move your templates to the new + location. + + -- Stig Sandbeck Mathisen Sun, 14 Feb 2010 15:33:30 +0100 --- puppet-2.7.1.orig/debian/puppet-common.install +++ puppet-2.7.1/debian/puppet-common.install @@ -0,0 +1,3 @@ +debian/puppet.conf etc/puppet +debian/tmp/usr/bin/puppet usr/bin +debian/tmp/usr/lib/ruby/1.8/* usr/lib/ruby/1.8 --- puppet-2.7.1.orig/debian/puppet.dirs +++ puppet-2.7.1/debian/puppet.dirs @@ -0,0 +1 @@ +usr/sbin --- puppet-2.7.1.orig/debian/puppet-testsuite.lintian-overrides +++ puppet-2.7.1/debian/puppet-testsuite.lintian-overrides @@ -0,0 +1,4 @@ +# Upstream distributes it like this +puppet-testsuite binary: executable-not-elf-or-script +puppet-testsuite binary: script-not-executable +puppet-testsuite binary: unusual-interpreter --- puppet-2.7.1.orig/debian/puppetmaster.init +++ puppet-2.7.1/debian/puppetmaster.init @@ -0,0 +1,125 @@ +#! /bin/sh +### BEGIN INIT INFO +# Provides: puppetmaster +# Required-Start: $network $named $remote_fs $syslog +# Required-Stop: $network $named $remote_fs $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +### END INIT INFO + +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin +DAEMON=/usr/bin/puppet +DAEMON_OPTS="" +NAME=master +DESC="puppet master" + +test -x $DAEMON || exit 0 + +[ -r /etc/default/puppetmaster ] && . /etc/default/puppetmaster + +. /lib/lsb/init-functions + +if [ ! -d /var/run/puppet ]; then + mkdir -p /var/run/puppet +fi + +chown puppet:puppet /var/run/puppet + +is_true() { + if [ "x$1" = "xtrue" -o "x$1" = "xyes" -o "x$1" = "x0" ] ; then + return 0 + else + return 1 + fi +} + +start_puppet_master() { + if is_true "$START" ; then + if [ "$SERVERTYPE" = "mongrel" ] + then + DAEMON_OPTS="$DAEMON_OPTS --servertype=mongrel" + NUMSTART=0 + STARTPORT=$PORT + while [ $NUMSTART -lt $PUPPETMASTERS ]; do + start-stop-daemon --start --quiet --pidfile=/var/run/puppet/${NAME}-${STARTPORT}.pid \ + --startas $DAEMON -- $NAME $DAEMON_OPTS --masterport=$STARTPORT --pidfile=/var/run/puppet/${NAME}-${STARTPORT}.pid + STARTPORT=$(($STARTPORT + 1)) + NUMSTART=$(($NUMSTART + 1)) + done + else + start-stop-daemon --start --quiet --pidfile /var/run/puppet/${NAME}.pid \ + --startas $DAEMON -- $NAME $DAEMON_OPTS --masterport=$PORT + fi + else + echo "" + echo "puppetmaster not configured to start, please edit /etc/default/puppetmaster to enable" + fi +} + +stop_puppet_master() { + if [ "$SERVERTYPE" = "mongrel" ] + then + NUMSTART=0 + STOPPORT=$PORT + while [ $NUMSTART -lt $PUPPETMASTERS ]; do + start-stop-daemon --stop --quiet --oknodo --pidfile /var/run/puppet/${NAME}-${STOPPORT}.pid + rm -f /var/run/puppet/${NAME}-${STOPPORT}.pid + STOPPORT=$(($STOPPORT + 1)) + NUMSTART=$(($NUMSTART + 1)) + done + else + start-stop-daemon --stop --quiet --oknodo --pidfile /var/run/puppet/${NAME}.pid + fi +} + +status_puppet_master() { + if is_true "$START" ; then + if [ "$SERVERTYPE" = "mongrel" ] + then + NUMSTART=0 + STARTPORT=$PORT + while [ $NUMSTART -lt $PUPPETMASTERS ]; do + status_of_proc -p "/var/run/puppet/${NAME}-${STARTPORT}.pid" \ + "${DAEMON}" "${NAME}-${STARTPORT}" + STARTPORT=$(($STARTPORT + 1)) + NUMSTART=$(($NUMSTART + 1)) + done + else + status_of_proc -p "/var/run/puppet/${NAME}.pid" "${DAEMON}" "${NAME}" + fi + else + echo "" + echo "puppetmaster not configured to start" + fi +} + + +case "$1" in + start) + log_begin_msg "Starting $DESC" + start_puppet_master + log_end_msg $? + ;; + stop) + log_begin_msg "Stopping $DESC" + stop_puppet_master + log_end_msg $? + ;; + reload) + # Do nothing, as Puppetmaster rechecks its config automatically + ;; + status) + status_puppet_master + ;; + restart|force-reload) + log_begin_msg "Restarting $DESC" + stop_puppet_master + sleep 1 + start_puppet_master + log_end_msg $? + ;; + *) + echo "Usage: $0 {start|stop|status|restart|force-reload}" >&2 + exit 1 + ;; +esac --- puppet-2.7.1.orig/debian/etckeeper-commit-pre +++ puppet-2.7.1/debian/etckeeper-commit-pre @@ -0,0 +1,10 @@ +#!/bin/sh + +PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + +which etckeeper > /dev/null 2>&1 || exit 0 + +etckeeper commit "saving uncommitted changes in /etc prior to puppet catalog run" + +# Failure of etckeeper should not be fatal. +exit 0 --- puppet-2.7.1.orig/debian/puppet-testsuite.install +++ puppet-2.7.1/debian/puppet-testsuite.install @@ -0,0 +1,3 @@ +test/* /usr/share/puppet-testsuite/test +spec/* /usr/share/puppet-testsuite/spec +Rakefile /usr/share/puppet-testsuite/ --- puppet-2.7.1.orig/debian/TODO.Debian +++ puppet-2.7.1/debian/TODO.Debian @@ -0,0 +1 @@ +* clean up initscripts per http://mail.madstop.com/pipermail/puppet-dev/2006-June/001069.html --- puppet-2.7.1.orig/debian/puppet-el.emacsen-install +++ puppet-2.7.1/debian/puppet-el.emacsen-install @@ -0,0 +1,25 @@ +#!/bin/sh +# +# emacsen install script for the Debian GNU/Linux puppet-el package + +FLAVOR=$1 +PACKAGE=puppet-el + +ELDIR=/usr/share/emacs/site-lisp/ +ELCDIR=/usr/share/${FLAVOR}/site-lisp/${PACKAGE} +ELFILE="puppet-mode.el" +FLAGS="-batch -no-site-file -l path.el -f batch-byte-compile" + +if [ ${FLAVOR} != emacs ]; then + echo install/${PACKAGE}: Byte-compiling for ${FLAVOR} + + install -m 755 -d ${ELCDIR} + cd ${ELDIR} + cp ${ELFILE} ${ELCDIR} + cd ${ELCDIR} + cat << EOF > path.el +(setq load-path (cons "." load-path) byte-compile-warnings nil) +EOF + ${FLAVOR} ${FLAGS} ${ELFILE} + rm -f ${ELFILE} path.el +fi --- puppet-2.7.1.orig/debian/puppet-el.emacsen-remove +++ puppet-2.7.1/debian/puppet-el.emacsen-remove @@ -0,0 +1,11 @@ +#!/bin/sh +set -e + +FLAVOR=$1 +PACKAGE=puppet-el +ELCFILE=puppet-mode.elc + +if [ ${FLAVOR} != emacs ]; then + echo remove/${PACKAGE}: Purging byte-compiled files for ${FLAVOR} + rm -f /usr/share/${FLAVOR}/site-lisp/${ELCFILE} +fi --- puppet-2.7.1.orig/debian/puppet.lintian-overrides +++ puppet-2.7.1/debian/puppet.lintian-overrides @@ -0,0 +1,3 @@ +# Man pages are automatically generated, not much to do here +puppet binary: manpage-has-bad-whatis-entry +puppet binary: manpage-has-errors-from-man --- puppet-2.7.1.orig/debian/puppet-common.manpages +++ puppet-2.7.1/debian/puppet-common.manpages @@ -0,0 +1,2 @@ +man/man5/puppet.conf.5 +man/man8/puppet.8 --- puppet-2.7.1.orig/debian/puppetmaster.postinst +++ puppet-2.7.1/debian/puppetmaster.postinst @@ -0,0 +1,5 @@ +#!/bin/sh + +set -e + +#DEBHELPER# --- puppet-2.7.1.orig/debian/vim-puppet.README.Debian +++ puppet-2.7.1/debian/vim-puppet.README.Debian @@ -0,0 +1,13 @@ +Dear user, this package provides the vim addon puppet, but it is not enabled +per default. + +If you want to enable it for your user account just execute + + vim-addons install puppet + +Similarly, to enable it for all users of this system just execute (as root): + + vim-addons -w install puppet + +vim-addons is provided by the vim-addon-manager package, have a look at its +manpage for more information. --- puppet-2.7.1.orig/debian/puppetmaster.logrotate +++ puppet-2.7.1/debian/puppetmaster.logrotate @@ -0,0 +1,11 @@ +/var/log/puppet/masterhttp.log { + missingok + notifempty + create 0644 puppet puppet + compress + rotate 4 + + postrotate + pkill -USR2 -u puppet -f puppetmasterd || true + endscript +} --- puppet-2.7.1.orig/debian/puppetmaster-common.manpages +++ puppet-2.7.1/debian/puppetmaster-common.manpages @@ -0,0 +1,3 @@ +man/man8/puppetrun.8 +man/man8/puppetca.8 +man/man8/puppetqd.8 --- puppet-2.7.1.orig/debian/puppet.conf +++ puppet-2.7.1/debian/puppet.conf @@ -0,0 +1,16 @@ +[main] +logdir=/var/log/puppet +vardir=/var/lib/puppet +ssldir=/var/lib/puppet/ssl +rundir=/var/run/puppet +factpath=$vardir/lib/facter +templatedir=$confdir/templates +prerun_command=/etc/puppet/etckeeper-commit-pre +postrun_command=/etc/puppet/etckeeper-commit-post + +[master] +# These are needed when the puppetmaster is run by passenger +# and can safely be removed if webrick is used. +ssl_client_header = SSL_CLIENT_S_DN +ssl_client_verify_header = SSL_CLIENT_VERIFY + --- puppet-2.7.1.orig/debian/compat +++ puppet-2.7.1/debian/compat @@ -0,0 +1 @@ +5 --- puppet-2.7.1.orig/debian/control +++ puppet-2.7.1/debian/control @@ -0,0 +1,143 @@ +Source: puppet +Section: admin +Priority: optional +Maintainer: Ubuntu Developers +XSBC-Original-Maintainer: Puppet Package Maintainers +Uploaders: Micah Anderson , Andrew Pollock , Nigel Kersten , Stig Sandbeck Mathisen +Build-Depends-Indep: ruby (>= 1.8.1), libopenssl-ruby, facter (>= 1.5) +Build-Depends: debhelper (>= 6.0.7~), openssl +Standards-Version: 3.9.2 +Vcs-Git: git://git.debian.org/git/pkg-puppet/puppet.git +Vcs-Browser: http://git.debian.org/?p=pkg-puppet/puppet.git +Homepage: http://projects.puppetlabs.com/projects/puppet + +Package: puppet-common +Architecture: all +Depends: ${misc:Depends}, ruby1.8, libxmlrpc-ruby, libopenssl-ruby, libshadow-ruby1.8, libaugeas-ruby1.8, adduser, facter, lsb-base, sysv-rc (>= 2.87) | file-rc +Recommends: lsb-release, debconf-utils +Suggests: libselinux-ruby1.8, librrd-ruby1.8 +Breaks: puppet (<< 2.6.0~rc2-1), puppetmaster (<< 0.25.4-1) +Description: Centralized configuration management + Puppet lets you centrally manage every important aspect of your system + using a cross-platform specification language that manages all the + separate elements normally aggregated in different files, like users, + cron jobs, and hosts, along with obviously discrete elements like + packages, services, and files. + . + Puppet's simple declarative specification language provides powerful + classing abilities for drawing out the similarities between hosts while + allowing them to be as specific as necessary, and it handles dependency + and prerequisite relationships between objects clearly and explicitly. + . + This package contains the puppet software and documentation. For the startup + scripts needed to run the puppet agent and master, see the "puppet" and + "puppetmaster" packages, respectively. + +Package: puppet +Architecture: all +Depends: ${misc:Depends}, puppet-common (= ${binary:Version}), ruby1.8 +Recommends: rdoc +Suggests: puppet-el, vim-puppet, etckeeper +Description: Centralized configuration management - agent startup and compatibility scripts + This package contains the startup script and compatbility scripts for the + puppet agent, which is the process responsible for configuring the local node. + . + Puppet lets you centrally manage every important aspect of your system + using a cross-platform specification language that manages all the + separate elements normally aggregated in different files, like users, + cron jobs, and hosts, along with obviously discrete elements like + packages, services, and files. + . + Puppet's simple declarative specification language provides powerful + classing abilities for drawing out the similarities between hosts while + allowing them to be as specific as necessary, and it handles dependency + and prerequisite relationships between objects clearly and explicitly. + +Package: puppetmaster-common +Architecture: all +Depends: ${misc:Depends}, ruby1.8, puppet-common (= ${binary:Version}), facter, lsb-base +Breaks: puppet (<< 0.24.7-1), puppetmaster (<< 2.6.1~rc2-1) +Replaces: puppetmaster (<< 2.6.1~rc2-1) +Suggests: apache2 | nginx, mongrel, puppet-el, vim-puppet, stompserver, libstomp-ruby1.8, + rails (>= 1.2.3-2), rdoc, libldap-ruby1.8 +Description: Puppet master common scripts + This package contains common scripts for the puppet master, + which is the server hosting manifests and files for the puppet nodes. + . + Puppet lets you centrally manage every important aspect of your system + using a cross-platform specification language that manages all the + separate elements normally aggregated in different files, like users, + cron jobs, and hosts, along with obviously discrete elements like + packages, services, and files. + . + Puppet's simple declarative specification language provides powerful + classing abilities for drawing out the similarities between hosts while + allowing them to be as specific as necessary, and it handles dependency + and prerequisite relationships between objects clearly and explicitly. + +Package: puppetmaster +Architecture: all +Depends: ${misc:Depends}, ruby1.8, puppetmaster-common (= ${source:Version}), facter, lsb-base +Breaks: puppet (<< 0.24.7-1) +Suggests: apache2 | nginx, mongrel, puppet-el, vim-puppet, stompserver, libstomp-ruby1.8, + rails (>= 1.2.3-2), rdoc, libldap-ruby1.8 +Description: Centralized configuration management - master startup and compatibility scripts + This package contains the startup and compatibility scripts for the puppet + master, which is the server hosting manifests and files for the puppet nodes. + . + Puppet lets you centrally manage every important aspect of your system + using a cross-platform specification language that manages all the + separate elements normally aggregated in different files, like users, + cron jobs, and hosts, along with obviously discrete elements like + packages, services, and files. + . + Puppet's simple declarative specification language provides powerful + classing abilities for drawing out the similarities between hosts while + allowing them to be as specific as necessary, and it handles dependency + and prerequisite relationships between objects clearly and explicitly. + +Package: puppetmaster-passenger +Architecture: all +Depends: ${misc:Depends}, ruby1.8, puppetmaster-common (= ${source:Version}), facter, lsb-base, libapache2-mod-passenger +Conflicts: puppetmaster (<< 2.6.1~rc2-1) +Replaces: puppetmaster (<< 2.6.1~rc2-1) +Description: Centralised configuration management - master setup to run under mod passenger + This package provides a puppetmaster running under mod passenger. + This configuration offers better performance and scalability. + . + Puppet lets you centrally manage every important aspect of your system + using a cross-platform specification language that manages all the + separate elements normally aggregated in different files, like users, + cron jobs, and hosts, along with obviously discrete elements like + packages, services, and files. + . + Puppet's simple declarative specification language provides powerful + classing abilities for drawing out the similarities between hosts while + allowing them to be as specific as necessary, and it handles dependency + and prerequisite relationships between objects clearly and explicitly. + . + +Package: vim-puppet +Architecture: all +Depends: ${misc:Depends} +Recommends: vim-addon-manager +Conflicts: puppet (<< ${source:Version}) +Description: syntax highlighting for puppet manifests in vim + The vim-puppet package provides filetype detection and syntax highlighting for + puppet manifests (files ending with ".pp"). + +Package: puppet-el +Architecture: all +Depends: ${misc:Depends}, emacsen-common +Conflicts: puppet (<< ${source:Version}) +Description: syntax highlighting for puppet manifests in emacs + The puppet-el package provides syntax highlighting for puppet manifests + +Package: puppet-testsuite +Architecture: all +Depends: ${misc:Depends}, ruby1.8, puppet-common (= ${source:Version}), facter, lsb-base, rails (>= 1.2.3-2), rdoc, libldap-ruby1.8, mongrel, librspec-ruby, git-core, libmocha-ruby1.8 +Recommends: cron +Suggests: ruby +Description: Centralized configuration management - test suite + This package provides all the tests from the upstream puppet source code. + The tests are used for improving the QA of the puppet package. --- puppet-2.7.1.orig/debian/puppet.postrm +++ puppet-2.7.1/debian/puppet.postrm @@ -0,0 +1,20 @@ +#!/bin/sh + +set -e + +# Remove renamed configuration files which are now handled by other +# packages +if dpkg-maintscript-helper supports rm_conffile 2>/dev/null; then + + dpkg-maintscript-helper rm_conffile \ + /etc/logrotate.d/puppet 2.6.4-2 puppet -- "$@" + + dpkg-maintscript-helper rm_conffile \ + /etc/logcheck/ignore.d.server/puppet 2.6.4-2 puppet -- "$@" + + dpkg-maintscript-helper rm_conffile \ + /etc/emacs/site-start.d/50puppet-mode-init.el 2.6.4-2 puppet -- "$@" + +fi + +#DEBHELPER# --- puppet-2.7.1.orig/debian/watch +++ puppet-2.7.1/debian/watch @@ -0,0 +1,2 @@ +version=3 +http://pkg-ruby-extras.alioth.debian.org/cgi-bin/gemwatch/puppet .*/puppet-(.*).tar.gz --- puppet-2.7.1.orig/debian/puppet-common.dirs +++ puppet-2.7.1/debian/puppet-common.dirs @@ -0,0 +1,7 @@ +etc/puppet +etc/puppet/manifests +etc/puppet/templates +etc/puppet/modules +usr/lib/ruby/1.8 +var/lib/puppet +var/log/puppet --- puppet-2.7.1.orig/debian/puppetmaster.dirs +++ puppet-2.7.1/debian/puppetmaster.dirs @@ -0,0 +1 @@ +usr/sbin --- puppet-2.7.1.orig/debian/puppet.logrotate +++ puppet-2.7.1/debian/puppet.logrotate @@ -0,0 +1,11 @@ +/var/log/puppet/*log { + missingok + create 0644 puppet puppet + compress + rotate 4 + + postrotate + [ -e /etc/init.d/puppetmaster ] && /etc/init.d/puppetmaster restart >/dev/null 2>&1 || true + [ -e /etc/init.d/puppet ] && /etc/init.d/puppet reload > /dev/null 2>&1 || true + endscript +} --- puppet-2.7.1.orig/debian/puppet.manpages +++ puppet-2.7.1/debian/puppet.manpages @@ -0,0 +1,4 @@ +man/man8/filebucket.8 +man/man8/puppetd.8 +man/man8/puppetdoc.8 +man/man8/ralsh.8 --- puppet-2.7.1.orig/debian/puppet-common.lintian-overrides +++ puppet-2.7.1/debian/puppet-common.lintian-overrides @@ -0,0 +1,5 @@ +# Man pages are automatically generated, not much to do here +puppet-common binary: manpage-has-bad-whatis-entry +puppet-common binary: manpage-has-errors-from-man +# These are "scripts" but do nothing other than providing documentation +puppet-common: script-not-executable --- puppet-2.7.1.orig/debian/fileserver.conf +++ puppet-2.7.1/debian/fileserver.conf @@ -0,0 +1,17 @@ +# This file consists of arbitrarily named sections/modules +# defining where files are served from and to whom + +# Define a section 'files' +# Adapt the allow/deny settings to your needs. Order +# for allow/deny does not matter, allow always takes precedence +# over deny +[files] + path /etc/puppet/files +# allow *.example.com +# deny *.evil.example.com +# allow 192.168.0.0/24 + +[plugins] +# allow *.example.com +# deny *.evil.example.com +# allow 192.168.0.0/24 --- puppet-2.7.1.orig/debian/puppetmaster-passenger.postrm +++ puppet-2.7.1/debian/puppetmaster-passenger.postrm @@ -0,0 +1,33 @@ +#!/bin/sh -e + +case "$1" in + purge) + if dpkg-statoverride --list /usr/share/puppet/rack/puppetmasterd/config.ru >/dev/null 2>&1 + then + dpkg-statoverride --remove /usr/share/puppet/rack/puppetmasterd/config.ru + fi + # Remove the puppetmaster site configuration on purge + rm -f /etc/apache2/sites-available/puppetmaster + ;; + remove) + # Disable the puppetmaster apache2 site configuration on package removal + a2dissite puppetmaster + if [ -x "/etc/init.d/apache2" ]; then + if [ -x "`which invoke-rc.d 2>/dev/null`" ]; then + invoke-rc.d apache2 force-reload || exit $? + else + /etc/init.d/apache2 force-reload || exit $? + fi + fi + ;; + upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) + ;; + *) + echo "postrm called with unknown argument \`$1'" >&2 + exit 1 + +esac + +#DEBHELPER# + +exit 0 --- puppet-2.7.1.orig/debian/changelog +++ puppet-2.7.1/debian/changelog @@ -0,0 +1,983 @@ +puppet (2.7.1-1ubuntu3.2~hardy1~ppa1) hardy; urgency=low + + * Drop debian/source/format + + -- Micah Gersten Fri, 30 Dec 2011 02:35:19 -0600 + +puppet (2.7.1-1ubuntu3.2) oneiric-security; urgency=low + + * SECURITY UPDATE: puppet master impersonation via incorrect certificates + - debian/patches/CVE-2011-3872.patch: refactor certificate handling. + - Thanks to upstream for providing the patch. + - CVE-2011-3872 + + -- Marc Deslauriers Mon, 24 Oct 2011 15:05:12 -0400 + +puppet (2.7.1-1ubuntu3) oneiric; urgency=low + + * SECURITY UPDATE: k5login can overwrite arbitrary files as root + - debian/patches/CVE-2011-3869.patch: adjust type/k5login.rb to securely + open the file before writing to it as root + - CVE-2011-3869 + * SECURITY UPDATE: didn't drop privileges before creating and changing + permissions on SSH keys + - debian/patches/CVE-2011-3870.patch: adjust ssh_authorized_key/parsed.rb + to drop privileges before creating the ssh directory and setting + permissions + - CVE-2011-3870 + * SECURITY UPDATE: fix predictable temporary filename in ralsh + - debian/patches/CVE-2011-3871.patch: adjust application/resource.rb to + use an unpredictable filename + - CVE-2011-3871 + * SECURITY UPDATE: file indirector injection, similar to CVE-2011-3848 + - secure-indirector-file-backed-terminus-base-cla.patch: Since the + indirector file backed terminus base class is only used by the test + suite, remove it and update test cases to use a continuing class. + + -- Jamie Strandboge Fri, 30 Sep 2011 08:29:40 -0500 + +puppet (2.7.1-1ubuntu2) oneiric; urgency=low + + * SECURITY UPDATE: unauthenticated directory traversal allows writing of + arbitrary files as puppet master + - debian/patches/CVE-2011-3848.patch: update lib/puppet/indirector.rb, + lib/puppet/indirector/ssl_file.rb, lib/puppet/indirector/yaml.rb, + spec/unit/indirector/ssl_file.rb and spec/unit/indirector/yaml.rb to + perform proper input validation. + - CVE-2011-3848 + - LP: #861182 + + -- Jamie Strandboge Wed, 28 Sep 2011 07:55:44 -0500 + +puppet (2.7.1-1ubuntu1) oneiric; urgency=low + + * Merge from debian unstable. Remaining changes: + - debian/puppetmaster-passenger.postinst: Use cacrl instead of hostcrl to + set the location of the CRL in apache2 configuration. Fix apache2 + configuration on upgrade as well (LP: #641001) + - move all puppet dependencies to puppet-common since all the code + actually located in puppet-common. + - move libagueas from a recommend to a dependency. + + -- Chuck Short Mon, 25 Jul 2011 01:00:37 +0100 + +puppet (2.7.1-1) UNRELEASED; urgency=low + + * New upstream version + * Bump Standards-Version (no changes) + * Adjust debian/source/options to allow for a VCS-generated patch + * Tell adduser not to create /var/lib/puppet (Closes: #609896) + * Use dpkg-statoverride to handle permissions + * Allow the use of file-rc (Closes: #625638) + * Use the pkg-ruby-extras watch service + + -- Stig Sandbeck Mathisen Sun, 17 Jul 2011 17:43:31 +0200 + +puppet (2.6.8-1ubuntu1) oneiric; urgency=low + + * Merge from debian unstable. Remaining changes: + - debian/puppetmaster-passenger.postinst: Use cacrl instead of hostcrl to + set the location of the CRL in apache2 configuration. Fix apache2 + configuration on upgrade as well (LP: #641001) + - move all puppet dependencies to puppet-common since all the code + actually located in puppet-common. + - move libagueas from a recommend to a dependency. + + -- Chuck Short Mon, 02 May 2011 12:37:51 +0100 + +puppet (2.6.8-1) unstable; urgency=low + + * New upstream version + + -- Stig Sandbeck Mathisen Thu, 28 Apr 2011 19:26:00 +0200 + +puppet (2.6.7-2) unstable; urgency=medium + + * Fix puppetmaster-passenger.postinst to get proper + ssl configs (Closes: #620635) + * Fix maintainer scripts ignoring errors + + -- Micah Anderson Mon, 11 Apr 2011 11:31:23 -0400 + +puppet (2.6.7-1) unstable; urgency=low + + * New upstream version + + -- Stig Sandbeck Mathisen Fri, 25 Mar 2011 21:10:07 +0100 + +puppet (2.6.6-1) unstable; urgency=low + + * New upstream release 2.6.6 + + -- Stig Sandbeck Mathisen Thu, 10 Mar 2011 08:42:00 +0100 + +puppet (2.6.6~rc1-1) experimental; urgency=low + + * New upstream release candidate + + -- Stig Sandbeck Mathisen Thu, 03 Mar 2011 21:00:31 +0100 + +puppet (2.6.5-1) unstable; urgency=low + + * New upstream version (Closes: #612894) + * Remove renamed configuration files now handled by other packages (Closes: #564947, #611615) + + -- Stig Sandbeck Mathisen Tue, 01 Mar 2011 13:59:58 +0100 + +puppet (2.6.4-2ubuntu2) natty; urgency=low + + * debian/puppetmaster.default + - fix remains of automated merge (LP: #726856) + + -- Andreas Moog Tue, 01 Mar 2011 14:04:06 +0100 + +puppet (2.6.4-2ubuntu1) natty; urgency=low + + * Merge from debian unstable. Remaining changes: + - debian/puppetmaster-passenger.postinst: Use cacrl instead of hostcrl to + set the location of the CRL in apache2 configuration. Fix apache2 + configuration on upgrade as well (LP: #641001) + - move all puppet dependencies to puppet-common since all the code + actually located in puppet-common. + - move libagueas from a recommend to a dependency. + + -- Chuck Short Tue, 08 Feb 2011 00:28:43 +0000 + +puppet (2.6.4-2) unstable; urgency=low + + * Release for unstable + * Move puppetstoredconfigclean to puppetmaster-common, and set ruby1.8 + as parser to match the rest of the puppet suite + + -- Stig Sandbeck Mathisen Mon, 07 Feb 2011 07:28:19 +0100 + +puppet (2.6.4-1) experimental; urgency=low + + [ Micah Anderson ] + * Make puppetqd honor flags from /etc/default/puppetqd (Closes: #605510) + * Remove the puppetqd PID file on stop (Closes: #605512) + * Add ext/puppetstoredconfigclean to puppetmaster:/usr/sbin + * Patch ext/logcheck/puppet to handle new puppet-master + Compiled log lines (Closes: #602336) + * Fix puppetqd initscript PID location + * Fix /etc/default/puppetmaster comments to match new section headings + * Fix puppetmaster/README.Debian to match new section headings + * Fix Should-Start init header in puppet initscript + + [ Mathias Gug ] + * New upstream version. + + [ Stig Sandbeck Mathisen ] + * debian/puppetmaster.logrotate: send SIGUSR2 on log rotation (Closes: + #602698) + * puppet-common: Add versioned dependency on sysv-rc + + [ martin f krafft ] + * Use update-rc.d enable/disable in the "debian" provider in the + "service" type (Closes: #573551) + + -- Stig Sandbeck Mathisen Wed, 26 Jan 2011 16:10:56 +0100 + +puppet (2.6.3-1) experimental; urgency=low + + [ Mathias Gug ] + * New upstream version. + + [ Stig Sandbeck Mathisen ] + * debian/control: Adjust dependencies for puppet-testsuite, depend on + puppet-common instead of puppet and puppetmaster + + -- Stig Sandbeck Mathisen Tue, 23 Nov 2010 10:40:31 +0100 + +puppet (2.6.3-0ubuntu1) natty; urgency=low + + * New upstream version. + + -- Mathias Gug Wed, 17 Nov 2010 13:30:18 -0500 + +puppet (2.6.3~rc3-0ubuntu1) natty; urgency=low + + * New upstream version + + -- Mathias Gug Fri, 12 Nov 2010 09:29:36 -0500 + +puppet (2.6.3~rc2-0ubuntu1) natty; urgency=low + + * New upstream version + + -- Mathias Gug Tue, 09 Nov 2010 17:47:53 -0500 + +puppet (2.6.3~rc1-0ubuntu1) natty; urgency=low + + * New upstream version + * debian/control: + - move all puppet dependencies to puppet-common since all the code is + actually located in puppet-common. + - move libaugeas from a recommend to a dependency. + + -- Mathias Gug Thu, 21 Oct 2010 12:52:13 -0400 + +puppet (2.6.1-1) experimental; urgency=low + + [ Mathias Gug ] + * New upstream version: + - Fix "Puppet standalone broken" (Closes: #594575) + * test/lib/puppettest/fakes.rb: Fix puppettest to use puppet system + library. + * debian/puppetmaster-passenger.postinst: Use cacrl instead of hostcrl to + set the location of the CRL in apache2 configuration. Fix apache2 + configuration on upgrade as well (LP: #641001). + * debian/control: + - move all puppet dependencies to puppet-common since all the code is + actually located in puppet-common. + - move libaugeas from a recommend to a dependency. + + [ Stig Sandbeck Mathisen ] + * Fix "require" path for puppet queue. + * Add dependency on "facter" for "puppet-common" + * Make sure the "puppet-common" package can be purged even when not fully + installed (Closes: #596163) + + -- Stig Sandbeck Mathisen Sat, 02 Oct 2010 12:14:37 +0200 + +puppet (2.6.1-0ubuntu2) maverick; urgency=low + + * debian/puppetmaster-passenger.postinst: Use cacrl instead of hostcrl to + set the location of the CRL in apache2 configuration. Fix apache2 + configuration on upgrade as well (LP: #641001). + + -- Mathias Gug Tue, 21 Sep 2010 13:53:10 -0400 + +puppet (2.6.1-0ubuntu1) maverick; urgency=low + + [ Stig Sandbeck Mathisen ] + * Add dependency on "facter" for "puppet-common" + * Make sure the "puppet-common" package can be purged even when not fully + installed (Closes: #596163) + + [ Mathias Gug ] + * New upstream version. + + -- Mathias Gug Tue, 14 Sep 2010 12:05:29 -0400 + +puppet (2.6.1~rc4-0ubuntu1) maverick; urgency=low + + [ Mathias Gug ] + * New upstream version: + - Fix "Puppet standalone broken" (Closes: #594575) + * test/lib/puppettest/fakes.rb: Fix puppettest to use puppet system + library. + + [ Stig Sandbeck Mathisen ] + * Fix "require" path for puppet queue. + + -- Mathias Gug Tue, 07 Sep 2010 10:44:22 -0400 + +puppet (2.6.1~rc3-1) experimental; urgency=low + + [ Mathias Gug ] + * New upstream version: + - fix config.ru file to run puppetmaster as a rack application. + (Closes: #593557) + * Fix test suite to run from a package install rather then from the source + directory: + + Rakefile: use system puppet.rb file to detect version. + + spec/unit/application/apply_spec.rb: Fix test suite to use puppet + system library. + + spec/spec_helper.rb: disable gem. + * Fix init service provider to correctly check the status of services + using upstart jobs (Closes: #584481, LP: #551544). + * etckeeper integration (Closes: #571127) + [server-lucid-puppet-etckeeper-integration]: + + debian/etckeeper-commit-post, debian/etckeeper-commit-pre: + Call "etckeeper commit" before and after catalog runs. + Silently bail out if etckeeper is not available. + + debian/puppet.conf: Call out to the etckeeper hooks using + the prerun_command and postrun_command hooks. + + debian/rules: Install the etckeeper hook scripts in /etc/puppet. + + debian/README.Debian: add note about etckeeper integration. + + debian/control: the puppet package suggests etckeeper. + * Create puppetmaster-passenger package to automatically setup the + puppetmaster to be run under mod passenger and apache2: + - create new puppetmaster-common package to share files between + puppetmaster (ie webrick) and puppetmaster-passenger. + - move puppetqd to puppetmaster-common. + - debian/puppet.conf: enable ssl options so that the default configuration + works out of the box under passenger. + * debian/puppet-common.postinst: set permissions and ownership of puppet log + directory. + * Move puppetmaster's Recommends to Suggests. + + [ Stig Sandbeck Mathisen ] + * Recommend lsb-release (Closes: #593606) + * Recommend debconf-utils (Closes: #593780) + * ext/puppetlast: removed from upstream + * Cherry-pick updated man pages from upstream + + -- Stig Sandbeck Mathisen Fri, 27 Aug 2010 14:32:00 +0200 + +puppet (2.6.0-2) unstable; urgency=low + + * Bump Standards-Version to 3.9.1 + * Release for unstable + + -- Stig Sandbeck Mathisen Wed, 28 Jul 2010 20:48:21 +0200 + +puppet (2.6.0-1) experimental; urgency=low + + * New upstream version + * Fix "short package description doesn't differ from binary package + puppet", update description of binary package. (Closes: #587364) + * Move /usr/bin/puppet to the puppet-common package + * debian/control: Convert Conflicts: to Breaks: + * debian/control: bump policy version and update project homepage + * debian/control: Update dependencies/breakages + * debian/copyright: Remove reference to deprecated bsd license file, + it is included already + * move manpage puppet(8) to package puppet-common + * Debian fix: set correct header in puppet.conf(5) + * Add puppetqd(8) to the puppetmaster package + + -- Stig Sandbeck Mathisen Tue, 20 Jul 2010 09:37:22 +0200 + +puppet (0.25.5-1) unstable; urgency=low + + [ Stig Sandbeck Mathisen ] + * New upstream version: 0.25.5 + * Adjust conflicts for puppet-common, to ensure upgrade goes well + + [ Mathias Gug ] + * Add /etc/puppet/templates and /etc/puppet/modules to the puppet- + common package (Closes: #571129) + * Add binary package "puppet-testsuite" (Closes: #584480) + + -- Stig Sandbeck Mathisen Fri, 25 Jun 2010 17:27:05 +0200 + +puppet (0.25.4-6) unstable; urgency=low + + * clarify passenger options in default/puppetmaster + * add patch to ext/rack/files/apache2.conf for debian-specific settings + * debian/control: add version depends on librack-ruby + * additional start-stop-daemon fix for puppet.init and puppetqd.init + * debian/rules: actually install config.ru owned by the puppet user, + this is necessary for proper suid of passenger (closes: #577366) + + -- Micah Anderson Tue, 20 Apr 2010 16:06:38 -0400 + +puppet (0.25.4-5) unstable; urgency=low + + * debian/puppetmaster.init: fix invocation of start-stop-daemon (closes: + * #578066) + + -- Andrew Pollock Sat, 17 Apr 2010 20:33:09 -0700 + +puppet (0.25.4-4) unstable; urgency=low + + [ Andrew Pollock ] + * debian/watch: update for new upstream location + * apply patch from Mathias Gug to add /etc/puppet to puppet-common's + directories so that it is removed on package purge (if empty). Also removes + /var/log/puppet on purge (closes: #571130) + + [ Micah Anderson ] + * add Suggests: libselinux-ruby1.8 as puppet supports it, but only + if the library is present + * cherry-pick: add puppetmasterd dbconnections option to increase + the rails 'max pool size' (redmine: #2568) + * fix puppetqd initscript status operation + + [ Andrew Pollock ] + * debian/rules: don't install config.ru owned by the puppet user (closes: + #577366) + + -- Andrew Pollock Thu, 15 Apr 2010 21:18:32 -0700 + +puppet (0.25.4-3) unstable; urgency=low + + [ Stig Sandbeck Mathisen ] + * Fix "puppetmaster and puppet scripts always return 0" with patch from + Mathias Gug, make sure return codes are actually used (Closes: #573473) + + [ Micah Anderson ] + * Disable default puppet.conf option pluginsync=true, see puppet-common.NEWS + * Suggest in puppetmaster package: libapache2-mod-passenger, librack-ruby + * Create puppetmaster.README with information about the server type + * Provide an example apache2.conf for libapache2-mod-passenger + * Ship the rack config.ru, and README as README.rack + * Fix puppet-el.emacsen-startup script to be properly installed + * debian/puppetmaster.init: Fix init stop action to not fail if the + puppetmaster is already stopped, Thanks Mathias Gug (Closes: #574677) + * Add Suggests: stompserver/libstomp-ruby1.8 - needed for puppetqd + * Add README.queueing to puppetmaster package which describes puppetqd + * Add /etc/init.d/puppetqd and defaults in /etc/defaults/puppetmaster + * Switch to dpkg-source 3.0 (quilt) format + + -- Micah Anderson Tue, 16 Mar 2010 12:27:07 -0400 + +puppet (0.25.4-2) unstable; urgency=low + + [ Stig Sandbeck Mathisen ] + * puppet: do not explicitly remove /var/lib/puppet on purge (Closes: #525852) + * upstream cherry-pick: Updated man pages and moved puppet.conf.8 to + puppet.conf.5 (Closes: #563567) + * Fix "Improper ownership of /var/lib/puppet/state", explicitly create this + in postinst (and remove in postrm on purge) (Closes: #462551) + * Fix "wrong default location for templates", update default settings, and + create puppet-common.NEWS with information (Closes: #484659) + * Move postinst and postrm handling of shared users and directories to + puppet-common (Closes: #570012) + + -- Stig Sandbeck Mathisen Tue, 16 Feb 2010 06:30:55 +0000 + +puppet (0.25.4-1) unstable; urgency=low + + [Nigel Kersten ] + * New upstream version 0.25.4 + + [ Micah Anderson ] + * Fix debian/rules typo in install of puppet-mode-init.el + * Fix which package puppet-mode-init.el gets installed into + * Add Suggests for vim-puppet and puppet-el on binary packages + + [ Stig Sandbeck Mathisen ] + * Update debian/copyright + * debian/{puppet,puppetmaster}.init: Add status argument, fix pid file + locations (Closes: #545975) + * Refactoring: Add binary packages for puppet-common, puppet-el, vim-puppet + + -- Stig Sandbeck Mathisen Mon, 01 Feb 2010 12:31:58 +0100 + +puppet (0.25.1-3) unstable; urgency=low + + [ Nigel Kersten ] + * Require modification of /etc/default/puppet to start puppet client daemon. + (closes: #518831) + * cherry pick upstream fix for puppetrun with tags (closes: #559092) + * cherry pick upstream fix for supplementary groups not being reset. + (CVE-2009-3564) (closes: #551073) + + [ Andrew Pollock ] + * debian/{puppet,puppetmaster}.pid: Correct the path to the pidfiles + (closes: #561231) + * debian/control: version the build dependency on facter (closes: #551055) + + -- Andrew Pollock Wed, 16 Dec 2009 11:36:39 -0800 + +puppet (0.25.1-2) unstable; urgency=low + + * Add puppetqd executable to puppetmaster package (closes: #554624) + + -- Nigel Kersten Thu, 05 Nov 2009 11:23:10 -0800 + +puppet (0.25.1-1) unstable; urgency=low + + * New upstream release of 0.25.1 + + -- Nigel Kersten Tue, 27 Oct 2009 10:35:40 -0700 + +puppet (0.25.0-1) unstable; urgency=low + + * New upstream release + * Tweak .install files to cope with new use of sbindir from upstream. + * Add the new auth.conf config file to the puppetmaster package. + + -- Nigel Kersten Sun, 16 Aug 2009 05:34:17 -0700 + +puppet (0.24.8-3) unstable; urgency=low + + [ Micah Anderson ] + * Make logcheck ignore 'Reopening log files' on puppetmaster (Closes: #538721) + + [ Nigel Kersten ] + * switch from unreleased to unstable. + + -- Nigel Kersten Sun, 16 Aug 2009 05:33:53 -0700 + +puppet (0.24.8-2) unstable; urgency=high + + [ Micah Anderson ] + * Cherry-pick upstream versioncmp fix (redmine:#2110) + + [ Andrew Pollock ] + * Enable waiting for certificates for the default value (upstream default + that was previously disabled or enabled with a 5 second value) + * Re-ship the vim syntax file in the correct location (it fell out after the + 0.24.5-3 upload) (closes: #530752) + * Re-add the 0.24.5-3 changelog entry + * debian/puppet.postrm: don't delete the user or group (closes: #528068, + #527381) + * debian/puppet.{preinst,postinst}: Applied modified patch from Stig + Sandbeck Mathisen to call conditionally call adduser in the postinst, if + it's available + + [ Nigel Kersten ] + * Switched to use install.rb, primarily to stop shebangs using /usr/bin/env + * Stopped using dh_movefiles, moved to dh_install + * debian/rules greatly cleaned up due to above two changes + + [ Andrew Pollock ] + * debian/control: add rdoc, libopenssl-ruby and facter to build dependencies + * debian/control: depend on ruby1.8 instead of ruby to placate Lintian + * debian/puppet.install: brown paper bag release averted; install + /usr/lib/ruby/1.8 + * debian/rules: ensure permissions on everything under /usr/lib/ruby/1.8 is + correct + + -- Andrew Pollock Tue, 16 Jun 2009 23:37:22 -0700 + +puppet (0.24.8-1) unstable; urgency=low + + * New upstream release + * debian/control: Add Nigel Kersten and myself as uploaders + * debian/changelog: wrap long lines + * debian/watch: ignore release candidates + * debian/compat: bump to 5 + * debian/control: bump Standards-Version (no changes) + + -- Andrew Pollock Mon, 13 Apr 2009 17:12:47 -0700 + +puppet (0.24.7-2) experimental; urgency=low + + * make puppetmaster conflict previous puppet due to man page move + + -- Micah Anderson Wed, 28 Jan 2009 10:28:23 -0500 + +puppet (0.24.7-1) experimental; urgency=low + + * New upstream release + * Fixed comment in defaults/puppetmaster (Closes: #510881) + * Fixed debian/puppetmaster.manpages and debian/puppet.manpages to + distribute + puppetrun and puppetca correctly, thanks Savvas Radevic (Closes: #511826) + * Added puppetmaster Recommends: libldap-ruby1.8 to silence puppetrun + (Closes: #512639) + * Added puppet Recommends: libaugeas-ruby1.8 for new Augeas support in this + release + + -- Micah Anderson Fri, 23 Jan 2009 09:27:09 -0500 + +puppet (0.24.6-1) experimental; urgency=low + + * New upstream release (Closes: #506129, #504624, #502163) + * Distribute filebucket binary (Closes: #499999) + * Fix missing check for START variable in defaults files (Closes: #498284) + * Fix maintainer scripts so that they do not ignore errors (set -e) + * Fix maintainer scripts so they don't have prepended paths (thanks lintian) + * Cherry-pick fixes from upstream: + - comparison of String with 0 failed (Closes: #500848) + - filename cannot handle ++ (Closes: #502163) + - tidy must specify size, age or both (Closes: #500852) + + -- Micah Anderson Sat, 29 Nov 2008 13:59:25 -0500 + +puppet (0.24.5-3) unstable; urgency=medium + + * Set wait for cert timeout to 5 secs, to avoid resource abuse (Closes: + #509566) + * Distribute filebucket binary (Closes: #499999) + * Place vim syntax in the correct location (LP: #181960) + + -- Thom May Wed, 07 Jan 2009 15:15:34 -0500 + +puppet (0.24.5-2) unstable; urgency=low + + * Fix puppetlast to work with 0.24.5 + * Adjust logcheck to match against new log messages in 0.24.5 + * Update standards version to 3.8.0 (no changes) + * Update changelog to reduce length of line to make lintian happy + + -- Micah Anderson Sat, 26 Jul 2008 15:43:45 -0400 + +puppet (0.24.5-1) unstable; urgency=low + + * New upstream release + * Applied patch from Martin Krafft to improve logcheck file installation + + -- Thom May Thu, 24 Jul 2008 10:58:08 +0100 + +puppet (0.24.4-8) unstable; urgency=low + + * Changed the default port value to 8140 in /etc/default/puppetmaster + to be consistent with the client default port. (Closes: #483823) + * Cherry-picked various bug fixes from upstream: + - further emacs mode updates from Russ Allbery + - misleading error if CA private key can not be decrypted (trac:#1271) + - fix missing bracket in documentation (trac:#1209) + - man pages updates (trac:#1211) + - add dump parameter to mount type (trac:#1212) + - fixed undefined variable in lib/puppet/util/settings.rb (trac:#1218) + - usermod problem on Solaris (trac:#1207) + - added native authorized_keys type + - test within a template if a variable or fact is defined (trac:#1177) + - Fixed Red Hat service disabling (trac:#1219) + - fix crontab provider parse error when line begins w/space (trac:#1216) + - Fix for latest method in rpm provider (trac:#1224) + - puppetd documentation updates (trac:#1227) + - Modified the 'factpath' setting to automatically configure Facter + to load facts there if a new enough version of Facter is used. + - Removing unused file lib/puppet/util/variables.rb (trac:#1229) + - Fixing transaction support for prefetching generated resources. + Previously, we prefetched then generated, which caused generated + resources that needed prefetching not to work. This just reorders + the calls, so generated resources now get prefetched. + - Respect "replace => false" for symlinks (trac:#1235) + - Added cron random function fixing ticket (trac:#311) + - No more clear_cache failures (trac:#1247) + - Fixed Rakefile to install non-.rb files (trac:#1266) + + -- Micah Anderson Sat, 31 May 2008 11:39:47 -0400 + +puppet (0.24.4-7) unstable; urgency=low + + * Update emacs-mode with changes cherry-picked from rra's repository + * Fix typo in puppetmaster.init (Closes: #480019) + * Fix variable name in /etc/default/puppetmaster comments + * Fix incorrect port increment in puppetmaster initscript when mongrel + is used, thanks Francois Deppierraz (Closes: #480263) + * Add puppetmaster.postrm to remove /var/log/puppet on purge + * Added debian/puppetmaster.dirs containing etc/puppet/manifests + * Remove puppet group on purge (Closes: #481511) + * Remove old config files and stray directories (Closes: #454681) + + -- Micah Anderson > Sat, 03 May 2008 16:18:32 -0400 + +puppet (0.24.4-6) unstable; urgency=low + + * Remove bashisms in puppetmaster.init + * Add puppetlast script + + -- Micah Anderson > Wed, 30 Apr 2008 07:37:04 -0400 + +puppet (0.24.4-5) unstable; urgency=low + + * Fix missing --pidfile piece for mongrel startup and make + stop consistent, thanks Bart Cortooms (Closes: #476840) + * Add trailing newline missing from default files + + -- Micah Anderson Sat, 19 Apr 2008 11:03:35 -0400 + +puppet (0.24.4-4) unstable; urgency=low + + * Create /etc/default/puppet and /etc/default/puppetmaster + * Modify /etc/init.d/puppetmaster to support mongrel instances + on multiple ports + * Remove no longer necessary .svn cleaning from debian/rules + * Added $network and $named appropriated places in the LSB + headers in puppet and puppetmaster initscripts, + thanks Sam Quigley + * Install ralsh (Closes: #476629) + * Cherry-pick upstream patches from 0.24.x branch: + - Install manpages + - Fix shebang issues (#1148) + - Updated fix for (#1020) + - Fix for (#1174) + - Emacs mode updates (#1160) + - Debian service [en|dis]able issue (#1161) + - User type group list validation enhancement + - Fix configtimeout issue (#1176) + + -- Micah Anderson Sun, 13 Apr 2008 19:18:46 -0400 + +puppet (0.24.4-3) unstable; urgency=low + + * Remove pi binary, puppetdoc provides this functionality now + (Closes: #472850) + + -- Micah Anderson Fri, 28 Mar 2008 12:38:30 -0400 + +puppet (0.24.4-2) unstable; urgency=low + + * Fix duplicate man8/puppetmasterd.8 install + + -- Micah Anderson Tue, 25 Mar 2008 22:58:22 -0400 + +puppet (0.24.4-1) unstable; urgency=low + + * New upstream release + * Install man pages missing from upstream release + + -- Micah Anderson Tue, 25 Mar 2008 18:17:02 -0400 + +puppet (0.24.3-1) unstable; urgency=low + + [ Micah Anderson] + * New upstream release + * Install man pages (Closes: #385529) + * Apply lsb formatted dependency info into initscripts, thanks + Petter Reinholdtsen (Closes: #462915) + * Install more robust puppet-mode.el + * Add factpath and pluginsync=true to the default puppet.conf so that + facts added through pluginsync are loaded by puppet + * Add [plugins] section to fileserver.conf + * Updated outdated debian/control substrvar for puppet to ${source:Version} + * Updated link in debian/copyright for new URL to license + * Updated copyright in debian/copyright + * Bumped standards version to 3.7.3.0 (no changes) + * Switch debhelper from Build-Depends-Indep to Build-Depends because it is + required to run clean target (lintian check: + clean-should-be-satisfied-by-build-depends) + * Moved homepage from Description to control field + * Added Vcs-Browser and Vcs-Git fields to debian/control + [ Thom May ] + * If puppet can't start, continue with package install + + -- Micah Anderson Sun, 09 Mar 2008 14:03:00 -0400 + +puppet (0.24.1-2) unstable; urgency=low + + * Set rundir correctly (Closes: #460203, #459579) + * Apply patch for puppet#1003 to enable collection of tagged resources + + -- Thom May Wed, 16 Jan 2008 11:08:55 +0100 + +puppet (0.24.1-1) unstable; urgency=low + + * New upstream release (Closes: #445626) + * Set maintainer to pkg-puppet-devel + + -- Thom May Sun, 30 Dec 2007 19:13:47 +0100 + +puppet (0.24.0-1) unstable; urgency=low + + * New upstream release + + -- Thom May Wed, 19 Dec 2007 16:00:34 +0100 + +puppet (0.23.2-15) unstable; urgency=low + + * No change upload setting maintainer to me whilst waiting for an alioth + project. + + -- Thom May Thu, 29 Nov 2007 10:44:50 +0100 + +puppet (0.23.2-14) unstable; urgency=low + + * Orphaning. + * Create /var/lib/puppet in the puppet package. Closes: #452506. + * Start the puppet init script after puppetmaster, to silence whiny bug + reports. Closes: #452064. + * Add a reload command to the Puppet init script. Closes: #452060. + + -- Matthew Palmer Thu, 29 Nov 2007 10:48:21 +1100 + +puppet (0.23.2-13) unstable; urgency=low + + * Drop quotes from an already-quoted value in a query. Closes: #448179. + * Remove excessive quoting from puppet/network/handler/master.rb. + Closes: #448221. + * Force removal of directories during pluginsync. Closes: #448180. + + -- Matthew Palmer Tue, 30 Oct 2007 14:55:19 +1100 + +puppet (0.23.2-12) unstable; urgency=low + + * Create /var/run/puppet and set the perms in the various initscripts, as + well as hardcoding the rundir better in configuration.rb and removing + the explicit rundir setting from puppet.conf. Closes: #447314. + * Apply additional patch given (backwards) to fix export/collect on some + database backends. Closes: #445591 (again!) + + -- Matthew Palmer Sat, 20 Oct 2007 11:28:50 +1000 + +puppet (0.23.2-11) unstable; urgency=low + + * Apply patch from puppet#786 to fix a problem with exported resources not + being properly detected as needing a rerun. Closes: #445591. + * Fix ignore handling for the plugins mount. Closes: #446390. + + -- Matthew Palmer Mon, 15 Oct 2007 09:11:25 +1000 + +puppet (0.23.2-10) unstable; urgency=low + + * Recycle connections when we change (or get) certs. + * Catch and retry more transient errors in the XMLRPC wrapper. + + -- Matthew Palmer Thu, 27 Sep 2007 15:06:11 +1000 + +puppet (0.23.2-9) unstable; urgency=low + + * Recycle the HTTP connection if we get an EPIPE during a request. + Closes: #444177. Thanks to Jos Backus for helping with testing. + + -- Matthew Palmer Thu, 27 Sep 2007 09:55:34 +1000 + +puppet (0.23.2-8) unstable; urgency=low + + * Remove extraneous debugging output accidentally left behind in the last + release. + * Fix spelling mistakes in debian/control and debian/puppet.preinst. + Closes: #444158. + + -- Matthew Palmer Thu, 27 Sep 2007 07:45:07 +1000 + +puppet (0.23.2-7) unstable; urgency=low + + * Ignore ENOENT errors in the module plugin syncing code, since they're + innocuous and expected. + * Allow facts that are downloaded through pluginsync to be used like any + other fact. + * Allow users to still have an old-style plugins mount if they want, by + specifying a path for the mount. Also track down a fault in old-style + fileserving which did strange slash-stripping. Closes: #443932. + + -- Matthew Palmer Tue, 25 Sep 2007 16:41:32 +1000 + +puppet (0.23.2-6) unstable; urgency=low + + * Patch rails/param_name.rb to stop query failures, as per puppet#784. + * Actually honour namevar. + * Only set dbuser if explicitly asked for. + * Fix annoying database deletion error for ParamValue objects. + * Add an accessor for ca_file, since older openssl-ruby only had a writer. + * Fix the fileserver to honour ignore. Thanks to Nathan Ward for the + bug report on IRC. + + -- Matthew Palmer Thu, 20 Sep 2007 16:10:41 +1000 + +puppet (0.23.2-5) unstable; urgency=low + + * Add some NEWS for the ssldir transition. Should have done that earlier. + * Remove the explicit mode change for vardir, and fix up the mode on + statedir, as well. Closes: #425496. + * Only set some database parameters if they're explicitly set; this makes + life easier for PgSQL ident auth. + * Allow empty config options. + + -- Matthew Palmer Thu, 13 Sep 2007 11:09:59 +1000 + +puppet (0.23.2-4) unstable; urgency=low + + * Fix puppet#776 in a slightly better way by only flushing the cache when + a value is changed, rather than whenever a value is read. + * Apply patch from puppet#755 to cache connections to the Puppetmaster, + which improves performance by more than a little. + * Modify the fileserver so that it provides a 'plugins' mount which + exports the union of the plugins directory of all modules. + + -- Matthew Palmer Fri, 31 Aug 2007 15:32:04 +1000 + +puppet (0.23.2-3) unstable; urgency=low + + * Clear the config value cache every time. This is a titchy little + performance hit, but it works around puppet#776 rather nicely. + + -- Matthew Palmer Fri, 24 Aug 2007 16:08:04 +1000 + +puppet (0.23.2-2) unstable; urgency=low + + * Move the SSL state directory to a more policy-friendly location, + /var/lib/puppet/ssl. + + -- Matthew Palmer Tue, 21 Aug 2007 12:54:40 +1000 + +puppet (0.23.2-1) unstable; urgency=low + + * New upstream release. + + -- Matthew Palmer Tue, 7 Aug 2007 12:47:49 +1000 + +puppet (0.23.1-1) unstable; urgency=low + + * New upstream release. + * Switch primary maintainer to me. Thanks jaq. + * Make the recommendation for rails >= 1.2.3-2, to avoid + incompatibilities. This breaks compatibility with stable, but the rails + package from unstable should install cleanly in stable. Closes: #433999 + + -- Matthew Palmer Sat, 21 Jul 2007 16:34:36 +1000 + +puppet (0.23.0-1) unstable; urgency=low + + * New upstream release. + - Includes a new configuration file handling system; see NEWS.Debian. + + -- Matthew Palmer Mon, 25 Jun 2007 09:55:12 +1000 + +puppet (0.22.4-2) unstable; urgency=low + + * Depend on libshadow-ruby1.8, for new password modification functionality + added to upstream 0.22.4. + * Several improvements from Micah Anderson: + - Better vim syntax installation process. + - Install Emacs syntax highlighting. + - Install logcheck rules. Closes: #421851. + + -- Matthew Palmer Thu, 3 May 2007 15:04:15 +1000 + +puppet (0.22.4-1) unstable; urgency=low + + * New upstream release. + + -- Matthew Palmer Wed, 2 May 2007 12:20:15 +1000 + +puppet (0.22.3-1) unstable; urgency=low + + * New upstream release. Closes: #415773. + * Switch to using our own logrotate config, and enhance it as per + David Schmitt's suggestions. Closes: #414282. + * Add puppetrun to the puppetmaster package, and actually put puppetdoc + into the puppet package. Closes: #419273. + * Copy vim syntax highlighting file into the puppet package, and add a + stanza to have Vim automatically highlight .pp files. Closes: #412868. + Thanks to David Schmitt for researching how to do all of that. + * Add a templatedir setting to the default puppetmasterd.conf to make it + obvious that it can be changed. Closes: #407506. + + -- Matthew Palmer Wed, 18 Apr 2007 14:03:33 +1000 + +puppet (0.22.1-1) unstable; urgency=low + + * New upstream release. + + -- Matthew Palmer Fri, 2 Feb 2007 09:06:46 +1100 + +puppet (0.22.0-1) unstable; urgency=low + + * New upstream release. + * Use --startas instead of --init in init scripts, which (according to + Paul Hampson) makes checking for already-running instances work. + Closes: #405912. + + -- Matthew Palmer Mon, 8 Jan 2007 08:41:35 +1100 + +puppet (0.20.1-1) unstable; urgency=low + + * New upstream release. (Closes: #387674) + * Rationalise the puppetmasterd init script. + * Add inclusion of /etc/default files for init scripts. (Closes: #388178) + * Add puppet.conf to match puppetd.conf. (Closes: #385646) + + -- Matthew Palmer Thu, 30 Nov 2006 10:54:19 +1100 + +puppet (0.18.4-1) unstable; urgency=low + + * New upstream release. + - Properly detect all services, including those in rcS.d. + (Closes: #378351) + * Add Homepage: to the long description. (Closes: #377896) + + -- Matthew Palmer Mon, 24 Jul 2006 19:46:06 +1000 + +puppet (0.18.3-1) unstable; urgency=low + + * New upstream version. + - Set DEBIAN_FRONTEND=noninteractive when installing Debian packages. + (Closes: #378338) + + -- Matthew Palmer Sun, 16 Jul 2006 10:58:50 +1000 + +puppet (0.18.1-1) unstable; urgency=low + + * Make Puppet not wait for a cert at all (to prevent startup hangs). + * Cleanup the init scripts to not have NO_START detritus. + * Apply puppet.debian-frontend, to set DEBIAN_FRONTEND=noninteractive on + package installation. + + -- Matthew Palmer Tue, 27 Jun 2006 15:05:32 +1000 + +puppet (0.18.0-1) unstable; urgency=low + + * Initial release. (Closes: #348625) + + -- Matthew Palmer Wed, 24 May 2006 13:10:01 +1000 + --- puppet-2.7.1.orig/debian/puppet.init +++ puppet-2.7.1/debian/puppet.init @@ -0,0 +1,84 @@ +#! /bin/sh +### BEGIN INIT INFO +# Provides: puppet +# Required-Start: $network $named $remote_fs $syslog +# Required-Stop: $network $named $remote_fs $syslog +# Should-Start: puppet +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +### END INIT INFO + +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin +DAEMON=/usr/bin/puppet +DAEMON_OPTS="agent" +NAME=agent +DESC="puppet agent" +PIDFILE="/var/run/puppet/${NAME}.pid" + +test -x $DAEMON || exit 0 + +[ -r /etc/default/puppet ] && . /etc/default/puppet + +. /lib/lsb/init-functions + +is_true() { + if [ "x$1" = "xtrue" -o "x$1" = "xyes" -o "x$1" = "x0" ] ; then + return 0 + else + return 1 + fi +} + +reload_puppet_agent() { + start-stop-daemon --stop --quiet --signal HUP --pidfile $PIDFILE +} + +start_puppet_agent() { + if is_true "$START" ; then + start-stop-daemon --start --quiet --pidfile $PIDFILE \ + --startas $DAEMON -- $NAME $DAEMON_OPTS + else + echo "" + echo "puppet not configured to start, please edit /etc/default/puppet to enable" + fi +} + +stop_puppet_agent() { + start-stop-daemon --stop --quiet --oknodo --pidfile $PIDFILE +} + +status_puppet_agent() { + status_of_proc -p "${PIDFILE}" "${DAEMON}" "${NAME}" +} + +case "$1" in + start) + log_begin_msg "Starting $DESC" + start_puppet_agent + log_end_msg $? + ;; + stop) + log_begin_msg "Stopping $DESC" + stop_puppet_agent + log_end_msg $? + ;; + reload) + log_begin_msg "Reloading $DESC" + reload_puppet_agent + log_end_msg $? + ;; + status) + status_puppet_agent + ;; + restart|force-reload) + log_begin_msg "Restarting $DESC" + stop_puppet_agent + sleep 1 + start_puppet_agent + log_end_msg $? + ;; + *) + echo "Usage: $0 {start|stop|status|restart|force-reload|reload}" >&2 + exit 1 + ;; +esac --- puppet-2.7.1.orig/debian/docs +++ puppet-2.7.1/debian/docs @@ -0,0 +1 @@ +README.md --- puppet-2.7.1.orig/debian/puppetmaster-common.install +++ puppet-2.7.1/debian/puppetmaster-common.install @@ -0,0 +1,5 @@ +debian/fileserver.conf etc/puppet +conf/auth.conf etc/puppet +debian/tmp/usr/sbin/puppetca usr/sbin +debian/tmp/usr/sbin/puppetrun usr/sbin +debian/tmp/usr/sbin/puppetqd usr/sbin --- puppet-2.7.1.orig/debian/copyright +++ puppet-2.7.1/debian/copyright @@ -0,0 +1,360 @@ +Format-Specification: http://svn.debian.org/wsvn/dep/web/deps/dep5.mdwn?op=file&rev=135 +Name: Puppet +Maintainer: + Andrew Pollock , + Micah Anderson , + Nigel Kersten , + Stig Sandbeck Mathisen +Source: git://github.com/reductivelabs/puppet.git, + http://reductivelabs.com/trac/puppet/wiki/DownloadingPuppet + +Copyright: 2005-2010 Reductive Labs, LLC +License: GPL-2+ + +Files: debian/* +Copyright: + Andrew Pollock , + Jamie Wilkinson , + Matthew Palmer , + Micah Anderson , + Nigel Kersten , + Stig Sandbeck Mathisen , + Thom May +License: GPL-2 + +Files: install.rb +Copyright: Austin Ziegler +License: GPL-2+ + +Files: conf/gentoo/init.d/puppet* +Copyright: Gentoo Foundation +License: GPL-2 + +Files: lib/puppet/util/rdoc/generators/template/puppet/puppet.rb +Copyright: The FaerieMUD Consortium. +License: CC-BY-1.0 + This work is licensed under the Creative Commons Attribution License. To view + a copy of this license, visit http://creativecommons.org/licenses/by/1.0/ or + send a letter to Creative Commons, 559 Nathan Abbott Way, Stanford, California + 94305, USA. + +Files: conf/osx/createpackage.sh +Copyright: Google Inc. +License: Apache + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + . + http://www.apache.org/licenses/LICENSE-2.0 + . + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License + . + On Debian systems, the full text of the Apache License can be found at + "/usr/share/common-licenses/Apache-2.0" + +Files: lib/puppet/external/event-loop/* +Copyright: Daniel Brockman +License: GPL-2+ + +Files: lib/puppet/external/nagios/parser.rb +Copyright: Minero Aoki +License: other + This program is free software. + You can distribute/modify this program under the same terms of ruby. + . + As a special exception, when this code is copied by Racc + into a Racc output file, you may use that output file + without restriction. + +Files: lib/puppet/network/http_server/mongrel.rb +Copyright: Manuel Holtgrewe, Luke Kanies +License: MIT + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + . + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + . + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + +Files: lib/puppet/provider/augeas/augeas.rb, lib/puppet/type/augeas.rb +Copyright: Red Hat Inc. +License: GPL-2+ + +Files: lib/puppet/provider/mcx/mcxcontent.rb +Copyright: Jeff McCune +License: GPL-2+ + +Files: lib/puppet/type/mcx.rb, spec/unit/type/mcx.rb +Copyright: Jeffrey J McCune. +License: GPL-2+ + +Files: lib/puppet/provider/nameservice/directoryservice.rb +Copyright: Jeff McCune +License: GPL-2 + +Files: lib/puppet/provider/package/pkgdmg.rb +Copyright: Jeff McCune Jeff McCune +License: GPL-2 + +Files: test/ral/providers/service/debian.rb +Copyright: David Schmitt +License: missing + +Files: examples/modules/sample-module/lib/puppet/parser/functions/hostname_to_dn.rb +Copyright: David Schmitt +License: BSD + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the Author nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + . + THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + +License: GPL-2 + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation (version 2 of the License) + . + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + . + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA + . + On Debian systems, the full text of the GNU General Public + License version 2 can be found in the file + "/usr/share/common-licenses/GPL-2". + +License: GPL-2+ + This program is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any + later version. + . + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty + of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + . + You should have received a copy of the GNU General Public + License along with this program; if not, write to the Free + Software Foundation, 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + . + On Debian systems, the full text of the GNU General Public + License version 2 can be found in the file + "/usr/share/common-licenses/GPL-2". + +License: CC-BY-1.0 + THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE + COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY + COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS + AUTHORIZED UNDER THIS LICENSE IS PROHIBITED. + . + BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE + BOUND BY THE TERMS OF THIS LICENSE. THE LICENSOR GRANTS YOU THE RIGHTS + CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND + CONDITIONS. + . + 1. Definitions + . + "Collective Work" means a work, such as a periodical issue, anthology or + encyclopedia, in which the Work in its entirety in unmodified form, along with + a number of other contributions, constituting separate and independent works in + themselves, are assembled into a collective whole. A work that constitutes a + Collective Work will not be considered a Derivative Work (as defined below) for + the purposes of this License. "Derivative Work" means a work based upon the + Work or upon the Work and other pre-existing works, such as a translation, + musical arrangement, dramatization, fictionalization, motion picture version, + sound recording, art reproduction, abridgment, condensation, or any other form + in which the Work may be recast, transformed, or adapted, except that a work + that constitutes a Collective Work will not be considered a Derivative Work for + the purpose of this License. + . + "Licensor" means the individual or entity that offers the Work under the terms + of this License. "Original Author" means the individual or entity who created + the Work. + . + "Work" means the copyrightable work of authorship offered under the terms of + this License. "You" means an individual or entity exercising rights under this + License who has not previously violated the terms of this License with respect + to the Work, or who has received express permission from the Licensor to + exercise rights under this License despite a previous violation. + . + 2. Fair Use Rights. Nothing in this license is intended to reduce, limit, or + restrict any rights arising from fair use, first sale or other limitations on + the exclusive rights of the copyright owner under copyright law or other + applicable laws. + . + 3. License Grant. Subject to the terms and conditions of this License, Licensor + hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the + duration of the applicable copyright) license to exercise the rights in the + Work as stated below: + . + a. to reproduce the Work, to incorporate the Work into one or more Collective + Works, and to reproduce the Work as incorporated in the Collective Works; + . + b. to create and reproduce Derivative Works; + . + c. to distribute copies or phonorecords of, display publicly, perform publicly, + and perform publicly by means of a digital audio transmission the Work + including as incorporated in Collective Works; + . + d. to distribute copies or phonorecords of, display publicly, perform publicly, + and perform publicly by means of a digital audio transmission Derivative Works; + . + The above rights may be exercised in all media and formats whether now known or + hereafter devised. The above rights include the right to make such + modifications as are technically necessary to exercise the rights in other + media and formats. All rights not expressly granted by Licensor are hereby + reserved. + . + 4. Restrictions. + . + The license granted in Section 3 above is expressly made subject to and limited + by the following restrictions: + . + a. You may distribute, publicly display, publicly perform, or publicly + digitally perform the Work only under the terms of this License, and You must + include a copy of, or the Uniform Resource Identifier for, this License with + every copy or phonorecord of the Work You distribute, publicly display, + publicly perform, or publicly digitally perform. You may not offer or impose + any terms on the Work that alter or restrict the terms of this License or the + recipients' exercise of the rights granted hereunder. You may not sublicense + the Work. You must keep intact all notices that refer to this License and to + the disclaimer of warranties. You may not distribute, publicly display, + publicly perform, or publicly digitally perform the Work with any technological + measures that control access or use of the Work in a manner inconsistent with + the terms of this License Agreement. The above applies to the Work as + incorporated in a Collective Work, but this does not require the Collective + Work apart from the Work itself to be made subject to the terms of this + License. If You create a Collective Work, upon notice from any Licensor You + must, to the extent practicable, remove from the Collective Work any reference + to such Licensor or the Original Author, as requested. If You create a + Derivative Work, upon notice from any Licensor You must, to the extent + practicable, remove from the Derivative Work any reference to such Licensor or + the Original Author, as requested. + . + b. If you distribute, publicly display, publicly perform, or publicly digitally + perform the Work or any Derivative Works or Collective Works, You must keep + intact all copyright notices for the Work and give the Original Author credit + reasonable to the medium or means You are utilizing by conveying the name (or + pseudonym if applicable) of the Original Author if supplied; the title of the + Work if supplied; in the case of a Derivative Work, a credit identifying the + use of the Work in the Derivative Work (e.g., "French translation of the Work + by Original Author," or "Screenplay based on original Work by Original + Author"). Such credit may be implemented in any reasonable manner; provided, + however, that in the case of a Derivative Work or Collective Work, at a minimum + such credit will appear where any other comparable authorship credit appears + and in a manner at least as prominent as such other comparable authorship + credit. + . + 5. Representations, Warranties and Disclaimer + . + a. By offering the Work for public release under this License, Licensor + represents and warrants that, to the best of Licensor's knowledge after + reasonable inquiry: + . + i. Licensor has secured all rights in the Work necessary to grant the license + rights hereunder and to permit the lawful exercise of the rights granted + hereunder without You having any obligation to pay any royalties, compulsory + license fees, residuals or any other payments; + . + ii. The Work does not infringe the copyright, trademark, publicity rights, common + law rights or any other right of any third party or constitute defamation, + invasion of privacy or other tortious injury to any third party. + . + b. EXCEPT AS EXPRESSLY STATED IN THIS LICENSE OR OTHERWISE AGREED IN WRITING OR + REQUIRED BY APPLICABLE LAW, THE WORK IS LICENSED ON AN "AS IS" BASIS, WITHOUT + WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT + LIMITATION, ANY WARRANTIES REGARDING THE CONTENTS OR ACCURACY OF THE WORK. + . + 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, + AND EXCEPT FOR DAMAGES ARISING FROM LIABILITY TO A THIRD PARTY RESULTING FROM + BREACH OF THE WARRANTIES IN SECTION 5, IN NO EVENT WILL LICENSOR BE LIABLE TO + YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR + EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF + LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + . + 7. Termination + . + a. This License and the rights granted hereunder will terminate automatically + upon any breach by You of the terms of this License. Individuals or entities + who have received Derivative Works or Collective Works from You under this + License, however, will not have their licenses terminated provided such + individuals or entities remain in full compliance with those licenses. Sections + 1, 2, 5, 6, 7, and 8 will survive any termination of this License. + . + b. Subject to the above terms and conditions, the license granted here is + perpetual (for the duration of the applicable copyright in the Work). + Notwithstanding the above, Licensor reserves the right to release the Work + under different license terms or to stop distributing the Work at any time; + provided, however that any such election will not serve to withdraw this + License (or any other license that has been, or is required to be, granted + under the terms of this License), and this License will continue in full force + and effect unless terminated as stated above. + . + 8. Miscellaneous + . + Each time You distribute or publicly digitally perform the Work or a Collective + Work, the Licensor offers to the recipient a license to the Work on the same + terms and conditions as the license granted to You under this License. + . + Each time You distribute or publicly digitally perform a Derivative Work, + Licensor offers to the recipient a license to the original Work on the same + terms and conditions as the license granted to You under this License. + . + If any provision of this License is invalid or unenforceable under applicable + law, it shall not affect the validity or enforceability of the remainder of the + terms of this License, and without further action by the parties to this + agreement, such provision shall be reformed to the minimum extent necessary to + make such provision valid and enforceable. + . + No term or provision of this License shall be deemed waived and no breach + consented to unless such waiver or consent shall be in writing and signed by + the party to be charged with such waiver or consent. + . + This License constitutes the entire agreement between the parties with respect + to the Work licensed here. There are no understandings, agreements or + representations with respect to the Work not specified here. Licensor shall not + be bound by any additional provisions that may appear in any communication from + You. This License may not be modified without the mutual written agreement of + the Licensor and You. --- puppet-2.7.1.orig/debian/puppet.default +++ puppet-2.7.1/debian/puppet.default @@ -0,0 +1,7 @@ +# Defaults for puppet - sourced by /etc/init.d/puppet + +# Start puppet on boot? +START=no + +# Startup options +DAEMON_OPTS="" --- puppet-2.7.1.orig/debian/puppet.preinst +++ puppet-2.7.1/debian/puppet.preinst @@ -0,0 +1,20 @@ +#!/bin/sh + +set -e + +# Remove renamed configuration files which are now handled by other +# packages +if dpkg-maintscript-helper supports rm_conffile 2>/dev/null; then + + dpkg-maintscript-helper rm_conffile \ + /etc/logrotate.d/puppet 2.6.4-2 puppet -- "$@" + + dpkg-maintscript-helper rm_conffile \ + /etc/logcheck/ignore.d.server/puppet 2.6.4-2 puppet -- "$@" + + dpkg-maintscript-helper rm_conffile \ + /etc/emacs/site-start.d/50puppet-mode-init.el 2.6.4-2 puppet -- "$@" + +fi + +#DEBHELPER# --- puppet-2.7.1.orig/debian/README.source +++ puppet-2.7.1/debian/README.source @@ -0,0 +1,2 @@ +The debian directory is now maintained on Alioth in git. +See http://pkg-puppet.alioth.debian.org/ for more information. --- puppet-2.7.1.orig/debian/puppetmaster.default +++ puppet-2.7.1/debian/puppetmaster.default @@ -0,0 +1,38 @@ +# Defaults for puppetmaster - sourced by /etc/init.d/puppetmaster + +# Start puppetmaster on boot? If you are using passenger, you should +# have this set to "no" +START=yes + +# Startup options +DAEMON_OPTS="" + +# What server type to run +# Options: +# webrick: default, cannot handle more than ~30 nodes +# mongrel: scales better than webrick because you can run +# multiple processes if you are getting +# connection-reset or End-of-file errors, switch to +# mongrel. Requires front-end web-proxy such as +# apache, nginx, or pound, more information: +# http://reductivelabs.com/trac/puppet/wiki/UsingMongrel +# passenger: no need to set this, be sure to set START=no above +SERVERTYPE=webrick + +# How many puppetmaster instances to start? Its pointless to set this +# higher than 1 if you are not using mongrel. +PUPPETMASTERS=1 + +# What port should the puppetmaster listen on (default: 8140). If +# PUPPETMASTERS is set to a number greater than 1, then the port for +# the first puppetmaster will be set to the port listed below, and +# further instances will be incremented by one +# +# NOTE: if you are using mongrel, then you will need to have a +# front-end web-proxy (such as apache, nginx, pound) that takes +# incoming requests on the port your clients are connecting to +# (default is: 8140), and then passes them off to the mongrel +# processes. In this case it is recommended to run your web-proxy on +# port 8140 and change the below number to something else, such as +# 18140. +PORT=8140 --- puppet-2.7.1.orig/debian/puppet.install +++ puppet-2.7.1/debian/puppet.install @@ -0,0 +1,4 @@ +debian/tmp/usr/bin/filebucket usr/bin +debian/tmp/usr/bin/puppetdoc usr/bin +debian/tmp/usr/sbin/puppetd usr/sbin +debian/tmp/usr/bin/ralsh usr/bin --- puppet-2.7.1.orig/debian/puppetmaster-passenger.postinst +++ puppet-2.7.1/debian/puppetmaster-passenger.postinst @@ -0,0 +1,61 @@ +#!/bin/sh + +set -e + +if [ "$1" = "configure" ]; then + + # Change the owner of the rack config.ru to be the puppet user + # because passenger will suid to that user, see #577366 + if ! dpkg-statoverride --list /usr/share/puppet/rack/puppetmasterd/config.ru >/dev/null 2>&1 + then + dpkg-statoverride --update --add puppet puppet 0644 /usr/share/puppet/rack/puppetmasterd/config.ru + fi + # Setup passenger configuration + if [ "$2" = "" ]; then + # Initialize puppetmaster CA and generate the master certificate + # only if the host doesn't already have any puppet ssl certificate. + # The ssl key and cert need to be available (eg generated) before + # apache2 is configured and started since apache2 ssl configuration + # uses the puppetmaster ssl files. + if [ ! -e "$(puppetca --configprint hostcert)" ]; then + puppetca --generate $(puppetca --configprint certname) + fi + # Setup apache2 configuration files + APACHE2_SITE_FILE="/etc/apache2/sites-available/puppetmaster" + if [ ! -e "${APACHE2_SITE_FILE}" ]; then + cp /usr/share/puppetmaster-passenger/apache2.site.conf.tmpl "${APACHE2_SITE_FILE}" + # Fix path to SSL files + sed -r -i "s|(SSLCertificateFile\s+).+$|\1$(puppet --configprint hostcert)|" "${APACHE2_SITE_FILE}" + sed -r -i "s|(SSLCertificateKeyFile\s+).+$|\1$(puppet --configprint hostprivkey)|" "${APACHE2_SITE_FILE}" + sed -r -i "s|(SSLCACertificateFile\s+).+$|\1$(puppet --configprint localcacert)|" "${APACHE2_SITE_FILE}" + sed -r -i "s|(SSLCertificateChainFile\s+).+$|\1$(puppet --configprint localcacert)|" "${APACHE2_SITE_FILE}" + sed -r -i "s|(SSLCARevocationFile\s+).+$|\1$(puppet --configprint cacrl)|" "${APACHE2_SITE_FILE}" + fi + a2enmod ssl + a2ensite puppetmaster + if [ -x "/etc/init.d/apache2" ]; then + # Seems that a restart is needed. reload breaks ssl apparently. + if [ -x "`which invoke-rc.d 2>/dev/null`" ]; then + invoke-rc.d apache2 restart || exit $? + else + /etc/init.d/apache2 restart || exit $? + fi + fi + fi + # Fix CRL file on upgrade to use the CA crl file instead of the host crl. + if dpkg --compare-versions "$2" lt-nl "2.6.1-1"; then + if [ -e /etc/apache2/sites-available/puppetmaster ]; then + sed -r -i 's|SSLCARevocationFile[[:space:]]+/var/lib/puppet/ssl/crl.pem$|SSLCARevocationFile /var/lib/puppet/ssl/ca/ca_crl.pem|' /etc/apache2/sites-available/puppetmaster + if [ -x "/etc/init.d/apache2" ]; then + # Seems that a restart is needed. reload breaks ssl apparently. + if [ -x "`which invoke-rc.d 2>/dev/null`" ]; then + invoke-rc.d apache2 restart || exit $? + else + /etc/init.d/apache2 restart || exit $? + fi + fi + fi + fi +fi + +#DEBHELPER# --- puppet-2.7.1.orig/debian/README.Debian +++ puppet-2.7.1/debian/README.Debian @@ -0,0 +1,8 @@ +puppet for Debian +------------------ + +The default puppet configuration in Debian will automatically integrate with +etckeeper if etckeeper is installed. puppet will automatically commit any +changes made to files in /etc via etckeeper before and after its run. + + -- Mathias Gug Wed, 18 Aug 2010 15:06:06 -0400 --- puppet-2.7.1.orig/debian/puppetmaster.install +++ puppet-2.7.1/debian/puppetmaster.install @@ -0,0 +1 @@ +debian/tmp/usr/sbin/puppetmasterd usr/sbin --- puppet-2.7.1.orig/debian/puppet-el.emacsen-startup +++ puppet-2.7.1/debian/puppet-el.emacsen-startup @@ -0,0 +1,9 @@ +;; -*-emacs-lisp-*- +;; +;; Emacs startup file for the Debian GNU/Linux puppet-el package + +(autoload 'puppet-mode "puppet-mode" "Major mode for editing puppet manifests") + +(add-to-list 'auto-mode-alist '("\\.pp$" . puppet-mode)) + + --- puppet-2.7.1.orig/debian/puppetmaster.NEWS +++ puppet-2.7.1/debian/puppetmaster.NEWS @@ -0,0 +1,13 @@ +puppet (2.6.1~rc3-1) experimental; urgency=low + + Some packages recommended by the puppetmaster package are now suggested + instead, since they are not needed for the default operation of the + puppetmaster package. This means they are not installed by default. + + The packages affected are "libldap-ruby1.8", used for external nodes in LDAP, + "rdoc", which is used by puppet doc, and "rails", which is used for stored + configurations. + + If you need this functionality, please install the packages explicitly. + + -- Stig Sandbeck Mathisen Fri, 27 Aug 2010 12:17:02 +0200 --- puppet-2.7.1.orig/debian/puppetmaster-passenger.dirs +++ puppet-2.7.1/debian/puppetmaster-passenger.dirs @@ -0,0 +1,4 @@ +usr/share/puppet/rack/puppetmasterd +usr/share/puppet/rack/puppetmasterd/public +usr/share/puppet/rack/puppetmasterd/tmp +usr/share/puppetmaster-passenger/ --- puppet-2.7.1.orig/debian/puppet-common.postinst +++ puppet-2.7.1/debian/puppet-common.postinst @@ -0,0 +1,35 @@ +#!/bin/sh + +set -e + +if [ "$1" = "configure" ]; then + + # Create the "puppet" user + if ! getent passwd puppet > /dev/null; then + adduser --quiet --system --group --home /var/lib/puppet \ + --no-create-home \ + --gecos "Puppet configuration management daemon" \ + puppet + fi + + # Set correct permissions and ownership for puppet directories + if ! dpkg-statoverride --list /var/log/puppet >/dev/null 2>&1; then + dpkg-statoverride --update --add puppet puppet 0750 /var/log/puppet + fi + + if ! dpkg-statoverride --list /var/lib/puppet >/dev/null 2>&1; then + dpkg-statoverride --update --add puppet puppet 0750 /var/lib/puppet + fi + + # Create folders common to "puppet" and "puppetmaster", which need + # to be owned by the "puppet" user + install --owner puppet --group puppet --directory \ + /var/lib/puppet/state + + # Handle + if [ -d /etc/puppet/ssl ] && [ ! -e /var/lib/puppet/ssl ] && grep -q 'ssldir=/var/lib/puppet/ssl' /etc/puppet/puppet.conf; then + mv /etc/puppet/ssl /var/lib/puppet/ssl + fi +fi + +#DEBHELPER# --- puppet-2.7.1.orig/debian/puppet-el.install +++ puppet-2.7.1/debian/puppet-el.install @@ -0,0 +1 @@ +ext/emacs/puppet-mode.el usr/share/emacs/site-lisp --- puppet-2.7.1.orig/debian/puppetmaster-common.puppetqd.default +++ puppet-2.7.1/debian/puppetmaster-common.puppetqd.default @@ -0,0 +1,27 @@ +# Defaults for puppetqd - sourced by /etc/init.d/puppetqd + +# Should puppetqd (the storeconfigs queuing broker) be started? +# This can take some load off of the puppetmaster by queuing the +# storeconfig updates to the database with puppetqd. You need +# to have the 'stompserver' package installed and running, and +# the following configured in your puppet.conf: +# +# [main] +# queue_type = stomp +# queue_source = stomp://localhost:61613 +# dbadapter = (sqlite3|mysql|postgresql) +# dbserver=localhost +# dbname=puppet +# dbuser=puppet +# dbpassword=xxxx +# dblocation = /var/lib/puppet/storeconfigs.sqlite <-- only if using sqlite +# +# [master] +# async_storeconfigs = true +# +# See: http://reductivelabs.com/trac/puppet/wiki/UsingStoredConfiguration +# +# Once you have the proper puppet.conf, and stompserver, you can enable +# the following: +PUPPETQD=no +PUPPETQD_OPTS="" --- puppet-2.7.1.orig/debian/puppetmaster.lintian-overrides +++ puppet-2.7.1/debian/puppetmaster.lintian-overrides @@ -0,0 +1,3 @@ +# Man pages are automatically generated, not much to do here +puppetmaster binary: manpage-has-bad-whatis-entry +puppetmaster binary: manpage-has-errors-from-man --- puppet-2.7.1.orig/debian/puppetmaster-common.puppetqd.init +++ puppet-2.7.1/debian/puppetmaster-common.puppetqd.init @@ -0,0 +1,84 @@ +#! /bin/sh +### BEGIN INIT INFO +# Provides: puppetqd +# Required-Start: $network $named $remote_fs $syslog +# Required-Stop: $network $named $remote_fs $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +### END INIT INFO + +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin +DAEMON=/usr/bin/puppet +DAEMON_OPTS="queue" +NAME=queue +DESC="puppet queue" +PIDFILE="/var/run/puppet/${NAME}.pid" + +test -x $DAEMON || exit 0 + +[ -r /etc/default/puppetqd ] && . /etc/default/puppetqd + +. /lib/lsb/init-functions + +if [ ! -d /var/run/puppet ]; then + mkdir -p /var/run/puppet +fi + +chown puppet:puppet /var/run/puppet + +is_true() { + if [ "x$1" = "xtrue" -o "x$1" = "xyes" -o "x$1" = "x0" ] ; then + return 0 + else + return 1 + fi +} + +start_puppet_queue() { + if is_true "$PUPPETQD" ; then + start-stop-daemon --start --quiet --pidfile=${PIDFILE} \ + --startas $DAEMON -- $NAME $DAEMON_OPTS $PUPPETQD_OPTS + fi +} + +stop_puppet_queue() { + start-stop-daemon --stop --quiet --oknodo --pidfile ${PIDFILE} + rm -f ${PIDFILE} +} + +status_puppet_queue() { + if is_true "$PUPPETQD" ; then + status_of_proc -p "${PIDFILE}" "${DAEMON}" "${NAME}" + else + echo "" + echo "puppetqd not configured to start" + fi +} + + +case "$1" in + start) + log_begin_msg "Starting $DESC" + start_puppet_queue + log_end_msg $? + ;; + stop) + log_begin_msg "Stopping $DESC" + stop_puppet_queue + log_end_msg $? + ;; + status) + status_puppet_queue + ;; + reload|restart|force-reload) + log_begin_msg "Restarting $DESC" + stop_puppet_queue + sleep 1 + start_puppet_queue + log_end_msg $? + ;; + *) + echo "Usage: $0 {start|stop|status|restart|force-reload}" >&2 + exit 1 + ;; +esac --- puppet-2.7.1.orig/debian/puppet-common.postrm +++ puppet-2.7.1/debian/puppet-common.postrm @@ -0,0 +1,32 @@ +#!/bin/sh -e + +case "$1" in + purge) + # Remove puppetd.conf (used in > 0.24) + rm -f /etc/puppet/puppetd.conf + + # Remove puppet state directory created by the postinst script. + # This directory can be removed without causing harm + # according to upstream documentation. + rm -rf /var/lib/puppet/state + if [ -d /var/lib/puppet ]; then + rmdir --ignore-fail-on-non-empty /var/lib/puppet + fi + + # Remove puppet log files + rm -rf /var/log/puppet/ + ;; + remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) + + + ;; + + *) + echo "postrm called with unknown argument \`$1'" >&2 + exit 1 + +esac + +#DEBHELPER# + +exit 0 --- puppet-2.7.1.orig/debian/puppetmaster.README.debian +++ puppet-2.7.1/debian/puppetmaster.README.debian @@ -0,0 +1,16 @@ +By default, the puppetmaster package is configured in +/etc/default/puppetmaster to utilize the webrick server included in +the application. This server is single-threaded, very slow and tends +to fall over when you get close to 30 nodes. You will start to see +connection-reset or End-of-file errors when this happens. + +You will want to a server that can scale better than webrick, such as +Passenger (aka. mod_rails), or mongrel, both with a front-end +web-proxy such as apache. + +If you would like to run Passenger (aka. mod_rails) with apache install the +puppetmaster-passenger package. It will automatically setup an apache2 virtual +host that will run puppetmaster under Passenger. + +For more information: +http://projects.reductivelabs.com/projects/puppet/wiki/Using_Passenger --- puppet-2.7.1.orig/debian/puppet.NEWS +++ puppet-2.7.1/debian/puppet.NEWS @@ -0,0 +1,86 @@ +puppet (0.25.4-1) unstable; urgency=low + + In this packaging version we add a "puppet-common" package, on which the + packages "puppet" and "puppetmaster" depend. + + The editor syntax highlighting code previously in the "puppet" package has + been moved to the packages "puppet-el" and "vim-puppet". + + -- Stig Sandbeck Mathisen Tue, 19 Jan 2010 13:09:36 +0000 + +puppet (0.25.1-3) unstable; urgency=low + + The default setting for puppet starting on boot has been changed from true + to false. This means the puppet client will not automatically start on + install or boot until this file is modified. + + Note that the last Debian packaged version of puppet 0.24.x is too old to + communicate with a 0.25.x puppetmaster server. To resolve this, either + upgrade your client to 0.25.x, or install puppet 0.24.8 from source from: + http://reductivelabs.com/downloads/puppet/puppet-0.24.8.tgz + + -- Nigel Kersten Wed, 16 Dec 2009 12:49:03 -0800 + +puppet (0.23.2-12) unstable; urgency=low + + * Handling of the rundir setting has been changed; we now store PID files + in /var/run/puppet, and the initscripts have been modified to ensure + that this directory exists on startup. It is no longer necessary to set + rundir explicitly in /etc/puppet/puppet.conf, and you should ensure that + you have no explicit rundir setting in your puppet.conf unless you want + to use a custom rundir setting for your own local purposes. + + -- Matthew Palmer Sat, 20 Oct 2007 11:58:58 +1000 + +puppet (0.23.2-3) unstable; urgency=low + + * This version of Puppet makes a fairly major change to the location of + the CA and certificates, from /etc/puppet/ssl to the more FHS-compliant + location /var/lib/puppet/ssl. This is to be both policy-compliant and + to match the location of the ssldir in other distributions. + + If you have transitioned to using the consolidated puppet.conf config + file, there should be no problems. If you are using a stock + puppet.conf, the change should be made for you automatically, while if + you've customised puppet.conf the ssldir will be left where it is and + you should transition to the new location manually. + + The only source of problems is if you're still using per-program config + files (puppetd.conf, puppetmasterd.conf, etc). I haven't been able to + work out a damage-free way of transitioning to the new location, so + things will likely break for you -- ssldir will have been moved to + /var/lib/puppet/ssl, but your puppet programs will use the + old config file (with the default ssldir of /etc/puppet/ssl. In this + case, you'll likely get all sorts of certificate-related problems. + + The solution is to either switch to using puppet.conf (which is + necessary anyway because support for the deprecated per-program config + files will be going away sometime) with the new ssldir setting, or add + the ssldir setting to all your per-program config files (this includes + creating them for programs that don't already have a config file, like + puppetca.conf). Then delete /etc/puppet/ssl (since it's not needed) and + use the existing SSL data that was moved to /var/lib/puppet/ssl. + + -- Matthew Palmer Fri, 24 Aug 2007 16:08:04 +1000 + +puppet (0.23.0-1) unstable; urgency=low + + * As of upstream 0.23.0, the configuration file layout has been largely + revamped. Now, instead of having one file per program, there is now + a single file, /etc/puppet/puppet.conf, which contains sections for + each program, as well as a "main" section that sets global config + options relevant for all programs. + + See http://reductivelabs.com/trac/puppet/wiki/ConfigurationReference for + more info. + + For backwards compatibility, all programs still read the per-program + configuration files, and will ignore the generic puppet.conf file if the + per-program file still exists. To prevent accidents, you will need to + do the configuration change manually, by rewriting puppet.conf to match + your local configuration parameters and then deleting the old files. If + you haven't changed any config parameters, then it should be as simple + as deleting puppetd.conf and puppetmasterd.conf and restarting the + daemons, as the configuration itself hasn't changed between versions. + + -- Matthew Palmer Mon, 25 Jun 2007 10:43:53 +1000 --- puppet-2.7.1.orig/debian/rules +++ puppet-2.7.1/debian/rules @@ -0,0 +1,132 @@ +#!/usr/bin/make -f +# -*- makefile -*- + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +INSTALL=install -Dp + +prefix := $(CURDIR)/debian/tmp +bindir := $(prefix)/usr/bin +sbindir := $(prefix)/usr/sbin +libdir := $(prefix)/usr/lib +localstatedir := $(prefix)/var +rubylibdir := $(libdir)/ruby/1.8 +sysconfdir := $(prefix)/etc +pkgconfdir := $(sysconfdir)/puppet + +ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS))) + CFLAGS += -O0 +else + CFLAGS += -O2 +endif + +configure: configure-stamp +configure-stamp: + dh_testdir + touch configure-stamp + + +build-arch: build +build-indep: build + +build: build-stamp +build-stamp: configure-stamp + dh_testdir + touch build-stamp + +clean: + dh_testdir + dh_testroot + rm -f build-stamp configure-stamp + rm -f debian/puppet-common.logcheck.ignore.server + dh_clean + +install: build + dh_testdir + dh_testroot + dh_clean -k + dh_installdirs + + # Note sbindir does nothing right now. Leaving in for future + # puppet versions where it is respected. + $(CURDIR)/install.rb --destdir=debian/tmp --bindir=/usr/bin \ + --sbindir=/usr/sbin --sitelibdir=/usr/lib/ruby/1.8 + + # strip executable bit from all the non-executable files. + find debian/tmp/usr/lib/ruby/1.8 -type f -perm /u+x,g+x,o+x -exec chmod a-x {} \; + # fix the permissions on all of the directories + find debian/tmp/usr/lib/ruby/1.8 -type d -exec chmod 755 {} \; + + # Vim auto-syntax-highlighting stuff + $(INSTALL) -m0644 ext/vim/syntax/puppet.vim \ + $(CURDIR)/debian/vim-puppet/usr/share/vim/addons/syntax/ + $(INSTALL) -m0644 ext/vim/ftdetect/puppet.vim \ + $(CURDIR)/debian/vim-puppet/usr/share/vim/addons/ftdetect/ + $(INSTALL) -m0644 debian/vim-puppet.yaml \ + $(CURDIR)/debian/vim-puppet/usr/share/vim/registry/ + + # Emacs mode + $(INSTALL) -m0644 ext/emacs/puppet-mode.el \ + $(CURDIR)/debian/puppet-el/usr/share/emacs/site-lisp/puppet-mode.el + + # etckeeper integration + $(INSTALL) -m0755 debian/etckeeper-commit-pre \ + $(CURDIR)/debian/puppet/etc/puppet/etckeeper-commit-pre + $(INSTALL) -m0755 debian/etckeeper-commit-post \ + $(CURDIR)/debian/puppet/etc/puppet/etckeeper-commit-post + + # Install the rack README as README.rack + $(INSTALL) -m0644 ext/rack/README \ + $(CURDIR)/debian/puppetmaster-passenger/usr/share/doc/puppetmaster-passenger/README.rack + # Install the config.ru + $(INSTALL) -m0644 ext/rack/files/config.ru \ + $(CURDIR)/debian/puppetmaster-passenger/usr/share/puppet/rack/puppetmasterd + # Install apache2 site configuration template + $(INSTALL) -m0644 ext/rack/files/apache2.conf \ + $(CURDIR)/debian/puppetmaster-passenger/usr/share/puppetmaster-passenger/apache2.site.conf.tmpl + + # strip off the .rb from the puppetstoredconfigclean.rb + $(INSTALL) -m 0755 ext/puppetstoredconfigclean.rb \ + $(CURDIR)/debian/puppetmaster-common/usr/sbin/puppetstoredconfigclean + # Explicitly set ruby1.8 as parser, as with the rest of puppet + sed -i -e 1s/ruby/ruby1.8/ \ + $(CURDIR)/debian/puppetmaster-common/usr/sbin/puppetstoredconfigclean + + dh_installexamples -p puppet-common examples/* + + $(INSTALL) -d -m0775 $(pkgconfdir)/templates + $(INSTALL) -d -m0775 $(pkgconfdir)/modules + + # Logcheck rules. Gee I wish you could specify a file to source + # in dh_installlogcheck. + ln ext/logcheck/puppet debian/puppet-common.logcheck.ignore.server + +# Build architecture-dependent files here. +binary-arch: build install + +# Build architecture-independent files here. +binary-indep: build install + dh_testdir + dh_testroot + dh_install -i + dh_installchangelogs -i CHANGELOG + dh_installdocs -i + dh_installemacsen + dh_installlogcheck + dh_installman + dh_installinit -ppuppetmaster + dh_installinit --name=puppetqd + dh_installinit -ppuppet --error-handler=true -- defaults 21 + dh_installlogrotate -i + dh_lintian -i + dh_compress -i + dh_fixperms -i + dh_installdeb -i + dh_shlibdeps -i + dh_gencontrol -i + dh_md5sums -i + dh_builddeb -i + +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary install configure --- puppet-2.7.1.orig/debian/vim-puppet.dirs +++ puppet-2.7.1/debian/vim-puppet.dirs @@ -0,0 +1,3 @@ +usr/share/vim/registry +usr/share/vim/addons/syntax +usr/share/vim/addons/ftdetect --- puppet-2.7.1.orig/debian/puppetmaster.manpages +++ puppet-2.7.1/debian/puppetmaster.manpages @@ -0,0 +1 @@ +man/man8/puppetmasterd.8 --- puppet-2.7.1.orig/debian/puppetmaster.postrm +++ puppet-2.7.1/debian/puppetmaster.postrm @@ -0,0 +1,5 @@ +#!/bin/sh -e + +#DEBHELPER# + +exit 0 --- puppet-2.7.1.orig/debian/etckeeper-commit-post +++ puppet-2.7.1/debian/etckeeper-commit-post @@ -0,0 +1,10 @@ +#!/bin/sh + +PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + +which etckeeper > /dev/null 2>&1 || exit 0 + +etckeeper commit "committing changes in /etc after puppet catalog run" + +# Failure of etckeeper should not be fatal. +exit 0 --- puppet-2.7.1.orig/debian/vim-puppet.yaml +++ puppet-2.7.1/debian/vim-puppet.yaml @@ -0,0 +1,5 @@ +addon: puppet +description: "Syntax highlighting for puppet" +files: + - ftdetect/puppet.vim + - syntax/puppet.vim --- puppet-2.7.1.orig/debian/source/options +++ puppet-2.7.1/debian/source/options @@ -0,0 +1 @@ +single-debian-patch --- puppet-2.7.1.orig/debian/patches/CVE-2011-3869.patch +++ puppet-2.7.1/debian/patches/CVE-2011-3869.patch @@ -0,0 +1,40 @@ +From bdf728edc4c0b0e0e416f9d3e542b6815a4d3c0a Mon Sep 17 00:00:00 2001 +From: Daniel Pittman +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 +--- + 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 + --- puppet-2.7.1.orig/debian/patches/CVE-2011-3870.patch +++ puppet-2.7.1/debian/patches/CVE-2011-3870.patch @@ -0,0 +1,104 @@ +From 8d9575775737c08c6cbfdf7f9a22f2ea4ab21b20 Mon Sep 17 00:00:00 2001 +From: Ricky Zhou +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 for discovering this and +supplying the security fix. Awesome work. + +Signed-off-by: Daniel Pittman +--- + lib/puppet/provider/ssh_authorized_key/parsed.rb | 19 ++++++++++--------- + .../provider/ssh_authorized_key/parsed_spec.rb | 16 ++++++++-------- + 2 files changed, 18 insertions(+), 17 deletions(-) + +Index: puppet-2.7.1/lib/puppet/provider/ssh_authorized_key/parsed.rb +=================================================================== +--- puppet-2.7.1.orig/lib/puppet/provider/ssh_authorized_key/parsed.rb 2011-06-27 09:50:51.000000000 -0500 ++++ puppet-2.7.1/lib/puppet/provider/ssh_authorized_key/parsed.rb 2011-09-30 08:23:03.000000000 -0500 +@@ -56,21 +56,22 @@ + 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 +Index: puppet-2.7.1/spec/unit/provider/ssh_authorized_key/parsed_spec.rb +=================================================================== +--- puppet-2.7.1.orig/spec/unit/provider/ssh_authorized_key/parsed_spec.rb 2011-06-27 09:50:51.000000000 -0500 ++++ puppet-2.7.1/spec/unit/provider/ssh_authorized_key/parsed_spec.rb 2011-09-30 08:23:03.000000000 -0500 +@@ -114,15 +114,15 @@ + @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 + +@@ -158,11 +158,11 @@ + @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 + +@@ -173,9 +173,9 @@ + @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 + --- puppet-2.7.1.orig/debian/patches/secure-indirector-file-backed-terminus-base-cla.patch +++ puppet-2.7.1/debian/patches/secure-indirector-file-backed-terminus-base-cla.patch @@ -0,0 +1,328 @@ +From a05502f2d8c627c08ae33c2fb49d5dbe36affb16 Mon Sep 17 00:00:00 2001 +From: Daniel Pittman +Date: Wed, 28 Sep 2011 23:59:49 -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 +--- + lib/puppet/indirector/file.rb | 79 --------------- + spec/unit/indirector/file_spec.rb | 179 --------------------------------- + spec/unit/indirector/terminus_spec.rb | 6 +- + 3 files changed, 3 insertions(+), 261 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 b72bf4d..0000000 +--- a/spec/unit/indirector/file_spec.rb ++++ /dev/null +@@ -1,179 +0,0 @@ +-#!/usr/bin/env rspec +-require 'spec_helper' +-require 'puppet/indirector/file' +- +- +-describe Puppet::Indirector::File do +- before :all 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) +- +- module Testing; end +- @file_class = class Testing::MyFile < Puppet::Indirector::File +- self +- 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 2f37c1f..ccd6fd2 100755 +--- a/spec/unit/indirector/terminus_spec.rb ++++ b/spec/unit/indirector/terminus_spec.rb +@@ -2,7 +2,7 @@ + require 'spec_helper' + require 'puppet/defaults' + require 'puppet/indirector' +-require 'puppet/indirector/file' ++require 'puppet/indirector/memory' + + describe Puppet::Indirector::Terminus, :'fails_on_ruby_1.9.2' => true do + before :each do +@@ -201,14 +201,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 + --- puppet-2.7.1.orig/debian/patches/debian-changes +++ puppet-2.7.1/debian/patches/debian-changes @@ -0,0 +1,109 @@ +Description: Undocumented upstream changes + This patch has been created by dpkg-source during the package build + but it might have accumulated changes from several uploads. Please + check the changelog to (hopefully) learn more on those changes. + +--- puppet-2.7.1.orig/Rakefile ++++ puppet-2.7.1/Rakefile +@@ -9,7 +9,7 @@ require 'rspec' + require "rspec/core/rake_task" + + module Puppet +- PUPPETVERSION = File.read('lib/puppet.rb')[/PUPPETVERSION *= *'(.*)'/,1] or fail "Couldn't find PUPPETVERSION" ++ PUPPETVERSION = File.read('/usr/lib/ruby/1.8/puppet.rb')[/PUPPETVERSION *= *'(.*)'/,1] or fail "Couldn't find PUPPETVERSION" + end + + Dir['tasks/**/*.rake'].each { |t| load t } +--- puppet-2.7.1.orig/test/lib/puppettest/fakes.rb ++++ puppet-2.7.1/test/lib/puppettest/fakes.rb +@@ -1,4 +1,4 @@ +-require File.expand_path(File.join(File.dirname(__FILE__), '../../../lib/puppet/util')) ++require '/usr/lib/ruby/1.8/puppet/util' + + module PuppetTest + # A baseclass for the faketypes. +--- puppet-2.7.1.orig/lib/puppet/provider/service/init.rb ++++ puppet-2.7.1/lib/puppet/provider/service/init.rb +@@ -134,7 +134,15 @@ Puppet::Type.type(:service).provide :ini + # we just return that; otherwise, we return false, which causes it to + # fallback to other mechanisms. + def statuscmd +- (@resource[:hasstatus] == :true) && [initscript, :status] ++ if @resource[:hasstatus] == :true then ++ # Workaround the fact that initctl status command doesn't return ++ # proper exit codes. Can be removed once LP: #552786 is fixed. ++ if File.symlink?(initscript) && File.readlink(initscript) == "/lib/init/upstart-job" then ++ ['sh', '-c', "LANG=C invoke-rc.d #{File::basename(initscript)} status | grep -q '^#{File::basename(initscript)}.*running'" ] ++ else ++ [initscript, :status ] ++ end ++ end + end + + end +--- puppet-2.7.1.orig/ext/rack/files/apache2.conf ++++ puppet-2.7.1/ext/rack/files/apache2.conf +@@ -1,12 +1,4 @@ +- +-# you probably want to tune these settings +-PassengerHighPerformance on +-PassengerMaxPoolSize 12 +-PassengerPoolIdleTime 1500 +-# PassengerMaxRequests 1000 +-PassengerStatThrottleRate 120 +-RackAutoDetect Off +-RailsAutoDetect Off ++# Based on http://projects.puppetlabs.com/projects/1/wiki/Using_Passenger + + Listen 8140 + +@@ -15,20 +7,32 @@ Listen 8140 + SSLProtocol -ALL +SSLv3 +TLSv1 + SSLCipherSuite ALL:!ADH:RC4+RSA:+HIGH:+MEDIUM:-LOW:-SSLv2:-EXP + +- SSLCertificateFile /etc/puppet/ssl/certs/squigley.namespace.at.pem +- SSLCertificateKeyFile /etc/puppet/ssl/private_keys/squigley.namespace.at.pem +- SSLCertificateChainFile /etc/puppet/ssl/ca/ca_crt.pem +- SSLCACertificateFile /etc/puppet/ssl/ca/ca_crt.pem ++ SSLCertificateKeyFile /var/lib/puppet/ssl/private_keys/puppetmaster.example.com.pem ++ SSLCertificateFile /var/lib/puppet/ssl/certs/puppetmaster.example.com.pem ++ SSLCACertificateFile /var/lib/puppet/ssl/ca/ca_crt.pem ++ SSLCertificateChainFile /var/lib/puppet/ssl/ca/ca_crt.pem + # If Apache complains about invalid signatures on the CRL, you can try disabling + # CRL checking by commenting the next line, but this is not recommended. +- SSLCARevocationFile /etc/puppet/ssl/ca/ca_crl.pem ++ SSLCARevocationFile /var/lib/puppet/ssl/ca/ca_crl.pem ++ # Set to require if this puppetmaster doesn't issue certificates ++ # to puppet clients. ++ # NB: this requires SSLCACertificateFile to include the CA cert ++ # issuing puppet client certificate. + SSLVerifyClient optional + SSLVerifyDepth 1 + SSLOptions +StdEnvVars + +- DocumentRoot /etc/puppet/rack/public/ ++ # Passenger options that can be set in a virtual host ++ # configuration block. ++ PassengerHighPerformance on ++ PassengerStatThrottleRate 120 ++ PassengerUseGlobalQueue on ++ RackAutoDetect Off ++ RailsAutoDetect Off + RackBaseURI / +- ++ ++ DocumentRoot /usr/share/puppet/rack/puppetmasterd/public ++ + Options None + AllowOverride None + Order allow,deny +--- puppet-2.7.1.orig/lib/puppet/network/rest_authconfig.rb ++++ puppet-2.7.1/lib/puppet/network/rest_authconfig.rb +@@ -18,6 +18,7 @@ module Puppet + { :acl => "/certificate/", :method => :find, :authenticated => false }, + { :acl => "/certificate_request", :method => [:find, :save], :authenticated => false }, + { :acl => "/status", :method => [:find], :authenticated => true }, ++ { :acl => "/resource", :method => [:find, :save, :search], :authenticated => true }, + ] + + def self.main --- puppet-2.7.1.orig/debian/patches/CVE-2011-3871.patch +++ puppet-2.7.1/debian/patches/CVE-2011-3871.patch @@ -0,0 +1,66 @@ +From 906da37374def334b62722acf84e4b0d1324e1f7 Mon Sep 17 00:00:00 2001 +From: Daniel Pittman +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 +--- + lib/puppet/application/resource.rb | 27 +++++++++++++++++---------- + 1 files changed, 17 insertions(+), 10 deletions(-) + +Index: puppet-2.7.1/lib/puppet/application/resource.rb +=================================================================== +--- puppet-2.7.1.orig/lib/puppet/application/resource.rb 2011-06-27 09:50:51.000000000 -0500 ++++ puppet-2.7.1/lib/puppet/application/resource.rb 2011-09-30 08:26:35.000000000 -0500 +@@ -187,18 +187,25 @@ + 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 --- puppet-2.7.1.orig/debian/patches/CVE-2011-3848.patch +++ puppet-2.7.1/debian/patches/CVE-2011-3848.patch @@ -0,0 +1,138 @@ +From f1577a1c58d35c041174f4c6d7ef6801171d8b28 Mon Sep 17 00:00:00 2001 +From: Daniel Pittman +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 for the +responsible disclosure, and useful analysis, around this defect. + +Signed-off-by: Daniel Pittman +--- + 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 86ede59..79a5f90 100644 +--- a/lib/puppet/indirector.rb ++++ b/lib/puppet/indirector.rb +@@ -48,4 +48,11 @@ module Puppet::Indirector + module ClassMethods + attr_reader :indirection + 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 7b12d25..7cfabb8 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 8ee19c8..f3467bb 100755 +--- a/spec/unit/indirector/ssl_file_spec.rb ++++ b/spec/unit/indirector/ssl_file_spec.rb +@@ -88,6 +88,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 29f6d46..3f3ca12 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 + --- puppet-2.7.1.orig/debian/patches/series +++ puppet-2.7.1/debian/patches/series @@ -0,0 +1,7 @@ +debian-changes +CVE-2011-3848.patch +CVE-2011-3869.patch +CVE-2011-3870.patch +CVE-2011-3871.patch +secure-indirector-file-backed-terminus-base-cla.patch +CVE-2011-3872.patch --- puppet-2.7.1.orig/debian/patches/CVE-2011-3872.patch +++ puppet-2.7.1/debian/patches/CVE-2011-3872.patch @@ -0,0 +1,6017 @@ +Description: fix puppet master impersonation via incorrect certificates +Origin: Patch provided by upstream +Subject: [PATCH 01/35] (#2848) Set `certdnsnames` values into the CSR. +Subject: [PATCH 02/35] (#2848) extract the subjectAltName value from the CSR. +Subject: [PATCH 03/35] (#2848) Reject unknown (== all) extensions on the CSR. +Subject: [PATCH 04/35] (#2848) Rewrite SSL Certificate Factory, fixing +Subject: [PATCH 05/35] (#7224) Add a helper to Puppet::SSL::Certificate to +Subject: [PATCH 06/35] (#2848) List subject alt names in output of puppet +Subject: [PATCH 07/35] (#2848) CSR subjectAltNames handling while signing. +Subject: [PATCH 08/35] (#2848) Use `certdnsnames` when bootstrapping a local +Subject: [PATCH 09/35] (#2848) Rename `certdnsnames` to match new behaviour. +Subject: [PATCH 10/35] (#2848) rename subject-alt-name option to +Subject: [PATCH 11/35] Wire up the `setbycli` slot in Puppet settings. +Subject: [PATCH 12/35] (#2848) Migrate `dns-alt-names` back to settings. +Subject: [PATCH 13/35] (#2848) Only mark `subjectAltName` critical if +Subject: [PATCH 14/35] (#2848) Don't enable `emailProtection` for server +Subject: [PATCH 15/35] (#2848) Don't strip the subjectAltName label when +Subject: [PATCH 16/35] (#2848) Consistently use `subject_alt_names` as +Subject: [PATCH 17/35] (#2848) Consistent return values from +Subject: [PATCH 18/35] (#2848) Remove unused xmlrpc code +Subject: [PATCH 19/35] (#2848) Rework the xmlrpc CA handler to use the modern +Subject: [PATCH 20/35] (#2848) Remove the legacy SSLCertificates code +Subject: [PATCH 21/35] (#2848) Eliminate redundant `master_dns_alt_names`. +Subject: [PATCH 22/35] s/not_to/should_not/ for older versions of RSpec 2. +Subject: [PATCH 23/35] Add `lines` alias for `each_line` in Ruby 1.8.5. +Subject: [PATCH 24/35] Ruby 1.8.5 compatibility changes in tests and code. +Subject: [PATCH 25/35] Add support for dns-alt-names option to `puppet +Subject: [PATCH 26/35] Add --allow-dns-alt-names option to `puppet +Subject: [PATCH 27/35] (#2848) Config options require '_', not '-'. +Subject: [PATCH 28/35] Better 1.8.5 compatible implementation of `lines`. +Subject: [PATCH 29/35] More 1.8.5 compatibility fixes. +Subject: [PATCH 30/35] Fix some inconsistencies from merging +Subject: [PATCH 31/35] Fix failing CA Interface specs on Ruby 1.9 +Subject: [PATCH 32/35] (maint) Remove ssl dir before starting a master with +Subject: [PATCH 33/35] Allow a master to bootstrap itself with dns_alt_names +Subject: [PATCH 34/35] Improve the error message when a CSR is rejected +Subject: [PATCH 35/35] maint: spec_helper should only get loaded once + +Index: puppet-2.7.1/acceptance/tests/ticket_5477_master_not_dectect_sitepp.rb +=================================================================== +--- puppet-2.7.1.orig/acceptance/tests/ticket_5477_master_not_dectect_sitepp.rb 2011-10-23 13:05:44.020903800 -0400 ++++ puppet-2.7.1/acceptance/tests/ticket_5477_master_not_dectect_sitepp.rb 2011-10-23 13:05:46.384903778 -0400 +@@ -10,37 +10,27 @@ + step "Master: kill running Puppet Master" + on master, "ps -U puppet | awk '/puppet/ { print \$1 }' | xargs kill" + +-# Run tests against Master first +-step "Master: mv site.pp file to /tmp, if existing" +-on master, "if [ -e /etc/puppet/manifests/site.pp ] ; then mv /etc/puppet/manifests/site.pp /tmp/site.pp-5477 ; fi" +- +-# Start Puppet Master +-#step "Master: Run Puppet Master in verbose mode" +-#on master, puppet_master("--verbose") +-step "Master: Start Puppet Master" +-on master, puppet_master("--certdnsnames=\"puppet:$(hostname -s):$(hostname -f)\" --verbose") +- +-# Allow puppet server to start accepting conections +-sleep 10 +- +-# Run test on Agents +-step "Agent: agent --test" +-agents.each { |agent| +- on agent, puppet_agent("--test") +-} +- +-# Create a new site.pp +-step "Master: create basic site.pp file" +-on master, "echo 'notify{ticket_5477_notify:}' > /etc/puppet/manifests/site.pp" +- +-sleep 20 +- +-step "Agent: puppet agent --test" +-agents.each { |agent| +- on agent, "puppet agent -t", :acceptable_exit_codes => [2] +- fail_test "Site.pp not detect at Master?" unless +- stdout.include? 'ticket_5477_notify' +-} ++on master, "rm -f #{manifest_file}" ++on hosts, "rm -rf /etc/puppet/ssl" ++ ++with_master_running_on(master, "--manifest #{manifest_file} --dns_alt_names=\"puppet, $(hostname -s), $(hostname -f)\" --verbose --filetimeout 1 --autosign true") do ++ # Run test on Agents ++ step "Agent: agent --test" ++ on agents, puppet_agent("--test --server #{master}") ++ ++ # Create a new site.pp ++ step "Master: create basic site.pp file" ++ on master, "echo 'notify{ticket_5477_notify:}' > /etc/puppet/manifests/site.pp" ++ ++ sleep 20 ++ ++ step "Agent: puppet agent --test" ++ agents.each { |agent| ++ on agent, "puppet agent -t", :acceptable_exit_codes => [2] ++ fail_test "Site.pp not detect at Master?" unless ++ stdout.include? 'ticket_5477_notify' ++ } ++end + + step "Clean-up site.pp" + on master, "rm /etc/puppet/manifests/site.pp" +Index: puppet-2.7.1/acceptance/tests/ticket_7117_broke_env_criteria_authconf.rb +=================================================================== +--- puppet-2.7.1.orig/acceptance/tests/ticket_7117_broke_env_criteria_authconf.rb 2011-10-23 13:05:44.000903802 -0400 ++++ puppet-2.7.1/acceptance/tests/ticket_7117_broke_env_criteria_authconf.rb 2011-10-23 13:05:46.384903778 -0400 +@@ -18,25 +18,18 @@ + on master, puppet_master("--certdnsnames=\"puppet:$(hostname -s):$(hostname -f)\" --verbose --noop") + # allow Master to start and initialize environment + +-step "Verify Puppet Master is ready to accept connections" +-host=agents.first +-time1 = Time.new +-until +- on(host, "curl -k https://#{master}:8140") do +- sleep 1 +- end +-time2 = Time.new +-elapsed = time2 - time1 +-Log.notify "Slept for #{elapsed} seconds waiting for Puppet Master to become ready" ++on hosts, "rm -rf /etc/puppet/ssl" + +-# Run test on Agents +-step "Agent: agent --test" +-on agents, puppet_agent("--test") ++with_master_running_on(master, "--dns_alt_names=\"puppet, $(hostname -s), $(hostname -f)\" --rest_authconfig /tmp/auth.conf-7117 --verbose --autosign true") do ++ # Run test on Agents ++ step "Run agent to upload facts" ++ on agents, puppet_agent("--test --server #{master}") + +-step "Fetch agent facts from Puppet Master" +-agents.each do |host| +- on(host, "curl -k -H \"Accept: yaml\" https://#{master}:8140/override/facts/\`hostname -f\`") do +- assert_match(/--- !ruby\/object:Puppet::Node::Facts/, stdout, "Agent Facts not returned for #{host}") ++ step "Fetch agent facts from Puppet Master" ++ agents.each do |host| ++ on(host, "curl -k -H \"Accept: yaml\" https://#{master}:8140/override/facts/\`hostname -f\`") do ++ assert_match(/--- !ruby\/object:Puppet::Node::Facts/, stdout, "Agent Facts not returned for #{host}") ++ end + end + end + +Index: puppet-2.7.1/lib/puppet/application/cert.rb +=================================================================== +--- puppet-2.7.1.orig/lib/puppet/application/cert.rb 2011-10-23 13:05:43.952903801 -0400 ++++ puppet-2.7.1/lib/puppet/application/cert.rb 2011-10-23 13:05:46.384903778 -0400 +@@ -10,6 +10,7 @@ + def subcommand + @subcommand + end ++ + def subcommand=(name) + # Handle the nasty, legacy mapping of "clean" to "destroy". + sub = name.to_sym +@@ -38,11 +39,15 @@ + + require 'puppet/ssl/certificate_authority/interface' + Puppet::SSL::CertificateAuthority::Interface::INTERFACE_METHODS.reject {|m| m == :destroy }.each do |method| +- option("--#{method}", "-#{method.to_s[0,1]}") do ++ option("--#{method.to_s.gsub('_','-')}", "-#{method.to_s[0,1]}") do + self.subcommand = method + end + end + ++ option("--[no-]allow-dns-alt-names") do |value| ++ options[:allow_dns_alt_names] = value ++ end ++ + option("--verbose", "-v") do + Puppet::Util::Log.level = :info + end +@@ -181,8 +186,8 @@ + hosts = command_line.args.collect { |h| h.downcase } + end + begin +- @ca.apply(:revoke, :to => hosts) if subcommand == :destroy +- @ca.apply(subcommand, :to => hosts, :digest => @digest) ++ @ca.apply(:revoke, options.merge(:to => hosts)) if subcommand == :destroy ++ @ca.apply(subcommand, options.merge(:to => hosts, :digest => @digest)) + rescue => detail + puts detail.backtrace if Puppet[:trace] + puts detail.to_s +@@ -202,6 +207,15 @@ + Puppet::SSL::Host.ca_location = :only + end + ++ # If we are generating, and the option came from the CLI, it gets added to ++ # the data. This will do the right thing for non-local certificates, in ++ # that the command line but *NOT* the config file option will apply. ++ if subcommand == :generate ++ if Puppet.settings.setting(:dns_alt_names).setbycli ++ options[:dns_alt_names] = Puppet[:dns_alt_names] ++ end ++ end ++ + begin + @ca = Puppet::SSL::CertificateAuthority.new + rescue => detail +Index: puppet-2.7.1/lib/puppet/application/kick.rb +=================================================================== +--- puppet-2.7.1.orig/lib/puppet/application/kick.rb 2011-10-23 13:05:43.932903801 -0400 ++++ puppet-2.7.1/lib/puppet/application/kick.rb 2011-10-23 13:05:46.384903778 -0400 +@@ -173,8 +173,6 @@ + end + + def main +- require 'puppet/network/client' +- + Puppet.warning "Failed to load ruby LDAP library. LDAP functionality will not be available" unless Puppet.features.ldap? + require 'puppet/util/ldap/connection' + +Index: puppet-2.7.1/lib/puppet/defaults.rb +=================================================================== +--- puppet-2.7.1.orig/lib/puppet/defaults.rb 2011-10-23 13:05:43.984903802 -0400 ++++ puppet-2.7.1/lib/puppet/defaults.rb 2011-10-23 13:05:46.384903778 -0400 +@@ -206,9 +206,58 @@ + to the fully qualified domain name.", + :call_on_define => true, # Call our hook with the default value, so we're always downcased + :hook => proc { |value| raise(ArgumentError, "Certificate names must be lower case; see #1168") unless value == value.downcase }}, +- :certdnsnames => ['', "The DNS names on the Server certificate as a colon-separated list. +- If it's anything other than an empty string, it will be used as an alias in the created +- certificate. By default, only the server gets an alias set up, and only for 'puppet'."], ++ :certdnsnames => { ++ :default => '', ++ :hook => proc do |value| ++ unless value.nil? or value == '' then ++ Puppet.warning < < { ++ :default => '', ++ :desc => < { + :default => "$ssldir/certs", + :owner => "service", +@@ -437,9 +486,11 @@ + authorization system for `puppet master`." + ], + :ca => [true, "Wether the master should function as a certificate authority."], +- :modulepath => {:default => "$confdir/modules:/usr/share/puppet/modules", +- :desc => "The search path for modules as a colon-separated list of +- directories.", :type => :setting }, # We don't want this to be considered a file, since it's multiple files. ++ :modulepath => { ++ :default => "$confdir/modules#{File::PATH_SEPARATOR}/usr/share/puppet/modules", ++ :desc => "The search path for modules as a list of directories separated by the '#{File::PATH_SEPARATOR}' character.", ++ :type => :setting # We don't want this to be considered a file, since it's multiple files. ++ }, + :ssl_client_header => ["HTTP_X_CLIENT_DN", "The header containing an authenticated + client's SSL DN. Only used with Mongrel. This header must be set by the proxy + to the authenticated client's SSL DN (e.g., `/CN=puppet.puppetlabs.com`). +Index: puppet-2.7.1/lib/puppet/face/certificate.rb +=================================================================== +--- puppet-2.7.1.orig/lib/puppet/face/certificate.rb 2011-10-23 13:05:43.968903801 -0400 ++++ puppet-2.7.1/lib/puppet/face/certificate.rb 2011-10-23 13:05:46.384903778 -0400 +@@ -47,10 +47,21 @@ + $ puppet certificate generate somenode.puppetlabs.lan --ca-location remote + EOT + ++ # Duplicate the option here explicitly to distinguish if it was passed arg ++ # us vs. set in the config file. ++ option "--dns-alt-names NAMES" do ++ summary "Additional DNS names to add to the certificate request" ++ description Puppet.settings.setting(:dns_alt_names).desc ++ end ++ + when_invoked do |name, options| + host = Puppet::SSL::Host.new(name) +- host.generate_certificate_request +- host.certificate_request.class.indirection.save(host.certificate_request) ++ ++ # If dns_alt_names are specified via the command line, we will always add ++ # them. Otherwise, they will default to the config file setting iff this ++ # cert is for the host we're running on. ++ ++ host.generate_certificate_request(:dns_alt_names => options[:dns_alt_names]) + end + end + +@@ -82,10 +93,28 @@ + $ puppet certificate sign somenode.puppetlabs.lan --ca-location remote + EOT + ++ option("--[no-]allow-dns-alt-names") do ++ summary "Whether or not to accept DNS alt names in the certificate request" ++ end ++ + when_invoked do |name, options| + host = Puppet::SSL::Host.new(name) +- host.desired_state = 'signed' +- Puppet::SSL::Host.indirection.save(host) ++ if options[:ca_location] == :remote ++ if options[:allow_dns_alt_names] ++ raise ArgumentError, "--allow-dns-alt-names may not be specified with a remote CA" ++ end ++ ++ host.desired_state = 'signed' ++ Puppet::SSL::Host.indirection.save(host) ++ else ++ # We have to do this case manually because we need to specify ++ # allow_dns_alt_names. ++ unless ca = Puppet::SSL::CertificateAuthority.instance ++ raise ArgumentError, "This process is not configured as a certificate authority" ++ end ++ ++ ca.sign(name, options[:allow_dns_alt_names]) ++ end + end + end + +Index: puppet-2.7.1/lib/puppet/network/client/ca.rb +=================================================================== +--- puppet-2.7.1.orig/lib/puppet/network/client/ca.rb 2011-10-23 13:05:43.624903804 -0400 ++++ /dev/null 1970-01-01 00:00:00.000000000 +0000 +@@ -1,56 +0,0 @@ +-require 'puppet/network/client' +- +-# Request a certificate from the remote system. +-class Puppet::Network::Client::CA < Puppet::Network::Client +- class InvalidCertificate < Puppet::Error; end +- +- def initialize(options = {}) +- options = symbolize_options(options) +- unless options.include?(:Server) or options.include?(:CA) +- options[:Server] = Puppet[:ca_server] +- options[:Port] = Puppet[:ca_port] +- end +- super(options) +- end +- +- # This client is really only able to request certificates for the +- # current host. It uses the Puppet.settings settings to figure everything out. +- def request_cert +- Puppet.settings.use(:main, :ssl) +- +- if cert = read_cert +- return cert +- end +- +- begin +- cert, cacert = @driver.getcert(csr.to_pem) +- rescue => detail +- puts detail.backtrace if Puppet[:trace] +- raise Puppet::Error.new("Certificate retrieval failed: #{detail}") +- end +- +- if cert.nil? or cert == "" +- return nil +- end +- +- begin +- @cert = OpenSSL::X509::Certificate.new(cert) +- @cacert = OpenSSL::X509::Certificate.new(cacert) +- rescue => detail +- raise InvalidCertificate.new( +- "Invalid certificate: #{detail}" +- ) +- end +- +- unless @cert.check_private_key(key) +- raise InvalidCertificate, "Certificate does not match private key. Try 'puppetca --clean #{Puppet[:certname]}' on the server." +- end +- +- # Only write the cert out if it passes validating. +- Puppet.settings.write(:hostcert) do |f| f.print cert end +- Puppet.settings.write(:localcacert) do |f| f.print cacert end +- +- @cert +- end +-end +- +Index: puppet-2.7.1/lib/puppet/network/client/file.rb +=================================================================== +--- puppet-2.7.1.orig/lib/puppet/network/client/file.rb 2011-10-23 13:05:43.604903804 -0400 ++++ /dev/null 1970-01-01 00:00:00.000000000 +0000 +@@ -1,6 +0,0 @@ +-class Puppet::Network::Client::File < Puppet::Network::Client::ProxyClient +- @handler = Puppet::Network::Handler.handler(:fileserver) +- @drivername = :FileServer +- self.mkmethods +-end +- +Index: puppet-2.7.1/lib/puppet/network/client/proxy.rb +=================================================================== +--- puppet-2.7.1.orig/lib/puppet/network/client/proxy.rb 2011-10-23 13:05:43.636903804 -0400 ++++ /dev/null 1970-01-01 00:00:00.000000000 +0000 +@@ -1,27 +0,0 @@ +-# unlike the other client classes (again, this design sucks) this class +-# is basically just a proxy class -- it calls its methods on the driver +-# and that's about it +-class Puppet::Network::Client::ProxyClient < Puppet::Network::Client +- def self.mkmethods +- interface = self.handler.interface +- namespace = interface.prefix +- +- +- interface.methods.each { |ary| +- method = ary[0] +- Puppet.debug "#{self}: defining #{namespace}.#{method}" +- define_method(method) { |*args| +- begin +- @driver.send(method, *args) +- rescue XMLRPC::FaultException => detail +- #Puppet.err "Could not call %s.%s: %s" % +- # [namespace, method, detail.faultString] +- #raise NetworkClientError, +- # "XMLRPC Error: #{detail.faultString}" +- raise NetworkClientError, detail.faultString +- end +- } +- } +- end +-end +- +Index: puppet-2.7.1/lib/puppet/network/client/report.rb +=================================================================== +--- puppet-2.7.1.orig/lib/puppet/network/client/report.rb 2011-10-23 13:05:43.648903804 -0400 ++++ /dev/null 1970-01-01 00:00:00.000000000 +0000 +@@ -1,26 +0,0 @@ +-class Puppet::Network::Client::Report < Puppet::Network::Client +- @handler = Puppet::Network::Handler.handler(:report) +- +- def initialize(hash = {}) +- hash[:Report] = self.class.handler.new if hash.include?(:Report) +- +- super(hash) +- end +- +- # Send our report. We get the transaction report and convert it to YAML +- # as appropriate. +- def report(transreport) +- report = YAML.dump(transreport) +- +- report = CGI.escape(report) unless self.local +- +- # Now send the report +- file = nil +- benchmark(:info, "Sent transaction report") do +- file = @driver.report(report) +- end +- +- file +- end +-end +- +Index: puppet-2.7.1/lib/puppet/network/client/runner.rb +=================================================================== +--- puppet-2.7.1.orig/lib/puppet/network/client/runner.rb 2011-10-23 13:05:43.608903804 -0400 ++++ /dev/null 1970-01-01 00:00:00.000000000 +0000 +@@ -1,10 +0,0 @@ +-class Puppet::Network::Client::Runner < Puppet::Network::Client::ProxyClient +- self.mkmethods +- +- def initialize(hash = {}) +- hash[:Runner] = self.class.handler.new if hash.include?(:Runner) +- +- super(hash) +- end +-end +- +Index: puppet-2.7.1/lib/puppet/network/client/status.rb +=================================================================== +--- puppet-2.7.1.orig/lib/puppet/network/client/status.rb 2011-10-23 13:05:43.616903804 -0400 ++++ /dev/null 1970-01-01 00:00:00.000000000 +0000 +@@ -1,4 +0,0 @@ +-class Puppet::Network::Client::Status < Puppet::Network::Client::ProxyClient +- self.mkmethods +-end +- +Index: puppet-2.7.1/lib/puppet/network/client.rb +=================================================================== +--- puppet-2.7.1.orig/lib/puppet/network/client.rb 2011-10-23 13:05:43.592903806 -0400 ++++ /dev/null 1970-01-01 00:00:00.000000000 +0000 +@@ -1,179 +0,0 @@ +-# the available clients +- +-require 'puppet' +-require 'puppet/network/xmlrpc/client' +-require 'puppet/util/subclass_loader' +-require 'puppet/util/methodhelper' +-require 'puppet/sslcertificates/support' +- +-require 'puppet/network/handler' +- +-require 'net/http' +- +-# Some versions of ruby don't have this method defined, which basically causes +-# us to never use ssl. Yay. +-class Net::HTTP +- def use_ssl? +- if defined?(@use_ssl) +- @use_ssl +- else +- false +- end +- end +- +- # JJM: This is a "backport" of sorts to older ruby versions which +- # do not have this accessor. See #896 for more information. +- attr_accessor :enable_post_connection_check unless Net::HTTP.method_defined? "enable_post_connection_check" +-end +- +-# The base class for all of the clients. Many clients just directly +-# call methods, but some of them need to do some extra work or +-# provide a different interface. +-class Puppet::Network::Client +- Client = self +- include Puppet::Util +- extend Puppet::Util::SubclassLoader +- include Puppet::Util::MethodHelper +- +- # This handles reading in the key and such-like. +- include Puppet::SSLCertificates::Support +- +- attr_accessor :schedule, :lastrun, :local, :stopping +- +- attr_reader :driver +- +- # Set up subclass loading +- handle_subclasses :client, "puppet/network/client" +- +- # Determine what clients look for when being passed an object for local +- # client/server stuff. E.g., you could call Client::CA.new(:CA => ca). +- def self.drivername +- @drivername ||= self.name +- end +- +- # Figure out the handler for our client. +- def self.handler +- @handler ||= Puppet::Network::Handler.handler(self.name) +- end +- +- # The class that handles xmlrpc interaction for us. +- def self.xmlrpc_client +- @xmlrpc_client ||= Puppet::Network::XMLRPCClient.handler_class(self.handler) +- end +- +- # Create our client. +- def initialize(hash) +- # to whom do we connect? +- @server = nil +- +- if hash.include?(:Cache) +- @cache = hash[:Cache] +- else +- @cache = true +- end +- +- driverparam = self.class.drivername +- if hash.include?(:Server) +- args = {:Server => hash[:Server]} +- @server = hash[:Server] +- args[:Port] = hash[:Port] || Puppet[:masterport] +- +- @driver = self.class.xmlrpc_client.new(args) +- +- self.read_cert +- +- # We have to start the HTTP connection manually before we start +- # sending it requests or keep-alive won't work. Note that with #1010, +- # we don't currently actually want keep-alive. +- @driver.start if @driver.respond_to? :start and Puppet::Network::HttpPool.keep_alive? +- +- @local = false +- elsif hash.include?(driverparam) +- @driver = hash[driverparam] +- if @driver == true +- @driver = self.class.handler.new +- end +- @local = true +- else +- raise Puppet::Network::ClientError, "#{self.class} must be passed a Server or #{driverparam}" +- end +- end +- +- # Are we a local client? +- def local? +- if @local +- true +- else +- false +- end +- end +- +- # Make sure we set the driver up when we read the cert in. +- def recycle_connection +- @driver.recycle_connection if @driver.respond_to?(:recycle_connection) +- end +- +- # A wrapper method to run and then store the last run time +- def runnow +- if self.stopping +- Puppet.notice "In shutdown progress; skipping run" +- return +- end +- begin +- self.run +- self.lastrun = Time.now.to_i +- rescue => detail +- puts detail.backtrace if Puppet[:trace] +- Puppet.err "Could not run #{self.class}: #{detail}" +- end +- end +- +- def run +- raise Puppet::DevError, "Client type #{self.class} did not override run" +- end +- +- def scheduled? +- if sched = self.schedule +- return sched.match?(self.lastrun) +- else +- return true +- end +- end +- +- def shutdown +- if self.stopping +- Puppet.notice "Already in shutdown" +- else +- self.stopping = true +- Puppet::Util::Storage.store if self.respond_to? :running? and self.running? +- rmpidfile +- end +- end +- +- # Start listening for events. We're pretty much just listening for +- # timer events here. +- def start +- # Create our timer. Puppet will handle observing it and such. +- +- timer = Puppet.newtimer( +- +- :interval => Puppet[:runinterval], +- :tolerance => 1, +- +- :start? => true +- ) do +- begin +- self.runnow if self.scheduled? +- rescue => detail +- puts detail.backtrace if Puppet[:trace] +- Puppet.err "Could not run client; got otherwise uncaught exception: #{detail}" +- end +- end +- +- # Run once before we start following the timer +- self.runnow +- end +- +- require 'puppet/network/client/proxy' +-end +- +Index: puppet-2.7.1/lib/puppet/network/handler/ca.rb +=================================================================== +--- puppet-2.7.1.orig/lib/puppet/network/handler/ca.rb 2011-10-23 13:05:43.704903803 -0400 ++++ puppet-2.7.1/lib/puppet/network/handler/ca.rb 2011-10-23 13:05:46.388903778 -0400 +@@ -1,10 +1,7 @@ + require 'openssl' + require 'puppet' +-require 'puppet/sslcertificates' + require 'xmlrpc/server' +- +-# Much of this was taken from QuickCert: +-# http://segment7.net/projects/ruby/QuickCert/ ++require 'puppet/network/handler' + + class Puppet::Network::Handler + class CA < Handler +@@ -18,73 +15,17 @@ + iface.add_method("array getcert(csr)") + } + +- def autosign +- if defined?(@autosign) +- @autosign +- else +- Puppet[:autosign] +- end +- end +- +- # FIXME autosign? should probably accept both hostnames and IP addresses +- def autosign?(hostname) +- # simple values are easy +- if autosign == true or autosign == false +- return autosign +- end +- +- # we only otherwise know how to handle files +- unless autosign =~ /^\// +- raise Puppet::Error, "Invalid autosign value #{autosign.inspect}" +- end +- +- unless FileTest.exists?(autosign) +- unless defined?(@@warnedonautosign) +- @@warnedonautosign = true +- Puppet.info "Autosign is enabled but #{autosign} is missing" +- end +- return false +- end +- auth = Puppet::Network::AuthStore.new +- File.open(autosign) { |f| +- f.each { |line| +- next if line =~ /^\s*#/ +- next if line =~ /^\s*$/ +- auth.allow(line.chomp) +- } +- } +- +- # for now, just cheat and pass a fake IP address to allowed? +- auth.allowed?(hostname, "127.1.1.1") +- end +- + def initialize(hash = {}) + Puppet.settings.use(:main, :ssl, :ca) +- @autosign = hash[:autosign] if hash.include? :autosign + +- @ca = Puppet::SSLCertificates::CA.new(hash) ++ @ca = Puppet::SSL::CertificateAuthority.instance + end + + # our client sends us a csr, and we either store it for later signing, + # or we sign it right away + def getcert(csrtext, client = nil, clientip = nil) +- csr = OpenSSL::X509::Request.new(csrtext) +- +- # Use the hostname from the CSR, not from the network. +- subject = csr.subject +- +- nameary = subject.to_a.find { |ary| +- ary[0] == "CN" +- } +- +- if nameary.nil? +- Puppet.err( +- "Invalid certificate request: could not retrieve server name" +- ) +- return "invalid" +- end +- +- hostname = nameary[1] ++ csr = Puppet::SSL::CertificateRequest.from_s(csrtext) ++ hostname = csr.name + + unless @ca + Puppet.notice "Host #{hostname} asked for signing from non-CA master" +@@ -93,57 +34,26 @@ + + # We used to save the public key, but it's basically unnecessary + # and it mucks with the permissions requirements. +- # save_pk(hostname, csr.public_key) +- +- certfile = File.join(Puppet[:certdir], [hostname, "pem"].join(".")) + + # first check to see if we already have a signed cert for the host +- cert, cacert = ca.getclientcert(hostname) +- if cert and cacert ++ cert = Puppet::SSL::Certificate.indirection.find(hostname) ++ cacert = Puppet::SSL::Certificate.indirection.find(@ca.host.name) ++ ++ if cert + Puppet.info "Retrieving existing certificate for #{hostname}" +- unless csr.public_key.to_s == cert.public_key.to_s ++ unless csr.content.public_key.to_s == cert.content.public_key.to_s + raise Puppet::Error, "Certificate request does not match existing certificate; run 'puppetca --clean #{hostname}'." + end +- return [cert.to_pem, cacert.to_pem] +- elsif @ca +- if self.autosign?(hostname) or client.nil? +- Puppet.info "Signing certificate for CA server" if client.nil? +- # okay, we don't have a signed cert +- # if we're a CA and autosign is turned on, then go ahead and sign +- # the csr and return the results +- Puppet.info "Signing certificate for #{hostname}" +- cert, cacert = @ca.sign(csr) +- #Puppet.info "Cert: #{cert.class}; Cacert: #{cacert.class}" +- return [cert.to_pem, cacert.to_pem] +- else # just write out the csr for later signing +- if @ca.getclientcsr(hostname) +- Puppet.info "Not replacing existing request from #{hostname}" +- else +- Puppet.notice "Host #{hostname} has a waiting certificate request" +- @ca.storeclientcsr(csr) +- end +- return ["", ""] +- end ++ [cert.to_s, cacert.to_s] + else +- raise "huh?" +- end +- end +- +- private ++ Puppet::SSL::CertificateRequest.indirection.save(csr) + +- # Save the public key. +- def save_pk(hostname, public_key) +- pkeyfile = File.join(Puppet[:publickeydir], [hostname, "pem"].join('.')) +- +- if FileTest.exists?(pkeyfile) +- currentkey = File.open(pkeyfile) { |k| k.read } +- unless currentkey == public_key.to_s +- raise Puppet::Error, "public keys for #{hostname} differ" ++ # We determine whether we signed the csr by checking if there's a certificate for it ++ if cert = Puppet::SSL::Certificate.indirection.find(hostname) ++ [cert.to_s, cacert.to_s] ++ else ++ nil + end +- else +- File.open(pkeyfile, "w", 0644) { |f| +- f.print public_key.to_s +- } + end + end + end +Index: puppet-2.7.1/lib/puppet/network/handler/master.rb +=================================================================== +--- puppet-2.7.1.orig/lib/puppet/network/handler/master.rb 2011-10-23 13:05:43.724903803 -0400 ++++ puppet-2.7.1/lib/puppet/network/handler/master.rb 2011-10-23 13:05:46.388903778 -0400 +@@ -1,6 +1,5 @@ + require 'openssl' + require 'puppet' +-require 'puppet/sslcertificates' + require 'xmlrpc/server' + require 'yaml' + +@@ -33,8 +32,6 @@ + + args[:Local] = true + +- @ca = (hash.include?(:CA) and hash[:CA]) ? Puppet::SSLCertificates::CA.new : nil +- + # This is only used by the cfengine module, or if --loadclasses was + # specified in +puppet+. + args[:Classes] = hash[:Classes] if hash.include?(:Classes) +Index: puppet-2.7.1/lib/puppet/network/handler/runner.rb +=================================================================== +--- puppet-2.7.1.orig/lib/puppet/network/handler/runner.rb 2011-10-23 13:05:43.692903805 -0400 ++++ puppet-2.7.1/lib/puppet/network/handler/runner.rb 2011-10-23 13:05:46.388903778 -0400 +@@ -1,4 +1,5 @@ + require 'puppet/run' ++require 'puppet/network/handler' + + class Puppet::Network::Handler + class MissingMasterError < RuntimeError; end # Cannot find the master client +Index: puppet-2.7.1/lib/puppet/network/http_server/webrick.rb +=================================================================== +--- puppet-2.7.1.orig/lib/puppet/network/http_server/webrick.rb 2011-10-23 13:05:43.660903804 -0400 ++++ /dev/null 1970-01-01 00:00:00.000000000 +0000 +@@ -1,155 +0,0 @@ +-require 'puppet' +-require 'webrick' +-require 'webrick/https' +-require 'fcntl' +- +-require 'puppet/sslcertificates/support' +-require 'puppet/network/xmlrpc/webrick_servlet' +-require 'puppet/network/http_server' +-require 'puppet/network/client' +-require 'puppet/network/handler' +- +-module Puppet +- class ServerError < RuntimeError; end +- module Network +- # The old-school, pure ruby webrick server, which is the default serving +- # mechanism. +- class HTTPServer::WEBrick < WEBrick::HTTPServer +- include Puppet::SSLCertificates::Support +- +- # Read the CA cert and CRL and populate an OpenSSL::X509::Store +- # with them, with flags appropriate for checking client +- # certificates for revocation +- def x509store +- unless File.exist?(Puppet[:cacrl]) +- # No CRL, no store needed +- return nil +- end +- crl = OpenSSL::X509::CRL.new(File.read(Puppet[:cacrl])) +- store = OpenSSL::X509::Store.new +- store.purpose = OpenSSL::X509::PURPOSE_ANY +- store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK if Puppet.settings[:certificate_revocation] +- raise Puppet::Error, "Could not find CA certificate" unless self.ca_cert +- +- store.add_file(Puppet[:localcacert]) +- store.add_crl(crl) +- store +- end +- +- # Set up the http log. +- def httplog +- args = [] +- +- # yuck; separate http logs +- file = nil +- Puppet.settings.use(:main, :ssl, Puppet[:name]) +- if Puppet.run_mode.master? +- file = Puppet[:masterhttplog] +- else +- file = Puppet[:httplog] +- end +- +- # open the log manually to prevent file descriptor leak +- file_io = open(file, "a+") +- file_io.sync +- file_io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) +- +- args << file_io +- args << WEBrick::Log::DEBUG if Puppet[:debug] +- +- log = WEBrick::Log.new(*args) +- +- +- log +- end +- +- # Create our server, yo. +- def initialize(hash = {}) +- Puppet.info "Starting server for Puppet version #{Puppet.version}" +- +- if handlers = hash[:Handlers] +- handler_instances = setup_handlers(handlers) +- else +- raise ServerError, "A server must have handlers" +- end +- +- unless self.read_cert +- if ca = handler_instances.find { |handler| handler.is_a?(Puppet::Network::Handler.ca) } +- request_cert(ca) +- else +- raise Puppet::Error, "No certificate and no CA; cannot get cert" +- end +- end +- +- setup_webrick(hash) +- +- begin +- super(hash) +- rescue => detail +- puts detail.backtrace if Puppet[:trace] +- raise Puppet::Error, "Could not start WEBrick: #{detail}" +- end +- +- # make sure children don't inherit the sockets +- listeners.each { |sock| +- sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) +- } +- +- Puppet.info "Listening on port #{hash[:Port]}" +- +- # this creates a new servlet for every connection, +- # but all servlets have the same list of handlers +- # thus, the servlets can have their own state -- passing +- # around the requests and such -- but the handlers +- # have a global state +- +- # mount has to be called after the server is initialized +- servlet = Puppet::Network::XMLRPC::WEBrickServlet.new( handler_instances) +- self.mount("/RPC2", servlet) +- end +- +- # Create a ca client to set up our cert for us. +- def request_cert(ca) +- client = Puppet::Network::Client.ca.new(:CA => ca) +- raise Puppet::Error, "Could get certificate" unless client.request_cert +- end +- +- # Create all of our handler instances. +- def setup_handlers(handlers) +- raise ServerError, "Handlers must have arguments" unless handlers.is_a?(Hash) +- +- handlers.collect { |handler, args| +- hclass = nil +- unless hclass = Puppet::Network::Handler.handler(handler) +- raise ServerError, "Invalid handler #{handler}" +- end +- hclass.new(args) +- } +- end +- +- # Handle all of the many webrick arguments. +- def setup_webrick(hash) +- hash[:Port] ||= Puppet[:masterport] +- hash[:Logger] ||= self.httplog +- hash[:AccessLog] ||= [ +- [ self.httplog, WEBrick::AccessLog::COMMON_LOG_FORMAT ], +- [ self.httplog, WEBrick::AccessLog::REFERER_LOG_FORMAT ] +- ] +- +- hash[:SSLCertificateStore] = x509store +- hash[:SSLCertificate] = self.cert +- hash[:SSLPrivateKey] = self.key +- hash[:SSLStartImmediately] = true +- hash[:SSLEnable] = true +- hash[:SSLCACertificateFile] = Puppet[:localcacert] +- hash[:SSLVerifyClient] = OpenSSL::SSL::VERIFY_PEER +- hash[:SSLCertName] = nil +- +- if addr = Puppet[:bindaddress] and addr != "" +- hash[:BindAddress] = addr +- end +- end +- end +- end +-end +- +Index: puppet-2.7.1/lib/puppet/network/xmlrpc/client.rb +=================================================================== +--- puppet-2.7.1.orig/lib/puppet/network/xmlrpc/client.rb 2011-10-23 13:05:43.560903805 -0400 ++++ /dev/null 1970-01-01 00:00:00.000000000 +0000 +@@ -1,211 +0,0 @@ +-require 'puppet/sslcertificates' +-require 'puppet/network/http_pool' +-require 'openssl' +-require 'puppet/external/base64' +- +-require 'xmlrpc/client' +-require 'net/https' +-require 'yaml' +- +-module Puppet::Network +- class ClientError < Puppet::Error; end +- class XMLRPCClientError < Puppet::Error; end +- class XMLRPCClient < ::XMLRPC::Client +- +- attr_accessor :puppet_server, :puppet_port +- @clients = {} +- +- class << self +- include Puppet::Util +- include Puppet::Util::ClassGen +- end +- +- # Create a netclient for each handler +- def self.mkclient(handler) +- interface = handler.interface +- namespace = interface.prefix +- +- # Create a subclass for every client type. This is +- # so that all of the methods are on their own class, +- # so that their namespaces can define the same methods if +- # they want. +- constant = handler.name.to_s.capitalize +- name = namespace.downcase +- newclient = genclass(name, :hash => @clients, :constant => constant) +- +- interface.methods.each { |ary| +- method = ary[0] +- newclient.send(:define_method,method) { |*args| +- make_rpc_call(namespace, method, *args) +- } +- } +- +- newclient +- end +- +- def self.handler_class(handler) +- @clients[handler] || self.mkclient(handler) +- end +- +- class ErrorHandler +- def initialize(&block) +- singleton_class.define_method(:execute, &block) +- end +- end +- +- # Use a class variable so all subclasses have access to it. +- @@error_handlers = {} +- +- def self.error_handler(exception) +- if handler = @@error_handlers[exception.class] +- return handler +- else +- return @@error_handlers[:default] +- end +- end +- +- def self.handle_error(*exceptions, &block) +- handler = ErrorHandler.new(&block) +- +- exceptions.each do |exception| +- @@error_handlers[exception] = handler +- end +- end +- +- handle_error(OpenSSL::SSL::SSLError) do |client, detail, namespace, method| +- if detail.message =~ /bad write retry/ +- Puppet.warning "Transient SSL write error; restarting connection and retrying" +- client.recycle_connection +- return :retry +- end +- ["certificate verify failed", "hostname was not match", "hostname not match"].each do |str| +- Puppet.warning "Certificate validation failed; consider using the certname configuration option" if detail.message.include?(str) +- end +- raise XMLRPCClientError, "Certificates were not trusted: #{detail}" +- end +- +- handle_error(:default) do |client, detail, namespace, method| +- if detail.message.to_s =~ /^Wrong size\. Was \d+, should be \d+$/ +- Puppet.warning "XMLRPC returned wrong size. Retrying." +- return :retry +- end +- Puppet.err "Could not call #{namespace}.#{method}: #{detail.inspect}" +- error = XMLRPCClientError.new(detail.to_s) +- error.set_backtrace detail.backtrace +- raise error +- end +- +- handle_error(OpenSSL::SSL::SSLError) do |client, detail, namespace, method| +- if detail.message =~ /bad write retry/ +- Puppet.warning "Transient SSL write error; restarting connection and retrying" +- client.recycle_connection +- return :retry +- end +- ["certificate verify failed", "hostname was not match", "hostname not match"].each do |str| +- Puppet.warning "Certificate validation failed; consider using the certname configuration option" if detail.message.include?(str) +- end +- raise XMLRPCClientError, "Certificates were not trusted: #{detail}" +- end +- +- handle_error(::XMLRPC::FaultException) do |client, detail, namespace, method| +- raise XMLRPCClientError, detail.faultString +- end +- +- handle_error(Errno::ECONNREFUSED) do |client, detail, namespace, method| +- msg = "Could not connect to #{client.host} on port #{client.port}" +- raise XMLRPCClientError, msg +- end +- +- handle_error(SocketError) do |client, detail, namespace, method| +- Puppet.err "Could not find server #{@host}: #{detail}" +- error = XMLRPCClientError.new("Could not find server #{client.host}") +- error.set_backtrace detail.backtrace +- raise error +- end +- +- handle_error(Errno::EPIPE, EOFError) do |client, detail, namespace, method| +- Puppet.info "Other end went away; restarting connection and retrying" +- client.recycle_connection +- return :retry +- end +- +- handle_error(Timeout::Error) do |client, detail, namespace, method| +- Puppet.err "Connection timeout calling #{namespace}.#{method}: #{detail}" +- error = XMLRPCClientError.new("Connection Timeout") +- error.set_backtrace(detail.backtrace) +- raise error +- end +- +- def make_rpc_call(namespace, method, *args) +- Puppet.debug "Calling #{namespace}.#{method}" +- begin +- call("#{namespace}.#{method}",*args) +- rescue SystemExit,NoMemoryError +- raise +- rescue Exception => detail +- retry if self.class.error_handler(detail).execute(self, detail, namespace, method) == :retry +- end +- ensure +- http.finish if http.started? +- end +- +- def http +- @http ||= Puppet::Network::HttpPool.http_instance(host, port, true) +- end +- +- attr_reader :host, :port +- +- def initialize(hash = {}) +- hash[:Path] ||= "/RPC2" +- hash[:Server] ||= Puppet[:server] +- hash[:Port] ||= Puppet[:masterport] +- hash[:HTTPProxyHost] ||= Puppet[:http_proxy_host] +- hash[:HTTPProxyPort] ||= Puppet[:http_proxy_port] +- +- if "none" == hash[:HTTPProxyHost] +- hash[:HTTPProxyHost] = nil +- hash[:HTTPProxyPort] = nil +- end +- +- +- super( +- +- hash[:Server], +- hash[:Path], +- hash[:Port], +- hash[:HTTPProxyHost], +- hash[:HTTPProxyPort], +- +- nil, # user +- nil, # password +- true, # use_ssl +- Puppet[:configtimeout] # use configured timeout (#1176) +- ) +- @http = Puppet::Network::HttpPool.http_instance(@host, @port) +- end +- +- # Get rid of our existing connection, replacing it with a new one. +- # This should only happen if we lose our connection somehow (e.g., an EPIPE) +- # or we've just downloaded certs and we need to create new http instances +- # with the certs added. +- def recycle_connection +- http.finish if http.started? +- @http = nil +- self.http # force a new one +- end +- +- def start +- @http.start unless @http.started? +- rescue => detail +- Puppet.err "Could not connect to server: #{detail}" +- end +- +- def local +- false +- end +- +- def local? +- false +- end +- end +-end +Index: puppet-2.7.1/lib/puppet/ssl/certificate_authority/interface.rb +=================================================================== +--- puppet-2.7.1.orig/lib/puppet/ssl/certificate_authority/interface.rb 2011-10-23 13:05:43.800903804 -0400 ++++ puppet-2.7.1/lib/puppet/ssl/certificate_authority/interface.rb 2011-10-23 13:05:46.388903778 -0400 +@@ -9,7 +9,7 @@ + + class InterfaceError < ArgumentError; end + +- attr_reader :method, :subjects, :digest ++ attr_reader :method, :subjects, :digest, :options + + # Actually perform the work. + def apply(ca) +@@ -35,49 +35,94 @@ + raise InterfaceError, "It makes no sense to generate all hosts; you must specify a list" if subjects == :all + + subjects.each do |host| +- ca.generate(host) ++ ca.generate(host, options) + end + end + + def initialize(method, options) + self.method = method +- self.subjects = options[:to] +- @digest = options[:digest] || :MD5 ++ self.subjects = options.delete(:to) ++ @digest = options.delete(:digest) || :MD5 ++ @options = options + end + + # List the hosts. + def list(ca) +- unless subjects +- puts ca.waiting?.join("\n") +- return nil +- end +- + signed = ca.list + requests = ca.waiting? + +- if subjects == :all ++ case subjects ++ when :all + hosts = [signed, requests].flatten +- elsif subjects == :signed ++ when :signed + hosts = signed.flatten ++ when nil ++ hosts = requests + else + hosts = subjects + end + ++ certs = {:signed => {}, :invalid => {}, :request => {}} ++ ++ return if hosts.empty? ++ + hosts.uniq.sort.each do |host| +- invalid = false + begin + ca.verify(host) unless requests.include?(host) + rescue Puppet::SSL::CertificateAuthority::CertificateVerificationError => details +- invalid = details.to_s ++ verify_error = details.to_s + end +- if not invalid and signed.include?(host) +- puts "+ #{host} (#{ca.fingerprint(host, @digest)})" +- elsif invalid +- puts "- #{host} (#{ca.fingerprint(host, @digest)}) (#{invalid})" ++ ++ if verify_error ++ cert = Puppet::SSL::Certificate.indirection.find(host) ++ certs[:invalid][host] = [cert, verify_error] ++ elsif signed.include?(host) ++ cert = Puppet::SSL::Certificate.indirection.find(host) ++ certs[:signed][host] = cert + else +- puts "#{host} (#{ca.fingerprint(host, @digest)})" ++ req = Puppet::SSL::CertificateRequest.indirection.find(host) ++ certs[:request][host] = req + end + end ++ ++ names = certs.values.map(&:keys).flatten ++ ++ name_width = names.sort_by(&:length).last.length rescue 0 ++ ++ output = [:request, :signed, :invalid].map do |type| ++ next if certs[type].empty? ++ ++ certs[type].map do |host,info| ++ format_host(ca, host, type, info, name_width) ++ end ++ end.flatten.compact.sort.join("\n") ++ ++ puts output ++ end ++ ++ def format_host(ca, host, type, info, width) ++ certish, verify_error = info ++ alt_names = case type ++ when :signed ++ certish.subject_alt_names ++ when :request ++ certish.subject_alt_names ++ else ++ [] ++ end ++ ++ alt_names.delete(host) ++ ++ alt_str = "(alt names: #{alt_names.join(', ')})" unless alt_names.empty? ++ ++ glyph = {:signed => '+', :request => ' ', :invalid => '-'}[type] ++ ++ name = host.ljust(width) ++ fingerprint = "(#{ca.fingerprint(host, @digest)})" ++ ++ explanation = "(#{verify_error})" if verify_error ++ ++ [glyph, name, fingerprint, alt_str, explanation].compact.join(' ') + end + + # Set the method to apply. +@@ -113,7 +158,7 @@ + list = subjects == :all ? ca.waiting? : subjects + raise InterfaceError, "No waiting certificate requests to sign" if list.empty? + list.each do |host| +- ca.sign(host) ++ ca.sign(host, options[:allow_dns_alt_names]) + end + end + +Index: puppet-2.7.1/lib/puppet/ssl/certificate_authority.rb +=================================================================== +--- puppet-2.7.1.orig/lib/puppet/ssl/certificate_authority.rb 2011-10-23 13:05:43.784903804 -0400 ++++ puppet-2.7.1/lib/puppet/ssl/certificate_authority.rb 2011-10-23 13:05:46.388903778 -0400 +@@ -11,6 +11,15 @@ + # it can also be seen as a general interface into all of the + # SSL stuff. + class Puppet::SSL::CertificateAuthority ++ # We will only sign extensions on this whitelist, ever. Any CSR with a ++ # requested extension that we don't recognize is rejected, against the risk ++ # that it will introduce some security issue through our ignorance of it. ++ # ++ # Adding an extension to this whitelist simply means we will consider it ++ # further, not that we will always accept a certificate with an extension ++ # requested on this list. ++ RequestExtensionWhitelist = %w{subjectAltName} ++ + require 'puppet/ssl/certificate_factory' + require 'puppet/ssl/inventory' + require 'puppet/ssl/certificate_revocation_list' +@@ -31,6 +40,14 @@ + cached_attr(:singleton_instance) { new } + end + ++ class CertificateSigningError < RuntimeError ++ attr_accessor :host ++ ++ def initialize(host) ++ @host = host ++ end ++ end ++ + def self.ca? + return false unless Puppet[:ca] + return false unless Puppet.run_mode.master? +@@ -52,7 +69,6 @@ + def apply(method, options) + raise ArgumentError, "You must specify the hosts to apply to; valid values are an array or the symbol :all" unless options[:to] + applier = Interface.new(method, options) +- + applier.apply(self) + end + +@@ -108,13 +124,16 @@ + end + + # Generate a new certificate. +- def generate(name) ++ def generate(name, options = {}) + raise ArgumentError, "A Certificate already exists for #{name}" if Puppet::SSL::Certificate.indirection.find(name) + host = Puppet::SSL::Host.new(name) + +- host.generate_certificate_request ++ # Pass on any requested subjectAltName field. ++ san = options[:dns_alt_names] + +- sign(name) ++ host = Puppet::SSL::Host.new(name) ++ host.generate_certificate_request(:dns_alt_names => san) ++ sign(name, !!san) + end + + # Generate our CA certificate. +@@ -123,14 +142,16 @@ + + host.generate_key unless host.key + +- # Create a new cert request. We do this +- # specially, because we don't want to actually +- # save the request anywhere. ++ # Create a new cert request. We do this specially, because we don't want ++ # to actually save the request anywhere. + request = Puppet::SSL::CertificateRequest.new(host.name) ++ ++ # We deliberately do not put any subjectAltName in here: the CA ++ # certificate absolutely does not need them. --daniel 2011-10-13 + request.generate(host.key) + + # Create a self-signed certificate. +- @certificate = sign(host.name, :ca, request) ++ @certificate = sign(host.name, false, request) + + # And make sure we initialize our CRL. + crl +@@ -223,20 +244,34 @@ + end + + # Sign a given certificate request. +- def sign(hostname, cert_type = :server, self_signing_csr = nil) ++ def sign(hostname, allow_dns_alt_names = false, self_signing_csr = nil) + # This is a self-signed certificate + if self_signing_csr ++ # # This is a self-signed certificate, which is for the CA. Since this ++ # # forces the certificate to be self-signed, anyone who manages to trick ++ # # the system into going through this path gets a certificate they could ++ # # generate anyway. There should be no security risk from that. + csr = self_signing_csr ++ cert_type = :ca + issuer = csr.content + else ++ allow_dns_alt_names = true if hostname == Puppet[:certname].downcase + unless csr = Puppet::SSL::CertificateRequest.indirection.find(hostname) + raise ArgumentError, "Could not find certificate request for #{hostname}" + end ++ ++ cert_type = :server + issuer = host.certificate.content ++ ++ # Make sure that the CSR conforms to our internal signing policies. ++ # This will raise if the CSR doesn't conform, but just in case... ++ check_internal_signing_policies(hostname, csr, allow_dns_alt_names) or ++ raise CertificateSigningError.new(hostname), "CSR had an unknown failure checking internal signing policies, will not sign!" + end + + cert = Puppet::SSL::Certificate.new(hostname) +- cert.content = Puppet::SSL::CertificateFactory.new(cert_type, csr.content, issuer, next_serial).result ++ cert.content = Puppet::SSL::CertificateFactory. ++ build(cert_type, csr, issuer, next_serial) + cert.content.sign(host.key.content, OpenSSL::Digest::SHA1.new) + + Puppet.notice "Signed certificate request for #{hostname}" +@@ -256,6 +291,47 @@ + cert + end + ++ def check_internal_signing_policies(hostname, csr, allow_dns_alt_names) ++ # Reject unknown request extensions. ++ unknown_req = csr.request_extensions. ++ reject {|x| RequestExtensionWhitelist.include? x["oid"] } ++ ++ if unknown_req and not unknown_req.empty? ++ names = unknown_req.map {|x| x["oid"] }.sort.uniq.join(", ") ++ raise CertificateSigningError.new(hostname), "CSR has request extensions that are not permitted: #{names}" ++ end ++ ++ # Wildcards: we don't allow 'em at any point. ++ # ++ # The stringification here makes the content visible, and saves us having ++ # to scrobble through the content of the CSR subject field to make sure it ++ # is what we expect where we expect it. ++ if csr.content.subject.to_s.include? '*' ++ raise CertificateSigningError.new(hostname), "CSR subject contains a wildcard, which is not allowed: #{csr.content.subject.to_s}" ++ end ++ ++ unless csr.subject_alt_names.empty? ++ # If you alt names are allowed, they are required. Otherwise they are ++ # disallowed. Self-signed certs are implicitly trusted, however. ++ unless allow_dns_alt_names ++ raise CertificateSigningError.new(hostname), "CSR '#{csr.name}' contains subject alternative names (#{csr.subject_alt_names.join(', ')}), which are disallowed. Use `puppet cert --allow-dns-alt-names sign #{csr.name}` to sign this request." ++ end ++ ++ # If subjectAltNames are present, validate that they are only for DNS ++ # labels, not any other kind. ++ unless csr.subject_alt_names.all? {|x| x =~ /^DNS:/ } ++ raise CertificateSigningError.new(hostname), "CSR '#{csr.name}' contains a subjectAltName outside the DNS label space: #{csr.subject_alt_names.join(', ')}. To continue, this CSR needs to be cleaned." ++ end ++ ++ # Check for wildcards in the subjectAltName fields too. ++ if csr.subject_alt_names.any? {|x| x.include? '*' } ++ raise CertificateSigningError.new(hostname), "CSR '#{csr.name}' subjectAltName contains a wildcard, which is not allowed: #{csr.subject_alt_names.join(', ')} To continue, this CSR needs to be cleaned." ++ end ++ end ++ ++ return true # good enough for us! ++ end ++ + # Verify a given host's certificate. + def verify(name) + unless cert = Puppet::SSL::Certificate.indirection.find(name) +Index: puppet-2.7.1/lib/puppet/ssl/certificate_factory.rb +=================================================================== +--- puppet-2.7.1.orig/lib/puppet/ssl/certificate_factory.rb 2011-10-23 13:05:43.816903802 -0400 ++++ puppet-2.7.1/lib/puppet/ssl/certificate_factory.rb 2011-10-23 13:05:46.392903778 -0400 +@@ -2,7 +2,7 @@ + + # The tedious class that does all the manipulations to the + # certificate to correctly sign it. Yay. +-class Puppet::SSL::CertificateFactory ++module Puppet::SSL::CertificateFactory + # How we convert from various units to the required seconds. + UNITMAP = { + "y" => 365 * 24 * 60 * 60, +@@ -11,75 +11,84 @@ + "s" => 1 + } + +- attr_reader :name, :cert_type, :csr, :issuer, :serial ++ def self.build(cert_type, csr, issuer, serial) ++ # Work out if we can even build the requested type of certificate. ++ build_extensions = "build_#{cert_type.to_s}_extensions" ++ respond_to?(build_extensions) or ++ raise ArgumentError, "#{cert_type.to_s} is an invalid certificate type!" ++ ++ # set up the certificate, and start building the content. ++ cert = OpenSSL::X509::Certificate.new ++ ++ cert.version = 2 # X509v3 ++ cert.subject = csr.content.subject ++ cert.issuer = issuer.subject ++ cert.public_key = csr.content.public_key ++ cert.serial = serial ++ ++ # Make the certificate valid as of yesterday, because so many people's ++ # clocks are out of sync. This gives one more day of validity than people ++ # might expect, but is better than making every person who has a messed up ++ # clock fail, and better than having every cert we generate expire a day ++ # before the user expected it to when they asked for "one year". ++ cert.not_before = Time.now - (60*60*24) ++ cert.not_after = Time.now + ttl + +- def initialize(cert_type, csr, issuer, serial) +- @cert_type, @csr, @issuer, @serial = cert_type, csr, issuer, serial ++ add_extensions_to(cert, csr, issuer, send(build_extensions)) + +- @name = @csr.subject +- end +- +- # Actually generate our certificate. +- def result +- @cert = OpenSSL::X509::Certificate.new +- +- @cert.version = 2 # X509v3 +- @cert.subject = @csr.subject +- @cert.issuer = @issuer.subject +- @cert.public_key = @csr.public_key +- @cert.serial = @serial +- +- build_extensions +- +- set_ttl +- +- @cert ++ return cert + end + + private + +- # This is pretty ugly, but I'm not really sure it's even possible to do +- # it any other way. +- def build_extensions +- @ef = OpenSSL::X509::ExtensionFactory.new +- +- @ef.subject_certificate = @cert +- +- if @issuer.is_a?(OpenSSL::X509::Request) # It's a self-signed cert +- @ef.issuer_certificate = @cert +- else +- @ef.issuer_certificate = @issuer ++ def self.add_extensions_to(cert, csr, issuer, extensions) ++ ef = OpenSSL::X509::ExtensionFactory. ++ new(cert, issuer.is_a?(OpenSSL::X509::Request) ? cert : issuer) ++ ++ # Extract the requested extensions from the CSR. ++ requested_exts = csr.request_extensions.inject({}) do |hash, re| ++ hash[re["oid"]] = [re["value"], re["critical"]] ++ hash + end + +- @subject_alt_name = [] +- @key_usage = nil +- @ext_key_usage = nil +- @extensions = [] +- +- method = "add_#{@cert_type.to_s}_extensions" +- +- begin +- send(method) +- rescue NoMethodError +- raise ArgumentError, "#{@cert_type} is an invalid certificate type" ++ # Produce our final set of extensions. We deliberately order these to ++ # build the way we want: ++ # 1. "safe" default values, like the comment, that no one cares about. ++ # 2. request extensions, from the CSR ++ # 3. extensions based on the type we are generating ++ # 4. overrides, which we always want to have in their form ++ # ++ # This ordering *is* security-critical, but we want to allow the user ++ # enough rope to shoot themselves in the foot, if they want to ignore our ++ # advice and externally approve a CSR that sets the basicConstraints. ++ # ++ # Swapping the order of 2 and 3 would ensure that you couldn't slip a ++ # certificate through where the CA constraint was true, though, if ++ # something went wrong up there. --daniel 2011-10-11 ++ defaults = { "nsComment" => "Puppet Ruby/OpenSSL Internal Certificate" } ++ override = { "subjectKeyIdentifier" => "hash" } ++ ++ exts = [defaults, requested_exts, extensions, override]. ++ inject({}) {|ret, val| ret.merge(val) } ++ ++ cert.extensions = exts.map do |oid, val| ++ val, crit = *val ++ val = val.join(', ') unless val.is_a? String ++ ++ # Enforce the X509v3 rules about subjectAltName being critical: ++ # specifically, it SHOULD NOT be critical if we have a subject, which we ++ # always do. --daniel 2011-10-18 ++ crit = false if oid == "subjectAltName" ++ ++ # val can be either a string, or [string, critical], and this does the ++ # right thing regardless of what we get passed. ++ ef.create_ext(oid, val, crit) + end +- +- @extensions << @ef.create_extension("nsComment", "Puppet Ruby/OpenSSL Generated Certificate") +- @extensions << @ef.create_extension("basicConstraints", @basic_constraint, true) +- @extensions << @ef.create_extension("subjectKeyIdentifier", "hash") +- @extensions << @ef.create_extension("keyUsage", @key_usage.join(",")) if @key_usage +- @extensions << @ef.create_extension("extendedKeyUsage", @ext_key_usage.join(",")) if @ext_key_usage +- @extensions << @ef.create_extension("subjectAltName", @subject_alt_name.join(",")) if ! @subject_alt_name.empty? +- +- @cert.extensions = @extensions +- +- # for some reason this _must_ be the last extension added +- @extensions << @ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always") if @cert_type == :ca + end + + # TTL for new certificates in seconds. If config param :ca_ttl is set, + # use that, otherwise use :ca_days for backwards compatibility +- def ttl ++ def self.ttl + ttl = Puppet.settings[:ca_ttl] + + return ttl unless ttl.is_a?(String) +@@ -89,57 +98,69 @@ + $1.to_i * UNITMAP[$2] + end + +- def set_ttl +- # Make the certificate valid as of yesterday, because +- # so many people's clocks are out of sync. +- from = Time.now - (60*60*24) +- @cert.not_before = from +- @cert.not_after = from + ttl +- end +- + # Woot! We're a CA. +- def add_ca_extensions +- @basic_constraint = "CA:TRUE" +- @key_usage = %w{cRLSign keyCertSign} ++ def self.build_ca_extensions ++ { ++ # This was accidentally omitted in the previous version of this code: an ++ # effort was made to add it last, but that actually managed to avoid ++ # adding it to the certificate at all. ++ # ++ # We have some sort of bug, which means that when we add it we get a ++ # complaint that the issuer keyid can't be fetched, which breaks all ++ # sorts of things in our test suite and, e.g., bootstrapping the CA. ++ # ++ # http://tools.ietf.org/html/rfc5280#section-4.2.1.1 says that, to be a ++ # conforming CA we MAY omit the field if we are self-signed, which I ++ # think gives us a pass in the specific case. ++ # ++ # It also notes that we MAY derive the ID from the subject and serial ++ # number of the issuer, or from the key ID, and we definitely have the ++ # former data, should we want to restore this... ++ # ++ # Anyway, preserving this bug means we don't risk breaking anything in ++ # the field, even though it would be nice to have. --daniel 2011-10-11 ++ # ++ # "authorityKeyIdentifier" => "keyid:always,issuer:always", ++ "keyUsage" => [%w{cRLSign keyCertSign}, true], ++ "basicConstraints" => ["CA:TRUE", true], ++ } + end + + # We're a terminal CA, probably not self-signed. +- def add_terminalsubca_extensions +- @basic_constraint = "CA:TRUE,pathlen:0" +- @key_usage = %w{cRLSign keyCertSign} ++ def self.build_terminalsubca_extensions ++ { ++ "keyUsage" => [%w{cRLSign keyCertSign}, true], ++ "basicConstraints" => ["CA:TRUE,pathlen:0", true], ++ } + end + + # We're a normal server. +- def add_server_extensions +- @basic_constraint = "CA:FALSE" +- dnsnames = Puppet[:certdnsnames] +- name = @name.to_s.sub(%r{/CN=},'') +- if dnsnames != "" +- dnsnames.split(':').each { |d| @subject_alt_name << 'DNS:' + d } +- @subject_alt_name << 'DNS:' + name # Add the fqdn as an alias +- elsif name == Facter.value(:fqdn) # we're a CA server, and thus probably the server +- @subject_alt_name << 'DNS:' + "puppet" # Add 'puppet' as an alias +- @subject_alt_name << 'DNS:' + name # Add the fqdn as an alias +- @subject_alt_name << 'DNS:' + name.sub(/^[^.]+./, "puppet.") # add puppet.domain as an alias +- end +- @key_usage = %w{digitalSignature keyEncipherment} +- @ext_key_usage = %w{serverAuth clientAuth emailProtection} ++ def self.build_server_extensions ++ { ++ "keyUsage" => [%w{digitalSignature keyEncipherment}, true], ++ "extendedKeyUsage" => [%w{serverAuth clientAuth}, true], ++ "basicConstraints" => ["CA:FALSE", true], ++ } + end + + # Um, no idea. +- def add_ocsp_extensions +- @basic_constraint = "CA:FALSE" +- @key_usage = %w{nonRepudiation digitalSignature} +- @ext_key_usage = %w{serverAuth OCSPSigning} ++ def self.build_ocsp_extensions ++ { ++ "keyUsage" => [%w{nonRepudiation digitalSignature}, true], ++ "extendedKeyUsage" => [%w{serverAuth OCSPSigning}, true], ++ "basicConstraints" => ["CA:FALSE", true], ++ } + end + + # Normal client. +- def add_client_extensions +- @basic_constraint = "CA:FALSE" +- @key_usage = %w{nonRepudiation digitalSignature keyEncipherment} +- @ext_key_usage = %w{clientAuth emailProtection} +- +- @extensions << @ef.create_extension("nsCertType", "client,email") ++ def self.build_client_extensions ++ { ++ "keyUsage" => [%w{nonRepudiation digitalSignature keyEncipherment}, true], ++ # We don't seem to use this, but that seems much more reasonable here... ++ "extendedKeyUsage" => [%w{clientAuth emailProtection}, true], ++ "basicConstraints" => ["CA:FALSE", true], ++ "nsCertType" => "client,email", ++ } + end + end + +Index: puppet-2.7.1/lib/puppet/ssl/certificate.rb +=================================================================== +--- puppet-2.7.1.orig/lib/puppet/ssl/certificate.rb 2011-10-23 13:05:43.768903803 -0400 ++++ puppet-2.7.1/lib/puppet/ssl/certificate.rb 2011-10-23 13:05:46.392903778 -0400 +@@ -27,6 +27,12 @@ + [:s] + end + ++ def subject_alt_names ++ alts = content.extensions.find{|ext| ext.oid == "subjectAltName"} ++ return [] unless alts ++ alts.value.split(/\s*,\s*/) ++ end ++ + def expiration + return nil unless content + content.not_after +Index: puppet-2.7.1/lib/puppet/ssl/certificate_request.rb +=================================================================== +--- puppet-2.7.1.orig/lib/puppet/ssl/certificate_request.rb 2011-10-23 13:05:43.752903803 -0400 ++++ puppet-2.7.1/lib/puppet/ssl/certificate_request.rb 2011-10-23 13:05:46.392903778 -0400 +@@ -35,8 +35,12 @@ + [:s] + end + ++ def extension_factory ++ @ef ||= OpenSSL::X509::ExtensionFactory.new ++ end ++ + # How to create a certificate request with our system defaults. +- def generate(key) ++ def generate(key, options = {}) + Puppet.info "Creating a new SSL certificate request for #{name}" + + # Support either an actual SSL key, or a Puppet key. +@@ -51,6 +55,19 @@ + csr.version = 0 + csr.subject = OpenSSL::X509::Name.new([["CN", common_name]]) + csr.public_key = key.public_key ++ ++ if options[:dns_alt_names] then ++ names = options[:dns_alt_names].split(/\s*,\s*/).map(&:strip) + [name] ++ names = names.sort.uniq.map {|name| "DNS:#{name}" }.join(", ") ++ names = extension_factory.create_extension("subjectAltName", names, false) ++ ++ extReq = OpenSSL::ASN1::Set([OpenSSL::ASN1::Sequence([names])]) ++ ++ # We only support the standard request extensions. If you really need ++ # msExtReq support, let us know and we can restore them. --daniel 2011-10-10 ++ csr.add_attribute(OpenSSL::X509::Attribute.new("extReq", extReq)) ++ end ++ + csr.sign(key, OpenSSL::Digest::MD5.new) + + raise Puppet::Error, "CSR sign verification failed; you need to clean the certificate request for #{name} on the server" unless csr.verify(key.public_key) +@@ -59,4 +76,74 @@ + Puppet.info "Certificate Request fingerprint (md5): #{fingerprint}" + @content + end ++ ++ # Return the set of extensions requested on this CSR, in a form designed to ++ # be useful to Ruby: a hash. Which, not coincidentally, you can pass ++ # successfully to the OpenSSL constructor later, if you want. ++ def request_extensions ++ raise Puppet::Error, "CSR needs content to extract fields" unless @content ++ ++ # Prefer the standard extReq, but accept the Microsoft specific version as ++ # a fallback, if the standard version isn't found. ++ ext = @content.attributes.find {|x| x.oid == "extReq" } or ++ @content.attributes.find {|x| x.oid == "msExtReq" } ++ return [] unless ext ++ ++ # Assert the structure and extract the names into an array of arrays. ++ unless ext.value.is_a? OpenSSL::ASN1::Set ++ raise Puppet::Error, "In #{ext.oid}, expected Set but found #{ext.value.class}" ++ end ++ ++ unless ext.value.value.is_a? Array ++ raise Puppet::Error, "In #{ext.oid}, expected Set[Array] but found #{ext.value.value.class}" ++ end ++ ++ unless ext.value.value.length == 1 ++ raise Puppet::Error, "In #{ext.oid}, expected Set[Array[...]], but found #{ext.value.value.length} items in the array" ++ end ++ ++ san = ext.value.value.first ++ unless san.is_a? OpenSSL::ASN1::Sequence ++ raise Puppet::Error, "In #{ext.oid}, expected Set[Array[Sequence[...]]], but found #{san.class}" ++ end ++ san = san.value ++ ++ # OK, now san should be the array of items, validate that... ++ index = -1 ++ san.map do |name| ++ index += 1 ++ ++ unless name.is_a? OpenSSL::ASN1::Sequence ++ raise Puppet::Error, "In #{ext.oid}, expected request extension record #{index} to be a Sequence, but found #{name.class}" ++ end ++ name = name.value ++ ++ # OK, turn that into an extension, to unpack the content. Lovely that ++ # we have to swap the order of arguments to the underlying method, or ++ # perhaps that the ASN.1 representation chose to pack them in a ++ # strange order where the optional component comes *earlier* than the ++ # fixed component in the sequence. ++ case name.length ++ when 2 ++ ev = OpenSSL::X509::Extension.new(name[0].value, name[1].value) ++ { "oid" => ev.oid, "value" => ev.value } ++ ++ when 3 ++ ev = OpenSSL::X509::Extension.new(name[0].value, name[2].value, name[1].value) ++ { "oid" => ev.oid, "value" => ev.value, "critical" => ev.critical? } ++ ++ else ++ raise Puppet::Error, "In #{ext.oid}, expected extension record #{index} to have two or three items, but found #{name.length}" ++ end ++ end.flatten ++ end ++ ++ def subject_alt_names ++ @subject_alt_names ||= request_extensions. ++ select {|x| x["oid"] = "subjectAltName" }. ++ map {|x| x["value"].split(/\s*,\s*/) }. ++ flatten. ++ sort. ++ uniq ++ end + end +Index: puppet-2.7.1/lib/puppet/ssl/host.rb +=================================================================== +--- puppet-2.7.1.orig/lib/puppet/ssl/host.rb 2011-10-23 13:05:43.736903803 -0400 ++++ puppet-2.7.1/lib/puppet/ssl/host.rb 2011-10-23 13:05:46.392903778 -0400 +@@ -156,11 +156,24 @@ + @certificate_request ||= CertificateRequest.indirection.find(name) + end + ++ def this_csr_is_for_the_current_host ++ name == Puppet[:certname].downcase ++ end ++ + # Our certificate request requires the key but that's all. +- def generate_certificate_request ++ def generate_certificate_request(options = {}) + generate_key unless key ++ ++ # If this is for the current machine... ++ if this_csr_is_for_the_current_host ++ # ...add our configured dns_alt_names ++ if Puppet[:dns_alt_names] and Puppet[:dns_alt_names] != '' ++ options[:dns_alt_names] ||= Puppet[:dns_alt_names] ++ end ++ end ++ + @certificate_request = CertificateRequest.new(name) +- @certificate_request.generate(key.content) ++ @certificate_request.generate(key.content, options) + begin + CertificateRequest.indirection.save(@certificate_request) + rescue +@@ -203,7 +216,7 @@ + # should use it to sign our request; else, just try to read + # the cert. + if ! certificate and ca = Puppet::SSL::CertificateAuthority.instance +- ca.sign(self.name) ++ ca.sign(self.name, true) + end + end + +Index: puppet-2.7.1/lib/puppet/sslcertificates/ca.rb +=================================================================== +--- puppet-2.7.1.orig/lib/puppet/sslcertificates/ca.rb 2011-10-23 13:05:43.860903802 -0400 ++++ /dev/null 1970-01-01 00:00:00.000000000 +0000 +@@ -1,375 +0,0 @@ +-require 'sync' +- +-class Puppet::SSLCertificates::CA +- include Puppet::Util::Warnings +- +- Certificate = Puppet::SSLCertificates::Certificate +- attr_accessor :keyfile, :file, :config, :dir, :cert, :crl +- +- def certfile +- @config[:cacert] +- end +- +- # Remove all traces of a given host. This is kind of hackish, but, eh. +- def clean(host) +- host = host.downcase +- [:csrdir, :signeddir, :publickeydir, :privatekeydir, :certdir].each do |name| +- dir = Puppet[name] +- +- file = File.join(dir, host + ".pem") +- +- if FileTest.exists?(file) +- begin +- if Puppet[:name] == "cert" +- puts "Removing #{file}" +- else +- Puppet.info "Removing #{file}" +- end +- File.unlink(file) +- rescue => detail +- raise Puppet::Error, "Could not delete #{file}: #{detail}" +- end +- end +- +- end +- end +- +- def host2csrfile(hostname) +- File.join(Puppet[:csrdir], [hostname.downcase, "pem"].join(".")) +- end +- +- # this stores signed certs in a directory unrelated to +- # normal client certs +- def host2certfile(hostname) +- File.join(Puppet[:signeddir], [hostname.downcase, "pem"].join(".")) +- end +- +- # Turn our hostname into a Name object +- def thing2name(thing) +- thing.subject.to_a.find { |ary| +- ary[0] == "CN" +- }[1] +- end +- +- def initialize(hash = {}) +- Puppet.settings.use(:main, :ca, :ssl) +- self.setconfig(hash) +- +- if Puppet[:capass] +- if FileTest.exists?(Puppet[:capass]) +- #puts "Reading #{Puppet[:capass]}" +- #system "ls -al #{Puppet[:capass]}" +- #File.read Puppet[:capass] +- @config[:password] = self.getpass +- else +- # Don't create a password if the cert already exists +- @config[:password] = self.genpass unless FileTest.exists?(@config[:cacert]) +- end +- end +- +- self.getcert +- init_crl +- unless FileTest.exists?(@config[:serial]) +- Puppet.settings.write(:serial) do |f| +- f << "%04X" % 1 +- end +- end +- end +- +- # Generate a new password for the CA. +- def genpass +- pass = "" +- 20.times { pass += (rand(74) + 48).chr } +- +- begin +- Puppet.settings.write(:capass) { |f| f.print pass } +- rescue Errno::EACCES => detail +- raise Puppet::Error, detail.to_s +- end +- pass +- end +- +- # Get the CA password. +- def getpass +- if @config[:capass] and File.readable?(@config[:capass]) +- return File.read(@config[:capass]) +- else +- raise Puppet::Error, "Could not decrypt CA key with password: #{detail}" +- end +- end +- +- # Get the CA cert. +- def getcert +- if FileTest.exists?(@config[:cacert]) +- @cert = OpenSSL::X509::Certificate.new( +- File.read(@config[:cacert]) +- ) +- else +- self.mkrootcert +- end +- end +- +- # Retrieve a client's CSR. +- def getclientcsr(host) +- csrfile = host2csrfile(host) +- return nil unless File.exists?(csrfile) +- +- OpenSSL::X509::Request.new(File.read(csrfile)) +- end +- +- # Retrieve a client's certificate. +- def getclientcert(host) +- certfile = host2certfile(host) +- return [nil, nil] unless File.exists?(certfile) +- +- [OpenSSL::X509::Certificate.new(File.read(certfile)), @cert] +- end +- +- # List certificates waiting to be signed. This returns a list of hostnames, not actual +- # files -- the names can be converted to full paths with host2csrfile. +- def list(dummy_argument=:work_arround_for_ruby_GC_bug) +- return Dir.entries(Puppet[:csrdir]).find_all { |file| +- file =~ /\.pem$/ +- }.collect { |file| +- file.sub(/\.pem$/, '') +- } +- end +- +- # List signed certificates. This returns a list of hostnames, not actual +- # files -- the names can be converted to full paths with host2csrfile. +- def list_signed(dummy_argument=:work_arround_for_ruby_GC_bug) +- return Dir.entries(Puppet[:signeddir]).find_all { |file| +- file =~ /\.pem$/ +- }.collect { |file| +- file.sub(/\.pem$/, '') +- } +- end +- +- # Create the root certificate. +- def mkrootcert +- # Make the root cert's name "Puppet CA: " plus the FQDN of the host running the CA. +- name = "Puppet CA: #{Facter["hostname"].value}" +- if domain = Facter["domain"].value +- name += ".#{domain}" +- end +- +- cert = Certificate.new( +- :name => name, +- :cert => @config[:cacert], +- :encrypt => @config[:capass], +- :key => @config[:cakey], +- :selfsign => true, +- :ttl => ttl, +- :type => :ca +- ) +- +- # This creates the cakey file +- Puppet::Util::SUIDManager.asuser(Puppet[:user], Puppet[:group]) do +- @cert = cert.mkselfsigned +- end +- Puppet.settings.write(:cacert) do |f| +- f.puts @cert.to_pem +- end +- Puppet.settings.write(:capub) do |f| +- f.puts @cert.public_key +- end +- cert +- end +- +- def removeclientcsr(host) +- csrfile = host2csrfile(host) +- raise Puppet::Error, "No certificate request for #{host}" unless File.exists?(csrfile) +- +- File.unlink(csrfile) +- end +- +- # Revoke the certificate with serial number SERIAL issued by this +- # CA. The REASON must be one of the OpenSSL::OCSP::REVOKED_* reasons +- def revoke(serial, reason = OpenSSL::OCSP::REVOKED_STATUS_KEYCOMPROMISE) +- time = Time.now +- revoked = OpenSSL::X509::Revoked.new +- revoked.serial = serial +- revoked.time = time +- enum = OpenSSL::ASN1::Enumerated(reason) +- ext = OpenSSL::X509::Extension.new("CRLReason", enum) +- revoked.add_extension(ext) +- @crl.add_revoked(revoked) +- store_crl +- end +- +- # Take the Puppet config and store it locally. +- def setconfig(hash) +- @config = {} +- Puppet.settings.params("ca").each { |param| +- param = param.intern if param.is_a? String +- if hash.include?(param) +- @config[param] = hash[param] +- Puppet[param] = hash[param] +- hash.delete(param) +- else +- @config[param] = Puppet[param] +- end +- } +- +- if hash.include?(:password) +- @config[:password] = hash[:password] +- hash.delete(:password) +- end +- +- raise ArgumentError, "Unknown parameters #{hash.keys.join(",")}" if hash.length > 0 +- +- [:cadir, :csrdir, :signeddir].each { |dir| +- raise Puppet::DevError, "#{dir} is undefined" unless @config[dir] +- } +- end +- +- # Sign a given certificate request. +- def sign(csr) +- unless csr.is_a?(OpenSSL::X509::Request) +- raise Puppet::Error, +- "CA#sign only accepts OpenSSL::X509::Request objects, not #{csr.class}" +- end +- +- raise Puppet::Error, "CSR sign verification failed" unless csr.verify(csr.public_key) +- +- serial = nil +- Puppet.settings.readwritelock(:serial) { |f| +- serial = File.read(@config[:serial]).chomp.hex +- # increment the serial +- f << "%04X" % (serial + 1) +- } +- +- newcert = Puppet::SSLCertificates.mkcert( +- :type => :server, +- :name => csr.subject, +- :ttl => ttl, +- :issuer => @cert, +- :serial => serial, +- :publickey => csr.public_key +- ) +- +- sign_with_key(newcert) +- +- self.storeclientcert(newcert) +- +- [newcert, @cert] +- end +- +- # Store the client's CSR for later signing. This is called from +- # server/ca.rb, and the CSRs are deleted once the certificate is actually +- # signed. +- def storeclientcsr(csr) +- host = thing2name(csr) +- +- csrfile = host2csrfile(host) +- raise Puppet::Error, "Certificate request for #{host} already exists" if File.exists?(csrfile) +- +- Puppet.settings.writesub(:csrdir, csrfile) do |f| +- f.print csr.to_pem +- end +- end +- +- # Store the certificate that we generate. +- def storeclientcert(cert) +- host = thing2name(cert) +- +- certfile = host2certfile(host) +- Puppet.notice "Overwriting signed certificate #{certfile} for #{host}" if File.exists?(certfile) +- +- Puppet::SSLCertificates::Inventory::add(cert) +- Puppet.settings.writesub(:signeddir, certfile) do |f| +- f.print cert.to_pem +- end +- end +- +- # TTL for new certificates in seconds. If config param :ca_ttl is set, +- # use that, otherwise use :ca_days for backwards compatibility +- def ttl +- days = @config[:ca_days] +- if days && days.size > 0 +- warnonce "Parameter ca_ttl is not set. Using depecated ca_days instead." +- return @config[:ca_days] * 24 * 60 * 60 +- else +- ttl = @config[:ca_ttl] +- if ttl.is_a?(String) +- unless ttl =~ /^(\d+)(y|d|h|s)$/ +- raise ArgumentError, "Invalid ca_ttl #{ttl}" +- end +- case $2 +- when 'y' +- unit = 365 * 24 * 60 * 60 +- when 'd' +- unit = 24 * 60 * 60 +- when 'h' +- unit = 60 * 60 +- when 's' +- unit = 1 +- else +- raise ArgumentError, "Invalid unit for ca_ttl #{ttl}" +- end +- return $1.to_i * unit +- else +- return ttl +- end +- end +- end +- +- private +- def init_crl +- if FileTest.exists?(@config[:cacrl]) +- @crl = OpenSSL::X509::CRL.new( +- File.read(@config[:cacrl]) +- ) +- else +- # Create new CRL +- @crl = OpenSSL::X509::CRL.new +- @crl.issuer = @cert.subject +- @crl.version = 1 +- store_crl +- @crl +- end +- end +- +- def store_crl +- # Increment the crlNumber +- e = @crl.extensions.find { |e| e.oid == 'crlNumber' } +- ext = @crl.extensions.reject { |e| e.oid == 'crlNumber' } +- crlNum = OpenSSL::ASN1::Integer(e ? e.value.to_i + 1 : 0) +- ext << OpenSSL::X509::Extension.new("crlNumber", crlNum) +- @crl.extensions = ext +- +- # Set last/next update +- now = Time.now +- @crl.last_update = now +- # Keep CRL valid for 5 years +- @crl.next_update = now + 5 * 365*24*60*60 +- +- sign_with_key(@crl) +- Puppet.settings.write(:cacrl) do |f| +- f.puts @crl.to_pem +- end +- end +- +- def sign_with_key(signable, digest = OpenSSL::Digest::SHA1.new) +- cakey = nil +- if @config[:password] +- begin +- cakey = OpenSSL::PKey::RSA.new( +- File.read(@config[:cakey]), @config[:password] +- ) +- rescue +- raise Puppet::Error, +- "Decrypt of CA private key with password stored in @config[:capass] not possible" +- end +- else +- cakey = OpenSSL::PKey::RSA.new( +- File.read(@config[:cakey]) +- ) +- end +- +- raise Puppet::Error, "CA Certificate is invalid" unless @cert.check_private_key(cakey) +- +- signable.sign(cakey, digest) +- end +-end +- +Index: puppet-2.7.1/lib/puppet/sslcertificates/certificate.rb +=================================================================== +--- puppet-2.7.1.orig/lib/puppet/sslcertificates/certificate.rb 2011-10-23 13:05:43.848903802 -0400 ++++ /dev/null 1970-01-01 00:00:00.000000000 +0000 +@@ -1,255 +0,0 @@ +-class Puppet::SSLCertificates::Certificate +- SSLCertificates = Puppet::SSLCertificates +- +- attr_accessor :certfile, :keyfile, :name, :dir, :hash, :type +- attr_accessor :key, :cert, :csr, :cacert +- +- @@params2names = { +- :name => "CN", +- :state => "ST", +- :country => "C", +- :email => "emailAddress", +- :org => "O", +- :city => "L", +- :ou => "OU" +- } +- +- def certname +- OpenSSL::X509::Name.new self.subject +- end +- +- def delete +- [@certfile,@keyfile].each { |file| +- File.unlink(file) if FileTest.exists?(file) +- } +- +- if @hash +- File.unlink(@hash) if FileTest.symlink?(@hash) +- end +- end +- +- def exists? +- FileTest.exists?(@certfile) +- end +- +- def getkey +- self.mkkey unless FileTest.exists?(@keyfile) +- if @password +- +- @key = OpenSSL::PKey::RSA.new( +- +- File.read(@keyfile), +- +- @password +- ) +- else +- @key = OpenSSL::PKey::RSA.new( +- File.read(@keyfile) +- ) +- end +- end +- +- def initialize(hash) +- raise Puppet::Error, "You must specify the common name for the certificate" unless hash.include?(:name) +- @name = hash[:name] +- +- # init a few variables +- @cert = @key = @csr = nil +- +- if hash.include?(:cert) +- @certfile = hash[:cert] +- @dir = File.dirname(@certfile) +- else +- @dir = hash[:dir] || Puppet[:certdir] +- @certfile = File.join(@dir, @name) +- end +- +- @cacertfile ||= File.join(Puppet[:certdir], "ca.pem") +- +- Puppet.recmkdir(@dir) unless FileTest.directory?(@dir) +- +- unless @certfile =~ /\.pem$/ +- @certfile += ".pem" +- end +- @keyfile = hash[:key] || File.join( +- Puppet[:privatekeydir], [@name,"pem"].join(".") +- ) +- Puppet.recmkdir(@dir) unless FileTest.directory?(@dir) +- +- [@keyfile].each { |file| +- dir = File.dirname(file) +- +- Puppet.recmkdir(dir) unless FileTest.directory?(dir) +- } +- +- @ttl = hash[:ttl] || 365 * 24 * 60 * 60 +- @selfsign = hash[:selfsign] || false +- @encrypt = hash[:encrypt] || false +- @replace = hash[:replace] || false +- @issuer = hash[:issuer] || nil +- +- if hash.include?(:type) +- case hash[:type] +- when :ca, :client, :server; @type = hash[:type] +- else +- raise "Invalid Cert type #{hash[:type]}" +- end +- else +- @type = :client +- end +- +- @params = {:name => @name} +- [:state, :country, :email, :org, :ou].each { |param| +- @params[param] = hash[param] if hash.include?(param) +- } +- +- if @encrypt +- if @encrypt =~ /^\// +- File.open(@encrypt) { |f| +- @password = f.read.chomp +- } +- else +- raise Puppet::Error, ":encrypt must be a path to a pass phrase file" +- end +- else +- @password = nil +- end +- +- @selfsign = hash.include?(:selfsign) && hash[:selfsign] +- end +- +- # this only works for servers, not for users +- def mkcsr +- self.getkey unless @key +- +- name = OpenSSL::X509::Name.new self.subject +- +- @csr = OpenSSL::X509::Request.new +- @csr.version = 0 +- @csr.subject = name +- @csr.public_key = @key.public_key +- @csr.sign(@key, OpenSSL::Digest::SHA1.new) +- +- #File.open(@csrfile, "w") { |f| +- # f << @csr.to_pem +- #} +- +- raise Puppet::Error, "CSR sign verification failed" unless @csr.verify(@key.public_key) +- +- @csr +- end +- +- def mkkey +- # @key is the file +- +- @key = OpenSSL::PKey::RSA.new(1024) +-# { |p,n| +-# case p +-# when 0; Puppet.info "key info: ." # BN_generate_prime +-# when 1; Puppet.info "key info: +" # BN_generate_prime +-# when 2; Puppet.info "key info: *" # searching good prime, +-# # n = #of try, +-# # but also data from BN_generate_prime +-# when 3; Puppet.info "key info: \n" # found good prime, n==0 - p, n==1 - q, +-# # but also data from BN_generate_prime +-# else; Puppet.info "key info: *" # BN_generate_prime +-# end +-# } +- +- if @password +- # passwdproc = proc { @password } +- +- keytext = @key.export( +- +- OpenSSL::Cipher::DES.new(:EDE3, :CBC), +- +- @password +- ) +- File.open(@keyfile, "w", 0400) { |f| +- f << keytext +- } +- else +- File.open(@keyfile, "w", 0400) { |f| +- f << @key.to_pem +- } +- end +- +- #cmd = "#{ossl} genrsa -out #{@key} 1024" +- end +- +- def mkselfsigned +- self.getkey unless @key +- +- raise Puppet::Error, "Cannot replace existing certificate" if @cert +- +- args = { +- :name => self.certname, +- :ttl => @ttl, +- :issuer => nil, +- :serial => 0x0, +- :publickey => @key.public_key +- } +- if @type +- args[:type] = @type +- else +- args[:type] = :server +- end +- @cert = SSLCertificates.mkcert(args) +- +- @cert.sign(@key, OpenSSL::Digest::SHA1.new) if @selfsign +- +- @cert +- end +- +- def subject(string = false) +- subj = @@params2names.collect { |param, name| +- [name, @params[param]] if @params.include?(param) +- }.reject { |ary| ary.nil? } +- +- if string +- return "/" + subj.collect { |ary| +- "%s=%s" % ary +- }.join("/") + "/" +- else +- return subj +- end +- end +- +- # verify that we can track down the cert chain or whatever +- def verify +- "openssl verify -verbose -CAfile /home/luke/.puppet/ssl/certs/ca.pem -purpose sslserver culain.madstop.com.pem" +- end +- +- def write +- files = { +- @certfile => @cert, +- @keyfile => @key, +- } +- files[@cacertfile] = @cacert if defined?(@cacert) +- +- files.each { |file,thing| +- if thing +- next if FileTest.exists?(file) +- +- text = nil +- +- if thing.is_a?(OpenSSL::PKey::RSA) and @password +- +- text = thing.export( +- +- OpenSSL::Cipher::DES.new(:EDE3, :CBC), +- +- @password +- ) +- else +- text = thing.to_pem +- end +- +- File.open(file, "w", 0660) { |f| f.print text } +- end +- } +- +- SSLCertificates.mkhash(Puppet[:certdir], @cacert, @cacertfile) if defined?(@cacert) +- end +-end +- +Index: puppet-2.7.1/lib/puppet/sslcertificates/inventory.rb +=================================================================== +--- puppet-2.7.1.orig/lib/puppet/sslcertificates/inventory.rb 2011-10-23 13:05:43.888903803 -0400 ++++ /dev/null 1970-01-01 00:00:00.000000000 +0000 +@@ -1,38 +0,0 @@ +-# A module for keeping track of all the certificates issued by the CA, ever +-# Maintains the file "$cadir/inventory.txt" +-module Puppet::SSLCertificates +- module Inventory +- +- # Add CERT to the inventory of issued certs in '$cadir/inventory.txt' +- # If no inventory exists yet, build an inventory and list all the +- # certificates that have been signed so far +- def self.add(cert) +- inited = false +- inited = true if FileTest.exists?(Puppet[:cert_inventory]) +- +- Puppet.settings.write(:cert_inventory, "a") do |f| +- f.puts((inited ? nil : self.init).to_s + format(cert)) +- end +- end +- +- private +- +- def self.init +- inv = "# Inventory of signed certificates\n" +- inv += "# SERIAL NOT_BEFORE NOT_AFTER SUBJECT\n" +- Dir.glob(File::join(Puppet[:signeddir], "*.pem")) do |f| +- inv += format(OpenSSL::X509::Certificate.new(File::read(f))) + "\n" +- end +- inv +- end +- +- def self.format(cert) +- iso = '%Y-%m-%dT%H:%M:%S%Z' +- return "0x%04x %s %s %s" % [cert.serial, +- cert.not_before.strftime(iso), +- cert.not_after.strftime(iso), +- cert.subject] +- end +- end +-end +- +Index: puppet-2.7.1/lib/puppet/sslcertificates/support.rb +=================================================================== +--- puppet-2.7.1.orig/lib/puppet/sslcertificates/support.rb 2011-10-23 13:05:43.872903802 -0400 ++++ /dev/null 1970-01-01 00:00:00.000000000 +0000 +@@ -1,146 +0,0 @@ +-require 'puppet/sslcertificates' +- +-# A module to handle reading of certificates. +-module Puppet::SSLCertificates::Support +- class MissingCertificate < Puppet::Error; end +- class InvalidCertificate < Puppet::Error; end +- +- attr_reader :cacert +- +- # Some metaprogramming to create methods for retrieving and creating keys. +- # This probably isn't fewer lines than defining each separately... +- def self.keytype(name, options, &block) +- var = "@#{name}" +- +- maker = "mk_#{name}" +- reader = "read_#{name}" +- +- unless param = options[:param] +- raise ArgumentError, "You must specify the parameter for the key" +- end +- +- unless klass = options[:class] +- raise ArgumentError, "You must specify the class for the key" +- end +- +- # Define the method that creates it. +- define_method(maker, &block) +- +- # Define the reading method. +- define_method(reader) do +- return nil unless FileTest.exists?(Puppet[param]) or rename_files_with_uppercase(Puppet[param]) +- +- begin +- instance_variable_set(var, klass.new(File.read(Puppet[param]))) +- rescue => detail +- raise InvalidCertificate, "Could not read #{param}: #{detail}" +- end +- end +- +- # Define the overall method, which just calls the reader and maker +- # as appropriate. +- define_method(name) do +- unless cert = instance_variable_get(var) +- unless cert = send(reader) +- cert = send(maker) +- Puppet.settings.write(param) { |f| f.puts cert.to_pem } +- end +- instance_variable_set(var, cert) +- end +- cert +- end +- end +- +- # The key pair. +- keytype :key, :param => :hostprivkey, :class => OpenSSL::PKey::RSA do +- Puppet.info "Creating a new SSL key at #{Puppet[:hostprivkey]}" +- key = OpenSSL::PKey::RSA.new(Puppet[:keylength]) +- +- # Our key meta programming can only handle one file, so we have +- # to separately write out the public key. +- Puppet.settings.write(:hostpubkey) do |f| +- f.print key.public_key.to_pem +- end +- return key +- end +- +- # Our certificate request +- keytype :csr, :param => :hostcsr, :class => OpenSSL::X509::Request do +- Puppet.info "Creating a new certificate request for #{Puppet[:certname]}" +- +- csr = OpenSSL::X509::Request.new +- csr.version = 0 +- csr.subject = OpenSSL::X509::Name.new([["CN", Puppet[:certname]]]) +- csr.public_key = key.public_key +- csr.sign(key, OpenSSL::Digest::MD5.new) +- +- return csr +- end +- +- keytype :cert, :param => :hostcert, :class => OpenSSL::X509::Certificate do +- raise MissingCertificate, "No host certificate" +- end +- +- keytype :ca_cert, :param => :localcacert, :class => OpenSSL::X509::Certificate do +- raise MissingCertificate, "No CA certificate" +- end +- +- # Request a certificate from the remote system. This does all of the work +- # of creating the cert request, contacting the remote system, and +- # storing the cert locally. +- def requestcert +- begin +- cert, cacert = caclient.getcert(@csr.to_pem) +- rescue => detail +- puts detail.backtrace if Puppet[:trace] +- raise Puppet::Error.new("Certificate retrieval failed: #{detail}") +- end +- +- if cert.nil? or cert == "" +- return nil +- end +- Puppet.settings.write(:hostcert) do |f| f.print cert end +- Puppet.settings.write(:localcacert) do |f| f.print cacert end +- #File.open(@certfile, "w", 0644) { |f| f.print cert } +- #File.open(@cacertfile, "w", 0644) { |f| f.print cacert } +- begin +- @cert = OpenSSL::X509::Certificate.new(cert) +- @cacert = OpenSSL::X509::Certificate.new(cacert) +- retrieved = true +- rescue => detail +- raise Puppet::Error.new( +- "Invalid certificate: #{detail}" +- ) +- end +- +- raise Puppet::DevError, "Received invalid certificate" unless @cert.check_private_key(@key) +- retrieved +- end +- +- # A hack method to deal with files that exist with a different case. +- # Just renames it; doesn't read it in or anything. +- def rename_files_with_uppercase(file) +- dir = File.dirname(file) +- short = File.basename(file) +- +- # If the dir isn't present, we clearly don't have the file. +- #return nil unless FileTest.directory?(dir) +- +- raise ArgumentError, "Tried to fix SSL files to a file containing uppercase" unless short.downcase == short +- +- return false unless File.directory?(dir) +- +- real_file = Dir.entries(dir).reject { |f| f =~ /^\./ }.find do |other| +- other.downcase == short +- end +- +- return nil unless real_file +- +- full_file = File.join(dir, real_file) +- +- Puppet.notice "Fixing case in #{full_file}; renaming to #{file}" +- File.rename(full_file, file) +- +- true +- end +-end +Index: puppet-2.7.1/lib/puppet/sslcertificates.rb +=================================================================== +--- puppet-2.7.1.orig/lib/puppet/sslcertificates.rb 2011-10-23 13:05:43.548903805 -0400 ++++ /dev/null 1970-01-01 00:00:00.000000000 +0000 +@@ -1,146 +0,0 @@ +-# The library for manipulating SSL certs. +- +-require 'puppet' +- +-raise Puppet::Error, "You must have the Ruby openssl library installed" unless Puppet.features.openssl? +- +-module Puppet::SSLCertificates +- #def self.mkcert(type, name, dnsnames, ttl, issuercert, issuername, serial, publickey) +- def self.mkcert(hash) +- [:type, :name, :ttl, :issuer, :serial, :publickey].each { |param| +- raise ArgumentError, "mkcert called without #{param}" unless hash.include?(param) +- } +- +- cert = OpenSSL::X509::Certificate.new +- # Make the certificate valid as of yesterday, because +- # so many people's clocks are out of sync. +- from = Time.now - (60*60*24) +- +- cert.subject = hash[:name] +- if hash[:issuer] +- cert.issuer = hash[:issuer].subject +- else +- # we're a self-signed cert +- cert.issuer = hash[:name] +- end +- cert.not_before = from +- cert.not_after = from + hash[:ttl] +- cert.version = 2 # X509v3 +- +- cert.public_key = hash[:publickey] +- cert.serial = hash[:serial] +- +- basic_constraint = nil +- key_usage = nil +- ext_key_usage = nil +- subject_alt_name = [] +- +- ef = OpenSSL::X509::ExtensionFactory.new +- +- ef.subject_certificate = cert +- +- if hash[:issuer] +- ef.issuer_certificate = hash[:issuer] +- else +- ef.issuer_certificate = cert +- end +- +- ex = [] +- case hash[:type] +- when :ca +- basic_constraint = "CA:TRUE" +- key_usage = %w{cRLSign keyCertSign} +- when :terminalsubca +- basic_constraint = "CA:TRUE,pathlen:0" +- key_usage = %w{cRLSign keyCertSign} +- when :server +- basic_constraint = "CA:FALSE" +- dnsnames = Puppet[:certdnsnames] +- name = hash[:name].to_s.sub(%r{/CN=},'') +- if dnsnames != "" +- dnsnames.split(':').each { |d| subject_alt_name << 'DNS:' + d } +- subject_alt_name << 'DNS:' + name # Add the fqdn as an alias +- elsif name == Facter.value(:fqdn) # we're a CA server, and thus probably the server +- subject_alt_name << 'DNS:' + "puppet" # Add 'puppet' as an alias +- subject_alt_name << 'DNS:' + name # Add the fqdn as an alias +- subject_alt_name << 'DNS:' + name.sub(/^[^.]+./, "puppet.") # add puppet.domain as an alias +- end +- key_usage = %w{digitalSignature keyEncipherment} +- ext_key_usage = %w{serverAuth clientAuth emailProtection} +- when :ocsp +- basic_constraint = "CA:FALSE" +- key_usage = %w{nonRepudiation digitalSignature} +- ext_key_usage = %w{serverAuth OCSPSigning} +- when :client +- basic_constraint = "CA:FALSE" +- key_usage = %w{nonRepudiation digitalSignature keyEncipherment} +- ext_key_usage = %w{clientAuth emailProtection} +- ex << ef.create_extension("nsCertType", "client,email") +- else +- raise Puppet::Error, "unknown cert type '#{hash[:type]}'" +- end +- +- +- ex << ef.create_extension( +- "nsComment", +- +- "Puppet Ruby/OpenSSL Generated Certificate") +- ex << ef.create_extension("basicConstraints", basic_constraint, true) +- ex << ef.create_extension("subjectKeyIdentifier", "hash") +- +- ex << ef.create_extension("keyUsage", key_usage.join(",")) if key_usage +- ex << ef.create_extension("extendedKeyUsage", ext_key_usage.join(",")) if ext_key_usage +- ex << ef.create_extension("subjectAltName", subject_alt_name.join(",")) if ! subject_alt_name.empty? +- +- #if @ca_config[:cdp_location] then +- # ex << ef.create_extension("crlDistributionPoints", +- # @ca_config[:cdp_location]) +- #end +- +- #if @ca_config[:ocsp_location] then +- # ex << ef.create_extension("authorityInfoAccess", +- # "OCSP;" << @ca_config[:ocsp_location]) +- #end +- cert.extensions = ex +- +- # for some reason this _must_ be the last extension added +- ex << ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always") if hash[:type] == :ca +- +- cert +- end +- +- def self.mkhash(dir, cert, certfile) +- # Make sure the hash is zero-padded to 8 chars +- hash = "%08x" % cert.issuer.hash +- hashpath = nil +- 10.times { |i| +- path = File.join(dir, "#{hash}.#{i}") +- if FileTest.exists?(path) +- if FileTest.symlink?(path) +- dest = File.readlink(path) +- if dest == certfile +- # the correct link already exists +- hashpath = path +- break +- else +- next +- end +- else +- next +- end +- end +- +- File.symlink(certfile, path) +- +- hashpath = path +- break +- } +- +- +- hashpath +- end +- require 'puppet/sslcertificates/certificate' +- require 'puppet/sslcertificates/inventory' +- require 'puppet/sslcertificates/ca' +-end +- +Index: puppet-2.7.1/lib/puppet/type/file.rb +=================================================================== +--- puppet-2.7.1.orig/lib/puppet/type/file.rb 2011-10-23 13:05:43.832903802 -0400 ++++ puppet-2.7.1/lib/puppet/type/file.rb 2011-10-23 13:05:46.392903778 -0400 +@@ -6,7 +6,6 @@ + require 'puppet/network/handler' + require 'puppet/util/diff' + require 'puppet/util/checksums' +-require 'puppet/network/client' + require 'puppet/util/backups' + + Puppet::Type.newtype(:file) do +Index: puppet-2.7.1/lib/puppet/util/monkey_patches.rb +=================================================================== +--- puppet-2.7.1.orig/lib/puppet/util/monkey_patches.rb 2011-10-23 13:05:43.920903801 -0400 ++++ puppet-2.7.1/lib/puppet/util/monkey_patches.rb 2011-10-23 13:05:46.392903778 -0400 +@@ -103,6 +103,8 @@ + ret += tmp.combination(num - 1).map{|a| a.unshift(e) } + end + end unless method_defined? :combination ++ ++ alias :count :length unless method_defined? :count + end + + +@@ -111,3 +113,20 @@ + Proc.new { |*args| args.shift.__send__(self, *args) } + end unless method_defined? :to_proc + end ++ ++ ++class String ++ def lines(separator = $/) ++ lines = split(separator) ++ block_given? and lines.each {|line| yield line } ++ lines ++ end ++end ++ ++class IO ++ def lines(separator = $/) ++ lines = split(separator) ++ block_given? and lines.each {|line| yield line } ++ lines ++ end ++end +Index: puppet-2.7.1/lib/puppet/util/settings.rb +=================================================================== +--- puppet-2.7.1.orig/lib/puppet/util/settings.rb 2011-10-23 13:05:43.904903803 -0400 ++++ puppet-2.7.1/lib/puppet/util/settings.rb 2011-10-23 13:05:46.396903779 -0400 +@@ -495,6 +495,11 @@ + end + type = legacy_to_mode(type, param) + @sync.synchronize do # yay, thread-safe ++ # Allow later inspection to determine if the setting was set on the ++ # command line, or through some other code path. Used for the ++ # `dns_alt_names` option during cert generate. --daniel 2011-10-18 ++ setting.setbycli = true if type == :cli ++ + @values[type][param] = value + @cache.clear + +Index: puppet-2.7.1/spec/integration/defaults_spec.rb +=================================================================== +--- puppet-2.7.1.orig/spec/integration/defaults_spec.rb 2011-10-23 13:05:43.392903806 -0400 ++++ puppet-2.7.1/spec/integration/defaults_spec.rb 2011-10-23 13:05:46.396903779 -0400 +@@ -22,6 +22,17 @@ + end + end + ++ describe "when :certdnsnames is set" do ++ it "should not fail" do ++ expect { Puppet[:certdnsnames] = 'fred:wilma' }.should_not raise_error ++ end ++ ++ it "should warn the value is ignored" do ++ Puppet.expects(:warning).with {|msg| msg =~ /CVE-2011-3872/ } ++ Puppet[:certdnsnames] = 'fred:wilma' ++ end ++ end ++ + describe "when configuring the :crl" do + it "should warn if :cacrl is set to false" do + Puppet.expects(:warning) +Index: puppet-2.7.1/spec/integration/network/client_spec.rb +=================================================================== +--- puppet-2.7.1.orig/spec/integration/network/client_spec.rb 2011-10-23 13:05:43.360903808 -0400 ++++ /dev/null 1970-01-01 00:00:00.000000000 +0000 +@@ -1,18 +0,0 @@ +-#!/usr/bin/env rspec +-require 'spec_helper' +- +-require 'puppet/network/client' +- +-describe Puppet::Network::Client do +- %w{ca file report runner status}.each do |name| +- it "should have a #{name} client" do +- Puppet::Network::Client.client(name).should be_instance_of(Class) +- end +- +- [:name, :handler, :drivername].each do |data| +- it "should have a #{data} value for the #{name} client" do +- Puppet::Network::Client.client(name).send(data).should_not be_nil +- end +- end +- end +-end +Index: puppet-2.7.1/spec/integration/network/handler_spec.rb +=================================================================== +--- puppet-2.7.1.orig/spec/integration/network/handler_spec.rb 2011-10-23 13:05:43.376903808 -0400 ++++ puppet-2.7.1/spec/integration/network/handler_spec.rb 2011-10-23 13:05:46.396903779 -0400 +@@ -1,7 +1,7 @@ + #!/usr/bin/env rspec + require 'spec_helper' + +-require 'puppet/network/client' ++require 'puppet/network/handler' + + describe Puppet::Network::Handler do + %w{ca filebucket fileserver master report runner status}.each do |name| +Index: puppet-2.7.1/spec/spec_helper.rb +=================================================================== +--- puppet-2.7.1.orig/spec/spec_helper.rb 2011-10-23 13:05:43.348903807 -0400 ++++ puppet-2.7.1/spec/spec_helper.rb 2011-10-23 13:05:46.396903779 -0400 +@@ -1,3 +1,6 @@ ++unless defined? SPEC_HELPER_IS_LOADED ++SPEC_HELPER_IS_LOADED = 1 ++ + dir = File.expand_path(File.dirname(__FILE__)) + $LOAD_PATH.unshift File.join(dir, 'lib') + +@@ -74,3 +77,5 @@ + GC.enable + end + end ++ ++end +Index: puppet-2.7.1/spec/unit/face/certificate_spec.rb +=================================================================== +--- puppet-2.7.1.orig/spec/unit/face/certificate_spec.rb 2011-10-23 13:05:43.524903805 -0400 ++++ puppet-2.7.1/spec/unit/face/certificate_spec.rb 2011-10-23 13:05:46.396903779 -0400 +@@ -5,14 +5,33 @@ + require 'puppet/ssl/host' + + describe Puppet::Face[:certificate, '0.0.1'] do ++ include PuppetSpec::Files ++ ++ let(:ca) { Puppet::SSL::CertificateAuthority.instance } ++ ++ before :each do ++ Puppet[:confdir] = tmpdir('conf') ++ Puppet::SSL::CertificateAuthority.stubs(:ca?).returns true ++ ++ Puppet::SSL::Host.ca_location = :local ++ ++ # We can't cache the CA between tests, because each one has its own SSL dir. ++ ca = Puppet::SSL::CertificateAuthority.new ++ Puppet::SSL::CertificateAuthority.stubs(:new).returns ca ++ Puppet::SSL::CertificateAuthority.stubs(:instance).returns ca ++ end ++ + it "should have a ca-location option" do + subject.should be_option :ca_location + end + + it "should set the ca location when invoked" do +- Puppet::SSL::Host.expects(:ca_location=).with(:foo) +- Puppet::SSL::Host.indirection.expects(:save) +- subject.sign "hello, friend", :ca_location => :foo ++ Puppet::SSL::Host.expects(:ca_location=).with(:local) ++ ca.expects(:sign).with do |name,options| ++ name == "hello, friend" ++ end ++ ++ subject.sign "hello, friend", :ca_location => :local + end + + it "(#7059) should set the ca location when an inherited action is invoked" do +@@ -20,4 +39,152 @@ + subject.indirection.expects(:find) + subject.find "hello, friend", :ca_location => :foo + end ++ ++ describe "#generate" do ++ let(:options) { {:ca_location => 'local'} } ++ let(:host) { Puppet::SSL::Host.new(hostname) } ++ let(:csr) { host.certificate_request } ++ ++ describe "for the current host" do ++ let(:hostname) { Puppet[:certname] } ++ ++ it "should generate a CSR for this host" do ++ subject.generate(hostname, options) ++ ++ csr.content.subject.to_s.should == "/CN=#{Puppet[:certname]}" ++ csr.name.should == Puppet[:certname] ++ end ++ ++ it "should add dns_alt_names from the global config if not otherwise specified" do ++ Puppet[:dns_alt_names] = 'from,the,config' ++ ++ subject.generate(hostname, options) ++ ++ expected = %W[DNS:from DNS:the DNS:config DNS:#{hostname}] ++ ++ csr.subject_alt_names.should =~ expected ++ end ++ ++ it "should add the provided dns_alt_names if they are specified" do ++ Puppet[:dns_alt_names] = 'from,the,config' ++ ++ subject.generate(hostname, options.merge(:dns_alt_names => 'explicit,alt,names')) ++ ++ expected = %W[DNS:explicit DNS:alt DNS:names DNS:#{hostname}] ++ ++ csr.subject_alt_names.should =~ expected ++ end ++ end ++ ++ describe "for another host" do ++ let(:hostname) { Puppet[:certname] + 'different' } ++ ++ it "should generate a CSR for the specified host" do ++ subject.generate(hostname, options) ++ ++ csr.content.subject.to_s.should == "/CN=#{hostname}" ++ csr.name.should == hostname ++ end ++ ++ it "should fail if a CSR already exists for the host" do ++ subject.generate(hostname, options) ++ ++ expect do ++ subject.generate(hostname, options) ++ end.to raise_error(RuntimeError, /#{hostname} already has a requested certificate; ignoring certificate request/) ++ end ++ ++ it "should add not dns_alt_names from the config file" do ++ Puppet[:dns_alt_names] = 'from,the,config' ++ ++ subject.generate(hostname, options) ++ ++ csr.subject_alt_names.should be_empty ++ end ++ ++ it "should add the provided dns_alt_names if they are specified" do ++ Puppet[:dns_alt_names] = 'from,the,config' ++ ++ subject.generate(hostname, options.merge(:dns_alt_names => 'explicit,alt,names')) ++ ++ expected = %W[DNS:explicit DNS:alt DNS:names DNS:#{hostname}] ++ ++ csr.subject_alt_names.should =~ expected ++ end ++ end ++ end ++ ++ describe "#sign" do ++ let(:options) { {:ca_location => 'local'} } ++ let(:host) { Puppet::SSL::Host.new(hostname) } ++ let(:hostname) { "foobar" } ++ ++ it "should sign the certificate request if one is waiting" do ++ subject.generate(hostname, options) ++ ++ subject.sign(hostname, options) ++ ++ host.certificate_request.should be_nil ++ host.certificate.should be_a(Puppet::SSL::Certificate) ++ host.state.should == 'signed' ++ end ++ ++ it "should fail if there is no waiting certificate request" do ++ expect do ++ subject.sign(hostname, options) ++ end.to raise_error(ArgumentError, /Could not find certificate request for #{hostname}/) ++ end ++ ++ describe "when ca_location is local" do ++ describe "when the request has dns alt names" do ++ before :each do ++ subject.generate(hostname, options.merge(:dns_alt_names => 'some,alt,names')) ++ end ++ ++ it "should refuse to sign the request if allow_dns_alt_names is not set" do ++ expect do ++ subject.sign(hostname, options) ++ end.to raise_error(Puppet::SSL::CertificateAuthority::CertificateSigningError, ++ /CSR '#{hostname}' contains subject alternative names \(.*?\), which are disallowed. Use `puppet cert --allow-dns-alt-names sign #{hostname}` to sign this request./i) ++ ++ host.state.should == 'requested' ++ end ++ ++ it "should sign the request if allow_dns_alt_names is set" do ++ expect do ++ subject.sign(hostname, options.merge(:allow_dns_alt_names => true)) ++ end.not_to raise_error ++ ++ host.state.should == 'signed' ++ end ++ end ++ ++ describe "when the request has no dns alt names" do ++ before :each do ++ subject.generate(hostname, options) ++ end ++ ++ it "should sign the request if allow_dns_alt_names is set" do ++ expect { subject.sign(hostname, options.merge(:allow_dns_alt_names => true)) }.not_to raise_error ++ ++ host.state.should == 'signed' ++ end ++ ++ it "should sign the request if allow_dns_alt_names is not set" do ++ expect { subject.sign(hostname, options) }.not_to raise_error ++ ++ host.state.should == 'signed' ++ end ++ end ++ end ++ ++ describe "when ca_location is remote" do ++ let(:options) { {:ca_location => :remote} } ++ it "should fail if allow-dns-alt-names is specified" do ++ expect do ++ subject.sign(hostname, options.merge(:allow_dns_alt_names => true)) ++ end ++ end ++ end ++ end + end +Index: puppet-2.7.1/spec/unit/indirector/certificate_request/ca_spec.rb +=================================================================== +--- puppet-2.7.1.orig/spec/unit/indirector/certificate_request/ca_spec.rb 2011-10-23 13:05:43.536903805 -0400 ++++ puppet-2.7.1/spec/unit/indirector/certificate_request/ca_spec.rb 2011-10-23 13:05:46.396903779 -0400 +@@ -6,8 +6,6 @@ + require 'spec_helper' + + require 'puppet/ssl/host' +-require 'puppet/sslcertificates' +-require 'puppet/sslcertificates/ca' + require 'puppet/indirector/certificate_request/ca' + + describe Puppet::SSL::CertificateRequest::Ca do +@@ -18,7 +16,6 @@ + + Puppet::SSL::Host.ca_location = :local + Puppet[:localcacert] = Puppet[:cacert] +- Puppet::SSLCertificates::CA.new.mkrootcert + + @ca = Puppet::SSL::CertificateAuthority.new + end +Index: puppet-2.7.1/spec/unit/network/client_spec.rb +=================================================================== +--- puppet-2.7.1.orig/spec/unit/network/client_spec.rb 2011-10-23 13:05:43.404903806 -0400 ++++ /dev/null 1970-01-01 00:00:00.000000000 +0000 +@@ -1,45 +0,0 @@ +-#!/usr/bin/env rspec +-# +-# Created by Luke Kanies on 2008-3-24. +-# Copyright (c) 2008. All rights reserved. +- +-require 'spec_helper' +- +-require 'puppet/network/client' +- +-describe Puppet::Network::Client do +- before do +- Puppet.settings.stubs(:use).returns(true) +- Puppet::Network::HttpPool.stubs(:cert_setup) +- end +- +- describe "when keep-alive is enabled" do +- before do +- Puppet::Network::HttpPool.stubs(:keep_alive?).returns true +- end +- it "should start the http client up on creation" do +- http = mock 'http' +- http.stub_everything +- http.expects(:start) +- Net::HTTP.stubs(:new).returns http +- +- # Pick a random subclass... +- Puppet::Network::Client.runner.new :Server => Puppet[:server] +- end +- end +- +- describe "when keep-alive is disabled" do +- before do +- Puppet::Network::HttpPool.stubs(:keep_alive?).returns false +- end +- it "should not start the http client up on creation" do +- http = mock 'http' +- http.stub_everything +- http.expects(:start).never +- Net::HTTP.stubs(:new).returns http +- +- # Pick a random subclass... +- Puppet::Network::Client.runner.new :Server => Puppet[:server] +- end +- end +-end +Index: puppet-2.7.1/spec/unit/network/handler/ca_spec.rb +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ puppet-2.7.1/spec/unit/network/handler/ca_spec.rb 2011-10-23 13:05:46.396903779 -0400 +@@ -0,0 +1,86 @@ ++require 'spec_helper' ++ ++require 'puppet/network/handler/ca' ++ ++describe Puppet::Network::Handler::CA do ++ include PuppetSpec::Files ++ ++ describe "#getcert" do ++ let(:host) { "testhost" } ++ let(:x509_name) { OpenSSL::X509::Name.new [['CN', host]] } ++ let(:key) { Puppet::SSL::Key.new(host).generate } ++ ++ let(:csr) do ++ csr = OpenSSL::X509::Request.new ++ csr.subject = x509_name ++ csr.public_key = key.public_key ++ csr ++ end ++ ++ let(:ca) { Puppet::SSL::CertificateAuthority.new } ++ let(:cacert) { ca.instance_variable_get(:@certificate) } ++ ++ before :each do ++ Puppet[:confdir] = tmpdir('conf') ++ ++ Puppet::SSL::CertificateAuthority.stubs(:ca?).returns true ++ Puppet::SSL::CertificateAuthority.stubs(:singleton_instance).returns ca ++ end ++ ++ it "should do nothing if the master is not a CA" do ++ Puppet::SSL::CertificateAuthority.stubs(:ca?).returns false ++ ++ csr = OpenSSL::X509::Request.new ++ subject.getcert(csr.to_pem).should == '' ++ end ++ ++ describe "when a certificate already exists for the host" do ++ let!(:cert) { ca.generate(host) } ++ ++ it "should return the existing cert if it matches the public key of the CSR" do ++ csr.public_key = cert.content.public_key ++ ++ subject.getcert(csr.to_pem).should == [cert.to_s, cacert.to_s] ++ end ++ ++ it "should fail if the public key of the CSR does not match the existing cert" do ++ expect do ++ subject.getcert(csr.to_pem) ++ end.to raise_error(Puppet::Error, /Certificate request does not match existing certificate/) ++ end ++ end ++ ++ describe "when autosign is enabled" do ++ before :each do ++ Puppet[:autosign] = true ++ end ++ ++ it "should return the new cert and the CA cert" do ++ cert_str, cacert_str = subject.getcert(csr.to_pem) ++ ++ returned_cert = Puppet::SSL::Certificate.from_s(cert_str) ++ returned_cacert = Puppet::SSL::Certificate.from_s(cacert_str) ++ ++ returned_cert.name.should == host ++ returned_cacert.content.subject.cmp(cacert.content.subject).should == 0 ++ end ++ end ++ ++ describe "when autosign is disabled" do ++ before :each do ++ Puppet[:autosign] = false ++ end ++ ++ it "should save the CSR without signing it" do ++ subject.getcert(csr.to_pem) ++ ++ Puppet::SSL::Certificate.indirection.find(host).should be_nil ++ Puppet::SSL::CertificateRequest.indirection.find(host).should be_a(Puppet::SSL::CertificateRequest) ++ end ++ ++ it "should not return a cert" do ++ subject.getcert(csr.to_pem).should be_nil ++ end ++ end ++ end ++end +Index: puppet-2.7.1/spec/unit/network/xmlrpc/client_spec.rb +=================================================================== +--- puppet-2.7.1.orig/spec/unit/network/xmlrpc/client_spec.rb 2011-10-23 13:05:43.420903806 -0400 ++++ /dev/null 1970-01-01 00:00:00.000000000 +0000 +@@ -1,172 +0,0 @@ +-#!/usr/bin/env rspec +-require 'puppet/network/client' +- +-require 'spec_helper' +- +-describe Puppet::Network::XMLRPCClient do +- describe "when performing the rpc call" do +- before do +- Puppet::SSL::Host.any_instance.stubs(:certificate_matches_key?).returns true +- @client = Puppet::Network::Client.report.xmlrpc_client.new +- @client.stubs(:call).returns "foo" +- end +- +- it "should call the specified namespace and method, with the specified arguments" do +- @client.expects(:call).with("puppetreports.report", "eh").returns "foo" +- @client.report("eh") +- end +- +- it "should return the results from the call" do +- @client.expects(:call).returns "foo" +- @client.report("eh").should == "foo" +- end +- +- it "should always close the http connection if it is still open after the call" do +- http = mock 'http' +- @client.stubs(:http).returns http +- +- http.expects(:started?).returns true +- http.expects(:finish) +- +- @client.report("eh").should == "foo" +- end +- +- it "should always close the http connection if it is still open after a call that raises an exception" do +- http = mock 'http' +- @client.stubs(:http).returns http +- +- @client.expects(:call).raises RuntimeError +- +- http.expects(:started?).returns true +- http.expects(:finish) +- +- lambda { @client.report("eh") }.should raise_error +- end +- +- describe "when returning the http instance" do +- it "should use the http pool to create the instance" do +- @client.instance_variable_set("@http", nil) +- @client.expects(:host).returns "myhost" +- @client.expects(:port).returns "myport" +- Puppet::Network::HttpPool.expects(:http_instance).with("myhost", "myport", true).returns "http" +- +- @client.http.should == "http" +- end +- +- it "should reuse existing instances" do +- @client.http.should equal(@client.http) +- end +- end +- +- describe "when recycling the connection" do +- it "should close the existing instance if it's open" do +- http = mock 'http' +- @client.stubs(:http).returns http +- +- http.expects(:started?).returns true +- http.expects(:finish) +- +- @client.recycle_connection +- end +- +- it "should force creation of a new instance" do +- Puppet::Network::HttpPool.expects(:http_instance).returns "second_http" +- +- @client.recycle_connection +- +- @client.http.should == "second_http" +- end +- end +- +- describe "and an exception is raised" do +- it "should raise XMLRPCClientError if XMLRPC::FaultException is raised" do +- error = XMLRPC::FaultException.new("foo", "bar") +- +- @client.expects(:call).raises(error) +- +- lambda { @client.report("eh") }.should raise_error(Puppet::Network::XMLRPCClientError) +- end +- +- it "should raise XMLRPCClientError if Errno::ECONNREFUSED is raised" do +- @client.expects(:call).raises(Errno::ECONNREFUSED) +- +- lambda { @client.report("eh") }.should raise_error(Puppet::Network::XMLRPCClientError) +- end +- +- it "should log and raise XMLRPCClientError if Timeout::Error is raised" do +- Puppet.expects(:err) +- @client.expects(:call).raises(Timeout::Error) +- +- lambda { @client.report("eh") }.should raise_error(Puppet::Network::XMLRPCClientError) +- end +- +- it "should log and raise XMLRPCClientError if SocketError is raised" do +- Puppet.expects(:err) +- @client.expects(:call).raises(SocketError) +- +- lambda { @client.report("eh") }.should raise_error(Puppet::Network::XMLRPCClientError) +- end +- +- it "should log, recycle the connection, and retry if Errno::EPIPE is raised" do +- @client.expects(:call).times(2).raises(Errno::EPIPE).then.returns "eh" +- +- Puppet.expects(:info) +- @client.expects(:recycle_connection) +- +- @client.report("eh") +- end +- +- it "should log, recycle the connection, and retry if EOFError is raised" do +- @client.expects(:call).times(2).raises(EOFError).then.returns "eh" +- +- Puppet.expects(:info) +- @client.expects(:recycle_connection) +- +- @client.report("eh") +- end +- +- it "should log and retry if an exception containing 'Wrong size' is raised" do +- error = RuntimeError.new("Wrong size. Was 15, should be 30") +- @client.expects(:call).times(2).raises(error).then.returns "eh" +- +- Puppet.expects(:warning) +- +- @client.report("eh") +- end +- +- it "should raise XMLRPCClientError if OpenSSL::SSL::SSLError is raised" do +- @client.expects(:call).raises(OpenSSL::SSL::SSLError) +- +- lambda { @client.report("eh") }.should raise_error(Puppet::Network::XMLRPCClientError) +- end +- +- it "should log and raise XMLRPCClientError if OpenSSL::SSL::SSLError is raised with certificate issues" do +- error = OpenSSL::SSL::SSLError.new("hostname was not match") +- @client.expects(:call).raises(error) +- +- Puppet.expects(:warning) +- +- lambda { @client.report("eh") }.should raise_error(Puppet::Network::XMLRPCClientError) +- end +- +- it "should log, recycle the connection, and retry if OpenSSL::SSL::SSLError is raised containing 'bad write retry'" do +- error = OpenSSL::SSL::SSLError.new("bad write retry") +- @client.expects(:call).times(2).raises(error).then.returns "eh" +- +- @client.expects(:recycle_connection) +- +- Puppet.expects(:warning) +- +- @client.report("eh") +- end +- +- it "should log and raise XMLRPCClientError if any other exception is raised" do +- @client.expects(:call).raises(RuntimeError) +- +- Puppet.expects(:err) +- +- lambda { @client.report("eh") }.should raise_error(Puppet::Network::XMLRPCClientError) +- end +- end +- end +-end +Index: puppet-2.7.1/spec/unit/ssl/certificate_authority/interface_spec.rb +=================================================================== +--- puppet-2.7.1.orig/spec/unit/ssl/certificate_authority/interface_spec.rb 2011-10-23 13:05:43.480903807 -0400 ++++ puppet-2.7.1/spec/unit/ssl/certificate_authority/interface_spec.rb 2011-10-23 13:05:46.396903779 -0400 +@@ -31,13 +31,13 @@ + end + describe "when initializing" do + it "should set its method using its settor" do +- @class.any_instance.expects(:method=).with(:generate) +- @class.new(:generate, :to => :all) ++ instance = @class.new(:generate, :to => :all) ++ instance.method.should == :generate + end + + it "should set its subjects using the settor" do +- @class.any_instance.expects(:subjects=).with(:all) +- @class.new(:generate, :to => :all) ++ instance = @class.new(:generate, :to => :all) ++ instance.subjects.should == :all + end + + it "should set the digest if given" do +@@ -53,23 +53,27 @@ + + describe "when setting the method" do + it "should set the method" do +- @class.new(:generate, :to => :all).method.should == :generate ++ instance = @class.new(:generate, :to => :all) ++ instance.method = :list ++ ++ instance.method.should == :list + end + + it "should fail if the method isn't a member of the INTERFACE_METHODS array" do +- Puppet::SSL::CertificateAuthority::Interface::INTERFACE_METHODS.expects(:include?).with(:thing).returns false +- +- lambda { @class.new(:thing, :to => :all) }.should raise_error(ArgumentError) ++ lambda { @class.new(:thing, :to => :all) }.should raise_error(ArgumentError, /Invalid method thing to apply/) + end + end + + describe "when setting the subjects" do + it "should set the subjects" do +- @class.new(:generate, :to => :all).subjects.should == :all ++ instance = @class.new(:generate, :to => :all) ++ instance.subjects = :signed ++ ++ instance.subjects.should == :signed + end + +- it "should fail if the subjects setting isn't :all or an array", :'fails_on_ruby_1.9.2' => true do +- lambda { @class.new(:generate, "other") }.should raise_error(ArgumentError) ++ it "should fail if the subjects setting isn't :all or an array" do ++ lambda { @class.new(:generate, :to => "other") }.should raise_error(ArgumentError, /Subjects must be an array or :all; not other/) + end + end + +@@ -117,8 +121,8 @@ + it "should call :generate on the CA for each host specified" do + @applier = @class.new(:generate, :to => %w{host1 host2}) + +- @ca.expects(:generate).with("host1") +- @ca.expects(:generate).with("host2") ++ @ca.expects(:generate).with("host1", {}) ++ @ca.expects(:generate).with("host2", {}) + + @applier.apply(@ca) + end +@@ -149,15 +153,24 @@ + + describe ":sign" do + describe "and an array of names was provided" do +- before do +- @applier = @class.new(:sign, :to => %w{host1 host2}) +- end ++ let(:applier) { @class.new(:sign, @options.merge(:to => %w{host1 host2})) } + + it "should sign the specified waiting certificate requests" do +- @ca.expects(:sign).with("host1") +- @ca.expects(:sign).with("host2") ++ @options = {:allow_dns_alt_names => false} + +- @applier.apply(@ca) ++ @ca.expects(:sign).with("host1", false) ++ @ca.expects(:sign).with("host2", false) ++ ++ applier.apply(@ca) ++ end ++ ++ it "should sign the certificate requests with alt names if specified" do ++ @options = {:allow_dns_alt_names => true} ++ ++ @ca.expects(:sign).with("host1", true) ++ @ca.expects(:sign).with("host2", true) ++ ++ applier.apply(@ca) + end + end + +@@ -165,8 +178,8 @@ + it "should sign all waiting certificate requests" do + @ca.stubs(:waiting?).returns(%w{cert1 cert2}) + +- @ca.expects(:sign).with("cert1") +- @ca.expects(:sign).with("cert2") ++ @ca.expects(:sign).with("cert1", nil) ++ @ca.expects(:sign).with("cert2", nil) + + @applier = @class.new(:sign, :to => :all) + @applier.apply(@ca) +@@ -182,63 +195,93 @@ + end + + describe ":list" do +- describe "and an empty array was provided" do +- it "should print a string containing all certificate requests" do +- @ca.expects(:waiting?).returns %w{host1 host2} +- @ca.stubs(:verify) ++ before :each do ++ @cert = Puppet::SSL::Certificate.new 'foo' ++ @csr = Puppet::SSL::CertificateRequest.new 'bar' ++ ++ @cert.stubs(:subject_alt_names).returns [] ++ @csr.stubs(:subject_alt_names).returns [] ++ ++ Puppet::SSL::Certificate.indirection.stubs(:find).returns @cert ++ Puppet::SSL::CertificateRequest.indirection.stubs(:find).returns @csr ++ ++ @ca.expects(:waiting?).returns %w{host1 host2 host3} ++ @ca.expects(:list).returns %w{host4 host5 host6} ++ @ca.stubs(:fingerprint).returns "fingerprint" ++ @ca.stubs(:verify) ++ end + +- @applier = @class.new(:list, :to => []) ++ describe "and an empty array was provided" do ++ it "should print all certificate requests" do ++ applier = @class.new(:list, :to => []) + +- @applier.expects(:puts).with "host1\nhost2" ++ applier.expects(:puts).with(<<-OUTPUT.chomp) ++ host1 (fingerprint) ++ host2 (fingerprint) ++ host3 (fingerprint) ++ OUTPUT + +- @applier.apply(@ca) ++ applier.apply(@ca) + end + end + + describe "and :all was provided" do + it "should print a string containing all certificate requests and certificates" do +- @ca.expects(:waiting?).returns %w{host1 host2} +- @ca.expects(:list).returns %w{host3 host4} +- @ca.stubs(:verify) +- @ca.stubs(:fingerprint).returns "fingerprint" +- @ca.expects(:verify).with("host3").raises(Puppet::SSL::CertificateAuthority::CertificateVerificationError.new(23), "certificate revoked") +- +- @applier = @class.new(:list, :to => :all) +- +- @applier.expects(:puts).with "host1 (fingerprint)" +- @applier.expects(:puts).with "host2 (fingerprint)" +- @applier.expects(:puts).with "- host3 (fingerprint) (certificate revoked)" +- @applier.expects(:puts).with "+ host4 (fingerprint)" ++ @ca.stubs(:verify).with("host4").raises(Puppet::SSL::CertificateAuthority::CertificateVerificationError.new(23), "certificate revoked") + +- @applier.apply(@ca) ++ applier = @class.new(:list, :to => :all) ++ ++ applier.expects(:puts).with(<<-OUTPUT.chomp) ++ host1 (fingerprint) ++ host2 (fingerprint) ++ host3 (fingerprint) +++ host5 (fingerprint) +++ host6 (fingerprint) ++- host4 (fingerprint) (certificate revoked) ++ OUTPUT ++ ++ applier.apply(@ca) + end + end + + describe "and :signed was provided" do + it "should print a string containing all signed certificate requests and certificates" do +- @ca.expects(:list).returns %w{host1 host2} ++ applier = @class.new(:list, :to => :signed) + +- @applier = @class.new(:list, :to => :signed) ++ applier.expects(:puts).with(<<-OUTPUT.chomp) +++ host4 (fingerprint) +++ host5 (fingerprint) +++ host6 (fingerprint) ++ OUTPUT + +- @applier.apply(@ca) ++ applier.apply(@ca) ++ end ++ ++ it "should include subject alt names if they are on the certificate request" do ++ @csr.stubs(:subject_alt_names).returns ["DNS:foo", "DNS:bar"] ++ ++ applier = @class.new(:list, :to => ['host1']) ++ ++ applier.expects(:puts).with(<<-OUTPUT.chomp) ++ host1 (fingerprint) (alt names: DNS:foo, DNS:bar) ++ OUTPUT ++ ++ applier.apply(@ca) + end + end + + describe "and an array of names was provided" do +- it "should print a string of all named hosts that have a waiting request" do +- @ca.expects(:waiting?).returns %w{host1 host2} +- @ca.expects(:list).returns %w{host3 host4} +- @ca.stubs(:fingerprint).returns "fingerprint" +- @ca.stubs(:verify) +- +- @applier = @class.new(:list, :to => %w{host1 host2 host3 host4}) +- +- @applier.expects(:puts).with "host1 (fingerprint)" +- @applier.expects(:puts).with "host2 (fingerprint)" +- @applier.expects(:puts).with "+ host3 (fingerprint)" +- @applier.expects(:puts).with "+ host4 (fingerprint)" ++ it "should print all named hosts" do ++ applier = @class.new(:list, :to => %w{host1 host2 host4 host5}) + +- @applier.apply(@ca) ++ applier.expects(:puts).with(<<-OUTPUT.chomp) ++ host1 (fingerprint) ++ host2 (fingerprint) +++ host4 (fingerprint) +++ host5 (fingerprint) ++ OUTPUT ++ ++ applier.apply(@ca) + end + end + end +Index: puppet-2.7.1/spec/unit/ssl/certificate_authority_spec.rb +=================================================================== +--- puppet-2.7.1.orig/spec/unit/ssl/certificate_authority_spec.rb 2011-10-23 13:05:43.452903806 -0400 ++++ puppet-2.7.1/spec/unit/ssl/certificate_authority_spec.rb 2011-10-23 13:05:46.396903779 -0400 +@@ -199,8 +199,9 @@ + request = mock 'request' + Puppet::SSL::CertificateRequest.expects(:new).with(@ca.host.name).returns request + request.expects(:generate).with(@ca.host.key) ++ request.stubs(:request_extensions => []) + +- @ca.expects(:sign).with(@host.name, :ca, request) ++ @ca.expects(:sign).with(@host.name, false, request) + + @ca.stubs :generate_password + +@@ -243,10 +244,10 @@ + Puppet::SSL::Certificate.indirection.stubs(:save) + + # Stub out the factory +- @factory = stub 'factory', :result => "my real cert" +- Puppet::SSL::CertificateFactory.stubs(:new).returns @factory ++ Puppet::SSL::CertificateFactory.stubs(:build).returns "my real cert" + +- @request = stub 'request', :content => "myrequest", :name => @name ++ @request_content = stub "request content stub", :subject => @name ++ @request = stub 'request', :name => @name, :request_extensions => [], :subject_alt_names => [], :content => @request_content + + # And the inventory + @inventory = stub 'inventory', :add => nil +@@ -297,37 +298,45 @@ + it "should not look up a certificate request for the host" do + Puppet::SSL::CertificateRequest.indirection.expects(:find).never + +- @ca.sign(@name, :ca, @request) ++ @ca.sign(@name, true, @request) + end + + it "should use a certificate type of :ca" do +- Puppet::SSL::CertificateFactory.expects(:new).with do |*args| ++ Puppet::SSL::CertificateFactory.expects(:build).with do |*args| + args[0] == :ca +- end.returns @factory ++ end.returns "my real cert" + @ca.sign(@name, :ca, @request) + end + + it "should pass the provided CSR as the CSR" do +- Puppet::SSL::CertificateFactory.expects(:new).with do |*args| +- args[1] == "myrequest" +- end.returns @factory ++ Puppet::SSL::CertificateFactory.expects(:build).with do |*args| ++ args[1] == @request ++ end.returns "my real cert" + @ca.sign(@name, :ca, @request) + end + + it "should use the provided CSR's content as the issuer" do +- Puppet::SSL::CertificateFactory.expects(:new).with do |*args| +- args[2] == "myrequest" +- end.returns @factory ++ Puppet::SSL::CertificateFactory.expects(:build).with do |*args| ++ args[2].subject == "myhost" ++ end.returns "my real cert" + @ca.sign(@name, :ca, @request) + end + + it "should pass the next serial as the serial number" do +- Puppet::SSL::CertificateFactory.expects(:new).with do |*args| ++ Puppet::SSL::CertificateFactory.expects(:build).with do |*args| + args[3] == @serial +- end.returns @factory ++ end.returns "my real cert" + @ca.sign(@name, :ca, @request) + end + ++ it "should sign the certificate request even if it contains alt names" do ++ @request.stubs(:subject_alt_names).returns %w[DNS:foo DNS:bar DNS:baz] ++ ++ expect do ++ @ca.sign(@name, false, @request) ++ end.should_not raise_error(Puppet::SSL::CertificateAuthority::CertificateSigningError) ++ end ++ + it "should save the resulting certificate" do + Puppet::SSL::Certificate.indirection.expects(:save).with(@cert) + +@@ -345,9 +354,9 @@ + end + + it "should use a certificate type of :server" do +- Puppet::SSL::CertificateFactory.expects(:new).with do |*args| ++ Puppet::SSL::CertificateFactory.expects(:build).with do |*args| + args[0] == :server +- end.returns @factory ++ end.returns "my real cert" + + @ca.sign(@name) + end +@@ -364,17 +373,45 @@ + lambda { @ca.sign(@name) }.should raise_error(ArgumentError) + end + ++ it "should fail if an unknown request extension is present" do ++ @request.stubs :request_extensions => [{ "oid" => "bananas", ++ "value" => "delicious" }] ++ expect { @ca.sign(@name) }. ++ should raise_error(/CSR has request extensions that are not permitted/) ++ end ++ ++ it "should fail if the CSR contains alt names and they are not expected" do ++ @request.stubs(:subject_alt_names).returns %w[DNS:foo DNS:bar DNS:baz] ++ ++ expect do ++ @ca.sign(@name, false) ++ end.to raise_error(Puppet::SSL::CertificateAuthority::CertificateSigningError, /CSR '#{@name}' contains subject alternative names \(.*?\), which are disallowed. Use `puppet cert --allow-dns-alt-names sign #{@name}` to sign this request./) ++ end ++ ++ it "should not fail if the CSR does not contain alt names and they are expected" do ++ @request.stubs(:subject_alt_names).returns [] ++ expect { @ca.sign(@name, true) }.should_not raise_error ++ end ++ ++ it "should reject alt names by default" do ++ @request.stubs(:subject_alt_names).returns %w[DNS:foo DNS:bar DNS:baz] ++ ++ expect do ++ @ca.sign(@name) ++ end.to raise_error(Puppet::SSL::CertificateAuthority::CertificateSigningError, /CSR '#{@name}' contains subject alternative names \(.*?\), which are disallowed. Use `puppet cert --allow-dns-alt-names sign #{@name}` to sign this request./) ++ end ++ + it "should use the CA certificate as the issuer" do +- Puppet::SSL::CertificateFactory.expects(:new).with do |*args| ++ Puppet::SSL::CertificateFactory.expects(:build).with do |*args| + args[2] == @cacert.content +- end.returns @factory ++ end.returns "my real cert" + @ca.sign(@name) + end + + it "should pass the next serial as the serial number" do +- Puppet::SSL::CertificateFactory.expects(:new).with do |*args| ++ Puppet::SSL::CertificateFactory.expects(:build).with do |*args| + args[3] == @serial +- end.returns @factory ++ end.returns "my real cert" + @ca.sign(@name) + end + +@@ -399,6 +436,80 @@ + + @ca.sign(@name) + end ++ ++ it "should check the internal signing policies" do ++ @ca.expects(:check_internal_signing_policies).returns true ++ @ca.sign(@name) ++ end ++ end ++ ++ context "#check_internal_signing_policies" do ++ before do ++ @serial = 10 ++ @ca.stubs(:next_serial).returns @serial ++ ++ Puppet::SSL::CertificateRequest.indirection.stubs(:find).with(@name).returns @request ++ @cert.stubs :save ++ end ++ ++ it "should reject a critical extension that isn't on the whitelist" do ++ @request.stubs(:request_extensions).returns [{ "oid" => "banana", ++ "value" => "yumm", ++ "critical" => true }] ++ expect { @ca.sign(@name) }.to raise_error( ++ Puppet::SSL::CertificateAuthority::CertificateSigningError, ++ /request extensions that are not permitted/ ++ ) ++ end ++ ++ it "should reject a non-critical extension that isn't on the whitelist" do ++ @request.stubs(:request_extensions).returns [{ "oid" => "peach", ++ "value" => "meh", ++ "critical" => false }] ++ expect { @ca.sign(@name) }.to raise_error( ++ Puppet::SSL::CertificateAuthority::CertificateSigningError, ++ /request extensions that are not permitted/ ++ ) ++ end ++ ++ it "should reject non-whitelist extensions even if a valid extension is present" do ++ @request.stubs(:request_extensions).returns [{ "oid" => "peach", ++ "value" => "meh", ++ "critical" => false }, ++ { "oid" => "subjectAltName", ++ "value" => "DNS:foo", ++ "critical" => true }] ++ expect { @ca.sign(@name) }.to raise_error( ++ Puppet::SSL::CertificateAuthority::CertificateSigningError, ++ /request extensions that are not permitted/ ++ ) ++ end ++ ++ it "should reject a subjectAltName for a non-DNS value" do ++ @request.stubs(:subject_alt_names).returns ['DNS:foo', 'email:bar@example.com'] ++ expect { @ca.sign(@name, true) }.to raise_error( ++ Puppet::SSL::CertificateAuthority::CertificateSigningError, ++ /subjectAltName outside the DNS label space/ ++ ) ++ end ++ ++ it "should reject a wildcard subject" do ++ @request.content.stubs(:subject). ++ returns(OpenSSL::X509::Name.new([["CN", "*.local"]])) ++ ++ expect { @ca.sign(@name) }.to raise_error( ++ Puppet::SSL::CertificateAuthority::CertificateSigningError, ++ /subject contains a wildcard/ ++ ) ++ end ++ ++ it "should reject a wildcard subjectAltName" do ++ @request.stubs(:subject_alt_names).returns ['DNS:foo', 'DNS:*.bar'] ++ expect { @ca.sign(@name, true) }.to raise_error( ++ Puppet::SSL::CertificateAuthority::CertificateSigningError, ++ /subjectAltName contains a wildcard/ ++ ) ++ end + end + + it "should create a certificate instance with the content set to the newly signed x509 certificate" do +@@ -763,8 +874,7 @@ + end + + it "should sign the generated request" do +- @ca.expects(:sign).with("him") +- ++ @ca.expects(:sign).with("him", false) + @ca.generate("him") + end + end +Index: puppet-2.7.1/spec/unit/ssl/certificate_factory_spec.rb +=================================================================== +--- puppet-2.7.1.orig/spec/unit/ssl/certificate_factory_spec.rb 2011-10-23 13:05:43.464903807 -0400 ++++ puppet-2.7.1/spec/unit/ssl/certificate_factory_spec.rb 2011-10-23 13:05:46.396903779 -0400 +@@ -4,103 +4,123 @@ + require 'puppet/ssl/certificate_factory' + + describe Puppet::SSL::CertificateFactory do +- before do +- @cert_type = mock 'cert_type' +- @name = mock 'name' +- @csr = stub 'csr', :subject => @name +- @issuer = mock 'issuer' +- @serial = mock 'serial' +- +- @factory = Puppet::SSL::CertificateFactory.new(@cert_type, @csr, @issuer, @serial) ++ let :serial do OpenSSL::BN.new('12') end ++ let :name do "example.local" end ++ let :x509_name do OpenSSL::X509::Name.new([['CN', name]]) end ++ let :key do Puppet::SSL::Key.new(name).generate end ++ let :csr do ++ csr = Puppet::SSL::CertificateRequest.new(name) ++ csr.generate(key) ++ csr + end +- +- describe "when initializing" do +- it "should set its :cert_type to its first argument" do +- @factory.cert_type.should equal(@cert_type) +- end +- +- it "should set its :csr to its second argument" do +- @factory.csr.should equal(@csr) +- end +- +- it "should set its :issuer to its third argument" do +- @factory.issuer.should equal(@issuer) +- end +- +- it "should set its :serial to its fourth argument" do +- @factory.serial.should equal(@serial) +- end +- +- it "should set its name to the subject of the csr" do +- @factory.name.should equal(@name) +- end ++ let :issuer do ++ cert = OpenSSL::X509::Certificate.new ++ cert.subject = OpenSSL::X509::Name.new([["CN", 'issuer.local']]) ++ cert + end + + describe "when generating the certificate" do +- before do +- @cert = mock 'cert' +- +- @cert.stub_everything +- +- @factory.stubs :build_extensions +- +- @factory.stubs :set_ttl +- +- @issuer_name = mock 'issuer_name' +- @issuer.stubs(:subject).returns @issuer_name +- +- @public_key = mock 'public_key' +- @csr.stubs(:public_key).returns @public_key +- +- OpenSSL::X509::Certificate.stubs(:new).returns @cert +- end +- + it "should return a new X509 certificate" do +- OpenSSL::X509::Certificate.expects(:new).returns @cert +- @factory.result.should equal(@cert) ++ subject.build(:server, csr, issuer, serial).should_not == ++ subject.build(:server, csr, issuer, serial) + end + + it "should set the certificate's version to 2" do +- @cert.expects(:version=).with 2 +- @factory.result ++ subject.build(:server, csr, issuer, serial).version.should == 2 + end + + it "should set the certificate's subject to the CSR's subject" do +- @cert.expects(:subject=).with @name +- @factory.result ++ cert = subject.build(:server, csr, issuer, serial) ++ cert.subject.should eql x509_name + end + + it "should set the certificate's issuer to the Issuer's subject" do +- @cert.expects(:issuer=).with @issuer_name +- @factory.result ++ cert = subject.build(:server, csr, issuer, serial) ++ cert.issuer.should eql issuer.subject + end + + it "should set the certificate's public key to the CSR's public key" do +- @cert.expects(:public_key=).with @public_key +- @factory.result ++ cert = subject.build(:server, csr, issuer, serial) ++ cert.public_key.should be_public ++ cert.public_key.to_s.should == csr.content.public_key.to_s + end + + it "should set the certificate's serial number to the provided serial number" do +- @cert.expects(:serial=).with @serial +- @factory.result ++ cert = subject.build(:server, csr, issuer, serial) ++ cert.serial.should == serial + end + +- it "should build extensions for the certificate" do +- @factory.expects(:build_extensions) +- @factory.result ++ it "should have 24 hours grace on the start of the cert" do ++ cert = subject.build(:server, csr, issuer, serial) ++ cert.not_before.should be_within(1).of(Time.now - 24*60*60) + end + +- it "should set the ttl of the certificate" do +- @factory.expects(:set_ttl) +- @factory.result ++ it "should set the default TTL of the certificate" do ++ ttl = Puppet::SSL::CertificateFactory.ttl ++ cert = subject.build(:server, csr, issuer, serial) ++ cert.not_after.should be_within(1).of(Time.now + ttl) + end +- end + +- describe "when building extensions" do +- it "should have tests" +- end ++ it "should respect a custom TTL for the CA" do ++ Puppet[:ca_ttl] = 12 ++ cert = subject.build(:server, csr, issuer, serial) ++ cert.not_after.should be_within(1).of(Time.now + 12) ++ end + +- describe "when setting the ttl" do +- it "should have tests" ++ it "should build extensions for the certificate" do ++ cert = subject.build(:server, csr, issuer, serial) ++ cert.extensions.map {|x| x.to_h }.find {|x| x["oid"] == "nsComment" }.should == ++ { "oid" => "nsComment", ++ "value" => "Puppet Ruby/OpenSSL Internal Certificate", ++ "critical" => false } ++ end ++ ++ # See #2848 for why we are doing this: we need to make sure that ++ # subjectAltName is set if the CSR has it, but *not* if it is set when the ++ # certificate is built! ++ it "should not add subjectAltNames from dns_alt_names" do ++ Puppet[:dns_alt_names] = 'one, two' ++ # Verify the CSR still has no extReq, just in case... ++ csr.request_extensions.should == [] ++ cert = subject.build(:server, csr, issuer, serial) ++ ++ cert.extensions.find {|x| x.oid == 'subjectAltName' }.should be_nil ++ end ++ ++ it "should add subjectAltName when the CSR requests them" do ++ Puppet[:dns_alt_names] = '' ++ ++ expect = %w{one two} + [name] ++ ++ csr = Puppet::SSL::CertificateRequest.new(name) ++ csr.generate(key, :dns_alt_names => expect.join(', ')) ++ ++ csr.request_extensions.should_not be_nil ++ csr.subject_alt_names.should =~ expect.map{|x| "DNS:#{x}"} ++ ++ cert = subject.build(:server, csr, issuer, serial) ++ san = cert.extensions.find {|x| x.oid == 'subjectAltName' } ++ san.should_not be_nil ++ expect.each do |name| ++ san.value.should =~ /DNS:#{name}\b/i ++ end ++ end ++ ++ # Can't check the CA here, since that requires way more infrastructure ++ # that I want to build up at this time. We can verify the critical ++ # values, though, which are non-CA certs. --daniel 2011-10-11 ++ { :ca => 'CA:TRUE', ++ :terminalsubca => ['CA:TRUE', 'pathlen:0'], ++ :server => 'CA:FALSE', ++ :ocsp => 'CA:FALSE', ++ :client => 'CA:FALSE', ++ }.each do |name, value| ++ it "should set basicConstraints for #{name} #{value.inspect}" do ++ cert = subject.build(name, csr, issuer, serial) ++ bc = cert.extensions.find {|x| x.oid == 'basicConstraints' } ++ bc.should be ++ bc.value.split(/\s*,\s*/).should =~ Array(value) ++ end ++ end + end + end +Index: puppet-2.7.1/spec/unit/ssl/certificate_request_spec.rb +=================================================================== +--- puppet-2.7.1.orig/spec/unit/ssl/certificate_request_spec.rb 2011-10-23 13:05:43.472903807 -0400 ++++ puppet-2.7.1/spec/unit/ssl/certificate_request_spec.rb 2011-10-24 07:45:31.964263047 -0400 +@@ -125,7 +125,7 @@ + + it "should set the CN to the :ca_name setting when the CSR is for a CA" do + subject = mock 'subject' +- Puppet.settings.expects(:value).with(:ca_name).returns "mycertname" ++ Puppet[:ca_name] = "mycertname" + OpenSSL::X509::Name.expects(:new).with { |subject| subject[0][1] == "mycertname" }.returns(subject) + @request.expects(:subject=).with(subject) + Puppet::SSL::CertificateRequest.new(Puppet::SSL::CA_NAME).generate(@key) +@@ -144,6 +144,67 @@ + @instance.generate(@key) + end + ++ context "without subjectAltName / dns_alt_names" do ++ before :each do ++ Puppet[:dns_alt_names] = "" ++ end ++ ++ ["extreq", "msExtReq"].each do |name| ++ it "should not add any #{name} attribute" do ++ @request.expects(:add_attribute).never ++ @request.expects(:attributes=).never ++ @instance.generate(@key) ++ end ++ ++ it "should return no subjectAltNames" do ++ @instance.generate(@key) ++ @instance.subject_alt_names.should be_empty ++ end ++ end ++ end ++ ++ context "with dns_alt_names" do ++ before :each do ++ Puppet[:dns_alt_names] = "one, two, three" ++ end ++ ++ ["extreq", "msExtReq"].each do |name| ++ it "should not add any #{name} attribute" do ++ @request.expects(:add_attribute).never ++ @request.expects(:attributes=).never ++ @instance.generate(@key) ++ end ++ ++ it "should return no subjectAltNames" do ++ @instance.generate(@key) ++ @instance.subject_alt_names.should be_empty ++ end ++ end ++ end ++ ++ context "with subjectAltName to generate request" do ++ before :each do ++ Puppet[:dns_alt_names] = "" ++ end ++ ++ it "should add an extreq attribute" do ++ @request.expects(:add_attribute).with do |arg| ++ arg.value.value.all? do |x| ++ x.value.all? do |y| ++ y.value[0].value == "subjectAltName" ++ end ++ end ++ end ++ ++ @instance.generate(@key, :dns_alt_names => 'one, two') ++ end ++ ++ it "should return the subjectAltName values" do ++ @instance.generate(@key, :dns_alt_names => 'one,two') ++ @instance.subject_alt_names.should =~ ["DNS:myname", "DNS:one", "DNS:two"] ++ end ++ end ++ + it "should sign the csr with the provided key and a digest" do + digest = mock 'digest' + OpenSSL::Digest::MD5.expects(:new).returns(digest) +@@ -194,6 +255,7 @@ + csr = Puppet::SSL::CertificateRequest.new("me") + terminus = mock 'terminus' + Puppet::SSL::CertificateRequest.indirection.expects(:prepare).returns(terminus) ++ Puppet::SSL::CertificateRequest.indirection.stubs(:cache?).returns false + terminus.expects(:save).with { |request| request.instance == csr && request.key == "me" } + + Puppet::SSL::CertificateRequest.indirection.save(csr) +@@ -207,6 +269,7 @@ + csr = Puppet::SSL::CertificateRequest.new("me") + terminus = mock 'terminus' + Puppet::SSL::CertificateRequest.indirection.expects(:prepare).returns(terminus) ++ Puppet::SSL::CertificateRequest.indirection.stubs(:cache?).returns false + terminus.expects(:save).with { |request| request.instance == csr && request.key == "me" } + + Puppet::SSL::CertificateRequest.indirection.save(csr) +Index: puppet-2.7.1/spec/unit/ssl/certificate_spec.rb +=================================================================== +--- puppet-2.7.1.orig/spec/unit/ssl/certificate_spec.rb 2011-10-23 13:05:43.500903805 -0400 ++++ puppet-2.7.1/spec/unit/ssl/certificate_spec.rb 2011-10-23 13:05:46.396903779 -0400 +@@ -89,6 +89,37 @@ + @certificate.should respond_to(:content) + end + ++ describe "#subject_alt_names" do ++ it "should list all alternate names when the extension is present" do ++ key = Puppet::SSL::Key.new('quux') ++ key.generate ++ ++ csr = Puppet::SSL::CertificateRequest.new('quux') ++ csr.generate(key, :dns_alt_names => 'foo, bar,baz') ++ ++ raw_csr = csr.content ++ ++ cert = Puppet::SSL::CertificateFactory.build('server', csr, raw_csr, 14) ++ certificate = @class.from_s(cert.to_pem) ++ certificate.subject_alt_names. ++ should =~ ['DNS:foo', 'DNS:bar', 'DNS:baz', 'DNS:quux'] ++ end ++ ++ it "should return an empty list of names if the extension is absent" do ++ key = Puppet::SSL::Key.new('quux') ++ key.generate ++ ++ csr = Puppet::SSL::CertificateRequest.new('quux') ++ csr.generate(key) ++ ++ raw_csr = csr.content ++ ++ cert = Puppet::SSL::CertificateFactory.build('client', csr, raw_csr, 14) ++ certificate = @class.from_s(cert.to_pem) ++ certificate.subject_alt_names.should be_empty ++ end ++ end ++ + it "should return a nil expiration if there is no actual certificate" do + @certificate.stubs(:content).returns nil + +Index: puppet-2.7.1/spec/unit/ssl/host_spec.rb +=================================================================== +--- puppet-2.7.1.orig/spec/unit/ssl/host_spec.rb 2011-10-23 13:05:43.492903806 -0400 ++++ puppet-2.7.1/spec/unit/ssl/host_spec.rb 2011-10-23 13:05:46.396903779 -0400 +@@ -2,10 +2,10 @@ + require 'spec_helper' + + require 'puppet/ssl/host' +-require 'puppet/sslcertificates' +-require 'puppet/sslcertificates/ca' + + describe Puppet::SSL::Host do ++ include PuppetSpec::Files ++ + before do + Puppet::SSL::Host.indirection.terminus_class = :file + @host = Puppet::SSL::Host.new("myname") +@@ -66,6 +66,48 @@ + Puppet::SSL::Host.localhost.should equal(host) + end + ++ it "should create a localhost cert if no cert is available and it is a CA with autosign and it is using DNS alt names" do ++ Puppet[:autosign] = true ++ Puppet[:confdir] = tmpdir('conf') ++ Puppet[:dns_alt_names] = "foo,bar,baz" ++ ca = Puppet::SSL::CertificateAuthority.new ++ Puppet::SSL::CertificateAuthority.stubs(:instance).returns ca ++ ++ localhost = Puppet::SSL::Host.localhost ++ cert = localhost.certificate ++ ++ cert.should be_a(Puppet::SSL::Certificate) ++ cert.subject_alt_names.should =~ %W[DNS:#{Puppet[:certname]} DNS:foo DNS:bar DNS:baz] ++ end ++ ++ context "with dns_alt_names" do ++ before :each do ++ Puppet[:dns_alt_names] = 'one, two' ++ ++ @key = stub('key content') ++ key = stub('key', :generate => true, :content => @key) ++ Puppet::SSL::Key.stubs(:new).returns key ++ Puppet::SSL::Key.indirection.stubs(:save).with(key) ++ ++ @cr = stub('certificate request') ++ Puppet::SSL::CertificateRequest.stubs(:new).returns @cr ++ Puppet::SSL::CertificateRequest.indirection.stubs(:save).with(@cr) ++ end ++ ++ it "should not include subjectAltName if not the local node" do ++ @cr.expects(:generate).with(@key, {}) ++ ++ Puppet::SSL::Host.new('not-the-' + Puppet[:certname]).generate ++ end ++ ++ it "should include subjectAltName if I am a CA" do ++ @cr.expects(:generate). ++ with(@key, { :dns_alt_names => Puppet[:dns_alt_names] }) ++ ++ Puppet::SSL::Host.localhost ++ end ++ end ++ + it "should always read the key for the localhost instance in from disk" do + host = stub 'host', :certificate => "eh" + Puppet::SSL::Host.expects(:new).returns host +@@ -382,7 +424,7 @@ + + key = stub 'key', :public_key => mock("public_key"), :content => "mycontent" + @host.stubs(:key).returns(key) +- @request.expects(:generate).with("mycontent") ++ @request.expects(:generate).with("mycontent", {}) + Puppet::SSL::CertificateRequest.indirection.expects(:save).with(@request) + + @host.generate_certificate_request.should be_true +@@ -574,7 +616,7 @@ + it "should use the CA to sign its certificate request if it does not have a certificate" do + @host.expects(:certificate).returns nil + +- @ca.expects(:sign).with(@host.name) ++ @ca.expects(:sign).with(@host.name, true) + + @host.generate + end +@@ -717,7 +759,6 @@ + before do + Puppet[:vardir] = tmpdir("ssl_test_vardir") + Puppet[:ssldir] = tmpdir("ssl_test_ssldir") +- Puppet::SSLCertificates::CA.new.mkrootcert + # localcacert is where each client stores the CA certificate + # cacert is where the master stores the CA certificate + # Since we need to play the role of both for testing we need them to be the same and exist +Index: puppet-2.7.1/spec/unit/sslcertificates/ca_spec.rb +=================================================================== +--- puppet-2.7.1.orig/spec/unit/sslcertificates/ca_spec.rb 2011-10-23 13:05:43.508903805 -0400 ++++ /dev/null 1970-01-01 00:00:00.000000000 +0000 +@@ -1,110 +0,0 @@ +-#!/usr/bin/env rspec +-require 'spec_helper' +- +-require 'puppet' +-require 'puppet/sslcertificates' +-require 'puppet/sslcertificates/ca' +- +-describe Puppet::SSLCertificates::CA do +- before :all do +- @hosts = %w{host.domain.com Other.Testing.Com} +- end +- +- before :each do +- Puppet::Util::SUIDManager.stubs(:asuser).yields +- file = Tempfile.new("ca_testing") +- @dir = file.path +- file.delete +- +- Puppet.settings[:confdir] = @dir +- Puppet.settings[:vardir] = @dir +- +- @ca = Puppet::SSLCertificates::CA.new +- end +- +- after :each do +- system("rm -rf #{@dir}") +- end +- +- describe 'when cleaning' do +- it 'should remove associated files' do +- dirs = [:csrdir, :signeddir, :publickeydir, :privatekeydir, :certdir] +- +- @hosts.each do |host| +- files = [] +- dirs.each do |dir| +- dir = Puppet[dir] +- +- # Case insensitivity is handled through downcasing +- file = File.join(dir, host.downcase + '.pem') +- +- File.open(file, "w") do |f| +- f.puts "testing" +- end +- +- files << file +- end +- +- lambda { @ca.clean(host) }.should_not raise_error +- +- files.reject {|f| ! File.exists?(f)}.should be_empty +- end +- end +- end +- +- describe 'when mapping hosts to files' do +- it 'should correctly return the certfile' do +- @hosts.each do |host| +- value = nil +- lambda { value = @ca.host2certfile host }.should_not raise_error +- +- File.join(Puppet[:signeddir], host.downcase + '.pem').should == value +- end +- end +- +- it 'should correctly return the csrfile' do +- @hosts.each do |host| +- value = nil +- lambda { value = @ca.host2csrfile host }.should_not raise_error +- +- File.join(Puppet[:csrdir], host.downcase + '.pem').should == value +- end +- end +- end +- +- describe 'when listing' do +- it 'should find all csr' do +- list = [] +- +- # Make some fake CSRs +- @hosts.each do |host| +- file = File.join(Puppet[:csrdir], host.downcase + '.pem') +- File.open(file, 'w') { |f| f.puts "yay" } +- list << host.downcase +- end +- +- @ca.list.sort.should == list.sort +- end +- end +- +- describe 'when creating a root certificate' do +- before :each do +- lambda { @ca.mkrootcert }.should_not raise_exception +- end +- +- it 'should store the public key' do +- File.exists?(Puppet[:capub]).should be_true +- end +- +- it 'should prepend "Puppet CA: " to the fqdn as the ca_name by default' do +- host_mock_fact = mock() +- host_mock_fact.expects(:value).returns('myhost') +- domain_mock_fact = mock() +- domain_mock_fact.expects(:value).returns('puppetlabs.lan') +- Facter.stubs(:[]).with('hostname').returns(host_mock_fact) +- Facter.stubs(:[]).with('domain').returns(domain_mock_fact) +- +- @ca.mkrootcert.name.should == 'Puppet CA: myhost.puppetlabs.lan' +- end +- end +-end +Index: puppet-2.7.1/spec/unit/util/settings_spec.rb +=================================================================== +--- puppet-2.7.1.orig/spec/unit/util/settings_spec.rb 2011-10-23 13:05:43.516903805 -0400 ++++ puppet-2.7.1/spec/unit/util/settings_spec.rb 2011-10-23 13:05:46.396903779 -0400 +@@ -128,6 +128,16 @@ + @settings[:myval].should == "" + end + ++ it "should flag settings from the CLI" do ++ @settings.handlearg("--myval") ++ @settings.setting(:myval).setbycli.should be_true ++ end ++ ++ it "should not flag settings memory" do ++ @settings[:myval] = "12" ++ @settings.setting(:myval).setbycli.should be_false ++ end ++ + it "should clear the cache when setting getopt-specific values" do + @settings.setdefaults :mysection, :one => ["whah", "yay"], :two => ["$one yay", "bah"] + @settings[:two].should == "whah yay" +Index: puppet-2.7.1/test/certmgr/certmgr.rb +=================================================================== +--- puppet-2.7.1.orig/test/certmgr/certmgr.rb 2011-10-23 13:05:43.232903808 -0400 ++++ /dev/null 1970-01-01 00:00:00.000000000 +0000 +@@ -1,308 +0,0 @@ +-#!/usr/bin/env ruby +- +-require File.expand_path(File.dirname(__FILE__) + '/../lib/puppettest') +- +-require 'puppet' +-require 'puppet/sslcertificates.rb' +-require 'puppettest' +-require 'puppettest/certificates' +-require 'mocha' +- +-class TestCertMgr < Test::Unit::TestCase +- include PuppetTest::Certificates +- def setup +- super +- #@dir = File.join(Puppet[:certdir], "testing") +- @dir = File.join(@configpath, "certest") +- system("mkdir -p #{@dir}") +- +- Puppet::Util::SUIDManager.stubs(:asuser).yields +- end +- +- def testCreateSelfSignedCertificate +- cert = nil +- name = "testing" +- newcert = proc { +- +- Puppet::SSLCertificates::Certificate.new( +- +- :name => name, +- +- :selfsign => true +- ) +- } +- assert_nothing_raised { +- cert = newcert.call +- } +- assert_nothing_raised { +- cert.mkselfsigned +- } +- +- assert_raise(Puppet::Error) { +- cert.mkselfsigned +- } +- +- assert_nothing_raised { +- cert.write +- } +- +- assert(FileTest.exists?(cert.certfile)) +- +- assert_nothing_raised { +- cert.delete +- } +- +- assert_nothing_raised { +- cert = newcert.call +- } +- assert_nothing_raised { +- cert.mkselfsigned +- } +- +- assert_nothing_raised { +- cert.delete +- } +- +- end +- +- def disabled_testCreateEncryptedSelfSignedCertificate +- cert = nil +- name = "testing" +- keyfile = mkPassFile +- assert_nothing_raised { +- +- cert = Puppet::SSLCertificates::Certificate.new( +- +- :name => name, +- :selfsign => true, +- +- :capass => keyfile +- ) +- } +- assert_nothing_raised { +- cert.mkselfsigned +- } +- assert_nothing_raised { +- cert.mkhash +- } +- +- assert_raise(Puppet::Error) { +- cert.mkselfsigned +- } +- +- assert(FileTest.exists?(cert.certfile)) +- assert(FileTest.exists?(cert.hash)) +- +- assert_nothing_raised { +- cert.delete +- } +- +- assert_nothing_raised { +- cert.mkselfsigned +- } +- +- assert_nothing_raised { +- cert.delete +- } +- +- end +- +- def testCreateCA +- ca = nil +- assert_nothing_raised { +- ca = Puppet::SSLCertificates::CA.new +- } +- +- # make the CA again and verify it doesn't fail because everything +- # still exists +- assert_nothing_raised { +- ca = Puppet::SSLCertificates::CA.new +- } +- +- end +- +- def testSignCert +- ca = mkCA() +- +- cert = nil +- assert_nothing_raised { +- +- cert = Puppet::SSLCertificates::Certificate.new( +- +- :name => "signedcertest", +- :property => "TN", +- :city => "Nashville", +- :country => "US", +- :email => "luke@madstop.com", +- :org => "Puppet", +- :ou => "Development", +- +- :encrypt => mkPassFile() +- ) +- +- } +- +- assert_nothing_raised { +- cert.mkcsr +- } +- +- signedcert = nil +- cacert = nil +- +- assert_nothing_raised { +- signedcert, cacert = ca.sign(cert.csr) +- } +- +- assert_instance_of(OpenSSL::X509::Certificate, signedcert) +- assert_instance_of(OpenSSL::X509::Certificate, cacert) +- +- assert_nothing_raised { +- cert.cert = signedcert +- cert.cacert = cacert +- cert.write +- } +- #system("find #{Puppet[:ssldir]}") +- #system("cp -R #{Puppet[:ssldir]} /tmp/ssltesting") +- +- output = nil +- assert_nothing_raised { +- output = %x{openssl verify -CAfile #{Puppet[:cacert]} -purpose sslserver #{cert.certfile}} +- #output = %x{openssl verify -CApath #{Puppet[:certdir]} -purpose sslserver #{cert.certfile}} +- } +- +- assert_equal($CHILD_STATUS,0) +- assert_equal(File.join(Puppet[:certdir], "signedcertest.pem: OK\n"), output) +- end +- +- +- def test_interactiveca +- ca = nil +- +- assert_nothing_raised { +- ca = Puppet::SSLCertificates::CA.new +- } +- +- # basic initialization +- hostname = "test.hostname.com" +- cert = mkcert(hostname) +- +- # create the csr +- csr = nil +- assert_nothing_raised { +- csr = cert.mkcsr +- } +- +- assert_nothing_raised { +- ca.storeclientcsr(csr) +- } +- +- # store it +- pulledcsr = nil +- assert_nothing_raised { +- pulledcsr = ca.getclientcsr(hostname) +- } +- +- assert_equal(csr.to_pem, pulledcsr.to_pem) +- +- signedcert = nil +- assert_nothing_raised { +- signedcert, cacert = ca.sign(csr) +- } +- +- assert_instance_of(OpenSSL::X509::Certificate, signedcert) +- newsignedcert = nil +- assert_nothing_raised { +- newsignedcert, cacert = ca.getclientcert(hostname) +- } +- +- assert(newsignedcert) +- +- assert_equal(signedcert.to_pem, newsignedcert.to_pem) +- end +- +- def test_cafailures +- ca = mkCA() +- cert = cacert = nil +- assert_nothing_raised { +- cert, cacert = ca.getclientcert("nohost") +- } +- assert_nil(cert) +- end +- +- def test_crl +- ca = mkCA() +- h1 = mksignedcert(ca, "host1.example.com") +- h2 = mksignedcert(ca, "host2.example.com") +- +- assert(ca.cert.verify(ca.cert.public_key)) +- assert(h1.verify(ca.cert.public_key)) +- assert(h2.verify(ca.cert.public_key)) +- +- crl = ca.crl +- assert_not_nil(crl) +- +- store = mkStore(ca) +- assert( store.verify(ca.cert)) +- assert( store.verify(h1, [ca.cert])) +- assert( store.verify(h2, [ca.cert])) +- +- ca.revoke(h1.serial) +- +- oldcert = File.read(Puppet.settings[:cacert]) +- oldserial = File.read(Puppet.settings[:serial]) +- +- # Recreate the CA from disk +- ca = mkCA() +- newcert = File.read(Puppet.settings[:cacert]) +- newserial = File.read(Puppet.settings[:serial]) +- assert_equal(oldcert, newcert, "The certs are not equal after making a new CA.") +- assert_equal(oldserial, newserial, "The serials are not equal after making a new CA.") +- store = mkStore(ca) +- assert( store.verify(ca.cert), "Could not verify CA certs after reloading certs.") +- assert(!store.verify(h1, [ca.cert]), "Incorrectly verified revoked cert.") +- assert( store.verify(h2, [ca.cert]), "Could not verify certs with reloaded CA.") +- +- ca.revoke(h2.serial) +- assert_equal(1, ca.crl.extensions.size) +- +- # Recreate the CA from disk +- ca = mkCA() +- store = mkStore(ca) +- assert( store.verify(ca.cert)) +- assert(!store.verify(h1, [ca.cert]), "first revoked cert passed") +- assert(!store.verify(h2, [ca.cert]), "second revoked cert passed") +- end +- +- def test_ttl +- cert = mksignedcert +- assert_equal(5 * 365 * 24 * 60 * 60, cert.not_after - cert.not_before) +- +- Puppet[:ca_ttl] = 7 * 24 * 60 * 60 +- cert = mksignedcert +- assert_equal(7 * 24 * 60 * 60, cert.not_after - cert.not_before) +- +- Puppet[:ca_ttl] = "2y" +- cert = mksignedcert +- assert_equal(2 * 365 * 24 * 60 * 60, cert.not_after - cert.not_before) +- +- Puppet[:ca_ttl] = "2y" +- cert = mksignedcert +- assert_equal(2 * 365 * 24 * 60 * 60, cert.not_after - cert.not_before) +- +- Puppet[:ca_ttl] = "1h" +- cert = mksignedcert +- assert_equal(60 * 60, cert.not_after - cert.not_before) +- +- Puppet[:ca_ttl] = "900s" +- cert = mksignedcert +- assert_equal(900, cert.not_after - cert.not_before) +- +- # This needs to be last, to make sure that setting ca_days +- # overrides setting ca_ttl +- Puppet[:ca_days] = 3 +- cert = mksignedcert +- assert_equal(3 * 24 * 60 * 60, cert.not_after - cert.not_before) +- +- end +-end +- +Index: puppet-2.7.1/test/certmgr/inventory.rb +=================================================================== +--- puppet-2.7.1.orig/test/certmgr/inventory.rb 2011-10-23 13:05:43.260903809 -0400 ++++ /dev/null 1970-01-01 00:00:00.000000000 +0000 +@@ -1,69 +0,0 @@ +-#!/usr/bin/env ruby +- +-require File.expand_path(File.dirname(__FILE__) + '/../lib/puppettest') +- +-require 'puppet' +-require 'puppettest/certificates' +-require 'puppet/sslcertificates/inventory.rb' +-require 'mocha' +- +-class TestCertInventory < Test::Unit::TestCase +- include PuppetTest::Certificates +- +- Inventory = Puppet::SSLCertificates::Inventory +- +- def setup +- super +- Puppet::Util::SUIDManager.stubs(:asuser).yields +- end +- +- def test_format +- cert = mksignedcert +- +- format = nil +- assert_nothing_raised do +- format = Inventory.format(cert) +- end +- +- +- assert( +- format =~ /^0x0001 \S+ \S+ #{cert.subject}/, +- +- "Did not create correct format") +- end +- +- def test_init +- # First create a couple of certificates +- ca = mkCA +- +- cert1 = mksignedcert(ca, "host1.madstop.com") +- cert2 = mksignedcert(ca, "host2.madstop.com") +- +- init = nil +- assert_nothing_raised do +- init = Inventory.init +- end +- +- [cert1, cert2].each do |cert| +- assert(init.include?(cert.subject.to_s), "Did not catch #{cert.subject}") +- end +- end +- +- def test_add +- ca = mkCA +- cert = mksignedcert(ca, "host.domain.com") +- +- assert_nothing_raised do +- file = mock +- file.expects(:puts).with do |written| +- written.include? cert.subject.to_s +- end +- Puppet::Util::Settings.any_instance.stubs(:write) +- Puppet::Util::Settings.any_instance.expects(:write). +- with(:cert_inventory, 'a').yields(file) +- +- Puppet::SSLCertificates::Inventory.add(cert) +- end +- end +-end +- +Index: puppet-2.7.1/test/certmgr/support.rb +=================================================================== +--- puppet-2.7.1.orig/test/certmgr/support.rb 2011-10-23 13:05:43.244903808 -0400 ++++ /dev/null 1970-01-01 00:00:00.000000000 +0000 +@@ -1,105 +0,0 @@ +-#!/usr/bin/env ruby +- +-require File.expand_path(File.dirname(__FILE__) + '/../lib/puppettest') +- +-require 'puppettest' +-require 'puppet/sslcertificates/support' +-require 'mocha' +- +-class TestCertSupport < Test::Unit::TestCase +- include PuppetTest +- MissingCertificate = Puppet::SSLCertificates::Support::MissingCertificate +- +- class CertUser +- include Puppet::SSLCertificates::Support +- end +- +- def setup +- super +- Puppet::Util::SUIDManager.stubs(:asuser).yields +- @user = CertUser.new +- @ca = Puppet::SSLCertificates::CA.new +- @client = Puppet::Network::Client.ca.new(:CA => @ca) +- end +- +- # Yay, metaprogramming +- def test_keytype +- [:key, :csr, :cert, :ca_cert].each do |name| +- assert(Puppet::SSLCertificates::Support.method_defined?(name), "No retrieval method for #{name}") +- maker = "mk_#{name}" +- assert(Puppet::SSLCertificates::Support.method_defined?(maker), "No maker method for #{name}") +- end +- end +- +- def test_keys +- keys = [:hostprivkey, :hostpubkey].each { |n| Puppet[n] = tempfile } +- +- key = nil +- assert_nothing_raised do +- key = @user.key +- end +- +- assert_logged(:info, /Creating a new SSL/, "Did not log about new key") +- keys.each do |file| +- +- assert( +- FileTest.exists?(Puppet[file]), +- +- "Did not create #{file} key file") +- end +- +- # Make sure it's a valid key +- assert_nothing_raised("Created key is invalid") do +- OpenSSL::PKey::RSA.new(File.read(Puppet[:hostprivkey])) +- end +- +- # now make sure we can read it in +- other = CertUser.new +- assert_nothing_raised("Could not read key in") do +- other.key +- end +- +- assert_equal(@user.key.to_s, other.key.to_s, "Keys are not equal") +- end +- +- def test_csr +- csr = nil +- assert_nothing_raised("Could not create csr") do +- csr = @user.csr +- end +- +- assert(FileTest.exists?(Puppet[:hostcsr]), "did not create csr file") +- assert_instance_of(OpenSSL::X509::Request, csr) +- end +- +- def test_cacert +- @user = CertUser.new +- +- assert_raise(MissingCertificate, "Did not fail when missing cacert") do +- @user.ca_cert +- end +- end +- +- # Fixing #1382. This test will always fail on Darwin, because its +- # FS is case-insensitive. +- unless Facter.value(:operatingsystem) == "Darwin" +- def test_uppercase_files_are_renamed_and_read +- # Write a key out to disk in a file containing upper-case. +- key = OpenSSL::PKey::RSA.new(32) +- should_path = Puppet[:hostprivkey] +- +- dir, file = File.split(should_path) +- newfile = file.sub(/^([-a-z.0-9]+)\./) { $1.upcase + "."} +- upper_path = File.join(dir, newfile) +-p upper_path +- File.open(upper_path, "w") { |f| f.print key.to_s } +- +- user = CertUser.new +- +- assert_equal(key.to_s, user.read_key.to_s, "Did not read key in from disk") +- assert(! FileTest.exist?(upper_path), "Upper case file was not removed") +- assert(FileTest.exist?(should_path), "File was not renamed to lower-case file") +- assert_equal(key.to_s, user.read_key.to_s, "Did not read key in from disk") +- end +- end +-end +Index: puppet-2.7.1/test/language/functions.rb +=================================================================== +--- puppet-2.7.1.orig/test/language/functions.rb 2011-10-23 13:05:43.204903808 -0400 ++++ puppet-2.7.1/test/language/functions.rb 2011-10-23 13:05:46.400903779 -0400 +@@ -4,7 +4,6 @@ + + require 'puppet' + require 'puppet/parser/parser' +-require 'puppet/network/client' + require 'puppettest' + require 'puppettest/resourcetesting' + +Index: puppet-2.7.1/test/language/snippets.rb +=================================================================== +--- puppet-2.7.1.orig/test/language/snippets.rb 2011-10-23 13:05:43.220903808 -0400 ++++ puppet-2.7.1/test/language/snippets.rb 2011-10-23 13:05:46.400903779 -0400 +@@ -4,8 +4,6 @@ + + require 'puppet' + require 'puppet/parser/parser' +-require 'puppet/network/client' +-require 'puppet/network/handler' + require 'puppettest' + + class TestSnippets < Test::Unit::TestCase +@@ -69,13 +67,6 @@ + ast + end + +- def client +- args = { +- :Listen => false +- } +- Puppet::Network::Client.new(args) +- end +- + def ast2scope(ast) + scope = Puppet::Parser::Scope.new + ast.evaluate(scope) +Index: puppet-2.7.1/test/lib/puppettest/exetest.rb +=================================================================== +--- puppet-2.7.1.orig/test/lib/puppettest/exetest.rb 2011-10-23 13:05:43.332903807 -0400 ++++ puppet-2.7.1/test/lib/puppettest/exetest.rb 2011-10-23 13:05:46.400903779 -0400 +@@ -50,7 +50,7 @@ + args += " --confdir #{Puppet[:confdir]}" + args += " --rundir #{File.join(Puppet[:vardir], "run")}" + args += " --vardir #{Puppet[:vardir]}" +- args += " --certdnsnames #{Puppet[:certdnsnames]}" ++ args += " --dns_alt_names #{Puppet[:master_dns_alt_names]}" + args += " --masterport #{@@port}" + args += " --user #{Puppet::Util::SUIDManager.uid}" + args += " --group #{Puppet::Util::SUIDManager.gid}" +Index: puppet-2.7.1/test/lib/puppettest/servertest.rb +=================================================================== +--- puppet-2.7.1.orig/test/lib/puppettest/servertest.rb 2011-10-23 13:05:43.312903807 -0400 ++++ puppet-2.7.1/test/lib/puppettest/servertest.rb 2011-10-23 13:05:46.400903779 -0400 +@@ -1,5 +1,4 @@ + require 'puppettest' +-require 'puppet/network/http_server/webrick' + + module PuppetTest::ServerTest + include PuppetTest +Index: puppet-2.7.1/test/network/client/ca.rb +=================================================================== +--- puppet-2.7.1.orig/test/network/client/ca.rb 2011-10-23 13:05:43.148903810 -0400 ++++ /dev/null 1970-01-01 00:00:00.000000000 +0000 +@@ -1,69 +0,0 @@ +-#!/usr/bin/env ruby +- +-require File.expand_path(File.dirname(__FILE__) + '/../../lib/puppettest') +- +-require 'mocha' +-require 'puppettest' +-require 'puppet/network/client/ca' +-require 'puppet/sslcertificates/support' +- +-class TestClientCA < Test::Unit::TestCase +- include PuppetTest::ServerTest +- +- def setup +- Puppet::Util::SUIDManager.stubs(:asuser).yields +- super +- @ca = Puppet::Network::Handler.ca.new +- @client = Puppet::Network::Client.ca.new :CA => @ca +- end +- +- def test_request_cert +- assert_nothing_raised("Could not request cert") do +- @client.request_cert +- end +- +- [:hostprivkey, :hostcert, :localcacert].each do |name| +- assert(FileTest.exists?(Puppet.settings[name]), "Did not create cert #{name}") +- end +- end +- +- # Make sure the ca defaults to specific ports and names +- def test_ca_server +- Puppet.settings.stubs(:value).returns "eh" +- Puppet.settings.expects(:value).with(:ca_server).returns("myca") +- Puppet.settings.expects(:value).with(:ca_port).returns(321) +- Puppet.settings.stubs(:value).with(:http_proxy_host).returns(nil) +- Puppet.settings.stubs(:value).with(:http_proxy_port).returns(nil) +- Puppet.settings.stubs(:value).with(:http_keepalive).returns(false) +- Puppet.settings.stubs(:value).with(:configtimeout).returns(180) +- +- # Just throw an error; the important thing is the values, not what happens next. +- Net::HTTP.stubs(:new).with("myca", 321, nil, nil).raises(ArgumentError) +- assert_raise(ArgumentError) { Puppet::Network::Client.ca.new } +- end +- +- # #578 +- def test_invalid_certs_are_not_written +- # Run the get once, which should be valid +- +- assert_nothing_raised("Could not get a certificate") do +- @client.request_cert +- end +- +- # Now remove the cert and keys, so we get a broken cert +- File.unlink(Puppet[:hostcert]) +- File.unlink(Puppet[:localcacert]) +- File.unlink(Puppet[:hostprivkey]) +- +- @client = Puppet::Network::Client.ca.new :CA => @ca +- @ca.expects(:getcert).returns("yay") # not a valid cert +- # Now make sure it fails, since we'll get the old cert but have new keys +- assert_raise(Puppet::Network::Client::CA::InvalidCertificate, "Did not fail on invalid cert") do +- @client.request_cert +- end +- +- # And then make sure the cert isn't written to disk +- assert(! FileTest.exists?(Puppet[:hostcert]), "Invalid cert got written to disk") +- end +-end +- +Index: puppet-2.7.1/test/network/client/dipper.rb +=================================================================== +--- puppet-2.7.1.orig/test/network/client/dipper.rb 2011-10-23 13:05:43.132903809 -0400 ++++ /dev/null 1970-01-01 00:00:00.000000000 +0000 +@@ -1,34 +0,0 @@ +-#!/usr/bin/env ruby +- +-require File.expand_path(File.dirname(__FILE__) + '/../../lib/puppettest') +- +-require 'puppettest' +-require 'puppet/file_bucket/dipper' +- +-class TestDipperClient < Test::Unit::TestCase +- include PuppetTest::ServerTest +- +- def setup +- super +- @dipper = Puppet::FileBucket::Dipper.new(:Path => tempfile) +- end +- +- # Make sure we can create a new file with 'restore'. +- def test_restore_to_new_file +- file = tempfile +- text = "asdf;lkajseofiqwekj" +- File.open(file, "w") { |f| f.puts text } +- md5 = nil +- assert_nothing_raised("Could not send file") do +- md5 = @dipper.backup(file) +- end +- +- newfile = tempfile +- assert_nothing_raised("could not restore to new path") do +- @dipper.restore(newfile, md5) +- end +- +- assert_equal(File.read(file), File.read(newfile), "did not restore correctly") +- end +-end +- +Index: puppet-2.7.1/test/network/handler/ca.rb +=================================================================== +--- puppet-2.7.1.orig/test/network/handler/ca.rb 2011-10-23 13:05:43.188903808 -0400 ++++ /dev/null 1970-01-01 00:00:00.000000000 +0000 +@@ -1,273 +0,0 @@ +-#!/usr/bin/env ruby +- +-require File.expand_path(File.dirname(__FILE__) + '/../../lib/puppettest') +- +-require 'puppettest' +-require 'puppet/network/handler/ca' +-require 'mocha' +- +-$short = (ARGV.length > 0 and ARGV[0] == "short") +- +-class TestCA < Test::Unit::TestCase +- include PuppetTest::ServerTest +- +- def setup +- Puppet::Util::SUIDManager.stubs(:asuser).yields +- super +- end +- +- # Verify that we're autosigning. We have to autosign a "different" machine, +- # since we always autosign the CA server's certificate. +- def test_autocertgeneration +- ca = nil +- +- # create our ca +- assert_nothing_raised { +- ca = Puppet::Network::Handler.ca.new(:autosign => true) +- } +- +- # create a cert with a fake name +- key = nil +- csr = nil +- cert = nil +- hostname = "test.domain.com" +- assert_nothing_raised { +- cert = Puppet::SSLCertificates::Certificate.new( +- :name => "test.domain.com" +- ) +- } +- +- # make the request +- assert_nothing_raised { +- cert.mkcsr +- } +- +- # and get it signed +- certtext = nil +- cacerttext = nil +- assert_nothing_raised { +- certtext, cacerttext = ca.getcert(cert.csr.to_s) +- } +- +- # they should both be strings +- assert_instance_of(String, certtext) +- assert_instance_of(String, cacerttext) +- +- # and they should both be valid certs +- assert_nothing_raised { +- OpenSSL::X509::Certificate.new(certtext) +- } +- assert_nothing_raised { +- OpenSSL::X509::Certificate.new(cacerttext) +- } +- +- # and pull it again, just to make sure we're getting the same thing +- newtext = nil +- assert_nothing_raised { +- newtext, cacerttext = ca.getcert( +- cert.csr.to_s, "test.reductivelabs.com", "127.0.0.1" +- ) +- } +- +- assert_equal(certtext,newtext) +- end +- +- # this time don't use autosign +- def test_storeAndSign +- ca = nil +- caserv = nil +- +- # make our CA server +- assert_nothing_raised { +- caserv = Puppet::Network::Handler.ca.new(:autosign => false) +- } +- +- # retrieve the actual ca object +- assert_nothing_raised { +- ca = caserv.ca +- } +- +- # make our test cert again +- key = nil +- csr = nil +- cert = nil +- hostname = "test.domain.com" +- assert_nothing_raised { +- cert = Puppet::SSLCertificates::Certificate.new( +- :name => "anothertest.domain.com" +- ) +- } +- # and the CSR +- assert_nothing_raised { +- cert.mkcsr +- } +- +- # retrieve them +- certtext = nil +- assert_nothing_raised { +- certtext, cacerttext = caserv.getcert( +- cert.csr.to_s, "test.reductivelabs.com", "127.0.0.1" +- ) +- } +- +- # verify we got nothing back, since autosign is off +- assert_equal("", certtext) +- +- # now sign it manually, with the CA object +- x509 = nil +- assert_nothing_raised { +- x509, cacert = ca.sign(cert.csr) +- } +- +- # and write it out +- cert.cert = x509 +- assert_nothing_raised { +- cert.write +- } +- +- assert(File.exists?(cert.certfile)) +- +- # now get them again, and verify that we actually get them +- newtext = nil +- assert_nothing_raised { +- newtext, cacerttext = caserv.getcert(cert.csr.to_s) +- } +- +- assert(newtext) +- assert_nothing_raised { +- OpenSSL::X509::Certificate.new(newtext) +- } +- +- # Now verify that we can clean a given host's certs +- assert_nothing_raised { +- ca.clean("anothertest.domain.com") +- } +- +- assert(!File.exists?(cert.certfile), "Cert still exists after clean") +- end +- +- # and now test the autosign file +- def test_autosign +- autosign = File.join(tmpdir, "autosigntesting") +- @@tmpfiles << autosign +- File.open(autosign, "w") { |f| +- f.puts "hostmatch.domain.com" +- f.puts "*.other.com" +- } +- +- caserv = nil +- assert_nothing_raised { +- caserv = Puppet::Network::Handler.ca.new(:autosign => autosign) +- } +- +- # make sure we know what's going on +- assert(caserv.autosign?("hostmatch.domain.com")) +- assert(caserv.autosign?("fakehost.other.com")) +- assert(!caserv.autosign?("kirby.reductivelabs.com")) +- assert(!caserv.autosign?("culain.domain.com")) +- end +- +- # verify that things aren't autosigned by default +- def test_nodefaultautosign +- caserv = nil +- assert_nothing_raised { +- caserv = Puppet::Network::Handler.ca.new +- } +- +- # make sure we know what's going on +- assert(!caserv.autosign?("hostmatch.domain.com")) +- assert(!caserv.autosign?("fakehost.other.com")) +- assert(!caserv.autosign?("kirby.reductivelabs.com")) +- assert(!caserv.autosign?("culain.domain.com")) +- end +- +- # We want the CA to autosign its own certificate, because otherwise +- # the puppetmasterd CA does not autostart. +- def test_caautosign +- server = nil +- Puppet.stubs(:master?).returns true +- assert_nothing_raised { +- +- server = Puppet::Network::HTTPServer::WEBrick.new( +- +- :Port => @@port, +- +- :Handlers => { +- :CA => {}, # so that certs autogenerate +- :Status => nil +- } +- ) +- } +- end +- +- # Make sure true/false causes the file to be ignored. +- def test_autosign_true_beats_file +- caserv = nil +- assert_nothing_raised { +- caserv = Puppet::Network::Handler.ca.new +- } +- +- host = "hostname.domain.com" +- +- # Create an autosign file +- file = tempfile +- Puppet[:autosign] = file +- +- File.open(file, "w") { |f| +- f.puts host +- } +- +- # Start with "false" +- Puppet[:autosign] = false +- +- assert(! caserv.autosign?(host), "Host was incorrectly autosigned") +- +- # Then set it to true +- Puppet[:autosign] = true +- assert(caserv.autosign?(host), "Host was not autosigned") +- # And try a different host +- assert(caserv.autosign?("other.yay.com"), "Host was not autosigned") +- +- # And lastly the file +- Puppet[:autosign] = file +- assert(caserv.autosign?(host), "Host was not autosigned") +- +- # And try a different host +- assert(! caserv.autosign?("other.yay.com"), "Host was autosigned") +- end +- +- # Make sure that a CSR created with keys that don't match the existing +- # cert throws an exception on the server. +- def test_mismatched_public_keys_throws_exception +- ca = Puppet::Network::Handler.ca.new +- +- # First initialize the server +- client = Puppet::Network::Client.ca.new :CA => ca +- client.request_cert +- File.unlink(Puppet[:hostcsr]) +- +- # Now use a different cert name +- Puppet[:certname] = "my.host.com" +- client = Puppet::Network::Client.ca.new :CA => ca +- firstcsr = client.csr +- File.unlink(Puppet[:hostcsr]) if FileTest.exists?(Puppet[:hostcsr]) +- +- assert_nothing_raised("Could not get cert") do +- ca.getcert(firstcsr.to_s) +- end +- +- # Now get rid of the public key, forcing a new csr +- File.unlink(Puppet[:hostprivkey]) +- +- client = Puppet::Network::Client.ca.new :CA => ca +- +- second_csr = client.csr +- +- assert(firstcsr.to_s != second_csr.to_s, "CSR did not change") +- +- assert_raise(Puppet::Error, "CA allowed mismatched keys") do +- ca.getcert(second_csr.to_s) +- end +- end +-end +- +Index: puppet-2.7.1/test/network/server/mongrel_test.rb +=================================================================== +--- puppet-2.7.1.orig/test/network/server/mongrel_test.rb 2011-10-23 13:05:43.160903810 -0400 ++++ /dev/null 1970-01-01 00:00:00.000000000 +0000 +@@ -1,99 +0,0 @@ +-#!/usr/bin/env ruby +- +-require File.expand_path(File.dirname(__FILE__) + '/../../lib/puppettest') +- +-require 'puppettest' +-require 'mocha' +- +-class TestMongrelServer < PuppetTest::TestCase +- confine "Missing mongrel" => Puppet.features.mongrel? +- +- include PuppetTest::ServerTest +- +- def mkserver(handlers = nil) +- handlers ||= { :Status => nil } +- mongrel = Puppet::Network::HTTPServer::Mongrel.new(handlers) +- end +- +- # Make sure client info is correctly extracted. +- def test_client_info +- obj = Object.new +- obj.singleton_class.send(:attr_accessor, :params) +- params = {} +- obj.params = params +- +- mongrel = mkserver +- +- ip = Facter.value(:ipaddress) +- params["REMOTE_ADDR"] = ip +- params[Puppet[:ssl_client_header]] = "" +- params[Puppet[:ssl_client_verify_header]] = "failure" +- info = nil +- Resolv.expects(:getname).with(ip).returns("host.domain.com").times(4) +- assert_nothing_raised("Could not call client_info") do +- info = mongrel.send(:client_info, obj) +- end +- assert(! info.authenticated?, "Client info object was marked valid even though headers were missing") +- assert_equal(ip, info.ip, "Did not copy over ip correctly") +- +- assert_equal("host.domain.com", info.name, "Did not copy over hostname correctly") +- +- # Now pass the X-Forwarded-For header and check it is preferred over REMOTE_ADDR +- params["REMOTE_ADDR"] = '127.0.0.1' +- params["HTTP_X_FORWARDED_FOR"] = ip +- info = nil +- assert_nothing_raised("Could not call client_info") do +- info = mongrel.send(:client_info, obj) +- end +- assert(! info.authenticated?, "Client info object was marked valid even though headers were missing") +- assert_equal(ip, info.ip, "Did not copy over ip correctly") +- +- assert_equal("host.domain.com", info.name, "Did not copy over hostname correctly") +- +- # Now add a valid auth header. +- params["REMOTE_ADDR"] = ip +- params["HTTP_X_FORWARDED_FOR"] = nil +- params[Puppet[:ssl_client_header]] = "/CN=host.domain.com" +- assert_nothing_raised("Could not call client_info") do +- info = mongrel.send(:client_info, obj) +- end +- assert(! info.authenticated?, "Client info object was marked valid even though the verify header was fals") +- assert_equal(ip, info.ip, "Did not copy over ip correctly") +- assert_equal("host.domain.com", info.name, "Did not copy over hostname correctly") +- +- # Now change the verify header to be true +- params[Puppet[:ssl_client_verify_header]] = "SUCCESS" +- assert_nothing_raised("Could not call client_info") do +- info = mongrel.send(:client_info, obj) +- end +- +- assert(info.authenticated?, "Client info object was not marked valid even though all headers were correct") +- assert_equal(ip, info.ip, "Did not copy over ip correctly") +- assert_equal("host.domain.com", info.name, "Did not copy over hostname correctly") +- +- # Now try it with a different header name +- params.delete(Puppet[:ssl_client_header]) +- Puppet[:ssl_client_header] = "header_testing" +- params["header_testing"] = "/CN=other.domain.com" +- info = nil +- assert_nothing_raised("Could not call client_info with other header") do +- info = mongrel.send(:client_info, obj) +- end +- +- assert(info.authenticated?, "Client info object was not marked valid even though the header was present") +- assert_equal(ip, info.ip, "Did not copy over ip correctly") +- assert_equal("other.domain.com", info.name, "Did not copy over hostname correctly") +- +- # Now make sure it's considered invalid without that header +- params.delete("header_testing") +- info = nil +- assert_nothing_raised("Could not call client_info with no header") do +- info = mongrel.send(:client_info, obj) +- end +- +- assert(! info.authenticated?, "Client info object was marked valid without header") +- assert_equal(ip, info.ip, "Did not copy over ip correctly") +- assert_equal(Resolv.getname(ip), info.name, "Did not look up hostname correctly") +- end +-end +- +Index: puppet-2.7.1/test/network/server/webrick.rb +=================================================================== +--- puppet-2.7.1.orig/test/network/server/webrick.rb 2011-10-23 13:05:43.172903810 -0400 ++++ /dev/null 1970-01-01 00:00:00.000000000 +0000 +@@ -1,128 +0,0 @@ +-#!/usr/bin/env ruby +- +-require File.expand_path(File.dirname(__FILE__) + '/../../lib/puppettest') +- +-require 'puppettest' +-require 'puppet/network/http_server/webrick' +-require 'mocha' +- +-class TestWebrickServer < Test::Unit::TestCase +- include PuppetTest::ServerTest +- +- def setup +- Puppet::Util::SUIDManager.stubs(:asuser).yields +- super +- end +- +- def teardown +- super +- Puppet::Network::HttpPool.clear_http_instances +- end +- +- # Make sure we can create a server, and that it knows how to create its +- # certs by default. +- def test_basics +- server = nil +- assert_raise(Puppet::Error, "server succeeded with no cert") do +- +- server = Puppet::Network::HTTPServer::WEBrick.new( +- +- :Port => @@port, +- +- :Handlers => { +- :Status => nil +- } +- ) +- end +- +- assert_nothing_raised("Could not create simple server") do +- +- server = Puppet::Network::HTTPServer::WEBrick.new( +- +- :Port => @@port, +- +- :Handlers => { +- :CA => {}, # so that certs autogenerate +- :Status => nil +- } +- ) +- end +- +- assert(server, "did not create server") +- +- assert(server.cert, "did not retrieve cert") +- end +- +- # test that we can connect to the server +- # we have to use fork here, because we apparently can't use threads +- # to talk to other threads +- def test_connect_with_fork +- Puppet[:autosign] = true +- serverpid, server = mk_status_server +- +- # create a status client, and verify it can talk +- client = mk_status_client +- +- assert(client.cert, "did not get cert for client") +- +- retval = nil +- assert_nothing_raised("Could not connect to server") { +- retval = client.status +- } +- assert_equal(1, retval) +- end +- +- def mk_status_client +- client = nil +- +- assert_nothing_raised { +- +- client = Puppet::Network::Client.status.new( +- +- :Server => "localhost", +- +- :Port => @@port +- ) +- } +- client +- end +- +- def mk_status_server +- server = nil +- Puppet[:certdnsnames] = "localhost" +- assert_nothing_raised { +- +- server = Puppet::Network::HTTPServer::WEBrick.new( +- +- :Port => @@port, +- +- :Handlers => { +- :CA => {}, # so that certs autogenerate +- :Status => nil +- } +- ) +- +- } +- +- pid = fork { +- Puppet.run_mode.stubs(:master?).returns true +- assert_nothing_raised { +- trap(:INT) { server.shutdown } +- server.start +- } +- } +- @@tmppids << pid +- [pid, server] +- end +- +- def kill_and_wait(pid, file) +- %x{kill -INT #{pid} 2>/dev/null} +- count = 0 +- while count < 30 && File::exist?(file) +- count += 1 +- sleep(1) +- end +- assert(count < 30, "Killing server #{pid} failed") +- end +-end +- +Index: puppet-2.7.1/test/network/xmlrpc/client.rb +=================================================================== +--- puppet-2.7.1.orig/test/network/xmlrpc/client.rb 2011-10-23 13:05:43.120903809 -0400 ++++ /dev/null 1970-01-01 00:00:00.000000000 +0000 +@@ -1,45 +0,0 @@ +-#!/usr/bin/env ruby +- +-require File.expand_path(File.dirname(__FILE__) + '/../../lib/puppettest') +- +-require 'puppettest' +-require 'puppet/network/xmlrpc/client' +-require 'mocha' +- +-class TestXMLRPCClient < Test::Unit::TestCase +- include PuppetTest +- +- def setup +- Puppet::Util::SUIDManager.stubs(:asuser).yields +- super +- end +- +- def test_set_backtrace +- error = Puppet::Network::XMLRPCClientError.new("An error") +- assert_nothing_raised do +- error.set_backtrace ["caller"] +- end +- assert_equal(["caller"], error.backtrace) +- end +- +- # Make sure we correctly generate a netclient +- def test_handler_class +- # Create a test handler +- klass = Puppet::Network::XMLRPCClient +- yay = Class.new(Puppet::Network::Handler) do +- @interface = XMLRPC::Service::Interface.new("yay") { |iface| +- iface.add_method("array getcert(csr)") +- } +- +- @name = :Yay +- end +- Object.const_set("Yay", yay) +- +- net = nil +- assert_nothing_raised("Failed when retrieving client for handler") do +- net = klass.handler_class(yay) +- end +- +- assert(net, "did not get net client") +- end +-end +Index: puppet-2.7.1/test/rails/rails.rb +=================================================================== +--- puppet-2.7.1.orig/test/rails/rails.rb 2011-10-23 13:05:43.300903807 -0400 ++++ puppet-2.7.1/test/rails/rails.rb 2011-10-23 13:05:46.400903779 -0400 +@@ -5,7 +5,6 @@ + require 'puppet' + require 'puppet/rails' + require 'puppet/parser/parser' +-require 'puppet/network/client' + require 'puppettest' + require 'puppettest/parsertesting' + require 'puppettest/resourcetesting' +Index: puppet-2.7.1/test/ral/type/filesources.rb +=================================================================== +--- puppet-2.7.1.orig/test/ral/type/filesources.rb 2011-10-23 13:05:43.280903808 -0400 ++++ puppet-2.7.1/test/ral/type/filesources.rb 2011-10-23 13:05:46.400903779 -0400 +@@ -227,66 +227,6 @@ + file + end + +- def test_unmountedNetworkSources +- server = nil +- mounts = { +- "/" => "root", +- "/noexistokay" => "noexist" +- } +- +- fileserverconf = mkfileserverconf(mounts) +- +- Puppet[:autosign] = true +- Puppet[:masterport] = @port +- Puppet[:certdnsnames] = "localhost" +- +- serverpid = nil +- assert_nothing_raised("Could not start on port #{@port}") { +- +- server = Puppet::Network::HTTPServer::WEBrick.new( +- +- :Port => @port, +- +- :Handlers => { +- :CA => {}, # so that certs autogenerate +- :FileServer => { +- :Config => fileserverconf +- } +- } +- ) +- +- } +- +- serverpid = fork { +- assert_nothing_raised { +- #trap(:INT) { server.shutdown; Kernel.exit! } +- trap(:INT) { server.shutdown } +- server.start +- } +- } +- @@tmppids << serverpid +- +- sleep(1) +- +- name = File.join(tmpdir, "nosourcefile") +- +- file = Puppet::Type.type(:file).new( +- +- :source => "puppet://localhost/noexist/file", +- +- :name => name +- ) +- +- assert_raise Puppet::Error do +- file.retrieve +- end +- +- comp = mk_catalog(file) +- comp.apply +- +- assert(!FileTest.exists?(name), "File with no source exists anyway") +- end +- + def test_sourcepaths + files = [] + 3.times { --- puppet-2.7.1.orig/ext/rack/files/apache2.conf +++ puppet-2.7.1/ext/rack/files/apache2.conf @@ -1,12 +1,4 @@ - -# you probably want to tune these settings -PassengerHighPerformance on -PassengerMaxPoolSize 12 -PassengerPoolIdleTime 1500 -# PassengerMaxRequests 1000 -PassengerStatThrottleRate 120 -RackAutoDetect Off -RailsAutoDetect Off +# Based on http://projects.puppetlabs.com/projects/1/wiki/Using_Passenger Listen 8140 @@ -15,20 +7,32 @@ SSLProtocol -ALL +SSLv3 +TLSv1 SSLCipherSuite ALL:!ADH:RC4+RSA:+HIGH:+MEDIUM:-LOW:-SSLv2:-EXP - SSLCertificateFile /etc/puppet/ssl/certs/squigley.namespace.at.pem - SSLCertificateKeyFile /etc/puppet/ssl/private_keys/squigley.namespace.at.pem - SSLCertificateChainFile /etc/puppet/ssl/ca/ca_crt.pem - SSLCACertificateFile /etc/puppet/ssl/ca/ca_crt.pem + SSLCertificateKeyFile /var/lib/puppet/ssl/private_keys/puppetmaster.example.com.pem + SSLCertificateFile /var/lib/puppet/ssl/certs/puppetmaster.example.com.pem + SSLCACertificateFile /var/lib/puppet/ssl/ca/ca_crt.pem + SSLCertificateChainFile /var/lib/puppet/ssl/ca/ca_crt.pem # If Apache complains about invalid signatures on the CRL, you can try disabling # CRL checking by commenting the next line, but this is not recommended. - SSLCARevocationFile /etc/puppet/ssl/ca/ca_crl.pem + SSLCARevocationFile /var/lib/puppet/ssl/ca/ca_crl.pem + # Set to require if this puppetmaster doesn't issue certificates + # to puppet clients. + # NB: this requires SSLCACertificateFile to include the CA cert + # issuing puppet client certificate. SSLVerifyClient optional SSLVerifyDepth 1 SSLOptions +StdEnvVars - DocumentRoot /etc/puppet/rack/public/ + # Passenger options that can be set in a virtual host + # configuration block. + PassengerHighPerformance on + PassengerStatThrottleRate 120 + PassengerUseGlobalQueue on + RackAutoDetect Off + RailsAutoDetect Off RackBaseURI / - + + DocumentRoot /usr/share/puppet/rack/puppetmasterd/public + Options None AllowOverride None Order allow,deny