diff options
author | Brice Figureau <brice-puppet@daysofwonder.com> | 2009-12-29 15:27:54 +0100 |
---|---|---|
committer | James Turnbull <james@lovedthanlost.net> | 2010-01-19 08:37:23 +1100 |
commit | 3e9677f00a09d0249713ed2fa503e42b07f6d978 (patch) | |
tree | 0b99bb4cd9039bb220ee75f2520b37920a6b7628 | |
parent | 91c44b439794a87111ab1a0726a2ad08981c839e (diff) | |
download | puppet-3e9677f00a09d0249713ed2fa503e42b07f6d978.tar.gz puppet-3e9677f00a09d0249713ed2fa503e42b07f6d978.tar.xz puppet-3e9677f00a09d0249713ed2fa503e42b07f6d978.zip |
Feature #2839 - fingerprint certificate
This patch adds several things:
* certificate fingerprinting in --list mode
* a puppetca action called "--fingerprint" to display fingerprints
of given certificates (or all including CSR)
* a --fingerprint puppetd option to display client certificates
* each time a CSR is generated, its fingerprint is displayed in the log
It is also possible to use --digest in puppetca and puppetd to specify a specific digest
algorithm.
Signed-off-by: Brice Figureau <brice-puppet@daysofwonder.com>
-rw-r--r-- | lib/puppet/application/puppetca.rb | 8 | ||||
-rw-r--r-- | lib/puppet/application/puppetd.rb | 33 | ||||
-rw-r--r-- | lib/puppet/ssl/base.rb | 17 | ||||
-rw-r--r-- | lib/puppet/ssl/certificate_authority.rb | 9 | ||||
-rw-r--r-- | lib/puppet/ssl/certificate_authority/interface.rb | 28 | ||||
-rw-r--r-- | lib/puppet/ssl/certificate_request.rb | 2 | ||||
-rwxr-xr-x | sbin/puppetca | 11 | ||||
-rwxr-xr-x | sbin/puppetd | 23 | ||||
-rw-r--r-- | spec/unit/application/puppetca.rb | 15 | ||||
-rwxr-xr-x | spec/unit/application/puppetd.rb | 75 | ||||
-rwxr-xr-x | spec/unit/ssl/base.rb | 40 | ||||
-rwxr-xr-x | spec/unit/ssl/certificate_authority.rb | 37 | ||||
-rwxr-xr-x | spec/unit/ssl/certificate_authority/interface.rb | 114 | ||||
-rwxr-xr-x | spec/unit/ssl/certificate_request.rb | 14 |
14 files changed, 367 insertions, 59 deletions
diff --git a/lib/puppet/application/puppetca.rb b/lib/puppet/application/puppetca.rb index adc1a6ff5..7362f2a18 100644 --- a/lib/puppet/application/puppetca.rb +++ b/lib/puppet/application/puppetca.rb @@ -6,7 +6,7 @@ Puppet::Application.new(:puppetca) do should_parse_config - attr_accessor :mode, :all, :ca + attr_accessor :mode, :all, :ca, :digest def find_mode(opt) modes = Puppet::SSL::CertificateAuthority::Interface::INTERFACE_METHODS @@ -22,6 +22,10 @@ Puppet::Application.new(:puppetca) do @all = true end + option("--digest DIGEST") do |arg| + @digest = arg + end + option("--debug", "-d") do |arg| Puppet::Util::Log.level = :debug end @@ -44,7 +48,7 @@ Puppet::Application.new(:puppetca) do end begin @ca.apply(:revoke, :to => hosts) if @mode == :destroy - @ca.apply(@mode, :to => hosts) + @ca.apply(@mode, :to => hosts, :digest => @digest) rescue => detail puts detail.backtrace if Puppet[:trace] puts detail.to_s diff --git a/lib/puppet/application/puppetd.rb b/lib/puppet/application/puppetd.rb index c99b9eaff..ed2c450ee 100644 --- a/lib/puppet/application/puppetd.rb +++ b/lib/puppet/application/puppetd.rb @@ -9,7 +9,7 @@ Puppet::Application.new(:puppetd) do should_parse_config - attr_accessor :explicit_waitforcert, :args, :agent, :daemon + attr_accessor :explicit_waitforcert, :args, :agent, :daemon, :host preinit do # Do an initial trap, so that cancels don't get a stack trace. @@ -30,7 +30,9 @@ Puppet::Application.new(:puppetd) do :disable => false, :client => true, :fqdn => nil, - :serve => [] + :serve => [], + :digest => :MD5, + :fingerprint => false, }.each do |opt,val| options[opt] = val end @@ -49,6 +51,9 @@ Puppet::Application.new(:puppetd) do option("--test","-t") option("--verbose","-v") + option("--fingerprint") + option("--digest DIGEST") + option("--serve HANDLER", "-s") do |arg| if Puppet::Network::Handler.handler(arg) options[:serve] << arg.to_sym @@ -92,10 +97,20 @@ Puppet::Application.new(:puppetd) do end dispatch do + return :fingerprint if options[:fingerprint] return :onetime if options[:onetime] return :main end + command(:fingerprint) do + unless cert = host.certificate || host.certificate_request + $stderr.puts "Fingerprint asked but no certificate nor certificate request have yet been issued" + exit(1) + return + end + Puppet.notice cert.fingerprint(options[:digest]) + end + command(:onetime) do unless options[:client] $stderr.puts "onetime is specified but there is no client" @@ -220,10 +235,10 @@ Puppet::Application.new(:puppetd) do Puppet.settings.use :main, :puppetd, :ssl - # We need to specify a ca location for things to work, but - # until the REST cert transfers are working, it needs to - # be local. - Puppet::SSL::Host.ca_location = :remote + # We need to specify a ca location for things to work + # in fingerprint mode we just need access to the local files and + # we don't need a ca. + Puppet::SSL::Host.ca_location = options[:fingerprint] ? :none : :remote Puppet::Transaction::Report.terminus_class = :rest @@ -246,8 +261,10 @@ Puppet::Application.new(:puppetd) do @daemon.daemonize end - host = Puppet::SSL::Host.new - cert = host.wait_for_cert(options[:waitforcert]) + @host = Puppet::SSL::Host.new + unless options[:fingerprint] + cert = @host.wait_for_cert(options[:waitforcert]) + end @objects = [] diff --git a/lib/puppet/ssl/base.rb b/lib/puppet/ssl/base.rb index d67861f4b..6c74b7565 100644 --- a/lib/puppet/ssl/base.rb +++ b/lib/puppet/ssl/base.rb @@ -54,6 +54,23 @@ class Puppet::SSL::Base content.to_text end + def fingerprint(md = :MD5) + require 'openssl/digest' + + # ruby 1.8.x openssl digest constants are string + # but in 1.9.x they are symbols + mds = md.to_s.upcase + if OpenSSL::Digest.constants.include?(mds) + md = mds + elsif OpenSSL::Digest.constants.include?(mds.to_sym) + md = mds.to_sym + else + raise ArgumentError, "#{md} is not a valid digest algorithm for fingerprinting certificate #{name}" + end + + OpenSSL::Digest.hexdigest(md, content.to_der).scan(/../).join(':').upcase + end + private def wrapped_class diff --git a/lib/puppet/ssl/certificate_authority.rb b/lib/puppet/ssl/certificate_authority.rb index 8e4fd7a08..9fe67cc8a 100644 --- a/lib/puppet/ssl/certificate_authority.rb +++ b/lib/puppet/ssl/certificate_authority.rb @@ -53,7 +53,7 @@ class Puppet::SSL::CertificateAuthority unless options[:to] raise ArgumentError, "You must specify the hosts to apply to; valid values are an array or the symbol :all" end - applier = Interface.new(method, options[:to]) + applier = Interface.new(method, options) applier.apply(self) end @@ -291,6 +291,13 @@ class Puppet::SSL::CertificateAuthority end end + def fingerprint(name, md = :MD5) + unless cert = Puppet::SSL::Certificate.find(name) || Puppet::SSL::CertificateRequest.find(name) + raise ArgumentError, "Could not find a certificate or csr for %s" % name + end + cert.fingerprint(md) + end + # List the waiting certificate requests. def waiting? Puppet::SSL::CertificateRequest.search("*").collect { |r| r.name } diff --git a/lib/puppet/ssl/certificate_authority/interface.rb b/lib/puppet/ssl/certificate_authority/interface.rb index 3f91434e3..d2dc7b9b5 100644 --- a/lib/puppet/ssl/certificate_authority/interface.rb +++ b/lib/puppet/ssl/certificate_authority/interface.rb @@ -2,11 +2,11 @@ # on the CA. It's only used by the 'puppetca' executable, and its # job is to provide a CLI-like interface to the CA class. class Puppet::SSL::CertificateAuthority::Interface - INTERFACE_METHODS = [:destroy, :list, :revoke, :generate, :sign, :print, :verify] + INTERFACE_METHODS = [:destroy, :list, :revoke, :generate, :sign, :print, :verify, :fingerprint] class InterfaceError < ArgumentError; end - attr_reader :method, :subjects + attr_reader :method, :subjects, :digest # Actually perform the work. def apply(ca) @@ -38,9 +38,10 @@ class Puppet::SSL::CertificateAuthority::Interface end end - def initialize(method, subjects) + def initialize(method, options) self.method = method - self.subjects = subjects + self.subjects = options[:to] + @digest = options[:digest] || :MD5 end # List the hosts. @@ -67,11 +68,11 @@ class Puppet::SSL::CertificateAuthority::Interface invalid = details.to_s end if not invalid and signed.include?(host) - puts "+ " + host + puts "+ #{host} (#{ca.fingerprint(host, @digest)})" elsif invalid - puts "- " + host + " (" + invalid + ")" + puts "- #{host} (#{ca.fingerprint(host, @digest)}) (#{invalid})" else - puts host + puts "#{host} (#{ca.fingerprint(host, @digest)})" end end end @@ -84,7 +85,7 @@ class Puppet::SSL::CertificateAuthority::Interface # Print certificate information. def print(ca) - (subjects == :all ? ca.list : subjects).each do |host| + (subjects == :all ? ca.list : subjects).each do |host| if value = ca.print(host) puts value else @@ -93,6 +94,17 @@ class Puppet::SSL::CertificateAuthority::Interface end end + # Print certificate information. + def fingerprint(ca) + (subjects == :all ? ca.list + ca.waiting?: subjects).each do |host| + if value = ca.fingerprint(host, @digest) + puts "#{host} #{value}" + else + Puppet.err "Could not find certificate for %s" % host + end + end + end + # Sign a given certificate. def sign(ca) list = subjects == :all ? ca.waiting? : subjects diff --git a/lib/puppet/ssl/certificate_request.rb b/lib/puppet/ssl/certificate_request.rb index 4008ababe..f18fe4a16 100644 --- a/lib/puppet/ssl/certificate_request.rb +++ b/lib/puppet/ssl/certificate_request.rb @@ -43,6 +43,8 @@ class Puppet::SSL::CertificateRequest < Puppet::SSL::Base raise Puppet::Error, "CSR sign verification failed; you need to clean the certificate request for %s on the server" % name unless csr.verify(key.public_key) @content = csr + Puppet.info "Certificate Request fingerprint (md5): #{fingerprint}" + @content end def save(args = {}) diff --git a/sbin/puppetca b/sbin/puppetca index 27ba916b5..eab594ba6 100755 --- a/sbin/puppetca +++ b/sbin/puppetca @@ -10,7 +10,8 @@ # # puppetca [-h|--help] [-V|--version] [-d|--debug] [-v|--verbose] # [-g|--generate] [-l|--list] [-s|--sign] [-r|--revoke] -# [-p|--print] [-c|--clean] [--verify] [host] +# [-p|--print] [-c|--clean] [--verify] [--digest DIGEST] +# [--fingerprint] [host] # # = Description # @@ -35,6 +36,11 @@ # Operate on all items. Currently only makes sense with '--sign', # '--clean', or '--list'. # +# digest:: +# Set the digest for fingerprinting (defaults to md5). Valid values depends +# on your openssl and openssl ruby extension version, but should contain at +# least md5, sha1, md2, sha256. +# # clean:: # Remove all files related to a host from puppetca's storage. This is # useful when rebuilding hosts, since new certificate signing requests @@ -62,6 +68,9 @@ # print:: # Print the full-text version of a host's certificate. # +# fingerprint:: +# Print the DIGEST (defaults to md5) fingerprint of a host's certificate. +# # revoke:: # Revoke the certificate of a client. The certificate can be specified # either by its serial number, given as a decimal number or a hexadecimal diff --git a/sbin/puppetd b/sbin/puppetd index bf7d02838..fd78dc631 100755 --- a/sbin/puppetd +++ b/sbin/puppetd @@ -12,7 +12,8 @@ # [--detailed-exitcodes] [--disable] [--enable] # [-h|--help] [--fqdn <host name>] [-l|--logdest syslog|<file>|console] # [-o|--onetime] [--serve <handler>] [-t|--test] [--noop] -# [-V|--version] [-v|--verbose] [-w|--waitforcert <seconds>] +# [--digest <digest>] [--fingerprint] [-V|--version] +# [-v|--verbose] [-w|--waitforcert <seconds>] # # = Description # @@ -35,7 +36,7 @@ # configuration every 30 minutes. # # Some flags are meant specifically for interactive use -- in particular, -# +test+ and +tags+ are useful. +test+ enables verbose logging, causes +# +test+, +tags+ or +fingerprint+ are useful. +test+ enables verbose logging, causes # the daemon to stay in the foreground, exits if the server's configuration is # invalid (this happens if, for instance, you've left a syntax error on the # server), and exits after running the configuration once (rather than hanging @@ -51,6 +52,15 @@ # which would only apply that small portion of the configuration during your # testing, rather than applying the whole thing. # +# +fingerprint+ is a one-time flag. In this mode +puppetd+ will run once and +# display on the console (and in the log) the current certificate (or certificate +# request) fingerprint. Providing the +--digest+ option allows to use a different +# digest algorithm to generate the fingerprint. The main use is to verify that +# before signing a certificate request on the master, the certificate request the +# master received is the same as the one the client sent (to prevent against +# man-in-the-middle attacks when signing certificates). +# +# # = Options # # Note that any configuration parameter that's valid in the configuration file @@ -72,6 +82,11 @@ # debug:: # Enable full debugging. # +# digest:: +# Change the certificate fingerprinting digest algorithm. The default is MD5. +# Valid values depends on the version of OpenSSL installed, but should always +# at least contain MD5, MD2, SHA1 and SHA256. +# # detailed-exitcodes:: # Provide transaction information via exit codes. If this is enabled, an # exit code of '2' means there were changes, and an exit code of '4' means @@ -119,6 +134,10 @@ # Run the configuration once, rather than as a long-running daemon. This is # useful for interactively running puppetd. # +# fingerprint:: +# Display the current certificate or certificate signing request fingerprint +# and then exit. Use the +--digest+ option to change the digest algorithm used. +# # serve:: # Start another type of server. By default, +puppetd+ will start # a service handler that allows authenticated and authorized remote nodes to diff --git a/spec/unit/application/puppetca.rb b/spec/unit/application/puppetca.rb index 3a535f394..132a03c1f 100644 --- a/spec/unit/application/puppetca.rb +++ b/spec/unit/application/puppetca.rb @@ -39,6 +39,12 @@ describe "PuppetCA" do @puppetca.handle_debug(0) end + it "should set the fingerprint digest with the --digest option" do + @puppetca.handle_digest(:digest) + + @puppetca.digest.should == :digest + end + it "should set mode to :destroy for --clean" do @puppetca.handle_clean(0) @puppetca.mode.should == :destroy @@ -129,6 +135,15 @@ describe "PuppetCA" do @puppetca.main end + it "should send the currently set digest" do + ARGV.stubs(:collect).returns(["host"]) + @puppetca.handle_digest(:digest) + + @ca.expects(:apply).with { |mode,to| to[:digest] == :digest} + + @puppetca.main + end + it "should delegate to ca.apply with current set mode" do @puppetca.mode = "currentmode" ARGV.stubs(:collect).returns(["host"]) diff --git a/spec/unit/application/puppetd.rb b/spec/unit/application/puppetd.rb index dc061ea10..246c39958 100755 --- a/spec/unit/application/puppetd.rb +++ b/spec/unit/application/puppetd.rb @@ -34,6 +34,10 @@ describe "puppetd" do @puppetd.should respond_to(:onetime) end + it "should declare a fingerprint command" do + @puppetd.should respond_to(:fingerprint) + end + it "should declare a preinit block" do @puppetd.should respond_to(:run_preinit) end @@ -73,6 +77,17 @@ describe "puppetd" do @puppetd.options[:serve].should == [] end + it "should use MD5 as default digest algorithm" do + @puppetd.run_preinit + + @puppetd.options[:digest].should == :MD5 + end + + it "should not fingerprint by default" do + @puppetd.run_preinit + + @puppetd.options[:fingerprint].should be_false + end end describe "when handling options" do @@ -86,7 +101,7 @@ describe "puppetd" do @old_argv.each { |a| ARGV << a } end - [:centrallogging, :disable, :enable, :debug, :fqdn, :test, :verbose].each do |option| + [:centrallogging, :disable, :enable, :debug, :fqdn, :test, :verbose, :digest].each do |option| it "should declare handle_#{option} method" do @puppetd.should respond_to("handle_#{option}".to_sym) end @@ -299,6 +314,13 @@ describe "puppetd" do @puppetd.run_setup end + it "should install a none ca location in fingerprint mode" do + @puppetd.options.stubs(:[]).with(:fingerprint).returns(true) + Puppet::SSL::Host.expects(:ca_location=).with(:none) + + @puppetd.run_setup + end + it "should tell the report handler to use REST" do Puppet::Transaction::Report.expects(:terminus_class=).with(:rest) @@ -382,6 +404,14 @@ describe "puppetd" do @puppetd.run_setup end + it "should not wait for a certificate in fingerprint mode" do + @puppetd.options.stubs(:[]).with(:fingerprint).returns(true) + @puppetd.options.stubs(:[]).with(:waitforcert).returns(123) + @host.expects(:wait_for_cert).never + + @puppetd.run_setup + end + it "should setup listen if told to and not onetime" do Puppet.stubs(:[]).with(:listen).returns(true) @puppetd.options.stubs(:[]).with(:onetime).returns(false) @@ -440,6 +470,13 @@ describe "puppetd" do before :each do @puppetd.agent = @agent @puppetd.daemon = @daemon + @puppetd.options.stubs(:[]).with(:fingerprint).returns(false) + end + + it "should dispatch to fingerprint if --fingerprint is used" do + @puppetd.options.stubs(:[]).with(:fingerprint).returns(true) + + @puppetd.get_command.should == :fingerprint end it "should dispatch to onetime if --onetime is used" do @@ -448,7 +485,7 @@ describe "puppetd" do @puppetd.get_command.should == :onetime end - it "should dispatch to main if --onetime is not used" do + it "should dispatch to main if --onetime and --fingerprint are not used" do @puppetd.options.stubs(:[]).with(:onetime).returns(false) @puppetd.get_command.should == :main @@ -516,7 +553,39 @@ describe "puppetd" do end end - describe "without --onetime" do + describe "with --fingerprint" do + before :each do + @cert = stub_everything 'cert' + @puppetd.options.stubs(:[]).with(:fingerprint).returns(true) + @puppetd.options.stubs(:[]).with(:digest).returns(:MD5) + @host = stub_everything 'host' + @puppetd.stubs(:host).returns(@host) + end + + it "should fingerprint the certificate if it exists" do + @host.expects(:certificate).returns(@cert) + @cert.expects(:fingerprint).with(:MD5) + @puppetd.fingerprint + end + + it "should fingerprint the certificate request if no certificate have been signed" do + @host.expects(:certificate).returns(nil) + @host.expects(:certificate_request).returns(@cert) + @cert.expects(:fingerprint).with(:MD5) + @puppetd.fingerprint + end + + it "should display the fingerprint" do + @host.stubs(:certificate).returns(@cert) + @cert.stubs(:fingerprint).with(:MD5).returns("DIGEST") + + Puppet.expects(:notice).with("DIGEST") + + @puppetd.fingerprint + end + end + + describe "without --onetime and --fingerprint" do before :each do Puppet.stubs(:notice) @puppetd.options.stubs(:[]).with(:client) diff --git a/spec/unit/ssl/base.rb b/spec/unit/ssl/base.rb new file mode 100755 index 000000000..dfab3c843 --- /dev/null +++ b/spec/unit/ssl/base.rb @@ -0,0 +1,40 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/ssl/certificate' + +class TestCertificate < Puppet::SSL::Base; end + +describe Puppet::SSL::Certificate do + before :each do + @base = TestCertificate.new("name") + end + + describe "when fingerprinting content" do + before :each do + @cert = stub 'cert', :to_der => "DER" + @base.stubs(:content).returns(@cert) + OpenSSL::Digest.stubs(:constants).returns ["MD5", "DIGEST"] + end + + it "should digest the certificate DER value and return a ':' seperated nibblet string" do + @cert.expects(:to_der).returns("DER") + OpenSSL::Digest.expects(:hexdigest).with("MD5", "DER").returns "digest" + + @base.fingerprint.should == "DI:GE:ST" + end + + it "should raise an error if the digest algorithm is not defined" do + OpenSSL::Digest.expects(:constants).returns [] + + lambda { @base.fingerprint }.should raise_error + end + + it "should use the given digest algorithm" do + OpenSSL::Digest.expects(:hexdigest).with("DIGEST", "DER").returns "digest" + + @base.fingerprint(:digest).should == "DI:GE:ST" + end + end +end
\ No newline at end of file diff --git a/spec/unit/ssl/certificate_authority.rb b/spec/unit/ssl/certificate_authority.rb index 80114300e..4d2303a60 100755 --- a/spec/unit/ssl/certificate_authority.rb +++ b/spec/unit/ssl/certificate_authority.rb @@ -532,9 +532,9 @@ describe Puppet::SSL::CertificateAuthority do lambda { @ca.apply(:generate) }.should raise_error(ArgumentError) end - it "should create an Interface instance with the specified method and the subjects" do - Puppet::SSL::CertificateAuthority::Interface.expects(:new).with(:generate, :hosts).returns(stub('applier', :apply => nil)) - @ca.apply(:generate, :to => :hosts) + it "should create an Interface instance with the specified method and the options" do + Puppet::SSL::CertificateAuthority::Interface.expects(:new).with(:generate, :to => :host).returns(stub('applier', :apply => nil)) + @ca.apply(:generate, :to => :host) end it "should apply the Interface with itself as the argument" do @@ -583,6 +583,37 @@ describe Puppet::SSL::CertificateAuthority do end end + describe "and fingerprinting certificates" do + before :each do + @cert = stub 'cert', :name => "cert", :fingerprint => "DIGEST" + Puppet::SSL::Certificate.stubs(:find).with("myhost").returns @cert + Puppet::SSL::CertificateRequest.stubs(:find).with("myhost") + end + + it "should raise an error if the certificate or CSR cannot be found" do + Puppet::SSL::Certificate.expects(:find).with("myhost").returns nil + Puppet::SSL::CertificateRequest.expects(:find).with("myhost").returns nil + lambda { @ca.fingerprint("myhost") }.should raise_error + end + + it "should try to find a CSR if no certificate can be found" do + Puppet::SSL::Certificate.expects(:find).with("myhost").returns nil + Puppet::SSL::CertificateRequest.expects(:find).with("myhost").returns @cert + @cert.expects(:fingerprint) + @ca.fingerprint("myhost") + end + + it "should delegate to the certificate fingerprinting" do + @cert.expects(:fingerprint) + @ca.fingerprint("myhost") + end + + it "should propagate the digest algorithm to the certificate fingerprinting system" do + @cert.expects(:fingerprint).with(:digest) + @ca.fingerprint("myhost", :digest) + end + end + describe "and verifying certificates" do before do @store = stub 'store', :verify => true, :add_file => nil, :purpose= => nil, :add_crl => true, :flags= => nil diff --git a/spec/unit/ssl/certificate_authority/interface.rb b/spec/unit/ssl/certificate_authority/interface.rb index d741ec400..bcba298b2 100755 --- a/spec/unit/ssl/certificate_authority/interface.rb +++ b/spec/unit/ssl/certificate_authority/interface.rb @@ -9,7 +9,7 @@ describe "a normal interface method", :shared => true do @ca.expects(@method).with("host1") @ca.expects(@method).with("host2") - @applier = Puppet::SSL::CertificateAuthority::Interface.new(@method, %w{host1 host2}) + @applier = Puppet::SSL::CertificateAuthority::Interface.new(@method, :to => %w{host1 host2}) @applier.apply(@ca) end @@ -20,7 +20,7 @@ describe "a normal interface method", :shared => true do @ca.expects(@method).with("host1") @ca.expects(@method).with("host2") - @applier = Puppet::SSL::CertificateAuthority::Interface.new(@method, :all) + @applier = Puppet::SSL::CertificateAuthority::Interface.new(@method, :to => :all) @applier.apply(@ca) end @@ -33,30 +33,40 @@ describe Puppet::SSL::CertificateAuthority::Interface do describe "when initializing" do it "should set its method using its settor" do @class.any_instance.expects(:method=).with(:generate) - @class.new(:generate, :all) + @class.new(:generate, :to => :all) end it "should set its subjects using the settor" do @class.any_instance.expects(:subjects=).with(:all) - @class.new(:generate, :all) + @class.new(:generate, :to => :all) + end + + it "should set the digest if given" do + interface = @class.new(:generate, :to => :all, :digest => :digest) + interface.digest.should == :digest + end + + it "should set the digest to md5 if none given" do + interface = @class.new(:generate, :to => :all) + interface.digest.should == :MD5 end end describe "when setting the method" do it "should set the method" do - @class.new(:generate, :all).method.should == :generate + @class.new(:generate, :to => :all).method.should == :generate end it "should fail if the method isn't a member of the INTERFACE_METHODS array" do Puppet::SSL::CertificateAuthority::Interface::INTERFACE_METHODS.expects(:include?).with(:thing).returns false - lambda { @class.new(:thing, :all) }.should raise_error(ArgumentError) + lambda { @class.new(:thing, :to => :all) }.should raise_error(ArgumentError) end end describe "when setting the subjects" do it "should set the subjects" do - @class.new(:generate, :all).subjects.should == :all + @class.new(:generate, :to => :all).subjects.should == :all end it "should fail if the subjects setting isn't :all or an array" do @@ -65,7 +75,7 @@ describe Puppet::SSL::CertificateAuthority::Interface do end it "should have a method for triggering the application" do - @class.new(:generate, :all).should respond_to(:apply) + @class.new(:generate, :to => :all).should respond_to(:apply) end describe "when applying" do @@ -75,7 +85,7 @@ describe Puppet::SSL::CertificateAuthority::Interface do end it "should raise InterfaceErrors" do - @applier = @class.new(:revoke, :all) + @applier = @class.new(:revoke, :to => :all) @ca.expects(:list).raises Puppet::SSL::CertificateAuthority::Interface::InterfaceError @@ -83,7 +93,7 @@ describe Puppet::SSL::CertificateAuthority::Interface do end it "should log non-Interface failures rather than failing" do - @applier = @class.new(:revoke, :all) + @applier = @class.new(:revoke, :to => :all) @ca.expects(:list).raises ArgumentError @@ -94,19 +104,19 @@ describe Puppet::SSL::CertificateAuthority::Interface do describe "with an empty array specified and the method is not list" do it "should fail" do - @applier = @class.new(:sign, []) + @applier = @class.new(:sign, :to => []) lambda { @applier.apply(@ca) }.should raise_error(ArgumentError) end end describe ":generate" do it "should fail if :all was specified" do - @applier = @class.new(:generate, :all) + @applier = @class.new(:generate, :to => :all) lambda { @applier.apply(@ca) }.should raise_error(ArgumentError) end it "should call :generate on the CA for each host specified" do - @applier = @class.new(:generate, %w{host1 host2}) + @applier = @class.new(:generate, :to => %w{host1 host2}) @ca.expects(:generate).with("host1") @ca.expects(:generate).with("host2") @@ -141,7 +151,7 @@ describe Puppet::SSL::CertificateAuthority::Interface do describe ":sign" do describe "and an array of names was provided" do before do - @applier = @class.new(:sign, %w{host1 host2}) + @applier = @class.new(:sign, :to => %w{host1 host2}) end it "should sign the specified waiting certificate requests" do @@ -159,14 +169,14 @@ describe Puppet::SSL::CertificateAuthority::Interface do @ca.expects(:sign).with("cert1") @ca.expects(:sign).with("cert2") - @applier = @class.new(:sign, :all) + @applier = @class.new(:sign, :to => :all) @applier.apply(@ca) end it "should fail if there are no waiting certificate requests" do @ca.stubs(:waiting?).returns([]) - @applier = @class.new(:sign, :all) + @applier = @class.new(:sign, :to => :all) lambda { @applier.apply(@ca) }.should raise_error(Puppet::SSL::CertificateAuthority::Interface::InterfaceError) end end @@ -178,7 +188,7 @@ describe Puppet::SSL::CertificateAuthority::Interface do @ca.expects(:waiting?).returns %w{host1 host2} @ca.stubs(:verify) - @applier = @class.new(:list, []) + @applier = @class.new(:list, :to => []) @applier.expects(:puts).with "host1\nhost2" @@ -191,14 +201,15 @@ describe Puppet::SSL::CertificateAuthority::Interface 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, :all) + @applier = @class.new(:list, :to => :all) - @applier.expects(:puts).with "host1" - @applier.expects(:puts).with "host2" - @applier.expects(:puts).with "- host3 (certificate revoked)" - @applier.expects(:puts).with "+ host4" + @applier.expects(:puts).with "host1 (fingerprint)" + @applier.expects(:puts).with "host2 (fingerprint)" + @applier.expects(:puts).with "- host3 (fingerprint) (certificate revoked)" + @applier.expects(:puts).with "+ host4 (fingerprint)" @applier.apply(@ca) end @@ -208,14 +219,15 @@ describe Puppet::SSL::CertificateAuthority::Interface 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, %w{host1 host2 host3 host4}) + @applier = @class.new(:list, :to => %w{host1 host2 host3 host4}) - @applier.expects(:puts).with "host1" - @applier.expects(:puts).with "host2" - @applier.expects(:puts).with "+ host3" - @applier.expects(:puts).with "+ host4" + @applier.expects(:puts).with "host1 (fingerprint)" + @applier.expects(:puts).with "host2 (fingerprint)" + @applier.expects(:puts).with "+ host3 (fingerprint)" + @applier.expects(:puts).with "+ host4 (fingerprint)" @applier.apply(@ca) end @@ -227,7 +239,7 @@ describe Puppet::SSL::CertificateAuthority::Interface do it "should print all certificates" do @ca.expects(:list).returns %w{host1 host2} - @applier = @class.new(:print, :all) + @applier = @class.new(:print, :to => :all) @ca.expects(:print).with("host1").returns "h1" @applier.expects(:puts).with "h1" @@ -241,7 +253,7 @@ describe Puppet::SSL::CertificateAuthority::Interface do describe "and an array of names was provided" do it "should print each named certificate if found" do - @applier = @class.new(:print, %w{host1 host2}) + @applier = @class.new(:print, :to => %w{host1 host2}) @ca.expects(:print).with("host1").returns "h1" @applier.expects(:puts).with "h1" @@ -253,7 +265,7 @@ describe Puppet::SSL::CertificateAuthority::Interface do end it "should log any named but not found certificates" do - @applier = @class.new(:print, %w{host1 host2}) + @applier = @class.new(:print, :to => %w{host1 host2}) @ca.expects(:print).with("host1").returns "h1" @applier.expects(:puts).with "h1" @@ -265,5 +277,47 @@ describe Puppet::SSL::CertificateAuthority::Interface do end end end + + describe ":fingerprint" do + it "should fingerprint with the set digest algorithm" do + @applier = @class.new(:fingerprint, :to => %w{host1}, :digest => :digest) + + @ca.expects(:fingerprint).with("host1", :digest).returns "fingerprint1" + @applier.expects(:puts).with "host1 fingerprint1" + + @applier.apply(@ca) + end + + describe "and :all was provided" do + it "should fingerprint all certificates (including waiting ones)" do + @ca.expects(:list).returns %w{host1} + @ca.expects(:waiting?).returns %w{host2} + + @applier = @class.new(:fingerprint, :to => :all) + + @ca.expects(:fingerprint).with("host1", :MD5).returns "fingerprint1" + @applier.expects(:puts).with "host1 fingerprint1" + + @ca.expects(:fingerprint).with("host2", :MD5).returns "fingerprint2" + @applier.expects(:puts).with "host2 fingerprint2" + + @applier.apply(@ca) + end + end + + describe "and an array of names was provided" do + it "should print each named certificate if found" do + @applier = @class.new(:fingerprint, :to => %w{host1 host2}) + + @ca.expects(:fingerprint).with("host1", :MD5).returns "fingerprint1" + @applier.expects(:puts).with "host1 fingerprint1" + + @ca.expects(:fingerprint).with("host2", :MD5).returns "fingerprint2" + @applier.expects(:puts).with "host2 fingerprint2" + + @applier.apply(@ca) + end + end + end end end diff --git a/spec/unit/ssl/certificate_request.rb b/spec/unit/ssl/certificate_request.rb index 29bbc7bc1..a4eee92d6 100755 --- a/spec/unit/ssl/certificate_request.rb +++ b/spec/unit/ssl/certificate_request.rb @@ -106,7 +106,7 @@ describe Puppet::SSL::CertificateRequest do end it "should log that it is creating a new certificate request" do - Puppet.expects(:info) + Puppet.expects(:info).twice @instance.generate(@key) end @@ -164,6 +164,18 @@ describe Puppet::SSL::CertificateRequest do lambda { @instance.generate(@key) }.should raise_error(Puppet::Error) end + it "should fingerprint the request" do + @instance.expects(:fingerprint) + @instance.generate(@key) + end + + it "should display the fingerprint" do + Puppet.stubs(:info) + @instance.stubs(:fingerprint).returns("FINGERPRINT") + Puppet.expects(:info).with { |s| s =~ /FINGERPRINT/ } + @instance.generate(@key) + end + it "should return the generated request" do @instance.generate(@key).should equal(@request) end |