Skip to content

Commit f334eb9

Browse files
authored
Merge pull request #9107 from joshcooper/nickg_generate_request
(PUP-10589) Add a generate_request option to puppet ssl
2 parents 0a3ced0 + ebef1ef commit f334eb9

File tree

3 files changed

+82
-9
lines changed

3 files changed

+82
-9
lines changed

lib/puppet/application/ssl.rb

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ def help
5959
the CSR. Otherwise a new key pair will be generated. If a CSR has already
6060
been submitted with the given `certname`, then the operation will fail.
6161
62+
* generate_request:
63+
Generate a certificate signing request (CSR). If
64+
a private and public key pair already exist, they will be used to generate
65+
the CSR. Otherwise a new key pair will be generated.
66+
6267
* download_cert:
6368
Download a certificate for this host. If the current private key matches
6469
the downloaded certificate, then the certificate will be saved and used
@@ -136,6 +141,8 @@ def main
136141
unless cert
137142
raise Puppet::Error, _("The certificate for '%{name}' has not yet been signed") % { name: certname }
138143
end
144+
when 'generate_request'
145+
generate_request(certname)
139146
when 'verify'
140147
verify(certname)
141148
when 'clean'
@@ -162,13 +169,7 @@ def show(certname)
162169
def submit_request(ssl_context)
163170
key = @cert_provider.load_private_key(Puppet[:certname])
164171
unless key
165-
if Puppet[:key_type] == 'ec'
166-
Puppet.info _("Creating a new EC SSL key for %{name} using curve %{curve}") % { name: Puppet[:certname], curve: Puppet[:named_curve] }
167-
key = OpenSSL::PKey::EC.generate(Puppet[:named_curve])
168-
else
169-
Puppet.info _("Creating a new SSL key for %{name}") % { name: Puppet[:certname] }
170-
key = OpenSSL::PKey::RSA.new(Puppet[:keylength].to_i)
171-
end
172+
key = create_key(Puppet[:certname])
172173
@cert_provider.save_private_key(Puppet[:certname], key)
173174
end
174175

@@ -187,6 +188,20 @@ def submit_request(ssl_context)
187188
raise Puppet::Error.new(_("Failed to submit certificate request: %{message}") % { message: e.message }, e)
188189
end
189190

191+
def generate_request(certname)
192+
key = @cert_provider.load_private_key(certname)
193+
unless key
194+
key = create_key(certname)
195+
@cert_provider.save_private_key(certname, key)
196+
end
197+
198+
csr = @cert_provider.create_request(certname, key)
199+
@cert_provider.save_request(certname, csr)
200+
Puppet.notice _("Generated certificate request in '%{path}'") % { path: @cert_provider.to_path(Puppet[:requestdir], certname) }
201+
rescue => e
202+
raise Puppet::Error.new(_("Failed to generate certificate request: %{message}") % { message: e.message }, e)
203+
end
204+
190205
def download_cert(ssl_context)
191206
key = @cert_provider.load_private_key(Puppet[:certname])
192207

@@ -285,4 +300,14 @@ def fingerprint(cert)
285300
def create_route(ssl_context)
286301
@session.route_to(:ca, ssl_context: ssl_context)
287302
end
303+
304+
def create_key(certname)
305+
if Puppet[:key_type] == 'ec'
306+
Puppet.info _("Creating a new EC SSL key for %{name} using curve %{curve}") % { name: certname, curve: Puppet[:named_curve] }
307+
OpenSSL::PKey::EC.generate(Puppet[:named_curve])
308+
else
309+
Puppet.info _("Creating a new SSL key for %{name}") % { name: certname }
310+
OpenSSL::PKey::RSA.new(Puppet[:keylength].to_i)
311+
end
312+
end
288313
end

lib/puppet/x509/cert_provider.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -346,13 +346,17 @@ def load_request_from_pem(pem)
346346
OpenSSL::X509::Request.new(pem)
347347
end
348348

349-
private
350-
349+
# Return the path to the cert related object (key, CSR, cert, etc).
350+
#
351+
# @param base [String] base directory
352+
# @param name [String] the name associated with the cert related object
351353
def to_path(base, name)
352354
raise _("Certname %{name} must not contain unprintable or non-ASCII characters") % { name: name.inspect } unless name =~ VALID_CERTNAME
353355
File.join(base, "#{name.downcase}.pem")
354356
end
355357

358+
private
359+
356360
def permissions_for_setting(name)
357361
setting = Puppet.settings.setting(name)
358362
perm = { mode: setting.mode.to_i(8) }

spec/unit/application/ssl_spec.rb

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,50 @@ def expects_command_to_fail(message)
171171
end
172172
end
173173

174+
context 'when generating a CSR' do
175+
let(:csr_path) { Puppet[:hostcsr] }
176+
let(:requestdir) { Puppet[:requestdir] }
177+
178+
before do
179+
ssl.command_line.args << 'generate_request'
180+
end
181+
182+
it 'generates an RSA private key' do
183+
File.unlink(Puppet[:hostprivkey])
184+
185+
expects_command_to_pass(%r{Generated certificate request in '#{csr_path}'})
186+
end
187+
188+
it 'generates an EC private key' do
189+
Puppet[:key_type] = 'ec'
190+
File.unlink(Puppet[:hostprivkey])
191+
192+
expects_command_to_pass(%r{Generated certificate request in '#{csr_path}'})
193+
end
194+
195+
it 'registers OIDs' do
196+
expect(Puppet::SSL::Oids).to receive(:register_puppet_oids)
197+
198+
expects_command_to_pass(%r{Generated certificate request in '#{csr_path}'})
199+
end
200+
201+
it 'saves the CSR locally' do
202+
expects_command_to_pass(%r{Generated certificate request in '#{csr_path}'})
203+
204+
expect(Puppet::FileSystem).to be_exist(csr_path)
205+
end
206+
207+
it 'accepts dns alt names' do
208+
Puppet[:dns_alt_names] = 'majortom'
209+
210+
expects_command_to_pass
211+
212+
csr = Puppet::SSL::CertificateRequest.new(name)
213+
csr.read(csr_path)
214+
expect(csr.subject_alt_names).to include('DNS:majortom')
215+
end
216+
end
217+
174218
context 'when downloading a certificate' do
175219
before do
176220
ssl.command_line.args << 'download_cert'

0 commit comments

Comments
 (0)