2 require 'puppet_x/certregen/ca'
3 require 'puppet_x/certregen/certificate'
4 require 'puppet_x/certregen/crl'
5 require 'puppet/feature/chloride'
7 Puppet::Face.define(:certregen, '0.1.0') do
8 copyright "Puppet", 2016
9 summary "Regenerate the Puppet CA and client certificates"
12 This subcommand provides tools for monitoring the health of the Puppet CA, regenerating
13 expiring CA certificates, and remediation for expired CA certificates.
17 summary "Refresh the Puppet CA certificate and CRL"
19 option('--ca_serial SERIAL') do
20 summary 'The serial number (in hexadecimal) of the CA to rotate.'
23 when_invoked do |opts|
24 cert = Puppet::Face[:certregen, :current].cacert(:ca_serial => opts[:ca_serial])
25 crl = Puppet::Face[:certregen, :current].crl()
29 when_rendering :console do |(cert, crl)|
30 "CA expiration is now #{cert.content.not_after}\n" + \
31 "CRL next update is now #{crl.content.next_update}"
36 summary "Regenerate the Puppet CA certificate"
39 This subcommand generates a new CA certificate that can replace the existing CA certificate.
40 The new CA certificate uses the same subject as the current CA certificate and reuses the
41 key pair associated with the current CA certificate, so all certificates signed by the old
42 CA certificate will remain valid.
45 option('--ca_serial SERIAL') do
46 summary 'The serial number (in hexadecimal) of the CA to rotate.'
49 when_invoked do |opts|
50 ca = PuppetX::Certregen::CA.setup
52 current_ca_serial = ca.host.certificate.content.serial.to_s(16)
53 if opts[:ca_serial].nil?
54 raise "The serial number of the CA certificate to rotate must be provided. If you " \
55 "are sure that you want to rotate the CA certificate, rerun this command with " \
56 "--ca_serial #{current_ca_serial}"
57 elsif opts[:ca_serial] != current_ca_serial
58 raise "The serial number of the current CA certificate (#{current_ca_serial}) "\
59 "does not match the serial number given on the command line (#{opts[:ca_serial]}). "\
60 "If you are sure that you want to rotate the CA certificate, rerun this command with "\
61 "--ca_serial #{current_ca_serial}"
64 PuppetX::Certregen::CA.backup
65 PuppetX::Certregen::CA.regenerate(ca)
66 Puppet::SSL::Certificate.indirection.find(Puppet::SSL::CA_NAME)
69 when_rendering(:console) do |cert|
70 "CA expiration is now #{cert.content.not_after}"
75 summary 'Update the lastUpdate and nextUpdate field for the CA CRL'
77 when_invoked do |opts|
78 ca = PuppetX::Certregen::CA.setup
79 PuppetX::Certregen::CRL.refresh(ca)
82 when_rendering(:console) do |crl|
83 "CRL next update is now #{crl.content.next_update}"
87 action(:healthcheck) do
88 summary "Check for expiring certificates"
91 This subcommand checks for certificates that are nearing or past expiration.
95 summary "Report certificate expiry for all nodes, including nodes that aren't near expiration."
98 when_invoked do |opts|
99 ca = PuppetX::Certregen::CA.setup
101 certs = Puppet::SSL::Certificate.indirection.search('*').select do |cert|
102 opts[:all] || PuppetX::Certregen::Certificate.expiring?(cert)
105 cacert = ca.host.certificate
106 certs << cacert if (opts[:all] || PuppetX::Certregen::Certificate.expiring?(cacert))
108 certs.sort { |a, b| a.content.not_after <=> b.content.not_after }
111 when_rendering :console do |certs|
113 "No certificates are approaching expiration."
116 str = "#{cert.name.inspect} #{cert.digest.to_s}\n"
117 expiry = PuppetX::Certregen::Certificate.expiry(cert)
118 str << "Status: #{expiry[:status]}\n"
119 str << "Expiration date: #{expiry[:expiration_date]}\n"
120 if expiry[:expires_in]
121 str << "Expires in: #{expiry[:expires_in]}\n"
128 when_rendering :pson do |certs|
132 :digest => cert.digest.to_s,
133 :expiry => PuppetX::Certregen::Certificate.expiry(cert)
138 when_rendering :yaml do |certs|
142 :digest => cert.digest.to_s,
143 :expiry => PuppetX::Certregen::Certificate.expiry(cert)
149 action(:redistribute) do
150 summary "Redistribute the regenerated CA certificate and CRL to nodes in PuppetDB"
153 Redistribute the regenerated CA certificate and CRL to active nodes in PuppetDB. This command is
154 only necessary if the CA certificate is expired and a new CA certificate needs to be manually
157 This subcommand depends on the `chloride` gem, which is not included with this Puppet face.
159 Distributing the CA certificate via SSH requires either a private ssh key (given by the
160 `--ssh_key_file` flag) or entering the password when prompted. If password auth is used,
161 the `highline` gem should be installed so that the entered password is not echoed to the
165 option('--username USER') do
166 summary "The username to use when logging into the remote machine"
169 option('--ssh_key_file FILE') do
170 summary "The SSH key file to use for authentication"
171 default_to { "~/.ssh/id_rsa" }
174 when_invoked do |opts|
175 unless Puppet.features.chloride?
176 raise "Unable to distribute CA certificate: the chloride gem is not available."
181 config.merge!(username: opts[:username]) if opts[:username]
182 config.merge!(ssh_key_file: File.expand_path(opts[:ssh_key_file])) if opts[:ssh_key_file]
184 ca = PuppetX::Certregen::CA.setup
185 cacert = ca.host.certificate
186 if PuppetX::Certregen::Certificate.expiring?(cacert)
187 Puppet.err "Refusing to distribute CA certificate: certificate is pending expiration."
191 rv = {succeeded: [], failed: []}
192 PuppetX::Certregen::CA.certnames.each do |certname|
194 PuppetX::Certregen::CA.distribute(certname, config)
195 rv[:succeeded] << certname
197 Puppet.log_exception(e)
198 rv[:failed] << certname