Add puppetlabs/certregen module
[mirror/dsa-puppet.git] / 3rdparty / modules / certregen / lib / puppet / face / certregen.rb
1 require 'puppet/face'
2 require 'puppet_x/certregen/ca'
3 require 'puppet_x/certregen/certificate'
4 require 'puppet_x/certregen/crl'
5 require 'puppet/feature/chloride'
6
7 Puppet::Face.define(:certregen, '0.1.0') do
8   copyright "Puppet", 2016
9   summary "Regenerate the Puppet CA and client certificates"
10
11   description <<-EOT
12     This subcommand provides tools for monitoring the health of the Puppet CA, regenerating
13     expiring CA certificates, and remediation for expired CA certificates.
14   EOT
15
16   action(:ca) do
17     summary "Refresh the Puppet CA certificate and CRL"
18
19     option('--ca_serial SERIAL') do
20       summary 'The serial number (in hexadecimal) of the CA to rotate.'
21     end
22
23     when_invoked do |opts|
24       cert = Puppet::Face[:certregen, :current].cacert(:ca_serial => opts[:ca_serial])
25       crl = Puppet::Face[:certregen, :current].crl()
26       [cert, crl]
27     end
28
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}"
32     end
33   end
34
35   action(:cacert) do
36     summary "Regenerate the Puppet CA certificate"
37
38     description <<-EOT
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.
43     EOT
44
45     option('--ca_serial SERIAL') do
46       summary 'The serial number (in hexadecimal) of the CA to rotate.'
47     end
48
49     when_invoked do |opts|
50       ca = PuppetX::Certregen::CA.setup
51
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}"
62       end
63
64       PuppetX::Certregen::CA.backup
65       PuppetX::Certregen::CA.regenerate(ca)
66       Puppet::SSL::Certificate.indirection.find(Puppet::SSL::CA_NAME)
67     end
68
69     when_rendering(:console) do |cert|
70       "CA expiration is now #{cert.content.not_after}"
71     end
72   end
73
74   action(:crl) do
75     summary 'Update the lastUpdate and nextUpdate field for the CA CRL'
76
77     when_invoked do |opts|
78       ca = PuppetX::Certregen::CA.setup
79       PuppetX::Certregen::CRL.refresh(ca)
80     end
81
82     when_rendering(:console) do |crl|
83       "CRL next update is now #{crl.content.next_update}"
84     end
85   end
86
87   action(:healthcheck) do
88     summary "Check for expiring certificates"
89
90     description <<-EOT
91       This subcommand checks for certificates that are nearing or past expiration.
92     EOT
93
94     option('--all') do
95       summary "Report certificate expiry for all nodes, including nodes that aren't near expiration."
96     end
97
98     when_invoked do |opts|
99       ca = PuppetX::Certregen::CA.setup
100
101       certs = Puppet::SSL::Certificate.indirection.search('*').select do |cert|
102         opts[:all] || PuppetX::Certregen::Certificate.expiring?(cert)
103       end
104
105       cacert = ca.host.certificate
106       certs << cacert if (opts[:all] || PuppetX::Certregen::Certificate.expiring?(cacert))
107
108       certs.sort { |a, b| a.content.not_after <=> b.content.not_after }
109     end
110
111     when_rendering :console do |certs|
112       if certs.empty?
113         "No certificates are approaching expiration."
114       else
115         certs.map do |cert|
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"
122           end
123           str
124         end
125       end
126     end
127
128     when_rendering :pson do |certs|
129       certs.map do |cert|
130         {
131           :name => cert.name,
132           :digest => cert.digest.to_s,
133           :expiry => PuppetX::Certregen::Certificate.expiry(cert)
134         }
135       end
136     end
137
138     when_rendering :yaml do |certs|
139       certs.map do |cert|
140         {
141           :name => cert.name,
142           :digest => cert.digest.to_s,
143           :expiry => PuppetX::Certregen::Certificate.expiry(cert)
144         }
145       end
146     end
147   end
148
149   action(:redistribute) do
150     summary "Redistribute the regenerated CA certificate and CRL to nodes in PuppetDB"
151
152     description <<-EOT
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
155       distributed via SSH.
156
157       This subcommand depends on the `chloride` gem, which is not included with this Puppet face.
158
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
162       terminal.
163     EOT
164
165     option('--username USER') do
166       summary "The username to use when logging into the remote machine"
167     end
168
169     option('--ssh_key_file FILE') do
170       summary "The SSH key file to use for authentication"
171       default_to { "~/.ssh/id_rsa" }
172     end
173
174     when_invoked do |opts|
175       unless Puppet.features.chloride?
176         raise "Unable to distribute CA certificate: the chloride gem is not available."
177       end
178
179       config = {}
180
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]
183
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."
188         exit 1
189       end
190
191       rv = {succeeded: [], failed: []}
192       PuppetX::Certregen::CA.certnames.each do |certname|
193         begin
194           PuppetX::Certregen::CA.distribute(certname, config)
195           rv[:succeeded] << certname
196         rescue => e
197           Puppet.log_exception(e)
198           rv[:failed] << certname
199         end
200       end
201
202       rv
203     end
204   end
205 end