1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
|
require 'puppet/face'
require 'puppet_x/certregen/ca'
require 'puppet_x/certregen/certificate'
require 'puppet_x/certregen/crl'
require 'puppet/feature/chloride'
Puppet::Face.define(:certregen, '0.1.0') do
copyright "Puppet", 2016
summary "Regenerate the Puppet CA and client certificates"
description <<-EOT
This subcommand provides tools for monitoring the health of the Puppet CA, regenerating
expiring CA certificates, and remediation for expired CA certificates.
EOT
action(:ca) do
summary "Refresh the Puppet CA certificate and CRL"
option('--ca_serial SERIAL') do
summary 'The serial number (in hexadecimal) of the CA to rotate.'
end
when_invoked do |opts|
cert = Puppet::Face[:certregen, :current].cacert(:ca_serial => opts[:ca_serial])
crl = Puppet::Face[:certregen, :current].crl()
[cert, crl]
end
when_rendering :console do |(cert, crl)|
"CA expiration is now #{cert.content.not_after}\n" + \
"CRL next update is now #{crl.content.next_update}"
end
end
action(:cacert) do
summary "Regenerate the Puppet CA certificate"
description <<-EOT
This subcommand generates a new CA certificate that can replace the existing CA certificate.
The new CA certificate uses the same subject as the current CA certificate and reuses the
key pair associated with the current CA certificate, so all certificates signed by the old
CA certificate will remain valid.
EOT
option('--ca_serial SERIAL') do
summary 'The serial number (in hexadecimal) of the CA to rotate.'
end
when_invoked do |opts|
ca = PuppetX::Certregen::CA.setup
current_ca_serial = ca.host.certificate.content.serial.to_s(16)
if opts[:ca_serial].nil?
raise "The serial number of the CA certificate to rotate must be provided. If you " \
"are sure that you want to rotate the CA certificate, rerun this command with " \
"--ca_serial #{current_ca_serial}"
elsif opts[:ca_serial] != current_ca_serial
raise "The serial number of the current CA certificate (#{current_ca_serial}) "\
"does not match the serial number given on the command line (#{opts[:ca_serial]}). "\
"If you are sure that you want to rotate the CA certificate, rerun this command with "\
"--ca_serial #{current_ca_serial}"
end
PuppetX::Certregen::CA.backup
PuppetX::Certregen::CA.regenerate(ca)
Puppet::SSL::Certificate.indirection.find(Puppet::SSL::CA_NAME)
end
when_rendering(:console) do |cert|
"CA expiration is now #{cert.content.not_after}"
end
end
action(:crl) do
summary 'Update the lastUpdate and nextUpdate field for the CA CRL'
when_invoked do |opts|
ca = PuppetX::Certregen::CA.setup
PuppetX::Certregen::CRL.refresh(ca)
end
when_rendering(:console) do |crl|
"CRL next update is now #{crl.content.next_update}"
end
end
action(:healthcheck) do
summary "Check for expiring certificates"
description <<-EOT
This subcommand checks for certificates that are nearing or past expiration.
EOT
option('--all') do
summary "Report certificate expiry for all nodes, including nodes that aren't near expiration."
end
when_invoked do |opts|
ca = PuppetX::Certregen::CA.setup
certs = Puppet::SSL::Certificate.indirection.search('*').select do |cert|
opts[:all] || PuppetX::Certregen::Certificate.expiring?(cert)
end
cacert = ca.host.certificate
certs << cacert if (opts[:all] || PuppetX::Certregen::Certificate.expiring?(cacert))
certs.sort { |a, b| a.content.not_after <=> b.content.not_after }
end
when_rendering :console do |certs|
if certs.empty?
"No certificates are approaching expiration."
else
certs.map do |cert|
str = "#{cert.name.inspect} #{cert.digest.to_s}\n"
expiry = PuppetX::Certregen::Certificate.expiry(cert)
str << "Status: #{expiry[:status]}\n"
str << "Expiration date: #{expiry[:expiration_date]}\n"
if expiry[:expires_in]
str << "Expires in: #{expiry[:expires_in]}\n"
end
str
end
end
end
when_rendering :pson do |certs|
certs.map do |cert|
{
:name => cert.name,
:digest => cert.digest.to_s,
:expiry => PuppetX::Certregen::Certificate.expiry(cert)
}
end
end
when_rendering :yaml do |certs|
certs.map do |cert|
{
:name => cert.name,
:digest => cert.digest.to_s,
:expiry => PuppetX::Certregen::Certificate.expiry(cert)
}
end
end
end
action(:redistribute) do
summary "Redistribute the regenerated CA certificate and CRL to nodes in PuppetDB"
description <<-EOT
Redistribute the regenerated CA certificate and CRL to active nodes in PuppetDB. This command is
only necessary if the CA certificate is expired and a new CA certificate needs to be manually
distributed via SSH.
This subcommand depends on the `chloride` gem, which is not included with this Puppet face.
Distributing the CA certificate via SSH requires either a private ssh key (given by the
`--ssh_key_file` flag) or entering the password when prompted. If password auth is used,
the `highline` gem should be installed so that the entered password is not echoed to the
terminal.
EOT
option('--username USER') do
summary "The username to use when logging into the remote machine"
end
option('--ssh_key_file FILE') do
summary "The SSH key file to use for authentication"
default_to { "~/.ssh/id_rsa" }
end
when_invoked do |opts|
unless Puppet.features.chloride?
raise "Unable to distribute CA certificate: the chloride gem is not available."
end
config = {}
config.merge!(username: opts[:username]) if opts[:username]
config.merge!(ssh_key_file: File.expand_path(opts[:ssh_key_file])) if opts[:ssh_key_file]
ca = PuppetX::Certregen::CA.setup
cacert = ca.host.certificate
if PuppetX::Certregen::Certificate.expiring?(cacert)
Puppet.err "Refusing to distribute CA certificate: certificate is pending expiration."
exit 1
end
rv = {succeeded: [], failed: []}
PuppetX::Certregen::CA.certnames.each do |certname|
begin
PuppetX::Certregen::CA.distribute(certname, config)
rv[:succeeded] << certname
rescue => e
Puppet.log_exception(e)
rv[:failed] << certname
end
end
rv
end
end
end
|