Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
openSUSE:11.4:Update
puppet.import5746
CVE-2011-3872.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File CVE-2011-3872.patch of Package puppet.import5746
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 {
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor