diff options
author | Luke Kanies <luke@madstop.com> | 2005-08-09 22:47:32 +0000 |
---|---|---|
committer | Luke Kanies <luke@madstop.com> | 2005-08-09 22:47:32 +0000 |
commit | bb4b5a544e85e5a56065ebb5dd175fbdea7280d2 (patch) | |
tree | c26ed941fe70d4674a6bea700135dd76f58a8d8c | |
parent | e2e247428faa15c299a5dc7bc802afa4a2dbc74a (diff) | |
download | puppet-bb4b5a544e85e5a56065ebb5dd175fbdea7280d2.tar.gz puppet-bb4b5a544e85e5a56065ebb5dd175fbdea7280d2.tar.xz puppet-bb4b5a544e85e5a56065ebb5dd175fbdea7280d2.zip |
done a lot of work on certificates; all tests except one puppetca test pass
git-svn-id: https://reductivelabs.com/svn/puppet/library/trunk@523 980ebf18-57e1-0310-9a29-db15c13687c0
-rw-r--r-- | lib/puppet.rb | 31 | ||||
-rw-r--r-- | lib/puppet/client.rb | 88 | ||||
-rw-r--r-- | lib/puppet/selector.rb | 1 | ||||
-rwxr-xr-x | lib/puppet/sslcertificates.rb | 791 | ||||
-rw-r--r-- | lib/puppet/transportable.rb | 4 | ||||
-rw-r--r-- | lib/puppet/type.rb | 8 | ||||
-rw-r--r-- | lib/puppet/type/package.rb | 1 | ||||
-rw-r--r-- | lib/puppet/type/pfile.rb | 5 | ||||
-rw-r--r-- | test/client/tc_client.rb | 123 | ||||
-rw-r--r-- | test/other/tc_selector.rb | 7 | ||||
-rwxr-xr-x | test/puppet/tc_defaults.rb | 6 | ||||
-rw-r--r-- | test/types/tc_service.rb | 5 |
12 files changed, 557 insertions, 513 deletions
diff --git a/lib/puppet.rb b/lib/puppet.rb index d499e7596..2fb9ffbbe 100644 --- a/lib/puppet.rb +++ b/lib/puppet.rb @@ -2,6 +2,8 @@ # $Id$ +$VERBOSE = true + require 'singleton' require 'puppet/log' @@ -35,9 +37,9 @@ module Puppet str = @message end - if Puppet[:debug] and @stack - str += @stack - end + #if Puppet[:debug] and @stack + # str += @stack.to_s + #end return str end @@ -57,6 +59,7 @@ module Puppet } # I keep wanting to use Puppet.error + # XXX this isn't actually working right now alias :error :err @defaults = { @@ -70,19 +73,23 @@ module Puppet :logfile => [:logdir, "puppet.log"], :masterlog => [:logdir, "puppetmaster.log"], :checksumfile => [:statedir, "checksums"], - :certdir => [:puppetconf, "certs"], - :rootcert => [:certdir, "ca.crt"], - :rootkey => [:certdir, "ca.key"], - :rootpub => [:certdir, "ca.pub"], - :localcert => [:certdir, "localhost.crt"], - :localkey => [:certdir, "localhost.key"], - :localpub => [:certdir, "localhost.pub"], + :ssldir => [:puppetconf, "ssl"], +# :certdir => [:ssldir, "certs"], +# :publickeydir => [:ssldir, "public_keys"], +# :privatekeydir => [:ssldir, "private_keys"], +# :csrdir => [:ssldir, "requests"], +# :cadir => [:ssldir, "ca"], +# :cacert => [:cadir, "ca.crt"], +# :cakey => [:cadir, "ca.key"], +# :capub => [:cadir, "ca.pub"], +# :localcert => [:ssldir, "localhost.crt"], +# :localkey => [:ssldir, "localhost.key"], +# :localpub => [:ssldir, "localhost.pub"], # and finally the simple answers, :server => "puppet", :rrdgraph => false, :noop => false, - :autosign => false, :parseonly => false, :puppetport => 8139, :masterport => 8140, @@ -182,6 +189,8 @@ module Puppet end end + # XXX this should all be done using puppet objects, not using + # normal mkdir def self.recmkdir(dir,mode = 0755) tmp = dir.sub(/^\//,'') path = [File::SEPARATOR] diff --git a/lib/puppet/client.rb b/lib/puppet/client.rb index 288782193..199e59b29 100644 --- a/lib/puppet/client.rb +++ b/lib/puppet/client.rb @@ -5,10 +5,10 @@ # the available clients require 'puppet' -require 'puppet/function' +require 'puppet/sslcertificates' require 'puppet/type' -#require 'puppet/fact' require 'facter' +require 'openssl' require 'puppet/transaction' require 'puppet/transportable' require 'puppet/metric' @@ -54,7 +54,9 @@ module Puppet class Client include Puppet - attr_accessor :local + attr_accessor :local, :secureinit + attr_reader :fqdn + def Client.facts facts = {} Facter.each { |name,fact| @@ -71,7 +73,7 @@ module Puppet if hash.include?(:Server) case hash[:Server] when String: - if $nonetworking + if $noclientnetworking raise NetworkClientError.new("Networking not available: %s" % $nonetworking) end @@ -94,6 +96,72 @@ module Puppet else raise ClientError.new("Must pass :Server to client") end + + if hash.include?(:FQDN) + @fqdn = hash[:FQDN] + else + hostname = Facter["hostname"].value + domain = Facter["domain"].value + @fqdn = [hostname, domain].join(".") + end + + @secureinit = hash[:NoSecureInit] || true + end + + def initcerts + return unless @secureinit + # verify we've got all of the certs set up and such + + # we are not going to encrypt our key, but we need at a minimum + # a keyfile and a certfile + certfile = File.join(Puppet[:certdir], [@fqdn, "pem"].join(".")) + keyfile = File.join(Puppet[:privatekeydir], [@fqdn, "pem"].join(".")) + publickeyfile = File.join(Puppet[:publickeydir], [@fqdn, "pem"].join(".")) + + [Puppet[:certdir], Puppet[:privatekeydir], Puppet[:csrdir], + Puppet[:publickeydir]].each { |dir| + unless FileTest.exists?(dir) + Puppet.recmkdir(dir, 0770) + end + } + if File.exists?(keyfile) + # load the key + @key = OpenSSL::PKey::RSA.new(File.read(keyfile)) + else + # create a new one and store it + Puppet.info "Creating a new SSL key at %s" % keyfile + @key = OpenSSL::PKey::RSA.new(Puppet[:keylength]) + File.open(keyfile, "w", 0660) { |f| f.print @key.to_pem } + File.open(publickeyfile, "w", 0660) { |f| f.print @key.public_key.to_pem } + + end + + unless File.exists?(certfile) + Puppet.info "Creating a new certificate request for %s" % @fqdn + name = OpenSSL::X509::Name.new([["CN", @fqdn]]) + + @csr = OpenSSL::X509::Request.new + @csr.version = 0 + @csr.subject = name + @csr.public_key = @key.public_key + @csr.sign(@key, OpenSSL::Digest::MD5.new) + + Puppet.info "Requesting certificate" + + cert = @driver.getcert(@csr.to_pem) + + if cert.nil? + raise Puppet::Error, "Failed to get certificate" + end + File.open(certfile, "w", 0660) { |f| f.print cert } + begin + @cert = OpenSSL::X509::Certificate.new(cert) + rescue => detail + raise Puppet::Error.new( + "Invalid certificate: %s" % detail + ) + end + end end def getconfig @@ -181,18 +249,6 @@ module Puppet #self.shutdown end - #def callfunc(name,args) - # Puppet.debug("Calling callfunc on %s" % name) - # if function = Puppet::Function[name] - # #debug("calling function %s" % function) - # value = function.call(args) - # #debug("from %s got %s" % [name,value]) - # return value - # else - # raise "Function '%s' not found" % name - # end - #end - private #def on_init diff --git a/lib/puppet/selector.rb b/lib/puppet/selector.rb index 6bfa1e2dd..50e55fb24 100644 --- a/lib/puppet/selector.rb +++ b/lib/puppet/selector.rb @@ -3,7 +3,6 @@ # $Id$ require 'puppet' -require 'puppet/fact' module Puppet #--------------------------------------------------------------- diff --git a/lib/puppet/sslcertificates.rb b/lib/puppet/sslcertificates.rb index 1f1619a67..13392dedf 100755 --- a/lib/puppet/sslcertificates.rb +++ b/lib/puppet/sslcertificates.rb @@ -8,31 +8,9 @@ require 'puppet' require 'openssl' -require 'getoptlong' module Puppet -module OpenSSL - @@config = "/etc/ssl/openssl.cnf" - - def self.config=(config) - @@config = config - end - - def self.config - return @@config - end - - def self.exec(cmd) - output = %x{#{cmd} 2>&1} - - #puts "\n\n%s\n\n" % cmd - unless $? == 0 - puts cmd - raise output - end - return output - end - +module SSLCertificates def self.mkdir(dir) # this is all a bunch of stupid hackery unless FileTest.exists?(dir) @@ -64,367 +42,309 @@ module OpenSSL Puppet::Type.allclear end - - class Config - include Enumerable - attr_accessor :path - - def [](name) - @sectionhash[name] - end - - def clear - @sectionhash.clear - @sectionary.clear + #def self.mkcert(type, name, days, issuercert, issuername, serial, publickey) + def self.mkcert(hash) + [:type, :name, :days, :issuer, :serial, :publickey].each { |param| + unless hash.include?(param) + raise ArgumentError, "mkcert called without %s" % param + end + } + cert = ::OpenSSL::X509::Certificate.new + from = Time.now + + cert.subject = hash[:name] + if hash[:issuer] + cert.issuer = hash[:issuer].subject + else + cert.issuer = cert.subject end - - def each - @sectionary.each { |section| - yield section - } + cert.not_before = from + cert.not_after = from + (hash[:days] * 24 * 60 * 60) + cert.version = 2 # X509v3 + + cert.public_key = hash[:publickey] + cert.serial = hash[:serial] + + basic_constraint = nil + key_usage = [] + ext_key_usage = [] + + case hash[:type] + when :ca: + basic_constraint = "CA:TRUE" + key_usage.push %w{cRLSign keyCertSign} + when :terminalsubca: + basic_constraint = "CA:TRUE,pathlen:0" + key_usage %w{cRLSign keyCertSign} + when :server: + basic_constraint = "CA:FALSE" + key_usage << %w{digitalSignature keyEncipherment} + ext_key_usage << "serverAuth" + 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} + else + raise "unknonwn cert type '%s'" % hash[:type] end - def initialize(path) - @path = path + key_usage.flatten! + ext_key_usage.flatten! - @sectionhash = {} - @sectionary = [] + ef = ::OpenSSL::X509::ExtensionFactory.new - # default to reading the config in - if FileTest.exists?(@path) - self.read - end + if hash[:issuer] + ef.issuer_certificate = hash[:issuer] + else + ef.issuer_certificate = cert end - def newsection(name) - sect = Section.new(name) - @sectionhash[sect.name] = sect - @sectionary.push sect - return sect - end - - def read - self.clear - section = self.newsection(:HEAD) - comments = "" - File.open(@path) { |f| - f.readlines.each { |line| - case line - when /^\s*#/: comments += line - when /^\[ (\w+) \]$/: - name = $1 - section = self.newsection(name) - when /^(\w+)\s*=\s*([^#]+)(#.*)*$/ - section.newparam($1, $2, comments) - comments = "" - when /^(\d+\.\w+)\s*=\s*([^#]+)(#.*)*$/ - section.newparam($1, $2, comments) - comments = "" - when /^\s*$/: # nothing - comments += line - else - puts "Could not match line %s" % line.inspect - end - } - } - end + ef.subject_certificate = cert - def to_s - @sectionary.collect { |section| - section.to_s - }.join("\n") + "\n" + ex = [] + ex << ef.create_extension("basicConstraints", basic_constraint, true) + ex << ef.create_extension("nsComment", + "Ruby/OpenSSL Generated Certificate") + ex << ef.create_extension("subjectKeyIdentifier", "hash") + #ex << ef.create_extension("nsCertType", "client,email") + unless key_usage.empty? then + ex << ef.create_extension("keyUsage", key_usage.join(",")) end - - def write - File.open(@path, "w") { |f| - f.print self.to_s - } + #ex << ef.create_extension("authorityKeyIdentifier", + # "keyid:always,issuer:always") + #ex << ef.create_extension("authorityKeyIdentifier", "keyid:always") + unless ext_key_usage.empty? then + ex << ef.create_extension("extendedKeyUsage", ext_key_usage.join(",")) end - class Section - include Enumerable - attr_accessor :name - - def [](name) - @paramhash[name] - end - - def []=(name,value) - if @paramhash.include?(name) - @paramhash[name] = value - else - self.newparam(name, value) - end - end - - def each - @paramary.each { |param| - yield param - } - end - - def include?(name) - @paramhash.include?(name) - end - - def initialize(name) - @name = name - - @paramhash = {} - @paramary = [] - end - def newparam(name, value, comments = nil) - if @paramhash.include?(name) - raise "%s already has a param %s" % [@name, name] - end - obj = Parameter.new(name, value, comments) - @paramhash[name] = obj - @paramary.push obj - end + #if @ca_config[:cdp_location] then + # ex << ef.create_extension("crlDistributionPoints", + # @ca_config[:cdp_location]) + #end - def to_s - str = "" - unless @name == :HEAD - str += "[ #{@name} ]\n" - end - return str + (@paramary.collect { |param| - param.to_s - }.join("\n")) - end - end + #if @ca_config[:ocsp_location] then + # ex << ef.create_extension("authorityInfoAccess", + # "OCSP;" << @ca_config[:ocsp_location]) + #end + cert.extensions = ex - class Parameter - attr_accessor :name, :value, :comments + #cmd = "#{ossl} req -nodes -new -x509 -keyout %s -out %s -config %s" % + # [@key, certfile, Puppet::SSLCertificates.config] - def initialize(name, value, comments) - @name = name - @value = value.sub(/\s+$/,'') - @comments = comments - end + # write the cert out + #File.open(certfile, "w") { |f| f << cert.to_pem } - def to_s - if comments and comments != "" - return "%s%s = %s" % [@comments, @name, @value] - else - return "%s = %s" % [@name, @value] - end - end - end + return cert end + class CA attr_accessor :keyfile, :file, :config, :dir, :cert - @@DEFAULTCONF = %{# -# Default configuration to use when one is not provided on the command line. -# -[ ca ] -default_ca = local_ca - -# -# Default location of directories and files needed to generate -# certificates. -# -[ local_ca ] -certificate = BASEDIR/cacert.pem -database = BASEDIR/index.txt -new_certs_dir = BASEDIR/certs -private_key = BASEDIR/private/cakey.pem -serial = BASEDIR/serial - -# -# Default expiration and encryption policies for certificates. -# -default_crl_days = 365 -default_days = 1825 -default_md = md5 - -policy = local_ca_policy -x509_extensions = local_ca_extensions - -# -# Default policy to use when generating server certificates. The following -# fields must be defined in the server certificate. -# -[ local_ca_policy ] -commonName = supplied -stateOrProvinceName = supplied -countryName = supplied -emailAddress = supplied -organizationName = supplied -organizationalUnitName = supplied - -# -# x509 extensions to use when generating server certificates. -# -[ local_ca_extensions ] -subjectAltName = DNS:altname.somewhere.com -basicConstraints = CA:false -nsCertType = server - -# -# The default policy to use when -# generating the root certificate. -# -[ req ] -default_bits = 2048 -default_keyfile = BASEDIR/private/cakey.pem -default_md = md5 - -prompt = no -distinguished_name = root_ca_distinguished_name -x509_extensions = root_ca_extensions + @@params = [ + :certdir, + :publickeydir, + :privatekeydir, + :cadir, + :cakey, + :cacert, + :capass, + :capub, + :csrdir, + :signeddir, + :serial, + :privatedir, + :ca_crl_days, + :ca_days, + :ca_md, + :req_bits, + :keylength, + :autosign + ] + + @@defaults = { + :certdir => [:ssldir, "certs"], + :publickeydir => [:ssldir, "public_keys"], + :privatekeydir => [:ssldir, "private_keys"], + :cadir => [:ssldir, "ca"], + :cacert => [:cadir, "ca_crt.pem"], + :cakey => [:cadir, "ca_key.pem"], + :capub => [:cadir, "ca_pub.pem"], + :csrdir => [:cadir, "requests"], + :signeddir => [:cadir, "signed"], + :capass => [:cadir, "ca.pass"], + :serial => [:cadir, "serial"], + :privatedir => [:ssldir, "private"], + :passfile => [:privatedir, "password"], + :autosign => [:ssldir, "autosign"], + :ca_crl_days => 365, + :ca_days => 1825, + :ca_md => "md5", + :req_bits => 2048, + :keylength => 1024, + } -# -# Root Certificate Authority distin- guished name. Change these fields to -# your local environment. -# -[ root_ca_distinguished_name ] -commonName = Reductive Labs Root Certificate Authority -stateOrProvinceName = Some State -countryName = US -emailAddress = root@somename.somewhere.com -organizationName = Root Certificate Authority + @@params.each { |param| + Puppet.setdefault(param,@@defaults[param]) + } -[ root_ca_extensions ] -basicConstraints = CA:true -} - def init - self.mkconfig - self.mkcert + def host2csrfile(hostname) + File.join(Puppet[:csrdir], [hostname, "pem"].join(".")) end - def initialize(hash) - unless hash.include?(:dir) - raise "You must specify the base directory for the CA" - end - @dir = hash[:dir] - - @file = hash[:file] || File.join(@dir, "ca.cnf") - - unless FileTest.exists?(@dir) - Puppet::OpenSSL.mkdir(@dir) - end - - @config = self.getconfig - - @certfile = @config["local_ca"]["certificate"].value.chomp - @keyfile = @config["local_ca"]["private_key"].value.chomp + # this stores signed certs in a directory unrelated to + # normal client certs + def host2certfile(hostname) + File.join(Puppet[:signeddir], [hostname, "pem"].join(".")) + end - certdir = @config["local_ca"]["new_certs_dir"].value.chomp - Puppet::OpenSSL.mkdir(certdir) - - @serial = @config["local_ca"]["serial"].value.chomp - unless FileTest.exists?(@serial) - unless FileTest.exists?(File.dirname(@serial)) - Puppet::OpenSSL.mkdir(File.dirname(@serial)) - end - File.open(@serial, "w", 0600) { |f| f.puts "01" } - end - - database = @config["local_ca"]["database"].value.chomp - unless FileTest.exists?(database) - unless FileTest.exists?(File.dirname(database)) - Puppet::OpenSSL.mkdir(File.dirname(database)) - end - File.open(database, "w", 0600) { |f| f.print "" } - end + def thing2name(thing) + thing.subject.to_a.find { |ary| + ary[0] == "CN" + }[1] + end - @days = @config["local_ca"]["default_crl_days"].value.to_i || 365 - - unless @certfile - raise "could not retrieve cert path" - end + def initialize(hash = {}) + self.setconfig(hash) - unless @keyfile - raise "could not retrieve key file" + self.getcert + unless FileTest.exists?(@config[:serial]) + File.open(@config[:serial], "w") { |f| + f << "%04X" % 0 + } end - if hash.include?(:password) - @password = hash[:password] - else - @passfile = hash[:passfile] || File.join(@dir, "private", "phrase") - @password = self.genpass + if Puppet[:capass] and ! FileTest.exists?(Puppet[:capass]) + self.genpass end - - @cert = self.getcert end def genpass pass = "" 20.times { pass += (rand(74) + 48).chr } - unless @passfile + unless @config[:capass] raise "No passfile" end - Puppet::OpenSSL.mkdir(File.dirname(@passfile)) - File.open(@passfile, "w", 0600) { |f| f.print pass } + Puppet::SSLCertificates.mkdir(File.dirname(@config[:capass])) + File.open(@config[:capass], "w", 0600) { |f| f.print pass } return pass end def getcert - if FileTest.exists?(@certfile) - return Puppet::OpenSSL::Certificate.new( - :name => "CAcert", - :cert => @certfile, - :encrypt => @passfile, - :key => @keyfile + if FileTest.exists?(@config[:cacert]) + @cert = OpenSSL::X509::Certificate.new( + File.read(@config[:cacert]) ) else - return self.mkrootcert + self.mkrootcert end end - def getconfig - if FileTest.exists?(@file) - return Puppet::OpenSSL::Config.new(@file) - else - return self.mkconfig + def getclientcsr(host) + csrfile = host2csrfile(host) + unless File.exists?(csrfile) + return nil end + + return OpenSSL::X509::Request.new(File.read(csrfile)) + end + + def getclientcert(host) + certfile = host2certfile(host) + unless File.exists?(certfile) + return nil + end + + return OpenSSL::X509::Certificate.new(File.read(certfile)) + end + + def list + return Dir.entries(Puppet[:csrdir]).reject { |file| + file =~ /^\.+$/ + }.collect { |file| + file.sub(/\.pem$/, '') + } end def mkrootcert cert = Certificate.new( :name => "CAcert", - :cert => @certfile, - :encrypt => @passfile, - :key => @keyfile, + :cert => @config[:cacert], + :encrypt => @config[:passfile], + :key => @config[:cakey], :selfsign => true, :length => 1825 ) @cert = cert.mkselfsigned + File.open(@config[:cacert], "w", 0660) { |f| + f.puts @cert.to_pem + } @key = cert.key - return cert end - def mkconfig - File.open(@file, "w") { |f| f.print @@DEFAULTCONF } + def removeclientcsr(host) + csrfile = host2csrfile(host) + unless File.exists?(csrfile) + raise Puppet::Error, "No certificate request for %s" % host + end - config = Puppet::OpenSSL::Config.new(@file) + File.unlink(csrfile) + end - config.each { |section| - section.each { |param| - value = param.value.sub(/BASEDIR/, @dir) - param.value = value - } + def setconfig(hash) + @config = {} + @@params.each { |param| + if hash.include?(param) + begin + @config[param] = hash[param] + Puppet[param] = hash[param] + hash.delete(param) + rescue => detail + puts detail + exit + end + else + begin + @config[param] = Puppet[param] + rescue => detail + puts detail + exit + end + end } - config.write + if hash.include?(:password) + @config[:password] = hash[:password] + hash.delete(:password) + end + + if hash.length > 0 + raise ArgumentError, "Unknown parameters %s" % hash.keys.join(",") + end - return config + [:cadir, :csrdir, :signeddir].each { |dir| + unless @config[dir] + raise "%s is undefined" % dir + end + unless FileTest.exists?(@config[dir]) + Puppet::SSLCertificates.mkdir(@config[dir]) + end + } end - def sign(cert) - unless cert.is_a?(Puppet::OpenSSL::Certificate) - raise "CA#sign only accepts Puppet::OpenSSL::Certificate objects" + def sign(csr) + unless csr.is_a?(OpenSSL::X509::Request) + raise "CA#sign only accepts OpenSSL::X509::Request objects, not %s" % + csr.class end - csr = ::OpenSSL::X509::Request.new( - File.read(cert.csrfile) - ) - unless csr.verify(csr.public_key) raise "CSR sign verification failed" end @@ -432,39 +352,66 @@ basicConstraints = CA:true # i should probably check key length... # read the ca cert in - if cert.exists? - raise "Cannot sign existing certificates" - end - cacert = ::OpenSSL::X509::Certificate.new( - File.read(@certfile) + File.read(@config[:cacert]) ) ca_keypair = ::OpenSSL::PKey::RSA.new( - File.read(@keyfile), @password + File.read(@config[:cakey]), @config[:password] ) - serial = File.read(@serial).chomp.hex - newcert = cert.mkcert( - cacert, cacert.subject, serial, ca_keypair.public_key + serial = File.read(@config[:serial]).chomp.hex + newcert = SSLCertificates.mkcert( + :type => :server, + :name => csr.subject, + :days => @config[:ca_days], + :issuer => cacert, + :serial => serial, + :publickey => ca_keypair.public_key ) # increment the serial - File.open(@serial, "w") { |f| + File.open(@config[:serial], "w") { |f| f << "%04X" % (serial + 1) } newcert.sign(ca_keypair, ::OpenSSL::Digest::SHA1.new) - File.open(cert.certfile, "w", 0644) { |f| - f << newcert.to_pem - } + self.storeclientcert(newcert) + return newcert end + + def storeclientcsr(csr) + host = thing2name(csr) + + csrfile = host2csrfile(host) + if File.exists?(csrfile) + raise Puppet::Error, "Certificate request for %s already exists" % host + end + + File.open(csrfile, "w", 0660) { |f| + f.print csr.to_pem + } + end + + def storeclientcert(cert) + host = thing2name(cert) + + certfile = host2certfile(host) + if File.exists?(certfile) + Puppet.notice "Overwriting signed certificate for %s" % host + end + + File.open(certfile, "w", 0660) { |f| + f.print cert.to_pem + } + end + end class Certificate - attr_accessor :certfile, :keyfile, :name, :dir, :hash, :csrfile, :type + attr_accessor :certfile, :keyfile, :name, :dir, :hash, :type attr_accessor :key, :cert, :csr @@params2names = { @@ -482,13 +429,13 @@ basicConstraints = CA:true end def delete - [@certfile,@keyfile,@csrfile].each { |file| + [@certfile,@keyfile].each { |file| if FileTest.exists?(file) File.unlink(file) end } - if @hash + if defined? @hash and @hash if FileTest.symlink?(@hash) File.unlink(@hash) end @@ -496,7 +443,7 @@ basicConstraints = CA:true end def exists? - FileTest.exists?(@certfile) + return FileTest.exists?(@certfile) end def getkey @@ -521,26 +468,39 @@ basicConstraints = CA:true end @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] - unless hash.include?(:dir) - raise "You must specify the directory in which to store certs" - end + @dir = hash[:dir] || Puppet[:certdir] @certfile = File.join(@dir, @name) end unless FileTest.directory?(@dir) - Puppet::OpenSSL.mkdir(@dir) + Puppet::SSLCertificates.mkdir(@dir) end unless @certfile =~ /\.pem$/ @certfile += ".pem" end - @keyfile = hash[:key] || File.join(@dir, @name + "_key.pem") - @csrfile = hash[:csr] || File.join(@dir, @name + "_csr.pem") + @keyfile = hash[:key] || File.join( + Puppet[:privatekeydir], [@name,"pem"].join(".") + ) + unless FileTest.directory?(@dir) + Puppet::SSLCertificates.mkdir(@dir) + end + + [@keyfile].each { |file| + dir = File.dirname(file) + + unless FileTest.directory?(dir) + Puppet::SSLCertificates.mkdir(dir) + end + } + @days = hash[:length] || 365 @selfsign = hash[:selfsign] || false @encrypt = hash[:encrypt] || false @@ -583,109 +543,9 @@ basicConstraints = CA:true end end - def mkcert(issuercert, issuername, serial, publickey) - unless issuercert or @selfsign - raise "Certs must either have an issuer or must be self-signed" - end - - if self.exists? - raise "Cannot replace existing certificate" - end - - @cert = ::OpenSSL::X509::Certificate.new - from = Time.now - - @cert.subject = self.certname - @cert.issuer = issuername - @cert.not_before = from - @cert.not_after = from + (@days * 24 * 60 * 60) - @cert.version = 2 # X509v3 - - @cert.public_key = publickey - @cert.serial = serial - - basic_constraint = nil - key_usage = [] - ext_key_usage = [] - - case @type - when :ca: - basic_constraint = "CA:TRUE" - key_usage.push %w{cRLSign keyCertSign} - when :terminalsubca: - basic_constraint = "CA:TRUE,pathlen:0" - key_usage %w{cRLSign keyCertSign} - when :server: - basic_constraint = "CA:FALSE" - key_usage << %w{digitalSignature keyEncipherment} - ext_key_usage << "serverAuth" - 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} - else - raise "unknonwn cert type '%s'" % @type - end - - key_usage.flatten! - ext_key_usage.flatten! - - ef = ::OpenSSL::X509::ExtensionFactory.new - - if issuercert - ef.issuer_certificate = issuercert - else - ef.issuer_certificate = @cert - end - - ef.subject_certificate = @cert - - ex = [] - ex << ef.create_extension("basicConstraints", basic_constraint, true) - ex << ef.create_extension("nsComment", - "Ruby/OpenSSL Generated Certificate") - ex << ef.create_extension("subjectKeyIdentifier", "hash") - #ex << ef.create_extension("nsCertType", "client,email") - unless key_usage.empty? then - ex << ef.create_extension("keyUsage", key_usage.join(",")) - end - #ex << ef.create_extension("authorityKeyIdentifier", - # "keyid:always,issuer:always") - #ex << ef.create_extension("authorityKeyIdentifier", "keyid:always") - unless ext_key_usage.empty? then - ex << ef.create_extension("extendedKeyUsage", ext_key_usage.join(",")) - end - - - #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 - - #cmd = "#{ossl} req -nodes -new -x509 -keyout %s -out %s -config %s" % - # [@key, @certfile, Puppet::OpenSSL.config] - - @cert.sign(@key, ::OpenSSL::Digest::SHA1.new) if @selfsign - - # write the cert out - File.open(@certfile, "w") { |f| f << @cert.to_pem } - - return @cert - end - # this only works for servers, not for users def mkcsr - unless @key + unless defined? @key and @key self.getkey end @@ -697,14 +557,16 @@ basicConstraints = CA:true @csr.public_key = @key.public_key @csr.sign(@key, ::OpenSSL::Digest::MD5.new) - File.open(@csrfile, "w") { |f| - f << @csr.to_pem - } + #File.open(@csrfile, "w") { |f| + # f << @csr.to_pem + #} + return @csr end def mkhash - hash = Puppet::OpenSSL.exec("openssl x509 -noout -hash -in %s" % @certfile).chomp + hash = "%x" % @cert.issuer.hash + path = nil 10.times { |i| path = File.join(@dir, "%s.%s" % [hash, i]) if FileTest.exists?(path) @@ -728,6 +590,8 @@ basicConstraints = CA:true @hash = path break } + + return path end def mkkey @@ -754,12 +618,24 @@ basicConstraints = CA:true end def mkselfsigned - unless @key + unless defined? @key and @key self.getkey end - self.mkcert(nil, self.certname, 0x0, @key.public_key) -# self.mkhash + if defined? @cert and @cert + raise Puppet::Error, "Cannot replace existing certificate" + end + + @cert = SSLCertificates.mkcert( + :type => :server, + :name => self.certname, + :days => @days, + :issuer => nil, + :serial => 0x0, + :publickey => @key.public_key + ) + + @cert.sign(@key, ::OpenSSL::Digest::SHA1.new) if @selfsign end def subject(string = false) @@ -777,6 +653,31 @@ basicConstraints = CA:true return subj end end + + def write + files = { + @certfile => @cert, + @keyfile => @key, + } + #@csrfile => @csr + + files.each { |file,thing| + if defined? thing and thing + if FileTest.exists?(file) + newtext = File.open(file) { |f| f.read } + if newtext != thing.to_pem + raise "Cannot replace existing %s" % thing.class + else + next + end + end + + File.open(file, "w", 0660) { |f| f.print thing.to_pem } + end + } + + self.mkhash + end end end end diff --git a/lib/puppet/transportable.rb b/lib/puppet/transportable.rb index eab0e9a53..ebabf3a27 100644 --- a/lib/puppet/transportable.rb +++ b/lib/puppet/transportable.rb @@ -54,7 +54,7 @@ module Puppet if type = Puppet::Type.type(self.type) retobj = type.new(self) else - raise "Could not find object type %s" % self.type + raise Puppet::Error.new("Could not find object type %s" % self.type) end return retobj @@ -143,6 +143,7 @@ module Puppet Puppet.err "Failed to create %s %s: %s" % [child.type,child.name,except.message] if Puppet[:debug] + puts child.inspect puts except.stack end next @@ -150,6 +151,7 @@ module Puppet Puppet.err "Failed to create %s %s: %s" % [child.type,child.name,except.message] if Puppet[:debug] + puts child.inspect puts caller end next diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index c6b9dc5b1..cacc78c28 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -701,7 +701,13 @@ class Type < Puppet::Element order.flatten.each { |name| if hash.include?(name) - self[name] = hash[name] + begin + self[name] = hash[name] + rescue => detail + raise Puppet::DevError.new( + "Could not set %s on %s" % [name, self.class] + ) + end hash.delete name end } diff --git a/lib/puppet/type/package.rb b/lib/puppet/type/package.rb index 77c866c08..4b618d020 100644 --- a/lib/puppet/type/package.rb +++ b/lib/puppet/type/package.rb @@ -3,7 +3,6 @@ # $Id$ require 'puppet/type/state' -require 'puppet/fact' module Puppet class PackageError < Puppet::Error; end diff --git a/lib/puppet/type/pfile.rb b/lib/puppet/type/pfile.rb index fd5bc74b0..c0e05f5a8 100644 --- a/lib/puppet/type/pfile.rb +++ b/lib/puppet/type/pfile.rb @@ -444,7 +444,9 @@ module Puppet if stat = @parent.stat(true) self.is = stat.mode & 007777 unless defined? @fixed - self.dirfix + if defined? @should and @should + self.dirfix + end end else self.is = -1 @@ -503,7 +505,6 @@ module Puppet end def should=(value) - require 'puppet/fact' method = nil gid = nil gname = nil diff --git a/test/client/tc_client.rb b/test/client/tc_client.rb index 33f40dcf0..2ddf53c98 100644 --- a/test/client/tc_client.rb +++ b/test/client/tc_client.rb @@ -7,36 +7,109 @@ end require 'puppet' require 'puppet/client' -#require 'puppet/server' -require 'puppet/fact' +require 'puppet/server' require 'test/unit' require 'puppettest.rb' # $Id$ class TestClient < Test::Unit::TestCase -# def test_local -# client = nil -# server = nil -# assert_nothing_raised() { -# server = Puppet::Master.new( -# :File => file, -# :Local => true -# ) -# } -# assert_nothing_raised() { -# client = Puppet::Client.new(:Server => server) -# } -# -# facts = %w{operatingsystem operatingsystemrelease} -# facts.each { |fact| -# assert_equal( -# Puppet::Fact[fact], -# client.callfunc("fact",fact) -# ) -# } -# end - - def test_files + def setup + Puppet[:loglevel] = :debug if __FILE__ == $0 + @@tmpfiles = [] + end + + def teardown + Puppet::Type.allclear + @@tmpfiles.each { |f| + if FileTest.exists?(f) + system("rm -rf %s" % f) + end + } + end + + def test_sslInitWithAutosigningLocalServer + Puppet[:autosign] = true + Puppet[:ssldir] = "/tmp/puppetclientcertests" + @@tmpfiles.push Puppet[:ssldir] + + file = File.join($puppetbase, "examples", "code", "head") + + server = nil + assert_nothing_raised { + server = Puppet::Master.new( + :File => file, + :Local => true, + :CA => true + ) + } + client = nil + assert_nothing_raised { + client = Puppet::Client.new(:Server => server) + } + assert_nothing_raised { + client.initcerts + } + + certfile = File.join(Puppet[:certdir], [client.fqdn, "pem"].join(".")) + keyfile = File.join(Puppet[:privatekeydir], [client.fqdn, "pem"].join(".")) + publickeyfile = File.join(Puppet[:publickeydir], [client.fqdn, "pem"].join(".")) + + assert(File.exists?(keyfile)) + assert(File.exists?(certfile)) + assert(File.exists?(publickeyfile)) + end + + def test_sslInitWithNonsigningLocalServer + Puppet[:autosign] = false + Puppet[:ssldir] = "/tmp/puppetclientcertests" + @@tmpfiles.push Puppet[:ssldir] + + file = File.join($puppetbase, "examples", "code", "head") + + server = nil + assert_nothing_raised { + server = Puppet::Master.new( + :File => file, + :Local => true, + :CA => true + ) + } + client = nil + assert_nothing_raised { + client = Puppet::Client.new(:Server => server) + } + certfile = File.join(Puppet[:certdir], [client.fqdn, "pem"].join(".")) + assert_raise(Puppet::Error) { + client.initcerts + } + assert(! File.exists?(certfile)) + + ca = nil + assert_nothing_raised { + ca = Puppet::SSLCertificates::CA.new() + } + + + csr = nil + assert_nothing_raised { + csr = ca.getclientcsr(client.fqdn) + } + + assert(csr) + + cert = nil + assert_nothing_raised { + cert = ca.sign(csr) + File.open(certfile, "w") { |f| f.print cert.to_pem } + } + + # this time it should get the cert correctly + assert_nothing_raised { + client.initcerts + } + + # this isn't a very good test, since i just wrote the file out + assert(File.exists?(certfile)) end end diff --git a/test/other/tc_selector.rb b/test/other/tc_selector.rb index 7c8ffbd68..47e0d9ff5 100644 --- a/test/other/tc_selector.rb +++ b/test/other/tc_selector.rb @@ -5,14 +5,15 @@ if __FILE__ == $0 end require 'puppet/selector' +require 'facter' require 'test/unit' # $Id$ class TestSelector < Test::Unit::TestCase def setup - @os = Puppet::Fact["operatingsystem"] - @hostname = Puppet::Fact["hostname"] + @os = Facter["operatingsystem"].value + @hostname = Facter["hostname"].value Puppet[:loglevel] = :debug if __FILE__ == $0 end @@ -22,7 +23,7 @@ class TestSelector < Test::Unit::TestCase assert_nothing_raised() { selector = Puppet::Selector.new { |select| select.add("value1") { - Puppet::Fact["hostname"] == @hostname + Facter["hostname"].value == @hostname } } } diff --git a/test/puppet/tc_defaults.rb b/test/puppet/tc_defaults.rb index 06edd7873..0f46e6496 100755 --- a/test/puppet/tc_defaults.rb +++ b/test/puppet/tc_defaults.rb @@ -10,9 +10,9 @@ require 'test/unit' # $Id$ class TestPuppetDefaults < Test::Unit::TestCase - @@dirs = %w{rrddir puppetconf puppetvar logdir statedir certdir bucketdir} - @@files = %w{logfile checksumfile localcert localkey localpub - rootcert rootkey rootpub manifest masterlog} + @@dirs = %w{rrddir puppetconf puppetvar logdir statedir} + @@files = %w{logfile checksumfile + manifest masterlog} @@normals = %w{puppetport masterport server} @@booleans = %w{rrdgraph noop} def testStringOrParam diff --git a/test/types/tc_service.rb b/test/types/tc_service.rb index a46f158a1..4bbc37594 100644 --- a/test/types/tc_service.rb +++ b/test/types/tc_service.rb @@ -33,6 +33,7 @@ class TestService < Test::Unit::TestCase def teardown Puppet::Type.allclear + Kernel.system("pkill sleeper") end def test_process_start @@ -83,8 +84,4 @@ class TestService < Test::Unit::TestCase ) } end - - def teardown - Kernel.system("pkill sleeper") - end end |