diff --git a/lib/puppet/network/handler/ca.rb b/lib/puppet/network/handler/ca.rb index ebb6fc427..1dabeee2f 100644 --- a/lib/puppet/network/handler/ca.rb +++ b/lib/puppet/network/handler/ca.rb @@ -1,151 +1,61 @@ require 'openssl' require 'puppet' -require 'puppet/sslcertificates' require 'xmlrpc/server' - -# Much of this was taken from QuickCert: -# http://segment7.net/projects/ruby/QuickCert/ +require 'puppet/network/handler' class Puppet::Network::Handler class CA < Handler attr_reader :ca desc "Provides an interface for signing CSRs. Accepts a CSR and returns the CA certificate and the signed certificate, or returns nil if the cert is not signed." @interface = XMLRPC::Service::Interface.new("puppetca") { |iface| iface.add_method("array getcert(csr)") } - def autosign - if defined?(@autosign) - @autosign - else - Puppet[:autosign] - end - 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 #{autosign.inspect}" - end - - unless FileTest.exists?(autosign) - unless defined?(@@warnedonautosign) - @@warnedonautosign = true - Puppet.info "Autosign is enabled but #{autosign} is missing" - 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? - auth.allowed?(hostname, "127.1.1.1") - end - def initialize(hash = {}) Puppet.settings.use(:main, :ssl, :ca) - @autosign = hash[:autosign] if hash.include? :autosign - @ca = Puppet::SSLCertificates::CA.new(hash) + @ca = Puppet::SSL::CertificateAuthority.instance end # our client sends us a csr, and we either store it for later signing, # or we sign it right away def getcert(csrtext, client = nil, clientip = nil) - csr = OpenSSL::X509::Request.new(csrtext) - - # Use the hostname from the CSR, not from the network. - subject = csr.subject - - nameary = subject.to_a.find { |ary| - ary[0] == "CN" - } - - if nameary.nil? - Puppet.err( - "Invalid certificate request: could not retrieve server name" - ) - return "invalid" - end - - hostname = nameary[1] + csr = Puppet::SSL::CertificateRequest.from_s(csrtext) + hostname = csr.name unless @ca Puppet.notice "Host #{hostname} asked for signing from non-CA master" return "" end # We used to save the public key, but it's basically unnecessary # and it mucks with the permissions requirements. - # save_pk(hostname, csr.public_key) - - certfile = File.join(Puppet[:certdir], [hostname, "pem"].join(".")) # first check to see if we already have a signed cert for the host - cert, cacert = ca.getclientcert(hostname) - if cert and cacert + cert = Puppet::SSL::Certificate.find(hostname) + cacert = Puppet::SSL::Certificate.find(@ca.host.name) + + if cert Puppet.info "Retrieving existing certificate for #{hostname}" - unless csr.public_key.to_s == cert.public_key.to_s + unless csr.content.public_key.to_s == cert.content.public_key.to_s raise Puppet::Error, "Certificate request does not match existing certificate; run 'puppetca --clean #{hostname}'." end - return [cert.to_pem, cacert.to_pem] - elsif @ca - if self.autosign?(hostname) or client.nil? - Puppet.info "Signing certificate for CA server" if client.nil? - # okay, we don't have a signed cert - # if we're a CA and autosign is turned on, then go ahead and sign - # the csr and return the results - Puppet.info "Signing certificate for #{hostname}" - cert, cacert = @ca.sign(csr) - #Puppet.info "Cert: #{cert.class}; Cacert: #{cacert.class}" - return [cert.to_pem, cacert.to_pem] - else # just write out the csr for later signing - if @ca.getclientcsr(hostname) - Puppet.info "Not replacing existing request from #{hostname}" - else - Puppet.notice "Host #{hostname} has a waiting certificate request" - @ca.storeclientcsr(csr) - end - return ["", ""] - end + [cert.to_s, cacert.to_s] else - raise "huh?" - end - end + csr.save - private - - # Save the public key. - def save_pk(hostname, public_key) - pkeyfile = File.join(Puppet[:publickeydir], [hostname, "pem"].join('.')) - - if FileTest.exists?(pkeyfile) - currentkey = File.open(pkeyfile) { |k| k.read } - unless currentkey == public_key.to_s - raise Puppet::Error, "public keys for #{hostname} differ" + # We determine whether we signed the csr by checking if there's a certificate for it + if cert = Puppet::SSL::Certificate.find(hostname) + [cert.to_s, cacert.to_s] + else + nil end - else - File.open(pkeyfile, "w", 0644) { |f| - f.print public_key.to_s - } end end end end diff --git a/lib/puppet/network/handler/master.rb b/lib/puppet/network/handler/master.rb index 62aab539e..5d1f031b0 100644 --- a/lib/puppet/network/handler/master.rb +++ b/lib/puppet/network/handler/master.rb @@ -1,89 +1,86 @@ require 'openssl' require 'puppet' -require 'puppet/sslcertificates' require 'xmlrpc/server' require 'yaml' class Puppet::Network::Handler class MasterError < Puppet::Error; end class Master < Handler desc "Puppet's configuration interface. Used for all interactions related to generating client configurations." include Puppet::Util attr_accessor :ast attr_reader :ca @interface = XMLRPC::Service::Interface.new("puppetmaster") { |iface| iface.add_method("string getconfig(string)") iface.add_method("int freshness()") } # Tell a client whether there's a fresh config for it def freshness(client = nil, clientip = nil) # Always force a recompile. Newer clients shouldn't do this (as of April 2008). Time.now.to_i end def initialize(hash = {}) args = {} @local = hash[:Local] args[:Local] = true - @ca = (hash.include?(:CA) and hash[:CA]) ? Puppet::SSLCertificates::CA.new : nil - # This is only used by the cfengine module, or if --loadclasses was # specified in +puppet+. args[:Classes] = hash[:Classes] if hash.include?(:Classes) end # Call our various handlers; this handler is getting deprecated. def getconfig(facts, format = "marshal", client = nil, clientip = nil) facts = decode_facts(facts) client ||= facts["hostname"] # Pass the facts to the fact handler Puppet::Node::Facts.indirection.save(Puppet::Node::Facts.new(client, facts)) unless local? catalog = Puppet::Resource::Catalog.indirection.find(client) case format when "yaml" return CGI.escape(catalog.extract.to_yaml) when "marshal" return CGI.escape(Marshal.dump(catalog.extract)) else raise "Invalid markup format '#{format}'" end end # def decode_facts(facts) if @local # we don't need to do anything, since we should already # have raw objects Puppet.debug "Our client is local" else Puppet.debug "Our client is remote" begin facts = YAML.load(CGI.unescape(facts)) rescue => detail raise XMLRPC::FaultException.new( 1, "Could not rebuild facts" ) end end facts end # Translate our configuration appropriately for sending back to a client. def translate(config) end end end diff --git a/lib/puppet/sslcertificates/ca.rb b/lib/puppet/sslcertificates/ca.rb index f3321bd29..2541c8113 100644 --- a/lib/puppet/sslcertificates/ca.rb +++ b/lib/puppet/sslcertificates/ca.rb @@ -1,375 +1,367 @@ require 'sync' class Puppet::SSLCertificates::CA include Puppet::Util::Warnings Certificate = Puppet::SSLCertificates::Certificate attr_accessor :keyfile, :file, :config, :dir, :cert, :crl def certfile @config[:cacert] end # Remove all traces of a given host. This is kind of hackish, but, eh. def clean(host) host = host.downcase [:csrdir, :signeddir, :publickeydir, :privatekeydir, :certdir].each do |name| dir = Puppet[name] file = File.join(dir, host + ".pem") if FileTest.exists?(file) begin if Puppet[:name] == "cert" puts "Removing #{file}" else Puppet.info "Removing #{file}" end File.unlink(file) rescue => detail raise Puppet::Error, "Could not delete #{file}: #{detail}" end end end end def host2csrfile(hostname) File.join(Puppet[:csrdir], [hostname.downcase, "pem"].join(".")) end # this stores signed certs in a directory unrelated to # normal client certs def host2certfile(hostname) File.join(Puppet[:signeddir], [hostname.downcase, "pem"].join(".")) end # Turn our hostname into a Name object def thing2name(thing) thing.subject.to_a.find { |ary| ary[0] == "CN" }[1] end def initialize(hash = {}) Puppet.settings.use(:main, :ca, :ssl) self.setconfig(hash) if Puppet[:capass] if FileTest.exists?(Puppet[:capass]) #puts "Reading #{Puppet[:capass]}" #system "ls -al #{Puppet[:capass]}" #File.read Puppet[:capass] @config[:password] = self.getpass else # Don't create a password if the cert already exists @config[:password] = self.genpass unless FileTest.exists?(@config[:cacert]) end end self.getcert init_crl unless FileTest.exists?(@config[:serial]) Puppet.settings.write(:serial) do |f| f << "%04X" % 1 end end end # Generate a new password for the CA. def genpass pass = "" 20.times { pass += (rand(74) + 48).chr } begin Puppet.settings.write(:capass) { |f| f.print pass } rescue Errno::EACCES => detail raise Puppet::Error, detail.to_s end pass end # Get the CA password. def getpass if @config[:capass] and File.readable?(@config[:capass]) return File.read(@config[:capass]) else raise Puppet::Error, "Could not decrypt CA key with password: #{detail}" end end # Get the CA cert. def getcert if FileTest.exists?(@config[:cacert]) @cert = OpenSSL::X509::Certificate.new( File.read(@config[:cacert]) ) else self.mkrootcert end end # Retrieve a client's CSR. def getclientcsr(host) csrfile = host2csrfile(host) return nil unless File.exists?(csrfile) OpenSSL::X509::Request.new(File.read(csrfile)) end # Retrieve a client's certificate. def getclientcert(host) certfile = host2certfile(host) return [nil, nil] unless File.exists?(certfile) [OpenSSL::X509::Certificate.new(File.read(certfile)), @cert] end # List certificates waiting to be signed. This returns a list of hostnames, not actual # files -- the names can be converted to full paths with host2csrfile. def list(dummy_argument=:work_arround_for_ruby_GC_bug) return Dir.entries(Puppet[:csrdir]).find_all { |file| file =~ /\.pem$/ }.collect { |file| file.sub(/\.pem$/, '') } end # List signed certificates. This returns a list of hostnames, not actual # files -- the names can be converted to full paths with host2csrfile. def list_signed(dummy_argument=:work_arround_for_ruby_GC_bug) return Dir.entries(Puppet[:signeddir]).find_all { |file| file =~ /\.pem$/ }.collect { |file| file.sub(/\.pem$/, '') } end # Create the root certificate. def mkrootcert # Make the root cert's name "Puppet CA: " plus the FQDN of the host running the CA. name = "Puppet CA: #{Facter["hostname"].value}" if domain = Facter["domain"].value name += ".#{domain}" end cert = Certificate.new( :name => name, :cert => @config[:cacert], :encrypt => @config[:capass], :key => @config[:cakey], :selfsign => true, :ttl => ttl, :type => :ca ) # This creates the cakey file Puppet::Util::SUIDManager.asuser(Puppet[:user], Puppet[:group]) do @cert = cert.mkselfsigned end Puppet.settings.write(:cacert) do |f| f.puts @cert.to_pem end Puppet.settings.write(:capub) do |f| f.puts @cert.public_key end cert end def removeclientcsr(host) csrfile = host2csrfile(host) raise Puppet::Error, "No certificate request for #{host}" unless File.exists?(csrfile) File.unlink(csrfile) end # Revoke the certificate with serial number SERIAL issued by this # CA. The REASON must be one of the OpenSSL::OCSP::REVOKED_* reasons def revoke(serial, reason = OpenSSL::OCSP::REVOKED_STATUS_KEYCOMPROMISE) time = Time.now revoked = OpenSSL::X509::Revoked.new revoked.serial = serial revoked.time = time enum = OpenSSL::ASN1::Enumerated(reason) ext = OpenSSL::X509::Extension.new("CRLReason", enum) revoked.add_extension(ext) @crl.add_revoked(revoked) store_crl end # Take the Puppet config and store it locally. def setconfig(hash) @config = {} Puppet.settings.params("ca").each { |param| param = param.intern if param.is_a? String if hash.include?(param) @config[param] = hash[param] Puppet[param] = hash[param] hash.delete(param) else @config[param] = Puppet[param] end } if hash.include?(:password) @config[:password] = hash[:password] hash.delete(:password) end raise ArgumentError, "Unknown parameters #{hash.keys.join(",")}" if hash.length > 0 [:cadir, :csrdir, :signeddir].each { |dir| raise Puppet::DevError, "#{dir} is undefined" unless @config[dir] } end # Sign a given certificate request. def sign(csr) unless csr.is_a?(OpenSSL::X509::Request) raise Puppet::Error, "CA#sign only accepts OpenSSL::X509::Request objects, not #{csr.class}" end raise Puppet::Error, "CSR sign verification failed" unless csr.verify(csr.public_key) serial = nil Puppet.settings.readwritelock(:serial) { |f| serial = File.read(@config[:serial]).chomp.hex # increment the serial f << "%04X" % (serial + 1) } - newcert = Puppet::SSLCertificates.mkcert( - :type => :server, - :name => csr.subject, - :ttl => ttl, - :issuer => @cert, - :serial => serial, - :publickey => csr.public_key - ) - + newcert = Puppet::SSL::CertificateFactory.build(:server, csr, @cert, serial) sign_with_key(newcert) self.storeclientcert(newcert) [newcert, @cert] end # Store the client's CSR for later signing. This is called from # server/ca.rb, and the CSRs are deleted once the certificate is actually # signed. def storeclientcsr(csr) host = thing2name(csr) csrfile = host2csrfile(host) raise Puppet::Error, "Certificate request for #{host} already exists" if File.exists?(csrfile) Puppet.settings.writesub(:csrdir, csrfile) do |f| f.print csr.to_pem end end # Store the certificate that we generate. def storeclientcert(cert) host = thing2name(cert) certfile = host2certfile(host) Puppet.notice "Overwriting signed certificate #{certfile} for #{host}" if File.exists?(certfile) Puppet::SSLCertificates::Inventory::add(cert) Puppet.settings.writesub(:signeddir, certfile) do |f| f.print cert.to_pem end end # TTL for new certificates in seconds. If config param :ca_ttl is set, # use that, otherwise use :ca_days for backwards compatibility def ttl days = @config[:ca_days] if days && days.size > 0 warnonce "Parameter ca_ttl is not set. Using depecated ca_days instead." return @config[:ca_days] * 24 * 60 * 60 else ttl = @config[:ca_ttl] if ttl.is_a?(String) unless ttl =~ /^(\d+)(y|d|h|s)$/ raise ArgumentError, "Invalid ca_ttl #{ttl}" end case $2 when 'y' unit = 365 * 24 * 60 * 60 when 'd' unit = 24 * 60 * 60 when 'h' unit = 60 * 60 when 's' unit = 1 else raise ArgumentError, "Invalid unit for ca_ttl #{ttl}" end return $1.to_i * unit else return ttl end end end private def init_crl if FileTest.exists?(@config[:cacrl]) @crl = OpenSSL::X509::CRL.new( File.read(@config[:cacrl]) ) else # Create new CRL @crl = OpenSSL::X509::CRL.new @crl.issuer = @cert.subject @crl.version = 1 store_crl @crl end end def store_crl # Increment the crlNumber e = @crl.extensions.find { |e| e.oid == 'crlNumber' } ext = @crl.extensions.reject { |e| e.oid == 'crlNumber' } crlNum = OpenSSL::ASN1::Integer(e ? e.value.to_i + 1 : 0) ext << OpenSSL::X509::Extension.new("crlNumber", crlNum) @crl.extensions = ext # Set last/next update now = Time.now @crl.last_update = now # Keep CRL valid for 5 years @crl.next_update = now + 5 * 365*24*60*60 sign_with_key(@crl) Puppet.settings.write(:cacrl) do |f| f.puts @crl.to_pem end end def sign_with_key(signable, digest = OpenSSL::Digest::SHA1.new) cakey = nil if @config[:password] begin cakey = OpenSSL::PKey::RSA.new( File.read(@config[:cakey]), @config[:password] ) rescue raise Puppet::Error, "Decrypt of CA private key with password stored in @config[:capass] not possible" end else cakey = OpenSSL::PKey::RSA.new( File.read(@config[:cakey]) ) end raise Puppet::Error, "CA Certificate is invalid" unless @cert.check_private_key(cakey) signable.sign(cakey, digest) end end diff --git a/spec/unit/network/handler/ca_spec.rb b/spec/unit/network/handler/ca_spec.rb new file mode 100644 index 000000000..d61360e20 --- /dev/null +++ b/spec/unit/network/handler/ca_spec.rb @@ -0,0 +1,86 @@ +require 'spec_helper' + +require 'puppet/network/handler/ca' + +describe Puppet::Network::Handler::CA do + include PuppetSpec::Files + + describe "#getcert" do + let(:host) { "testhost" } + let(:x509_name) { OpenSSL::X509::Name.new [['CN', host]] } + let(:key) { Puppet::SSL::Key.new(host).generate } + + let(:csr) do + csr = OpenSSL::X509::Request.new + csr.subject = x509_name + csr.public_key = key.public_key + csr + end + + let(:ca) { Puppet::SSL::CertificateAuthority.new } + let(:cacert) { ca.instance_variable_get(:@certificate) } + + before :each do + Puppet[:confdir] = tmpdir('conf') + + Puppet::SSL::CertificateAuthority.stubs(:ca?).returns true + Puppet::SSL::CertificateAuthority.stubs(:singleton_instance).returns ca + end + + it "should do nothing if the master is not a CA" do + Puppet::SSL::CertificateAuthority.stubs(:ca?).returns false + + csr = OpenSSL::X509::Request.new + subject.getcert(csr.to_pem).should == '' + end + + describe "when a certificate already exists for the host" do + let!(:cert) { ca.generate(host) } + + it "should return the existing cert if it matches the public key of the CSR" do + csr.public_key = cert.content.public_key + + subject.getcert(csr.to_pem).should == [cert.to_s, cacert.to_s] + end + + it "should fail if the public key of the CSR does not match the existing cert" do + expect do + subject.getcert(csr.to_pem) + end.to raise_error(Puppet::Error, /Certificate request does not match existing certificate/) + end + end + + describe "when autosign is enabled" do + before :each do + Puppet[:autosign] = true + end + + it "should return the new cert and the CA cert" do + cert_str, cacert_str = subject.getcert(csr.to_pem) + + returned_cert = Puppet::SSL::Certificate.from_s(cert_str) + returned_cacert = Puppet::SSL::Certificate.from_s(cacert_str) + + returned_cert.name.should == host + returned_cacert.content.subject.cmp(cacert.content.subject).should == 0 + end + end + + describe "when autosign is disabled" do + before :each do + Puppet[:autosign] = false + end + + it "should save the CSR without signing it" do + subject.getcert(csr.to_pem) + + Puppet::SSL::Certificate.find(host).should be_nil + Puppet::SSL::CertificateRequest.find(host).should be_a(Puppet::SSL::CertificateRequest) + end + + it "should not return a cert" do + subject.getcert(csr.to_pem).should be_nil + end + end + end +end diff --git a/test/network/handler/ca.rb b/test/network/handler/ca.rb deleted file mode 100755 index 0b33bab7a..000000000 --- a/test/network/handler/ca.rb +++ /dev/null @@ -1,220 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../lib/puppettest') - -require 'puppettest' -require 'puppet/network/handler/ca' -require 'mocha' - -$short = (ARGV.length > 0 and ARGV[0] == "short") - -class TestCA < Test::Unit::TestCase - include PuppetTest::ServerTest - - def setup - Puppet::Util::SUIDManager.stubs(:asuser).yields - super - end - - # Verify that we're autosigning. We have to autosign a "different" machine, - # since we always autosign the CA server's certificate. - def test_autocertgeneration - ca = nil - - # create our ca - assert_nothing_raised { - ca = Puppet::Network::Handler.ca.new(:autosign => true) - } - - # create a cert with a fake name - key = nil - csr = nil - cert = nil - hostname = "test.domain.com" - assert_nothing_raised { - cert = Puppet::SSLCertificates::Certificate.new( - :name => "test.domain.com" - ) - } - - # make the request - assert_nothing_raised { - cert.mkcsr - } - - # and get it signed - certtext = nil - cacerttext = nil - assert_nothing_raised { - certtext, cacerttext = ca.getcert(cert.csr.to_s) - } - - # they should both be strings - assert_instance_of(String, certtext) - assert_instance_of(String, cacerttext) - - # and they should both be valid certs - assert_nothing_raised { - OpenSSL::X509::Certificate.new(certtext) - } - assert_nothing_raised { - OpenSSL::X509::Certificate.new(cacerttext) - } - - # and pull it again, just to make sure we're getting the same thing - newtext = nil - assert_nothing_raised { - newtext, cacerttext = ca.getcert( - cert.csr.to_s, "test.reductivelabs.com", "127.0.0.1" - ) - } - - assert_equal(certtext,newtext) - end - - # this time don't use autosign - def test_storeAndSign - ca = nil - caserv = nil - - # make our CA server - assert_nothing_raised { - caserv = Puppet::Network::Handler.ca.new(:autosign => false) - } - - # retrieve the actual ca object - assert_nothing_raised { - ca = caserv.ca - } - - # make our test cert again - key = nil - csr = nil - cert = nil - hostname = "test.domain.com" - assert_nothing_raised { - cert = Puppet::SSLCertificates::Certificate.new( - :name => "anothertest.domain.com" - ) - } - # and the CSR - assert_nothing_raised { - cert.mkcsr - } - - # retrieve them - certtext = nil - assert_nothing_raised { - certtext, cacerttext = caserv.getcert( - cert.csr.to_s, "test.reductivelabs.com", "127.0.0.1" - ) - } - - # verify we got nothing back, since autosign is off - assert_equal("", certtext) - - # now sign it manually, with the CA object - x509 = nil - assert_nothing_raised { - x509, cacert = ca.sign(cert.csr) - } - - # and write it out - cert.cert = x509 - assert_nothing_raised { - cert.write - } - - assert(File.exists?(cert.certfile)) - - # now get them again, and verify that we actually get them - newtext = nil - assert_nothing_raised { - newtext, cacerttext = caserv.getcert(cert.csr.to_s) - } - - assert(newtext) - assert_nothing_raised { - OpenSSL::X509::Certificate.new(newtext) - } - - # Now verify that we can clean a given host's certs - assert_nothing_raised { - ca.clean("anothertest.domain.com") - } - - assert(!File.exists?(cert.certfile), "Cert still exists after clean") - end - - # and now test the autosign file - def test_autosign - autosign = File.join(tmpdir, "autosigntesting") - @@tmpfiles << autosign - File.open(autosign, "w") { |f| - f.puts "hostmatch.domain.com" - f.puts "*.other.com" - } - - caserv = nil - assert_nothing_raised { - caserv = Puppet::Network::Handler.ca.new(:autosign => autosign) - } - - # make sure we know what's going on - assert(caserv.autosign?("hostmatch.domain.com")) - assert(caserv.autosign?("fakehost.other.com")) - assert(!caserv.autosign?("kirby.reductivelabs.com")) - assert(!caserv.autosign?("culain.domain.com")) - end - - # verify that things aren't autosigned by default - def test_nodefaultautosign - caserv = nil - assert_nothing_raised { - caserv = Puppet::Network::Handler.ca.new - } - - # make sure we know what's going on - assert(!caserv.autosign?("hostmatch.domain.com")) - assert(!caserv.autosign?("fakehost.other.com")) - assert(!caserv.autosign?("kirby.reductivelabs.com")) - assert(!caserv.autosign?("culain.domain.com")) - end - - # Make sure true/false causes the file to be ignored. - def test_autosign_true_beats_file - caserv = nil - assert_nothing_raised { - caserv = Puppet::Network::Handler.ca.new - } - - host = "hostname.domain.com" - - # Create an autosign file - file = tempfile - Puppet[:autosign] = file - - File.open(file, "w") { |f| - f.puts host - } - - # Start with "false" - Puppet[:autosign] = false - - assert(! caserv.autosign?(host), "Host was incorrectly autosigned") - - # Then set it to true - Puppet[:autosign] = true - assert(caserv.autosign?(host), "Host was not autosigned") - # And try a different host - assert(caserv.autosign?("other.yay.com"), "Host was not autosigned") - - # And lastly the file - Puppet[:autosign] = file - assert(caserv.autosign?(host), "Host was not autosigned") - - # And try a different host - assert(! caserv.autosign?("other.yay.com"), "Host was autosigned") - end -end -