File CVE-2011-3872.patch of Package puppet.import5403
Description: fix puppet master impersonation via incorrect certificates
Origin: Patch provided by upstream, but be_within changed to be_close in
testsuite so it works with version of rspec in Ubuntu, and with the
first part removed, as it introduced a regression.
Subject: [PATCH 02/32] (#6928) backport Symbol#to_proc for Ruby < 1.8.7
Subject: [PATCH 03/32] (#6928) Don't blow up when the method is undefined...
Subject: [PATCH 04/32] (#2848) Set `certdnsnames` values into the CSR.
Subject: [PATCH 05/32] (#2848) extract the subjectAltName value from the CSR.
Subject: [PATCH 06/32] (#2848) Reject unknown (== all) extensions on the CSR.
Subject: [PATCH 07/32] (#2848) Rewrite SSL Certificate Factory, fixing
Subject: [PATCH 08/32] (#7224) Add a helper to Puppet::SSL::Certificate to
Subject: [PATCH 09/32] (#2848) List subject alt names in output of puppet
Subject: [PATCH 10/32] (#2848) CSR subjectAltNames handling while signing.
Subject: [PATCH 11/32] (#2848) Use `certdnsnames` when bootstrapping a local
Subject: [PATCH 12/32] (#2848) Rename `certdnsnames` to match new behaviour.
Subject: [PATCH 13/32] (#2848) rename subject-alt-name option to
Subject: [PATCH 14/32] Wire up the `setbycli` slot in Puppet settings.
Subject: [PATCH 15/32] (#2848) Migrate `dns-alt-names` back to settings.
Subject: [PATCH 16/32] (#2848) Only mark `subjectAltName` critical if
Subject: [PATCH 17/32] (#2848) Don't enable `emailProtection` for server
Subject: [PATCH 18/32] (#2848) Don't strip the subjectAltName label when
Subject: [PATCH 19/32] (#2848) Consistently use `subject_alt_names` as
Subject: [PATCH 20/32] (#2848) Consistent return values from
Subject: [PATCH 21/32] (#2848) Remove unused xmlrpc code
Subject: [PATCH 22/32] (#2848) Rework the xmlrpc CA handler to use the modern
Subject: [PATCH 23/32] (#2848) Remove the legacy SSLCertificates code
Subject: [PATCH 24/32] (#2848) Eliminate redundant `master_dns_alt_names`.
Subject: [PATCH 25/32] s/not_to/should_not/ for older versions of RSpec 2.
Subject: [PATCH 26/32] Add `lines` alias for `each_line` in Ruby 1.8.5.
Subject: [PATCH 27/32] (#2848) Config options require '_', not '-'.
Subject: [PATCH 28/32] Better 1.8.5 compatible implementation of `lines`.
Subject: [PATCH 29/32] More 1.8.5 compatibility fixes.
Subject: [PATCH 30/32] Backport Enumerable#count to Rubies < 1.8.7
Subject: [PATCH 31/32] Allow a master to bootstrap itself with dns_alt_names
Subject: [PATCH 32/32] Improve the error message when a CSR is rejected
Index: puppet-2.6.4/lib/puppet/application/cert.rb
===================================================================
--- puppet-2.6.4.orig/lib/puppet/application/cert.rb 2010-12-01 00:42:01.000000000 +0100
+++ puppet-2.6.4/lib/puppet/application/cert.rb 2011-11-01 10:51:35.082524339 +0100
@@ -45,6 +45,10 @@
Puppet::Util::Log.level = :info
end
+ option("--[no-]allow-dns-alt-names") do |value|
+ options[:allow_dns_alt_names] = value
+ end
+
def main
if @all
hosts = :all
@@ -54,8 +58,8 @@
hosts = command_line.args.collect { |h| h.downcase }
end
begin
- @ca.apply(:revoke, :to => hosts) if @cert_mode == :destroy
- @ca.apply(@cert_mode, :to => hosts, :digest => @digest)
+ @ca.apply(:revoke, options.merge(:to => hosts)) if @cert_mode == :destroy
+ @ca.apply(@cert_mode, options.merge(:to => hosts, :digest => @digest))
rescue => detail
puts detail.backtrace if Puppet[:trace]
puts detail.to_s
@@ -74,6 +78,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 @cert_mode == :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.6.4/lib/puppet/application/kick.rb
===================================================================
--- puppet-2.6.4.orig/lib/puppet/application/kick.rb 2010-12-01 00:42:01.000000000 +0100
+++ puppet-2.6.4/lib/puppet/application/kick.rb 2011-11-01 10:51:35.082524339 +0100
@@ -48,8 +48,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.6.4/lib/puppet/defaults.rb
===================================================================
--- puppet-2.6.4.orig/lib/puppet/defaults.rb 2010-12-01 00:42:43.000000000 +0100
+++ puppet-2.6.4/lib/puppet/defaults.rb 2011-11-01 10:51:35.083524369 +0100
@@ -191,9 +191,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 <<WARN
+The `certdnsnames` setting is no longer functional,
+after CVE-2011-3872. We ignore the value completely.
+
+For your own certificate request you can set `dns_alt_names` in the
+configuration and it will apply locally. There is no configuration option to
+set DNS alt names, or any other `subjectAltName` value, for another nodes
+certificate.
+
+Alternately you can use the `--dns_alt_names` command line option to set the
+labels added while generating your own CSR.
+WARN
+ end
+ end,
+ :desc => <<EOT
+The `certdnsnames` setting is no longer functional,
+after CVE-2011-3872. We ignore the value completely.
+
+For your own certificate request you can set `dns_alt_names` in the
+configuration and it will apply locally. There is no configuration option to
+set DNS alt names, or any other `subjectAltName` value, for another nodes
+certificate.
+
+Alternately you can use the `--dns_alt_names` command line option to set the
+labels added while generating your own CSR.
+EOT
+ },
+ :dns_alt_names => {
+ :default => '',
+ :desc => <<EOT,
+The comma-separated list of alternative DNS names to use for the local host.
+
+When the node generates a CSR for itself, these are added to the request
+as the desired `subjectAltName` in the certificate: additional DNS labels
+that the certificate is also valid answering as.
+
+This is generally required if you use a non-hostname `certname`, or if you
+want to use `puppet kick` or `puppet resource -H` and the primary certname
+does not match the DNS name you use to communicate with the host.
+
+This is unnecessary for agents, unless you intend to use them as a server for
+`puppet kick` or remote `puppet resource` management.
+
+It is rarely necessary for servers; it is usually helpful only if you need to
+have a pool of multiple load balanced masters, or for the same master to
+respond on two physically separate networks under different names.
+EOT
+ },
:certdir => {
:default => "$ssldir/certs",
:owner => "service",
Index: puppet-2.6.4/lib/puppet/network/client/ca.rb
===================================================================
--- puppet-2.6.4.orig/lib/puppet/network/client/ca.rb 2010-12-01 00:42:01.000000000 +0100
+++ /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.6.4/lib/puppet/network/client/file.rb
===================================================================
--- puppet-2.6.4.orig/lib/puppet/network/client/file.rb 2010-12-01 00:42:01.000000000 +0100
+++ /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.6.4/lib/puppet/network/client/proxy.rb
===================================================================
--- puppet-2.6.4.orig/lib/puppet/network/client/proxy.rb 2010-12-01 00:42:01.000000000 +0100
+++ /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.6.4/lib/puppet/network/client/report.rb
===================================================================
--- puppet-2.6.4.orig/lib/puppet/network/client/report.rb 2010-12-01 00:42:01.000000000 +0100
+++ /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.6.4/lib/puppet/network/client/runner.rb
===================================================================
--- puppet-2.6.4.orig/lib/puppet/network/client/runner.rb 2010-12-01 00:42:01.000000000 +0100
+++ /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.6.4/lib/puppet/network/client/status.rb
===================================================================
--- puppet-2.6.4.orig/lib/puppet/network/client/status.rb 2010-12-01 00:42:01.000000000 +0100
+++ /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.6.4/lib/puppet/network/client.rb
===================================================================
--- puppet-2.6.4.orig/lib/puppet/network/client.rb 2010-12-01 00:42:01.000000000 +0100
+++ /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.instance_methods.include? "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.6.4/lib/puppet/network/handler/ca.rb
===================================================================
--- puppet-2.6.4.orig/lib/puppet/network/handler/ca.rb 2010-12-01 00:42:01.000000000 +0100
+++ puppet-2.6.4/lib/puppet/network/handler/ca.rb 2011-11-01 10:51:35.084524399 +0100
@@ -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.find(hostname)
+ cacert = Puppet::SSL::Certificate.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
+ csr.save
- # 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.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.6.4/lib/puppet/network/handler/master.rb
===================================================================
--- puppet-2.6.4.orig/lib/puppet/network/handler/master.rb 2010-12-01 00:42:01.000000000 +0100
+++ puppet-2.6.4/lib/puppet/network/handler/master.rb 2011-11-01 10:51:35.084524399 +0100
@@ -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.6.4/lib/puppet/network/handler/runner.rb
===================================================================
--- puppet-2.6.4.orig/lib/puppet/network/handler/runner.rb 2010-12-01 00:42:01.000000000 +0100
+++ puppet-2.6.4/lib/puppet/network/handler/runner.rb 2011-11-01 10:51:35.085524429 +0100
@@ -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.6.4/lib/puppet/network/http_server/webrick.rb
===================================================================
--- puppet-2.6.4.orig/lib/puppet/network/http_server/webrick.rb 2010-12-01 00:42:01.000000000 +0100
+++ /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.6.4/lib/puppet/network/xmlrpc/client.rb
===================================================================
--- puppet-2.6.4.orig/lib/puppet/network/xmlrpc/client.rb 2010-12-01 00:42:01.000000000 +0100
+++ /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.6.4/lib/puppet/ssl/certificate_authority/interface.rb
===================================================================
--- puppet-2.6.4.orig/lib/puppet/ssl/certificate_authority/interface.rb 2010-12-01 00:42:02.000000000 +0100
+++ puppet-2.6.4/lib/puppet/ssl/certificate_authority/interface.rb 2011-11-01 10:51:35.086524459 +0100
@@ -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.6.4/lib/puppet/ssl/certificate_authority.rb
===================================================================
--- puppet-2.6.4.orig/lib/puppet/ssl/certificate_authority.rb 2010-12-01 00:42:02.000000000 +0100
+++ puppet-2.6.4/lib/puppet/ssl/certificate_authority.rb 2011-11-01 10:51:35.086524459 +0100
@@ -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'
@@ -25,6 +34,14 @@
end
end
+ class CertificateSigningError < RuntimeError
+ attr_accessor :host
+
+ def initialize(host)
+ @host = host
+ end
+ end
+
class << self
include Puppet::Util::Cacher
@@ -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,15 @@
end
# Generate a new certificate.
- def generate(name)
+ def generate(name, options = {})
raise ArgumentError, "A Certificate already exists for #{name}" if Puppet::SSL::Certificate.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 +141,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 +243,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.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 +290,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 unknown_req.count > 0
+ 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.find(name)
Index: puppet-2.6.4/lib/puppet/ssl/certificate_factory.rb
===================================================================
--- puppet-2.6.4.orig/lib/puppet/ssl/certificate_factory.rb 2010-12-01 00:42:02.000000000 +0100
+++ puppet-2.6.4/lib/puppet/ssl/certificate_factory.rb 2011-11-01 10:51:35.087524489 +0100
@@ -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.6.4/lib/puppet/ssl/certificate.rb
===================================================================
--- puppet-2.6.4.orig/lib/puppet/ssl/certificate.rb 2010-12-01 00:42:02.000000000 +0100
+++ puppet-2.6.4/lib/puppet/ssl/certificate.rb 2011-11-01 10:51:35.087524489 +0100
@@ -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.6.4/lib/puppet/ssl/certificate_request.rb
===================================================================
--- puppet-2.6.4.orig/lib/puppet/ssl/certificate_request.rb 2010-12-01 00:42:02.000000000 +0100
+++ puppet-2.6.4/lib/puppet/ssl/certificate_request.rb 2011-11-01 10:51:35.087524489 +0100
@@ -22,8 +22,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.
@@ -38,6 +42,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)
@@ -55,4 +72,74 @@
ca.autosign
end
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.count == 1
+ raise Puppet::Error, "In #{ext.oid}, expected Set[Array[...]], but found #{ext.value.value.count} 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.count
+ 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.count}"
+ 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.6.4/lib/puppet/ssl/host.rb
===================================================================
--- puppet-2.6.4.orig/lib/puppet/ssl/host.rb 2010-12-01 00:42:02.000000000 +0100
+++ puppet-2.6.4/lib/puppet/ssl/host.rb 2011-11-01 10:51:35.088524519 +0100
@@ -138,11 +138,24 @@
@certificate_request ||= CertificateRequest.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
@certificate_request.save
rescue
@@ -185,7 +198,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.6.4/lib/puppet/sslcertificates/ca.rb
===================================================================
--- puppet-2.6.4.orig/lib/puppet/sslcertificates/ca.rb 2010-12-01 00:42:02.000000000 +0100
+++ /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.6.4/lib/puppet/sslcertificates/certificate.rb
===================================================================
--- puppet-2.6.4.orig/lib/puppet/sslcertificates/certificate.rb 2010-12-01 00:42:02.000000000 +0100
+++ /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.6.4/lib/puppet/sslcertificates/inventory.rb
===================================================================
--- puppet-2.6.4.orig/lib/puppet/sslcertificates/inventory.rb 2010-12-01 00:42:02.000000000 +0100
+++ /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.6.4/lib/puppet/sslcertificates/monkey_patch.rb
===================================================================
--- puppet-2.6.4.orig/lib/puppet/sslcertificates/monkey_patch.rb 2010-11-29 08:42:18.000000000 +0100
+++ /dev/null 1970-01-01 00:00:00.000000000 +0000
@@ -1,6 +0,0 @@
-# This is the file that we use to add indirection to all the SSL Certificate classes.
-
-require 'puppet/indirector'
-
-OpenSSL::PKey::RSA.extend Puppet::Indirector
-OpenSSL::PKey::RSA.indirects :ssl_rsa, :terminus_class => :file
Index: puppet-2.6.4/lib/puppet/sslcertificates/support.rb
===================================================================
--- puppet-2.6.4.orig/lib/puppet/sslcertificates/support.rb 2010-12-01 00:42:02.000000000 +0100
+++ /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.6.4/lib/puppet/sslcertificates.rb
===================================================================
--- puppet-2.6.4.orig/lib/puppet/sslcertificates.rb 2010-12-01 00:42:02.000000000 +0100
+++ /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.6.4/lib/puppet/type/file.rb
===================================================================
--- puppet-2.6.4.orig/lib/puppet/type/file.rb 2010-12-01 00:42:02.000000000 +0100
+++ puppet-2.6.4/lib/puppet/type/file.rb 2011-11-01 10:51:35.090524581 +0100
@@ -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.6.4/lib/puppet/util/command_line/puppetca
===================================================================
--- puppet-2.6.4.orig/lib/puppet/util/command_line/puppetca 2010-12-01 20:19:57.000000000 +0100
+++ puppet-2.6.4/lib/puppet/util/command_line/puppetca 2011-11-01 10:51:35.091524612 +0100
@@ -56,6 +56,10 @@
# Generate a certificate for a named client. A certificate/keypair will be
# generated for each client named on the command line.
#
+# When generate is used the additional `--subject-alt-name` argument can be
+# used. The names, separated by `:`, passed will be added as the
+# subjectAltName of the final certificate.
+#
# help::
# Print this help message
#
@@ -83,6 +87,19 @@
# Sign an outstanding certificate request. Unless '--all' is specified,
# hosts must be listed after all flags.
#
+# Puppet will refuse to sign a CSR that requests a `subjectAltName`
+# extension unless you specify `--allow-subject-alt-name`. This is required
+# because of the critical security risks around allowing `subjectAltName`
+# from client generated certificates.
+#
+# To further enforce security, if `--allow-subject-alt-name` is given Puppet
+# will refuse to sign any certificate that does not have request additional
+# names.
+#
+# Finally, Puppet will still enforce security policy over the
+# `subjectAltName` field, and will refuse to allow unknown values, or
+# wildcards, as part of the certificate.
+#
# verbose::
# Enable verbosity.
#
@@ -98,6 +115,12 @@
# culain.madstop.com
# $ puppet cert -s culain.madstop.com
#
+# Signing a certificate with `subjectAltName` set, which will be requested
+# automatically when you bring up a new master in a distributed CA
+# environment:
+#
+# $ puppet cert --sign --allow-subject-alt-name master12.local
+#
# = Author
#
# Luke Kanies
@@ -106,5 +129,3 @@
#
# Copyright (c) 2005 Puppet Labs, LLC
# Licensed under the GNU Public License
-
-#Puppet::Application[:cert].run
Index: puppet-2.6.4/lib/puppet/util/monkey_patches.rb
===================================================================
--- puppet-2.6.4.orig/lib/puppet/util/monkey_patches.rb 2010-12-01 00:42:02.000000000 +0100
+++ puppet-2.6.4/lib/puppet/util/monkey_patches.rb 2011-11-01 10:51:35.091524612 +0100
@@ -48,3 +48,90 @@
end
end
+class Object
+ # 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
+
+ alias :count :length unless method_defined? :count
+end
+
+
+class Symbol
+ def to_proc
+ Proc.new { |*args| args.shift.__send__(self, *args) }
+ end unless method_defined? :to_proc
+end
+
+module Enumerable
+ # Use *args so we can distinguish no argument from nil.
+ def count(*args)
+ seq = 0
+ if !args.empty?
+ item = args[0]
+ each { |o| seq += 1 if item == o }
+ elsif block_given?
+ each { |o| seq += 1 if yield(o) }
+ else
+ each { seq += 1 }
+ end
+ seq
+ end unless method_defined? :count
+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.6.4/lib/puppet/util/settings.rb
===================================================================
--- puppet-2.6.4.orig/lib/puppet/util/settings.rb 2010-12-01 00:42:02.000000000 +0100
+++ puppet-2.6.4/lib/puppet/util/settings.rb 2011-11-01 10:51:35.091524612 +0100
@@ -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.6.4/spec/integration/defaults_spec.rb
===================================================================
--- puppet-2.6.4.orig/spec/integration/defaults_spec.rb 2010-12-01 00:42:02.000000000 +0100
+++ puppet-2.6.4/spec/integration/defaults_spec.rb 2011-11-01 10:51:35.092524643 +0100
@@ -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.6.4/spec/integration/network/client_spec.rb
===================================================================
--- puppet-2.6.4.orig/spec/integration/network/client_spec.rb 2010-12-01 00:42:02.000000000 +0100
+++ /dev/null 1970-01-01 00:00:00.000000000 +0000
@@ -1,19 +0,0 @@
-#!/usr/bin/env ruby
-
-require File.dirname(__FILE__) + '/../../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.6.4/spec/integration/network/handler_spec.rb
===================================================================
--- puppet-2.6.4.orig/spec/integration/network/handler_spec.rb 2010-12-01 00:42:02.000000000 +0100
+++ puppet-2.6.4/spec/integration/network/handler_spec.rb 2011-11-01 10:51:35.093524673 +0100
@@ -2,7 +2,7 @@
require File.dirname(__FILE__) + '/../../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.6.4/spec/unit/network/client_spec.rb
===================================================================
--- puppet-2.6.4.orig/spec/unit/network/client_spec.rb 2010-12-01 00:42:02.000000000 +0100
+++ /dev/null 1970-01-01 00:00:00.000000000 +0000
@@ -1,45 +0,0 @@
-#!/usr/bin/env ruby
-#
-# Created by Luke Kanies on 2008-3-24.
-# Copyright (c) 2008. All rights reserved.
-
-require File.dirname(__FILE__) + '/../../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.6.4/spec/unit/network/handler/ca_spec.rb
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ puppet-2.6.4/spec/unit/network/handler/ca_spec.rb 2011-11-01 10:51:35.093524673 +0100
@@ -0,0 +1,87 @@
+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
+ cert
+ 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.find(host).should be_nil
+ Puppet::SSL::CertificateRequest.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.6.4/spec/unit/ssl/certificate_authority/interface_spec.rb
===================================================================
--- puppet-2.6.4.orig/spec/unit/ssl/certificate_authority/interface_spec.rb 2010-12-01 00:42:02.000000000 +0100
+++ puppet-2.6.4/spec/unit/ssl/certificate_authority/interface_spec.rb 2011-11-01 10:51:35.094524703 +0100
@@ -32,13 +32,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
@@ -54,23 +54,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" do
- lambda { @class.new(:generate, "other") }.should raise_error(ArgumentError)
+ lambda { @class.new(:generate, :to => "other") }.should raise_error(ArgumentError, /Subjects must be an array or :all; not other/)
end
end
@@ -118,8 +122,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
@@ -150,15 +154,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
@@ -166,8 +179,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)
@@ -183,63 +196,89 @@
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
+ certish = stub('certish', :subject_alt_names => [])
+ Puppet::SSL::Certificate.indirection.stubs(:find).returns certish
+ Puppet::SSL::CertificateRequest.indirection.stubs(:find).returns certish
+
+ @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
+ request = stub 'request', :subject_alt_names => ["DNS:foo", "DNS:bar"]
+ Puppet::SSL::CertificateRequest.indirection.stubs(:find).returns(request)
+
+ 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.6.4/spec/unit/ssl/certificate_authority_spec.rb
===================================================================
--- puppet-2.6.4.orig/spec/unit/ssl/certificate_authority_spec.rb 2010-12-01 00:42:02.000000000 +0100
+++ puppet-2.6.4/spec/unit/ssl/certificate_authority_spec.rb 2011-11-01 10:51:35.095524732 +0100
@@ -200,8 +200,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 @@
@cert.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.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
@cert.expects(:save)
@@ -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.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.6.4/spec/unit/ssl/certificate_factory_spec.rb
===================================================================
--- puppet-2.6.4.orig/spec/unit/ssl/certificate_factory_spec.rb 2010-12-01 00:42:02.000000000 +0100
+++ puppet-2.6.4/spec/unit/ssl/certificate_factory_spec.rb 2011-11-01 10:51:35.096524761 +0100
@@ -5,103 +5,126 @@
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
+ let(:subject) { Puppet::SSL::CertificateFactory }
+ describe "when generating the certificate" do
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_close(Time.now - 24*60*60,1)
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_close(Time.now + ttl,1)
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_close(Time.now + 12,1)
+ 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)
+ exts = cert.extensions.map {|x| x.to_h }.group_by {|x| x["oid"] }
+ exts["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.6.4/spec/unit/ssl/certificate_request_spec.rb
===================================================================
--- puppet-2.6.4.orig/spec/unit/ssl/certificate_request_spec.rb 2010-12-01 00:42:02.000000000 +0100
+++ puppet-2.6.4/spec/unit/ssl/certificate_request_spec.rb 2011-11-01 10:51:35.096524761 +0100
@@ -126,7 +126,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)
@@ -145,6 +145,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)
Index: puppet-2.6.4/spec/unit/ssl/certificate_spec.rb
===================================================================
--- puppet-2.6.4.orig/spec/unit/ssl/certificate_spec.rb 2010-12-01 00:42:02.000000000 +0100
+++ puppet-2.6.4/spec/unit/ssl/certificate_spec.rb 2011-11-01 10:51:35.096524761 +0100
@@ -90,6 +90,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.6.4/spec/unit/ssl/host_spec.rb
===================================================================
--- puppet-2.6.4.orig/spec/unit/ssl/host_spec.rb 2010-12-01 00:42:02.000000000 +0100
+++ puppet-2.6.4/spec/unit/ssl/host_spec.rb 2011-11-01 10:51:35.097524791 +0100
@@ -5,6 +5,8 @@
require 'puppet/ssl/host'
describe Puppet::SSL::Host do
+ include PuppetSpec::Files
+
before do
@class = Puppet::SSL::Host
@host = @class.new("myname")
@@ -64,6 +66,46 @@
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, :save => true, :content => @key)
+ Puppet::SSL::Key.stubs(:new).returns key
+
+ @cr = stub('certificate request', :save => true)
+ Puppet::SSL::CertificateRequest.stubs(:new).returns @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
@@ -377,7 +419,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", {})
@request.expects(:save)
@host.generate_certificate_request.should be_true
@@ -566,7 +608,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
Index: puppet-2.6.4/spec/unit/sslcertificates/ca_spec.rb
===================================================================
--- puppet-2.6.4.orig/spec/unit/sslcertificates/ca_spec.rb 2010-12-01 00:42:02.000000000 +0100
+++ /dev/null 1970-01-01 00:00:00.000000000 +0000
@@ -1,110 +0,0 @@
-#!/usr/bin/env ruby
-require File.dirname(__FILE__) + '/../../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.6.4/spec/unit/util/settings_spec.rb
===================================================================
--- puppet-2.6.4.orig/spec/unit/util/settings_spec.rb 2010-12-01 00:42:03.000000000 +0100
+++ puppet-2.6.4/spec/unit/util/settings_spec.rb 2011-11-01 10:51:35.098524823 +0100
@@ -129,6 +129,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.6.4/test/certmgr/certmgr.rb
===================================================================
--- puppet-2.6.4.orig/test/certmgr/certmgr.rb 2010-12-01 00:42:03.000000000 +0100
+++ /dev/null 1970-01-01 00:00:00.000000000 +0000
@@ -1,308 +0,0 @@
-#!/usr/bin/env ruby
-
-require 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.6.4/test/certmgr/inventory.rb
===================================================================
--- puppet-2.6.4.orig/test/certmgr/inventory.rb 2010-12-01 00:42:03.000000000 +0100
+++ /dev/null 1970-01-01 00:00:00.000000000 +0000
@@ -1,69 +0,0 @@
-#!/usr/bin/env ruby
-
-require 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.6.4/test/certmgr/support.rb
===================================================================
--- puppet-2.6.4.orig/test/certmgr/support.rb 2010-12-01 00:42:03.000000000 +0100
+++ /dev/null 1970-01-01 00:00:00.000000000 +0000
@@ -1,105 +0,0 @@
-#!/usr/bin/env ruby
-
-require 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.6.4/test/language/functions.rb
===================================================================
--- puppet-2.6.4.orig/test/language/functions.rb 2010-12-01 00:42:03.000000000 +0100
+++ puppet-2.6.4/test/language/functions.rb 2011-11-01 10:51:35.099524856 +0100
@@ -4,7 +4,6 @@
require 'puppet'
require 'puppet/parser/parser'
-require 'puppet/network/client'
require 'puppettest'
require 'puppettest/resourcetesting'
Index: puppet-2.6.4/test/language/snippets.rb
===================================================================
--- puppet-2.6.4.orig/test/language/snippets.rb 2010-12-01 00:42:03.000000000 +0100
+++ puppet-2.6.4/test/language/snippets.rb 2011-11-01 10:51:35.099524856 +0100
@@ -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
@@ -66,13 +64,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.6.4/test/lib/puppettest/exetest.rb
===================================================================
--- puppet-2.6.4.orig/test/lib/puppettest/exetest.rb 2010-12-01 00:42:03.000000000 +0100
+++ puppet-2.6.4/test/lib/puppettest/exetest.rb 2011-11-01 10:51:35.099524856 +0100
@@ -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.6.4/test/lib/puppettest/servertest.rb
===================================================================
--- puppet-2.6.4.orig/test/lib/puppettest/servertest.rb 2010-12-01 00:42:03.000000000 +0100
+++ puppet-2.6.4/test/lib/puppettest/servertest.rb 2011-11-01 10:51:35.100524888 +0100
@@ -1,5 +1,4 @@
require 'puppettest'
-require 'puppet/network/http_server/webrick'
module PuppetTest::ServerTest
include PuppetTest
Index: puppet-2.6.4/test/network/client/ca.rb
===================================================================
--- puppet-2.6.4.orig/test/network/client/ca.rb 2010-12-01 00:42:03.000000000 +0100
+++ /dev/null 1970-01-01 00:00:00.000000000 +0000
@@ -1,69 +0,0 @@
-#!/usr/bin/env ruby
-
-require 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.6.4/test/network/client/dipper.rb
===================================================================
--- puppet-2.6.4.orig/test/network/client/dipper.rb 2010-12-01 00:42:03.000000000 +0100
+++ /dev/null 1970-01-01 00:00:00.000000000 +0000
@@ -1,34 +0,0 @@
-#!/usr/bin/env ruby
-
-require 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.6.4/test/network/handler/ca.rb
===================================================================
--- puppet-2.6.4.orig/test/network/handler/ca.rb 2010-12-01 00:42:03.000000000 +0100
+++ /dev/null 1970-01-01 00:00:00.000000000 +0000
@@ -1,273 +0,0 @@
-#!/usr/bin/env ruby
-
-require 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.6.4/test/network/server/mongrel_test.rb
===================================================================
--- puppet-2.6.4.orig/test/network/server/mongrel_test.rb 2010-12-01 00:42:03.000000000 +0100
+++ /dev/null 1970-01-01 00:00:00.000000000 +0000
@@ -1,105 +0,0 @@
-#!/usr/bin/env ruby
-
-require 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
-
- def test_daemonize
- mongrel = mkserver
-
- assert(mongrel.respond_to?(:daemonize), "Mongrel server does not respond to daemonize")
- end
-end
-
Index: puppet-2.6.4/test/network/server/webrick.rb
===================================================================
--- puppet-2.6.4.orig/test/network/server/webrick.rb 2010-12-01 00:42:03.000000000 +0100
+++ /dev/null 1970-01-01 00:00:00.000000000 +0000
@@ -1,128 +0,0 @@
-#!/usr/bin/env ruby
-
-require 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.6.4/test/network/xmlrpc/client.rb
===================================================================
--- puppet-2.6.4.orig/test/network/xmlrpc/client.rb 2010-12-01 00:42:03.000000000 +0100
+++ /dev/null 1970-01-01 00:00:00.000000000 +0000
@@ -1,45 +0,0 @@
-#!/usr/bin/env ruby
-
-require 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.6.4/test/rails/rails.rb
===================================================================
--- puppet-2.6.4.orig/test/rails/rails.rb 2010-12-01 00:42:03.000000000 +0100
+++ puppet-2.6.4/test/rails/rails.rb 2011-11-01 10:51:35.101524918 +0100
@@ -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.6.4/test/ral/type/filesources.rb
===================================================================
--- puppet-2.6.4.orig/test/ral/type/filesources.rb 2010-12-01 00:42:03.000000000 +0100
+++ puppet-2.6.4/test/ral/type/filesources.rb 2011-11-01 10:51:35.102524947 +0100
@@ -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 {