summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMax Martin <max@puppetlabs.com>2011-03-22 18:36:01 -0700
committerMax Martin <max@puppetlabs.com>2011-04-05 15:55:24 -0700
commite20e6185f7f26d02c7ea275f8adf43c088169129 (patch)
treed4e91cbf60456aecd9210cf2123fab1dc006ed99
parent7e71840e29cb09c772668a51ada3cab1e319e50f (diff)
downloadpuppet-e20e6185f7f26d02c7ea275f8adf43c088169129.tar.gz
puppet-e20e6185f7f26d02c7ea275f8adf43c088169129.tar.xz
puppet-e20e6185f7f26d02c7ea275f8adf43c088169129.zip
(#5528) Add REST API for signing, revoking, retrieving, cleaning certs
This commit introduces a new Indirector terminus, certificate_status, which allows for signing, revoking, listing, and cleaning SSL certificates over HTTP via REST. Documentation for these new features can be found in our REST API documentation on the docs site: http://docs.puppetlabs.com/guides/rest_api.html This documentation has not been updated as of the writing of this commit, but will be very soon. Puppet::SSL::Host is now fully integrated into the Indirector. Paired-with:Matt Robinson, Jacob Helwig, Jesse Wolfe, Richard Crowley, Luke Kanies
-rw-r--r--lib/puppet/indirector/certificate_status.rb4
-rw-r--r--lib/puppet/indirector/certificate_status/file.rb82
-rw-r--r--lib/puppet/indirector/certificate_status/rest.rb10
-rw-r--r--lib/puppet/network/http/api/v1.rb1
-rw-r--r--lib/puppet/ssl/host.rb78
-rw-r--r--spec/unit/indirector/certificate_status/file_spec.rb188
-rw-r--r--spec/unit/indirector/certificate_status/rest_spec.rb15
-rwxr-xr-xspec/unit/ssl/host_spec.rb192
8 files changed, 502 insertions, 68 deletions
diff --git a/lib/puppet/indirector/certificate_status.rb b/lib/puppet/indirector/certificate_status.rb
new file mode 100644
index 000000000..47c3adcd4
--- /dev/null
+++ b/lib/puppet/indirector/certificate_status.rb
@@ -0,0 +1,4 @@
+require 'puppet/indirector'
+
+class Puppet::Indirector::CertificateStatus
+end
diff --git a/lib/puppet/indirector/certificate_status/file.rb b/lib/puppet/indirector/certificate_status/file.rb
new file mode 100644
index 000000000..9061d9423
--- /dev/null
+++ b/lib/puppet/indirector/certificate_status/file.rb
@@ -0,0 +1,82 @@
+require 'puppet'
+require 'puppet/indirector/certificate_status'
+require 'puppet/ssl/certificate'
+require 'puppet/ssl/certificate_authority'
+require 'puppet/ssl/certificate_request'
+require 'puppet/ssl/host'
+require 'puppet/ssl/key'
+
+class Puppet::Indirector::CertificateStatus::File < Puppet::Indirector::Code
+ def ca
+ raise ArgumentError, "This process is not configured as a certificate authority" unless Puppet::SSL::CertificateAuthority.ca?
+ Puppet::SSL::CertificateAuthority.new
+ end
+
+ def destroy(request)
+ deleted = []
+ [
+ Puppet::SSL::Certificate,
+ Puppet::SSL::CertificateRequest,
+ Puppet::SSL::Key,
+ ].collect do |part|
+ if part.indirection.destroy(request.key)
+ deleted << "#{part}"
+ end
+ end
+
+ return "Nothing was deleted" if deleted.empty?
+ "Deleted for #{request.key}: #{deleted.join(", ")}"
+ end
+
+ def save(request)
+ if request.instance.desired_state == "signed"
+ certificate_request = Puppet::SSL::CertificateRequest.indirection.find(request.key)
+ raise Puppet::Error, "Cannot sign for host #{request.key} without a certificate request" unless certificate_request
+ ca.sign(request.key)
+ elsif request.instance.desired_state == "revoked"
+ certificate = Puppet::SSL::Certificate.indirection.find(request.key)
+ raise Puppet::Error, "Cannot revoke host #{request.key} because has it doesn't have a signed certificate" unless certificate
+ ca.revoke(request.key)
+ else
+ raise Puppet::Error, "State #{request.instance.desired_state} invalid; Must specify desired state of 'signed' or 'revoked' for host #{request.key}"
+ end
+
+ end
+
+ def search(request)
+ # Support historic interface wherein users provide classes to filter
+ # the search. When used via the REST API, the arguments must be
+ # a Symbol or an Array containing Symbol objects.
+ klasses = case request.options[:for]
+ when Class
+ [request.options[:for]]
+ when nil
+ [
+ Puppet::SSL::Certificate,
+ Puppet::SSL::CertificateRequest,
+ Puppet::SSL::Key,
+ ]
+ else
+ [request.options[:for]].flatten.map do |klassname|
+ indirection.class.model(klassname.to_sym)
+ end
+ end
+
+ klasses.collect do |klass|
+ klass.indirection.search(request.key, request.options)
+ end.flatten.collect do |result|
+ result.name
+ end.uniq.collect &Puppet::SSL::Host.method(:new)
+ end
+
+ def find(request)
+ ssl_host = Puppet::SSL::Host.new(request.key)
+ public_key = Puppet::SSL::Certificate.indirection.find(request.key)
+
+ if ssl_host.certificate_request || public_key
+ ssl_host
+ else
+ nil
+ end
+ end
+end
diff --git a/lib/puppet/indirector/certificate_status/rest.rb b/lib/puppet/indirector/certificate_status/rest.rb
new file mode 100644
index 000000000..c53b663b5
--- /dev/null
+++ b/lib/puppet/indirector/certificate_status/rest.rb
@@ -0,0 +1,10 @@
+require 'puppet/ssl/host'
+require 'puppet/indirector/rest'
+require 'puppet/indirector/certificate_status'
+
+class Puppet::Indirector::CertificateStatus::Rest < Puppet::Indirector::REST
+ desc "Sign, revoke, search for, or clean certificates & certificate requests over HTTP."
+
+ use_server_setting(:ca_server)
+ use_port_setting(:ca_port)
+end
diff --git a/lib/puppet/network/http/api/v1.rb b/lib/puppet/network/http/api/v1.rb
index dcb0e0a22..5fe143979 100644
--- a/lib/puppet/network/http/api/v1.rb
+++ b/lib/puppet/network/http/api/v1.rb
@@ -61,6 +61,7 @@ module Puppet::Network::HTTP::API::V1
# that leads to the fix being too long.
return :singular if indirection == "facts"
return :singular if indirection == "status"
+ return :singular if indirection == "certificate_status"
return :plural if indirection == "inventory"
result = (indirection =~ /s$|_search$/) ? :plural : :singular
diff --git a/lib/puppet/ssl/host.rb b/lib/puppet/ssl/host.rb
index 7f71ced99..b9215effd 100644
--- a/lib/puppet/ssl/host.rb
+++ b/lib/puppet/ssl/host.rb
@@ -1,3 +1,4 @@
+require 'puppet/indirector'
require 'puppet/ssl'
require 'puppet/ssl/key'
require 'puppet/ssl/certificate'
@@ -15,11 +16,17 @@ class Puppet::SSL::Host
CertificateRequest = Puppet::SSL::CertificateRequest
CertificateRevocationList = Puppet::SSL::CertificateRevocationList
+ extend Puppet::Indirector
+ indirects :certificate_status, :terminus_class => :file
+
attr_reader :name
attr_accessor :ca
attr_writer :key, :certificate, :certificate_request
+ # This accessor is used in instances for indirector requests to hold desired state
+ attr_accessor :desired_state
+
class << self
include Puppet::Util::Cacher
@@ -47,6 +54,13 @@ class Puppet::SSL::Host
CertificateRequest.indirection.terminus_class = terminus
CertificateRevocationList.indirection.terminus_class = terminus
+ host_map = {:ca => :file, :file => nil, :rest => :rest}
+ if term = host_map[terminus]
+ self.indirection.terminus_class = term
+ else
+ self.indirection.reset_terminus_class
+ end
+
if cache
# This is weird; we don't actually cache our keys, we
# use what would otherwise be the cache as our normal
@@ -85,30 +99,34 @@ class Puppet::SSL::Host
# Specify how we expect to interact with our certificate authority.
def self.ca_location=(mode)
- raise ArgumentError, "CA Mode can only be #{CA_MODES.collect { |m| m.to_s }.join(", ")}" unless CA_MODES.include?(mode)
+ modes = CA_MODES.collect { |m, vals| m.to_s }.join(", ")
+ raise ArgumentError, "CA Mode can only be one of: #{modes}" unless CA_MODES.include?(mode)
@ca_location = mode
configure_indirection(*CA_MODES[@ca_location])
end
- # Remove all traces of a given host
+ # Puppet::SSL::Host is actually indirected now so the original implementation
+ # has been moved into the certificate_status indirector. This method is in-use
+ # in `puppet cert -c <certname>`.
def self.destroy(name)
- [Key, Certificate, CertificateRequest].collect { |part| part.indirection.destroy(name) }.any? { |x| x }
+ indirection.destroy(name)
end
- # Search for more than one host, optionally only specifying
- # an interest in hosts with a given file type.
- # This just allows our non-indirected class to have one of
- # indirection methods.
- def self.search(options = {})
- classlist = [options[:for] || [Key, CertificateRequest, Certificate]].flatten
-
- # Collect the results from each class, flatten them, collect all of the names, make the name list unique,
- # then create a Host instance for each one.
- classlist.collect { |klass| klass.indirection.search }.flatten.collect { |r| r.name }.uniq.collect do |name|
- new(name)
+ def self.from_pson(pson)
+ instance = new(pson["name"])
+ if pson["desired_state"]
+ instance.desired_state = pson["desired_state"]
end
+ instance
+ end
+
+ # Puppet::SSL::Host is actually indirected now so the original implementation
+ # has been moved into the certificate_status indirector. This method does not
+ # appear to be in use in `puppet cert -l`.
+ def self.search(options = {})
+ indirection.search("*", options)
end
# Is this a ca host, meaning that all of its files go in the CA location?
@@ -221,6 +239,24 @@ class Puppet::SSL::Host
@ssl_store
end
+ def to_pson(*args)
+ my_cert = Puppet::SSL::Certificate.indirection.find(name)
+ pson_hash = { :name => name }
+
+ my_state = state
+
+ pson_hash[:state] = my_state
+ pson_hash[:desired_state] = desired_state if desired_state
+
+ if my_state == 'requested'
+ pson_hash[:fingerprint] = certificate_request.fingerprint
+ else
+ pson_hash[:fingerprint] = my_cert.fingerprint
+ end
+
+ pson_hash.to_pson(*args)
+ end
+
# Attempt to retrieve a cert, if we don't already have one.
def wait_for_cert(time)
begin
@@ -257,6 +293,20 @@ class Puppet::SSL::Host
end
end
end
+
+ def state
+ my_cert = Puppet::SSL::Certificate.indirection.find(name)
+ if certificate_request
+ return 'requested'
+ end
+
+ begin
+ Puppet::SSL::CertificateAuthority.new.verify(my_cert)
+ return 'signed'
+ rescue Puppet::SSL::CertificateAuthority::CertificateVerificationError
+ return 'revoked'
+ end
+ end
end
require 'puppet/ssl/certificate_authority'
diff --git a/spec/unit/indirector/certificate_status/file_spec.rb b/spec/unit/indirector/certificate_status/file_spec.rb
new file mode 100644
index 000000000..6cc0bb547
--- /dev/null
+++ b/spec/unit/indirector/certificate_status/file_spec.rb
@@ -0,0 +1,188 @@
+#!/usr/bin/env ruby
+
+require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper.rb')
+require 'puppet/ssl/host'
+require 'puppet/indirector/certificate_status'
+require 'tempfile'
+
+describe "Puppet::Indirector::CertificateStatus::File" do
+ include PuppetSpec::Files
+
+ before do
+ Puppet::SSL::CertificateAuthority.stubs(:ca?).returns true
+ @terminus = Puppet::SSL::Host.indirection.terminus(:file)
+
+ @tmpdir = tmpdir("certificate_status_ca_testing")
+ Puppet[:confdir] = @tmpdir
+ Puppet[:vardir] = @tmpdir
+
+ # localcacert is where each client stores the CA certificate
+ # cacert is where the master stores the CA certificate
+ # Since we need to play the role of both for testing we need them to be the same and exist
+ Puppet[:cacert] = Puppet[:localcacert]
+ end
+
+ def generate_csr(host)
+ host.generate_key
+ csr = Puppet::SSL::CertificateRequest.new(host.name)
+ csr.generate(host.key.content)
+ Puppet::SSL::CertificateRequest.indirection.save(csr)
+ end
+
+ def sign_csr(host)
+ host.desired_state = "signed"
+ @terminus.save(Puppet::Indirector::Request.new(:certificate_status, :save, host.name, host))
+ end
+
+ def generate_signed_cert(host)
+ generate_csr(host)
+ sign_csr(host)
+
+ @terminus.find(Puppet::Indirector::Request.new(:certificate_status, :find, host.name, host))
+ end
+
+ def generate_revoked_cert(host)
+ generate_signed_cert(host)
+
+ host.desired_state = "revoked"
+
+ @terminus.save(Puppet::Indirector::Request.new(:certificate_status, :save, host.name, host))
+ end
+
+ it "should be a terminus on SSL::Host" do
+ @terminus.should be_instance_of(Puppet::Indirector::CertificateStatus::File)
+ end
+
+ it "should create a CA instance if none is present" do
+ @terminus.ca.should be_instance_of(Puppet::SSL::CertificateAuthority)
+ end
+
+ describe "when creating the CA" do
+ it "should fail if it is not a valid CA" do
+ Puppet::SSL::CertificateAuthority.expects(:ca?).returns false
+ lambda { @terminus.ca }.should raise_error(ArgumentError, "This process is not configured as a certificate authority")
+ end
+ end
+
+ it "should be indirected with the name 'certificate_status'" do
+ Puppet::SSL::Host.indirection.name.should == :certificate_status
+ end
+
+ describe "when finding" do
+ before do
+ @host = Puppet::SSL::Host.new("foo")
+ Puppet.settings.use(:main)
+ end
+
+ it "should return the Puppet::SSL::Host when a CSR exists for the host" do
+ generate_csr(@host)
+ request = Puppet::Indirector::Request.new(:certificate_status, :find, "foo", @host)
+
+ retrieved_host = @terminus.find(request)
+
+ retrieved_host.name.should == @host.name
+ retrieved_host.certificate_request.content.to_s.chomp.should == @host.certificate_request.content.to_s.chomp
+ end
+
+ it "should return the Puppet::SSL::Host when a public key exist for the host" do
+ generate_signed_cert(@host)
+ request = Puppet::Indirector::Request.new(:certificate_status, :find, "foo", @host)
+
+ retrieved_host = @terminus.find(request)
+
+ retrieved_host.name.should == @host.name
+ retrieved_host.certificate.content.to_s.chomp.should == @host.certificate.content.to_s.chomp
+ end
+
+ it "should return nil when neither a CSR nor public key exist for the host" do
+ request = Puppet::Indirector::Request.new(:certificate_status, :find, "foo", @host)
+ @terminus.find(request).should == nil
+ end
+ end
+
+ describe "when saving" do
+ before do
+ @host = Puppet::SSL::Host.new("foobar")
+ Puppet.settings.use(:main)
+ end
+
+ describe "when signing a cert" do
+ before do
+ @host.desired_state = "signed"
+ @request = Puppet::Indirector::Request.new(:certificate_status, :save, "foobar", @host)
+ end
+
+ it "should fail if no CSR is on disk" do
+ lambda { @terminus.save(@request) }.should raise_error(Puppet::Error, /certificate request/)
+ end
+
+ it "should sign the on-disk CSR when it is present" do
+ signed_host = generate_signed_cert(@host)
+
+ signed_host.state.should == "signed"
+ Puppet::SSL::Certificate.indirection.find("foobar").should be_instance_of(Puppet::SSL::Certificate)
+ end
+ end
+
+ describe "when revoking a cert" do
+ before do
+ @request = Puppet::Indirector::Request.new(:certificate_status, :save, "foobar", @host)
+ end
+
+ it "should fail if no certificate is on disk" do
+ @host.desired_state = "revoked"
+ lambda { @terminus.save(@request) }.should raise_error(Puppet::Error, /Cannot revoke/)
+ end
+
+ it "should revoke the certificate when it is present" do
+ generate_revoked_cert(@host)
+
+ @host.state.should == 'revoked'
+ end
+ end
+ end
+
+ describe "when deleting" do
+ before do
+ Puppet.settings.use(:main)
+ end
+
+ it "should not delete anything if no certificate, request, or key is on disk" do
+ host = Puppet::SSL::Host.new("clean_me")
+ request = Puppet::Indirector::Request.new(:certificate_status, :delete, "clean_me", host)
+ @terminus.destroy(request).should == "Nothing was deleted"
+ end
+
+ it "should clean certs, cert requests, keys" do
+ signed_host = Puppet::SSL::Host.new("clean_signed_cert")
+ generate_signed_cert(signed_host)
+ signed_request = Puppet::Indirector::Request.new(:certificate_status, :delete, "clean_signed_cert", signed_host)
+ @terminus.destroy(signed_request).should == "Deleted for clean_signed_cert: Puppet::SSL::Certificate, Puppet::SSL::Key"
+
+ requested_host = Puppet::SSL::Host.new("clean_csr")
+ generate_csr(requested_host)
+ csr_request = Puppet::Indirector::Request.new(:certificate_status, :delete, "clean_csr", requested_host)
+ @terminus.destroy(csr_request).should == "Deleted for clean_csr: Puppet::SSL::CertificateRequest, Puppet::SSL::Key"
+ end
+ end
+
+ describe "when searching" do
+ it "should return a list of all hosts with certificate requests, signed certs, or revoked certs" do
+ Puppet.settings.use(:main)
+
+ signed_host = Puppet::SSL::Host.new("signed_host")
+ generate_signed_cert(signed_host)
+
+ requested_host = Puppet::SSL::Host.new("requested_host")
+ generate_csr(requested_host)
+
+ revoked_host = Puppet::SSL::Host.new("revoked_host")
+ generate_revoked_cert(revoked_host)
+
+ retrieved_hosts = @terminus.search(Puppet::Indirector::Request.new(:certificate_status, :search, "all", signed_host))
+
+ results = retrieved_hosts.map {|h| [h.name, h.state]}.sort{ |h,i| h[0] <=> i[0] }
+ results.should == [["ca","signed"],["requested_host","requested"],["revoked_host","revoked"],["signed_host","signed"]]
+ end
+ end
+end
diff --git a/spec/unit/indirector/certificate_status/rest_spec.rb b/spec/unit/indirector/certificate_status/rest_spec.rb
new file mode 100644
index 000000000..f44eac671
--- /dev/null
+++ b/spec/unit/indirector/certificate_status/rest_spec.rb
@@ -0,0 +1,15 @@
+#!/usr/bin/env ruby
+
+require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper.rb')
+require 'puppet/ssl/host'
+require 'puppet/indirector/certificate_status'
+
+describe "Puppet::CertificateStatus::Rest" do
+ before do
+ @terminus = Puppet::SSL::Host.indirection.terminus(:rest)
+ end
+
+ it "should be a terminus on Puppet::SSL::Host" do
+ @terminus.should be_instance_of(Puppet::Indirector::CertificateStatus::Rest)
+ end
+end
diff --git a/spec/unit/ssl/host_spec.rb b/spec/unit/ssl/host_spec.rb
index d8f15e738..885bd45e2 100755
--- a/spec/unit/ssl/host_spec.rb
+++ b/spec/unit/ssl/host_spec.rb
@@ -3,16 +3,19 @@
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
require 'puppet/ssl/host'
+require 'puppet/sslcertificates'
+require 'puppet/sslcertificates/ca'
describe Puppet::SSL::Host do
before do
- @class = Puppet::SSL::Host
- @host = @class.new("myname")
+ Puppet::SSL::Host.indirection.terminus_class = :file
+ @host = Puppet::SSL::Host.new("myname")
end
after do
# Cleaned out any cached localhost instance.
Puppet::Util::Cacher.expire
+ Puppet::SSL::Host.ca_location = :none
end
it "should use any provided name as its name" do
@@ -140,13 +143,6 @@ describe Puppet::SSL::Host do
end
describe "when specifying the CA location" do
- before do
- [Puppet::SSL::Key, Puppet::SSL::Certificate, Puppet::SSL::CertificateRequest, Puppet::SSL::CertificateRevocationList].each do |klass|
- klass.indirection.stubs(:terminus_class=)
- klass.indirection.stubs(:cache_class=)
- end
- end
-
it "should support the location ':local'" do
lambda { Puppet::SSL::Host.ca_location = :local }.should_not raise_error
end
@@ -168,80 +164,88 @@ describe Puppet::SSL::Host do
end
describe "as 'local'" do
- it "should set the cache class for Certificate, CertificateRevocationList, and CertificateRequest as :file" do
- Puppet::SSL::Certificate.indirection.expects(:cache_class=).with :file
- Puppet::SSL::CertificateRequest.indirection.expects(:cache_class=).with :file
- Puppet::SSL::CertificateRevocationList.indirection.expects(:cache_class=).with :file
-
+ before do
Puppet::SSL::Host.ca_location = :local
end
- it "should set the terminus class for Key as :file" do
- Puppet::SSL::Key.indirection.expects(:terminus_class=).with :file
+ it "should set the cache class for Certificate, CertificateRevocationList, and CertificateRequest as :file" do
+ Puppet::SSL::Certificate.indirection.cache_class.should == :file
+ Puppet::SSL::CertificateRequest.indirection.cache_class.should == :file
+ Puppet::SSL::CertificateRevocationList.indirection.cache_class.should == :file
+ end
- Puppet::SSL::Host.ca_location = :local
+ it "should set the terminus class for Key and Host as :file" do
+ Puppet::SSL::Key.indirection.terminus_class.should == :file
+ Puppet::SSL::Host.indirection.terminus_class.should == :file
end
it "should set the terminus class for Certificate, CertificateRevocationList, and CertificateRequest as :ca" do
- Puppet::SSL::Certificate.indirection.expects(:terminus_class=).with :ca
- Puppet::SSL::CertificateRequest.indirection.expects(:terminus_class=).with :ca
- Puppet::SSL::CertificateRevocationList.indirection.expects(:terminus_class=).with :ca
-
- Puppet::SSL::Host.ca_location = :local
+ Puppet::SSL::Certificate.indirection.terminus_class.should == :ca
+ Puppet::SSL::CertificateRequest.indirection.terminus_class.should == :ca
+ Puppet::SSL::CertificateRevocationList.indirection.terminus_class.should == :ca
end
end
describe "as 'remote'" do
- it "should set the cache class for Certificate, CertificateRevocationList, and CertificateRequest as :file" do
- Puppet::SSL::Certificate.indirection.expects(:cache_class=).with :file
- Puppet::SSL::CertificateRequest.indirection.expects(:cache_class=).with :file
- Puppet::SSL::CertificateRevocationList.indirection.expects(:cache_class=).with :file
-
+ before do
Puppet::SSL::Host.ca_location = :remote
end
- it "should set the terminus class for Key as :file" do
- Puppet::SSL::Key.indirection.expects(:terminus_class=).with :file
-
- Puppet::SSL::Host.ca_location = :remote
+ it "should set the cache class for Certificate, CertificateRevocationList, and CertificateRequest as :file" do
+ Puppet::SSL::Certificate.indirection.cache_class.should == :file
+ Puppet::SSL::CertificateRequest.indirection.cache_class.should == :file
+ Puppet::SSL::CertificateRevocationList.indirection.cache_class.should == :file
end
- it "should set the terminus class for Certificate, CertificateRevocationList, and CertificateRequest as :rest" do
- Puppet::SSL::Certificate.indirection.expects(:terminus_class=).with :rest
- Puppet::SSL::CertificateRequest.indirection.expects(:terminus_class=).with :rest
- Puppet::SSL::CertificateRevocationList.indirection.expects(:terminus_class=).with :rest
+ it "should set the terminus class for Key as :file" do
+ Puppet::SSL::Key.indirection.terminus_class.should == :file
+ end
- Puppet::SSL::Host.ca_location = :remote
+ it "should set the terminus class for Host, Certificate, CertificateRevocationList, and CertificateRequest as :rest" do
+ Puppet::SSL::Host.indirection.terminus_class.should == :rest
+ Puppet::SSL::Certificate.indirection.terminus_class.should == :rest
+ Puppet::SSL::CertificateRequest.indirection.terminus_class.should == :rest
+ Puppet::SSL::CertificateRevocationList.indirection.terminus_class.should == :rest
end
end
describe "as 'only'" do
- it "should set the terminus class for Key, Certificate, CertificateRevocationList, and CertificateRequest as :ca" do
- Puppet::SSL::Key.indirection.expects(:terminus_class=).with :ca
- Puppet::SSL::Certificate.indirection.expects(:terminus_class=).with :ca
- Puppet::SSL::CertificateRequest.indirection.expects(:terminus_class=).with :ca
- Puppet::SSL::CertificateRevocationList.indirection.expects(:terminus_class=).with :ca
-
+ before do
Puppet::SSL::Host.ca_location = :only
end
- it "should reset the cache class for Certificate, CertificateRevocationList, and CertificateRequest to nil" do
- Puppet::SSL::Certificate.indirection.expects(:cache_class=).with nil
- Puppet::SSL::CertificateRequest.indirection.expects(:cache_class=).with nil
- Puppet::SSL::CertificateRevocationList.indirection.expects(:cache_class=).with nil
+ it "should set the terminus class for Key, Certificate, CertificateRevocationList, and CertificateRequest as :ca" do
+ Puppet::SSL::Key.indirection.terminus_class.should == :ca
+ Puppet::SSL::Certificate.indirection.terminus_class.should == :ca
+ Puppet::SSL::CertificateRequest.indirection.terminus_class.should == :ca
+ Puppet::SSL::CertificateRevocationList.indirection.terminus_class.should == :ca
+ end
- Puppet::SSL::Host.ca_location = :only
+ it "should set the cache class for Certificate, CertificateRevocationList, and CertificateRequest to nil" do
+ Puppet::SSL::Certificate.indirection.cache_class.should be_nil
+ Puppet::SSL::CertificateRequest.indirection.cache_class.should be_nil
+ Puppet::SSL::CertificateRevocationList.indirection.cache_class.should be_nil
+ end
+
+ it "should set the terminus class for Host to :file" do
+ Puppet::SSL::Host.indirection.terminus_class.should == :file
end
end
describe "as 'none'" do
+ before do
+ Puppet::SSL::Host.ca_location = :none
+ end
+
it "should set the terminus class for Key, Certificate, CertificateRevocationList, and CertificateRequest as :file" do
- Puppet::SSL::Key.indirection.expects(:terminus_class=).with :file
- Puppet::SSL::Certificate.indirection.expects(:terminus_class=).with :file
- Puppet::SSL::CertificateRequest.indirection.expects(:terminus_class=).with :file
- Puppet::SSL::CertificateRevocationList.indirection.expects(:terminus_class=).with :file
+ Puppet::SSL::Key.indirection.terminus_class.should == :file
+ Puppet::SSL::Certificate.indirection.terminus_class.should == :file
+ Puppet::SSL::CertificateRequest.indirection.terminus_class.should == :file
+ Puppet::SSL::CertificateRevocationList.indirection.terminus_class.should == :file
+ end
- Puppet::SSL::Host.ca_location = :none
+ it "should set the terminus class for Host to 'none'" do
+ lambda { Puppet::SSL::Host.indirection.terminus_class }.should raise_error(Puppet::DevError)
end
end
end
@@ -271,8 +275,8 @@ describe Puppet::SSL::Host do
Puppet::SSL::Host.destroy("myhost").should be_true
end
- it "should return false if none of the classes returned true" do
- Puppet::SSL::Host.destroy("myhost").should be_false
+ it "should report that nothing was deleted if none of the classes returned true" do
+ Puppet::SSL::Host.destroy("myhost").should == "Nothing was deleted"
end
end
@@ -709,4 +713,84 @@ describe Puppet::SSL::Host do
@host.wait_for_cert(1)
end
end
+
+ describe "when handling PSON" do
+ include PuppetSpec::Files
+
+ before do
+ Puppet[:vardir] = tmpdir("ssl_test_vardir")
+ Puppet[:ssldir] = tmpdir("ssl_test_ssldir")
+ Puppet::SSLCertificates::CA.new.mkrootcert
+ # localcacert is where each client stores the CA certificate
+ # cacert is where the master stores the CA certificate
+ # Since we need to play the role of both for testing we need them to be the same and exist
+ Puppet[:cacert] = Puppet[:localcacert]
+
+ @ca=Puppet::SSL::CertificateAuthority.new
+ end
+
+ describe "when converting to PSON" do
+ it "should be able to identify a host with an unsigned certificate request" do
+ host = Puppet::SSL::Host.new("bazinga")
+ host.generate_certificate_request
+ pson_hash = {
+ "fingerprint" => host.certificate_request.fingerprint,
+ "desired_state" => 'requested',
+ "name" => host.name
+ }
+
+ result = PSON.parse(Puppet::SSL::Host.new(host.name).to_pson)
+ result["fingerprint"].should == pson_hash["fingerprint"]
+ result["name"].should == pson_hash["name"]
+ result["state"].should == pson_hash["desired_state"]
+ end
+
+ it "should be able to identify a host with a signed certificate" do
+ host = Puppet::SSL::Host.new("bazinga")
+ host.generate_certificate_request
+ @ca.sign(host.name)
+ pson_hash = {
+ "fingerprint" => Puppet::SSL::Certificate.indirection.find(host.name).fingerprint,
+ "desired_state" => 'signed',
+ "name" => host.name,
+ }
+
+ result = PSON.parse(Puppet::SSL::Host.new(host.name).to_pson)
+ result["fingerprint"].should == pson_hash["fingerprint"]
+ result["name"].should == pson_hash["name"]
+ result["state"].should == pson_hash["desired_state"]
+ end
+
+ it "should be able to identify a host with a revoked certificate" do
+ host = Puppet::SSL::Host.new("bazinga")
+ host.generate_certificate_request
+ @ca.sign(host.name)
+ @ca.revoke(host.name)
+ pson_hash = {
+ "fingerprint" => Puppet::SSL::Certificate.indirection.find(host.name).fingerprint,
+ "desired_state" => 'revoked',
+ "name" => host.name,
+ }
+
+ result = PSON.parse(Puppet::SSL::Host.new(host.name).to_pson)
+ result["fingerprint"].should == pson_hash["fingerprint"]
+ result["name"].should == pson_hash["name"]
+ result["state"].should == pson_hash["desired_state"]
+ end
+ end
+
+ describe "when converting from PSON" do
+ it "should return a Puppet::SSL::Host object with the specified desired state" do
+ host = Puppet::SSL::Host.new("bazinga")
+ host.desired_state="signed"
+ pson_hash = {
+ "name" => host.name,
+ "desired_state" => host.desired_state,
+ }
+ generated_host = Puppet::SSL::Host.from_pson(pson_hash)
+ generated_host.desired_state.should == host.desired_state
+ generated_host.name.should == host.name
+ end
+ end
+ end
end