summaryrefslogtreecommitdiffstats
path: root/lib/puppet
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 /lib/puppet
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
Diffstat (limited to 'lib/puppet')
-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
5 files changed, 161 insertions, 14 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'