diff options
| author | Max Martin <max@puppetlabs.com> | 2011-04-05 15:56:29 -0700 |
|---|---|---|
| committer | Max Martin <max@puppetlabs.com> | 2011-04-05 15:56:29 -0700 |
| commit | ff9b24fa7d60e7a224c78904ab451c4c39cb231f (patch) | |
| tree | fa38972bd3e446d8eedd17de3b15dbd7a69ca2dd /lib | |
| parent | b82bf9faa84447ac13b09d9d2db310fb3a35bd12 (diff) | |
| parent | e20e6185f7f26d02c7ea275f8adf43c088169129 (diff) | |
| download | puppet-ff9b24fa7d60e7a224c78904ab451c4c39cb231f.tar.gz puppet-ff9b24fa7d60e7a224c78904ab451c4c39cb231f.tar.xz puppet-ff9b24fa7d60e7a224c78904ab451c4c39cb231f.zip | |
Merge branch 'feature/master/5528-certificates_signing_api' into next
* feature/master/5528-certificates_signing_api:
(#5528) Add REST API for signing, revoking, retrieving, cleaning certs
Diffstat (limited to 'lib')
| -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' |
