diff options
| author | Max Martin <max@puppetlabs.com> | 2011-03-22 18:36:01 -0700 |
|---|---|---|
| committer | Max Martin <max@puppetlabs.com> | 2011-04-05 15:55:24 -0700 |
| commit | e20e6185f7f26d02c7ea275f8adf43c088169129 (patch) | |
| tree | d4e91cbf60456aecd9210cf2123fab1dc006ed99 /lib/puppet | |
| parent | 7e71840e29cb09c772668a51ada3cab1e319e50f (diff) | |
| download | puppet-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.rb | 4 | ||||
| -rw-r--r-- | lib/puppet/indirector/certificate_status/file.rb | 82 | ||||
| -rw-r--r-- | lib/puppet/indirector/certificate_status/rest.rb | 10 | ||||
| -rw-r--r-- | lib/puppet/network/http/api/v1.rb | 1 | ||||
| -rw-r--r-- | lib/puppet/ssl/host.rb | 78 |
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' |
