diff options
| author | Luke Kanies <luke@madstop.com> | 2008-05-06 15:39:18 -0500 |
|---|---|---|
| committer | Luke Kanies <luke@madstop.com> | 2008-05-06 15:39:18 -0500 |
| commit | a822ef9ce5c6d603f4a98b9dda0dbf4661528128 (patch) | |
| tree | d35c546f2321b49d7be7bff0a367564ed1ca350b | |
| parent | 38e2dcf35a1d9b19970d1fb253f6c09b0529e083 (diff) | |
Moving the CA Interface class to a separate file.
| -rw-r--r-- | lib/puppet/ssl/certificate_authority.rb | 143 | ||||
| -rw-r--r-- | lib/puppet/ssl/certificate_authority/interface.rb | 110 | ||||
| -rwxr-xr-x | spec/unit/ssl/certificate_authority.rb | 260 | ||||
| -rwxr-xr-x | spec/unit/ssl/certificate_authority/interface.rb | 265 |
4 files changed, 414 insertions, 364 deletions
diff --git a/lib/puppet/ssl/certificate_authority.rb b/lib/puppet/ssl/certificate_authority.rb index 9958575d5..9385110d2 100644 --- a/lib/puppet/ssl/certificate_authority.rb +++ b/lib/puppet/ssl/certificate_authority.rb @@ -14,115 +14,7 @@ class Puppet::SSL::CertificateAuthority require 'puppet/ssl/inventory' require 'puppet/ssl/certificate_revocation_list' - # This class is basically a hidden class that knows how to act - # on the CA. It's only used by the 'puppetca' executable, and its - # job is to provide a CLI-like interface to the CA class. - class Interface - INTERFACE_METHODS = [:destroy, :list, :revoke, :generate, :sign, :print, :verify] - - class InterfaceError < ArgumentError; end - - attr_reader :method, :subjects - - # Actually perform the work. - def apply(ca) - unless subjects or method == :list - raise ArgumentError, "You must provide hosts or :all when using %s" % method - end - - begin - if respond_to?(method) - return send(method, ca) - end - - (subjects == :all ? ca.list : subjects).each do |host| - ca.send(method, host) - end - rescue InterfaceError - raise - rescue => detail - puts detail.backtrace if Puppet[:trace] - Puppet.err "Could not call %s: %s" % [method, detail] - end - end - - def generate(ca) - raise InterfaceError, "It makes no sense to generate all hosts; you must specify a list" if subjects == :all - - subjects.each do |host| - ca.generate(host) - end - end - - def initialize(method, subjects) - self.method = method - self.subjects = subjects - end - - # List the hosts. - def list(ca) - unless subjects - puts ca.waiting?.join("\n") - return nil - end - - signed = ca.list - requests = ca.waiting? - - if subjects == :all - hosts = [signed, requests].flatten - else - hosts = subjects - end - - hosts.uniq.sort.each do |host| - if signed.include?(host) - puts "+ " + host - else - puts host - end - end - end - - # Set the method to apply. - def method=(method) - raise ArgumentError, "Invalid method %s to apply" % method unless INTERFACE_METHODS.include?(method) - @method = method - end - - # Print certificate information. - def print(ca) - (subjects == :all ? ca.list : subjects).each do |host| - if value = ca.print(host) - puts value - else - Puppet.err "Could not find certificate for %s" % host - end - end - end - - # Sign a given certificate. - def sign(ca) - list = subjects == :all ? ca.waiting? : subjects - raise InterfaceError, "No waiting certificate requests to sign" if list.empty? - list.each do |host| - ca.sign(host) - end - end - - # Set the list of hosts we're operating on. Also supports keywords. - def subjects=(value) - unless value == :all or value.is_a?(Array) - raise ArgumentError, "Subjects must be an array or :all; not %s" % value - end - - if value.is_a?(Array) and value.empty? - value = nil - end - - @subjects = value - end - end + require 'puppet/ssl/certificate_authority/interface' # If this process can function as a CA, then return a singleton # instance. @@ -150,6 +42,39 @@ class Puppet::SSL::CertificateAuthority applier.apply(self) 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 %s" % + autosign.inspect + end + + unless FileTest.exists?(autosign) + unless defined? @@warnedonautosign + @@warnedonautosign = true + Puppet.info "Autosign is enabled but %s is missing" % autosign + 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? + return auth.allowed?(hostname, "127.1.1.1") + end + # Retrieve (or create, if necessary) the certificate revocation list. def crl unless defined?(@crl) diff --git a/lib/puppet/ssl/certificate_authority/interface.rb b/lib/puppet/ssl/certificate_authority/interface.rb new file mode 100644 index 000000000..b355e21f0 --- /dev/null +++ b/lib/puppet/ssl/certificate_authority/interface.rb @@ -0,0 +1,110 @@ +# This class is basically a hidden class that knows how to act +# on the CA. It's only used by the 'puppetca' executable, and its +# job is to provide a CLI-like interface to the CA class. +class Puppet::SSL::CertificateAuthority::Interface + INTERFACE_METHODS = [:destroy, :list, :revoke, :generate, :sign, :print, :verify] + + class InterfaceError < ArgumentError; end + + attr_reader :method, :subjects + + # Actually perform the work. + def apply(ca) + unless subjects or method == :list + raise ArgumentError, "You must provide hosts or :all when using %s" % method + end + + begin + if respond_to?(method) + return send(method, ca) + end + + (subjects == :all ? ca.list : subjects).each do |host| + ca.send(method, host) + end + rescue InterfaceError + raise + rescue => detail + puts detail.backtrace if Puppet[:trace] + Puppet.err "Could not call %s: %s" % [method, detail] + end + end + + def generate(ca) + raise InterfaceError, "It makes no sense to generate all hosts; you must specify a list" if subjects == :all + + subjects.each do |host| + ca.generate(host) + end + end + + def initialize(method, subjects) + self.method = method + self.subjects = subjects + end + + # List the hosts. + def list(ca) + unless subjects + puts ca.waiting?.join("\n") + return nil + end + + signed = ca.list + requests = ca.waiting? + + if subjects == :all + hosts = [signed, requests].flatten + else + hosts = subjects + end + + hosts.uniq.sort.each do |host| + if signed.include?(host) + puts "+ " + host + else + puts host + end + end + end + + # Set the method to apply. + def method=(method) + raise ArgumentError, "Invalid method %s to apply" % method unless INTERFACE_METHODS.include?(method) + @method = method + end + + # Print certificate information. + def print(ca) + (subjects == :all ? ca.list : subjects).each do |host| + if value = ca.print(host) + puts value + else + Puppet.err "Could not find certificate for %s" % host + end + end + end + + # Sign a given certificate. + def sign(ca) + list = subjects == :all ? ca.waiting? : subjects + raise InterfaceError, "No waiting certificate requests to sign" if list.empty? + list.each do |host| + ca.sign(host) + end + end + + # Set the list of hosts we're operating on. Also supports keywords. + def subjects=(value) + unless value == :all or value.is_a?(Array) + raise ArgumentError, "Subjects must be an array or :all; not %s" % value + end + + if value.is_a?(Array) and value.empty? + value = nil + end + + @subjects = value + end +end + diff --git a/spec/unit/ssl/certificate_authority.rb b/spec/unit/ssl/certificate_authority.rb index bf8625445..12516b816 100755 --- a/spec/unit/ssl/certificate_authority.rb +++ b/spec/unit/ssl/certificate_authority.rb @@ -4,28 +4,6 @@ require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/ssl/certificate_authority' -describe "a normal interface method", :shared => true do - it "should call the method on the CA for each host specified if an array was provided" do - @ca.expects(@method).with("host1") - @ca.expects(@method).with("host2") - - @applier = Puppet::SSL::CertificateAuthority::Interface.new(@method, %w{host1 host2}) - - @applier.apply(@ca) - end - - it "should call the method on the CA for all existing certificates if :all was provided" do - @ca.expects(:list).returns %w{host1 host2} - - @ca.expects(@method).with("host1") - @ca.expects(@method).with("host2") - - @applier = Puppet::SSL::CertificateAuthority::Interface.new(@method, :all) - - @applier.apply(@ca) - end -end - describe Puppet::SSL::CertificateAuthority do after do # Clear out the var, yay unit tests. @@ -701,242 +679,14 @@ describe Puppet::SSL::CertificateAuthority do end end end -end - -describe Puppet::SSL::CertificateAuthority::Interface do - before do - @class = Puppet::SSL::CertificateAuthority::Interface - end - describe "when initializing" do - it "should set its method using its settor" do - @class.any_instance.expects(:method=).with(:generate) - @class.new(:generate, :all) - end - - it "should set its subjects using the settor" do - @class.any_instance.expects(:subjects=).with(:all) - @class.new(:generate, :all) - end - end - describe "when setting the method" do - it "should set the method" do - @class.new(:generate, :all).method.should == :generate - end + it "should have a method for triggering autosigning of available CSRs" - 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 + describe "when autosigning certificates" do + it "should do nothing if autosign is disabled" - lambda { @class.new(:thing, :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 - end - - it "should fail if the subjects setting isn't :all or an array" do - lambda { @class.new(:generate, "other") }.should raise_error(ArgumentError) - end - end + it "should do nothing if no autosign.conf exists" - it "should have a method for triggering the application" do - @class.new(:generate, :all).should respond_to(:apply) - end - - describe "when applying" do - before do - # We use a real object here, because :verify can't be stubbed, apparently. - @ca = Object.new - end - - it "should raise InterfaceErrors" do - @applier = @class.new(:revoke, :all) - - @ca.expects(:list).raises Puppet::SSL::CertificateAuthority::Interface::InterfaceError - - lambda { @applier.apply(@ca) }.should raise_error(Puppet::SSL::CertificateAuthority::Interface::InterfaceError) - end - - it "should log non-Interface failures rather than failing" do - @applier = @class.new(:revoke, :all) - - @ca.expects(:list).raises ArgumentError - - Puppet.expects(:err) - - lambda { @applier.apply(@ca) }.should_not raise_error - end - - describe "with an empty array specified and the method is not list" do - it "should fail" do - @applier = @class.new(:sign, []) - 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) - 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}) - - @ca.expects(:generate).with("host1") - @ca.expects(:generate).with("host2") - - @applier.apply(@ca) - end - end - - describe ":verify" do - before { @method = :verify } - #it_should_behave_like "a normal interface method" - - it "should call the method on the CA for each host specified if an array was provided" do - # LAK:NOTE Mocha apparently doesn't allow you to mock :verify, but I'm confident this works in real life. - end - - it "should call the method on the CA for all existing certificates if :all was provided" do - # LAK:NOTE Mocha apparently doesn't allow you to mock :verify, but I'm confident this works in real life. - end - end - - describe ":destroy" do - before { @method = :destroy } - it_should_behave_like "a normal interface method" - end - - describe ":revoke" do - before { @method = :revoke } - it_should_behave_like "a normal interface method" - end - - describe ":sign" do - describe "and an array of names was provided" do - before do - @applier = @class.new(:sign, %w{host1 host2}) - end - - it "should sign the specified waiting certificate requests" do - @ca.expects(:sign).with("host1") - @ca.expects(:sign).with("host2") - - @applier.apply(@ca) - end - end - - describe "and :all was provided" do - it "should sign all waiting certificate requests" do - @ca.stubs(:waiting?).returns(%w{cert1 cert2}) - - @ca.expects(:sign).with("cert1") - @ca.expects(:sign).with("cert2") - - @applier = @class.new(:sign, :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) - lambda { @applier.apply(@ca) }.should raise_error(Puppet::SSL::CertificateAuthority::Interface::InterfaceError) - end - end - end - - describe ":list" do - describe "and an empty array was provided" do - it "should print a string containing all certificate requests" do - @ca.expects(:waiting?).returns %w{host1 host2} - - @applier = @class.new(:list, []) - - @applier.expects(:puts).with "host1\nhost2" - - @applier.apply(@ca) - end - end - - describe "and :all was provided" do - it "should print a string containing all certificate requests and certificates" do - @ca.expects(:waiting?).returns %w{host1 host2} - @ca.expects(:list).returns %w{host3 host4} - - @applier = @class.new(:list, :all) - - @applier.expects(:puts).with "host1" - @applier.expects(:puts).with "host2" - @applier.expects(:puts).with "+ host3" - @applier.expects(:puts).with "+ host4" - - @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} - - @applier = @class.new(:list, %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.apply(@ca) - end - end - end - - describe ":print" do - describe "and :all was provided" do - it "should print all certificates" do - @ca.expects(:list).returns %w{host1 host2} - - @applier = @class.new(:print, :all) - - @ca.expects(:print).with("host1").returns "h1" - @applier.expects(:puts).with "h1" - - @ca.expects(:print).with("host2").returns "h2" - @applier.expects(:puts).with "h2" - - @applier.apply(@ca) - end - end - - describe "and an array of names was provided" do - it "should print each named certificate if found" do - @applier = @class.new(:print, %w{host1 host2}) - - @ca.expects(:print).with("host1").returns "h1" - @applier.expects(:puts).with "h1" - - @ca.expects(:print).with("host2").returns "h2" - @applier.expects(:puts).with "h2" - - @applier.apply(@ca) - end - - it "should log any named but not found certificates" do - @applier = @class.new(:print, %w{host1 host2}) - - @ca.expects(:print).with("host1").returns "h1" - @applier.expects(:puts).with "h1" - - @ca.expects(:print).with("host2").returns nil - Puppet.expects(:err).with { |msg| msg.include?("host2") } - - @applier.apply(@ca) - end - end - end + it "should sign all CSRs whose hostname matches the autosign configuration" end end diff --git a/spec/unit/ssl/certificate_authority/interface.rb b/spec/unit/ssl/certificate_authority/interface.rb new file mode 100755 index 000000000..617cfa6ba --- /dev/null +++ b/spec/unit/ssl/certificate_authority/interface.rb @@ -0,0 +1,265 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/ssl/certificate_authority' + +describe "a normal interface method", :shared => true do + it "should call the method on the CA for each host specified if an array was provided" do + @ca.expects(@method).with("host1") + @ca.expects(@method).with("host2") + + @applier = Puppet::SSL::CertificateAuthority::Interface.new(@method, %w{host1 host2}) + + @applier.apply(@ca) + end + + it "should call the method on the CA for all existing certificates if :all was provided" do + @ca.expects(:list).returns %w{host1 host2} + + @ca.expects(@method).with("host1") + @ca.expects(@method).with("host2") + + @applier = Puppet::SSL::CertificateAuthority::Interface.new(@method, :all) + + @applier.apply(@ca) + end +end + +describe Puppet::SSL::CertificateAuthority::Interface do + before do + @class = Puppet::SSL::CertificateAuthority::Interface + end + describe "when initializing" do + it "should set its method using its settor" do + @class.any_instance.expects(:method=).with(:generate) + @class.new(:generate, :all) + end + + it "should set its subjects using the settor" do + @class.any_instance.expects(:subjects=).with(:all) + @class.new(:generate, :all) + end + end + + describe "when setting the method" do + it "should set the method" do + @class.new(:generate, :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) + end + end + + describe "when setting the subjects" do + it "should set the subjects" do + @class.new(:generate, :all).subjects.should == :all + end + + it "should fail if the subjects setting isn't :all or an array" do + lambda { @class.new(:generate, "other") }.should raise_error(ArgumentError) + end + end + + it "should have a method for triggering the application" do + @class.new(:generate, :all).should respond_to(:apply) + end + + describe "when applying" do + before do + # We use a real object here, because :verify can't be stubbed, apparently. + @ca = Object.new + end + + it "should raise InterfaceErrors" do + @applier = @class.new(:revoke, :all) + + @ca.expects(:list).raises Puppet::SSL::CertificateAuthority::Interface::InterfaceError + + lambda { @applier.apply(@ca) }.should raise_error(Puppet::SSL::CertificateAuthority::Interface::InterfaceError) + end + + it "should log non-Interface failures rather than failing" do + @applier = @class.new(:revoke, :all) + + @ca.expects(:list).raises ArgumentError + + Puppet.expects(:err) + + lambda { @applier.apply(@ca) }.should_not raise_error + end + + describe "with an empty array specified and the method is not list" do + it "should fail" do + @applier = @class.new(:sign, []) + 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) + 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}) + + @ca.expects(:generate).with("host1") + @ca.expects(:generate).with("host2") + + @applier.apply(@ca) + end + end + + describe ":verify" do + before { @method = :verify } + #it_should_behave_like "a normal interface method" + + it "should call the method on the CA for each host specified if an array was provided" do + # LAK:NOTE Mocha apparently doesn't allow you to mock :verify, but I'm confident this works in real life. + end + + it "should call the method on the CA for all existing certificates if :all was provided" do + # LAK:NOTE Mocha apparently doesn't allow you to mock :verify, but I'm confident this works in real life. + end + end + + describe ":destroy" do + before { @method = :destroy } + it_should_behave_like "a normal interface method" + end + + describe ":revoke" do + before { @method = :revoke } + it_should_behave_like "a normal interface method" + end + + describe ":sign" do + describe "and an array of names was provided" do + before do + @applier = @class.new(:sign, %w{host1 host2}) + end + + it "should sign the specified waiting certificate requests" do + @ca.expects(:sign).with("host1") + @ca.expects(:sign).with("host2") + + @applier.apply(@ca) + end + end + + describe "and :all was provided" do + it "should sign all waiting certificate requests" do + @ca.stubs(:waiting?).returns(%w{cert1 cert2}) + + @ca.expects(:sign).with("cert1") + @ca.expects(:sign).with("cert2") + + @applier = @class.new(:sign, :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) + lambda { @applier.apply(@ca) }.should raise_error(Puppet::SSL::CertificateAuthority::Interface::InterfaceError) + end + end + end + + describe ":list" do + describe "and an empty array was provided" do + it "should print a string containing all certificate requests" do + @ca.expects(:waiting?).returns %w{host1 host2} + + @applier = @class.new(:list, []) + + @applier.expects(:puts).with "host1\nhost2" + + @applier.apply(@ca) + end + end + + describe "and :all was provided" do + it "should print a string containing all certificate requests and certificates" do + @ca.expects(:waiting?).returns %w{host1 host2} + @ca.expects(:list).returns %w{host3 host4} + + @applier = @class.new(:list, :all) + + @applier.expects(:puts).with "host1" + @applier.expects(:puts).with "host2" + @applier.expects(:puts).with "+ host3" + @applier.expects(:puts).with "+ host4" + + @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} + + @applier = @class.new(:list, %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.apply(@ca) + end + end + end + + describe ":print" do + describe "and :all was provided" do + it "should print all certificates" do + @ca.expects(:list).returns %w{host1 host2} + + @applier = @class.new(:print, :all) + + @ca.expects(:print).with("host1").returns "h1" + @applier.expects(:puts).with "h1" + + @ca.expects(:print).with("host2").returns "h2" + @applier.expects(:puts).with "h2" + + @applier.apply(@ca) + end + end + + describe "and an array of names was provided" do + it "should print each named certificate if found" do + @applier = @class.new(:print, %w{host1 host2}) + + @ca.expects(:print).with("host1").returns "h1" + @applier.expects(:puts).with "h1" + + @ca.expects(:print).with("host2").returns "h2" + @applier.expects(:puts).with "h2" + + @applier.apply(@ca) + end + + it "should log any named but not found certificates" do + @applier = @class.new(:print, %w{host1 host2}) + + @ca.expects(:print).with("host1").returns "h1" + @applier.expects(:puts).with "h1" + + @ca.expects(:print).with("host2").returns nil + Puppet.expects(:err).with { |msg| msg.include?("host2") } + + @applier.apply(@ca) + end + end + end + end +end |
