#!/usr/bin/env ruby # # = Synopsis # # Stand-alone certificate authority. Capable of generating certificates # but mostly meant for signing certificate requests from puppet clients. # # = Usage # # puppetca [-h|--help] [-V|--version] [-d|--debug] [-v|--verbose] # [-g|--generate] [-l|--list] [-s|--sign] [-r|--revoke] # [-p|--print] [-c|--clean] [--verify] [host] # # = Description # # Because the puppetmasterd daemon defaults to not signing client certificate # requests, this script is available for signing outstanding requests. It # can be used to list outstanding requests and then either sign them individually # or sign all of them. # # = Options # # Note that any configuration parameter that's valid in the configuration file # is also a valid long argument. For example, 'ssldir' is a valid configuration # parameter, so you can specify '--ssldir ' as an argument. # # See the configuration file documentation at # http://reductivelabs.com/projects/puppet/reference/configref.html for # the full list of acceptable parameters. A commented list of all # configuration options can also be generated by running puppetca with # '--genconfig'. # # all:: # Operate on all items. Currently only makes sense with '--sign', # '--clean', or '--list'. # # clean:: # Remove all files related to a host from puppetca's storage. This is # useful when rebuilding hosts, since new certificate signing requests # will only be honored if puppetca does not have a copy of a signed # certificate for that host. The certificate of the host remains valid. # If '--all' is specified then all host certificates, both signed and # unsigned, will be removed. # # debug:: # Enable full debugging. # # generate:: # Generate a certificate for a named client. A certificate/keypair will be # generated for each client named on the command line. # # help:: # Print this help message # # list:: # List outstanding certificate requests. If '--all' is specified, # signed certificates are also listed, prefixed by '+'. # # print:: # Print the full-text version of a host's certificate. # # revoke:: # Revoke the certificate of a client. The certificate can be specified # either by its serial number, given as a decimal number or a hexadecimal # number prefixed by '0x', or by its hostname. The certificate is revoked # by adding it to the Certificate Revocation List given by the 'cacrl' # config parameter. Note that the puppetmasterd needs to be restarted # after revoking certificates. # # sign:: # Sign an outstanding certificate request. Unless '--all' is specified, # hosts must be listed after all flags. # # verbose:: # Enable verbosity. # # version:: # Print the puppet version number and exit. # # verify:: # Verify the named certificate against the local CA certificate. # # = Example # # $ puppetca -l # culain.madstop.com # $ puppetca -s culain.madstop.com # # = Author # # Luke Kanies # # = Copyright # # Copyright (c) 2005 Reductive Labs, LLC # Licensed under the GNU Public License require 'puppet' require 'puppet/sslcertificates' require 'getoptlong' options = [ [ "--all", "-a", GetoptLong::NO_ARGUMENT ], [ "--clean", "-c", GetoptLong::NO_ARGUMENT ], [ "--debug", "-d", GetoptLong::NO_ARGUMENT ], [ "--generate", "-g", GetoptLong::NO_ARGUMENT ], [ "--help", "-h", GetoptLong::NO_ARGUMENT ], [ "--list", "-l", GetoptLong::NO_ARGUMENT ], [ "--print", "-p", GetoptLong::NO_ARGUMENT ], [ "--revoke", "-r", GetoptLong::NO_ARGUMENT ], [ "--sign", "-s", GetoptLong::NO_ARGUMENT ], [ "--verify", GetoptLong::NO_ARGUMENT ], [ "--version", "-V", GetoptLong::NO_ARGUMENT ], [ "--verbose", "-v", GetoptLong::NO_ARGUMENT ] ] # Add all of the config parameters as valid options. Puppet.settings.addargs(options) result = GetoptLong.new(*options) mode = nil all = false generate = nil modes = [:clean, :list, :revoke, :generate, :sign, :print, :verify] begin result.each { |opt,arg| case opt when "--all" all = true when "--debug" Puppet::Util::Log.level = :debug when "--generate" generate = arg mode = :generate when "--help" if Puppet.features.usage? RDoc::usage && exit else puts "No help available unless you have RDoc::usage installed" exit end when "--list" mode = :list when "--revoke" mode = :revoke when "--sign" mode = :sign when "--version" puts "%s" % Puppet.version exit when "--verbose" Puppet::Util::Log.level = :info else tmp = opt.sub("--", '').to_sym if modes.include?(tmp) mode = tmp else Puppet.settings.handlearg(opt, arg) end end } rescue GetoptLong::InvalidOption => detail $stderr.puts "Try '#{$0} --help'" exit(1) end # Now parse the config Puppet.parse_config if Puppet.settings.print_configs? exit(Puppet.settings.print_configs ? 0 : 1) end begin ca = Puppet::SSLCertificates::CA.new() rescue => detail if Puppet[:debug] puts detail.backtrace end puts detail.to_s exit(23) end unless mode $stderr.puts "You must specify a mode; see the output from --help" exit(12) end if [:verify, :print, :generate, :clean, :revoke, :list].include?(mode) hosts = ARGV.collect { |h| h.downcase } end if [:sign, :list].include?(mode) waiting = ca.list unless waiting.length > 0 or (mode == :list and all) puts "No certificates to sign" if ARGV.length > 0 exit(17) else exit(0) end end end case mode when :list waiting = ca.list if waiting.length > 0 puts waiting.join("\n") end if all puts ca.list_signed.collect { |cert | cert.sub(/^/,"+ ") }.join("\n") end when :clean if hosts.empty? and all == false $stderr.puts "You must specify one or more hosts to clean or --all to clean all host certificates" exit(24) end cleaned = false if all certs = ca.list certs |= ca.list_signed if certs.empty? $stderr.puts "No certificates to clean" exit(24) end certs.each do |c| ca.clean(c) end cleaned = true else hosts.each do |host| unless cert = ca.getclientcert(host)[0] || ca.getclientcsr(host) $stderr.puts "Could not find client certificate or request for %s" % host next end ca.clean(host) cleaned = true end end unless cleaned exit(27) end when :sign to_sign = ARGV.collect { |h| h.downcase } unless to_sign.length > 0 or all $stderr.puts( "You must specify one or more hosts to sign certificates for or --all to sign all certificates" ) exit(24) end unless all to_sign.each { |host| unless waiting.include?(host) $stderr.puts "No waiting request for %s" % host end } waiting = waiting.find_all { |host| to_sign.include?(host) } end waiting.each { |host| begin csr = ca.getclientcsr(host) rescue => detail $stderr.puts "Could not retrieve request for %s: %s" % [host, detail] end begin ca.sign(csr) $stderr.puts "Signed %s" % host rescue => detail $stderr.puts "Could not sign request for %s: %s" % [host, detail] end begin ca.removeclientcsr(host) rescue => detail $stderr.puts "Could not remove request for %s: %s" % [host, detail] end } when :generate # we need to generate a certificate for a host hosts.each { |host| puts "Generating certificate for %s" % host cert = Puppet::SSLCertificates::Certificate.new( :name => host ) cert.mkcsr signedcert, cacert = ca.sign(cert.csr) cert.cert = signedcert cert.cacert = cacert cert.write } when :print hosts.each { |h| cert = ca.getclientcert(h)[0] puts cert.to_text } when :revoke hosts.each { |h| serial = nil if h =~ /^0x[0-9a-f]+$/ serial = h.to_i(16) elsif h =~ /^[0-9]+$/ serial = h.to_i else cert = ca.getclientcert(h)[0] if cert.nil? $stderr.puts "Could not find client certificate for %s" % h else serial = cert.serial end end unless serial.nil? ca.revoke(serial) puts "Revoked certificate with serial #{serial}" end } when :verify unless ssl = %x{which openssl}.chomp raise "Can't verify certificates without the openssl binary and could not find one" end success = true cacert = Puppet[:localcacert] hosts.each do |host| print "%s: " % host file = ca.host2certfile(host) unless FileTest.exist?(file) puts "no certificate found" success = false next end command = %{#{ssl} verify -CAfile #{cacert} #{file}} output = %x{#{command}} if $? == 0 puts "valid" else puts output success = false end end else $stderr.puts "Invalid mode %s" % mode exit(42) end