Skip to content

Commit

Permalink
(maint) Merge 7.x into main
Browse files Browse the repository at this point in the history
  • Loading branch information
tvpartytonight committed Sep 21, 2023
2 parents 90720bd + 617e136 commit 63230e6
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 10 deletions.
39 changes: 32 additions & 7 deletions lib/puppet/application/ssl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ def help
the CSR. Otherwise a new key pair will be generated. If a CSR has already
been submitted with the given `certname`, then the operation will fail.
* generate_request:
Generate a certificate signing request (CSR). If
a private and public key pair already exist, they will be used to generate
the CSR. Otherwise a new key pair will be generated.
* download_cert:
Download a certificate for this host. If the current private key matches
the downloaded certificate, then the certificate will be saved and used
Expand Down Expand Up @@ -137,6 +142,8 @@ def main
unless cert
raise Puppet::Error, _("The certificate for '%{name}' has not yet been signed") % { name: certname }
end
when 'generate_request'
generate_request(certname)
when 'verify'
verify(certname)
when 'clean'
Expand All @@ -163,13 +170,7 @@ def show(certname)
def submit_request(ssl_context)
key = @cert_provider.load_private_key(Puppet[:certname])
unless key
if Puppet[:key_type] == 'ec'
Puppet.info _("Creating a new EC SSL key for %{name} using curve %{curve}") % { name: Puppet[:certname], curve: Puppet[:named_curve] }
key = OpenSSL::PKey::EC.generate(Puppet[:named_curve])
else
Puppet.info _("Creating a new SSL key for %{name}") % { name: Puppet[:certname] }
key = OpenSSL::PKey::RSA.new(Puppet[:keylength].to_i)
end
key = create_key(Puppet[:certname])
@cert_provider.save_private_key(Puppet[:certname], key)
end

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

def generate_request(certname)
key = @cert_provider.load_private_key(certname)
unless key
key = create_key(certname)
@cert_provider.save_private_key(certname, key)
end

csr = @cert_provider.create_request(certname, key)
@cert_provider.save_request(certname, csr)
Puppet.notice _("Generated certificate request in '%{path}'") % { path: @cert_provider.to_path(Puppet[:requestdir], certname) }
rescue => e
raise Puppet::Error.new(_("Failed to generate certificate request: %{message}") % { message: e.message }, e)
end

def download_cert(ssl_context)
key = @cert_provider.load_private_key(Puppet[:certname])

Expand Down Expand Up @@ -286,4 +301,14 @@ def fingerprint(cert)
def create_route(ssl_context)
@session.route_to(:ca, ssl_context: ssl_context)
end

def create_key(certname)
if Puppet[:key_type] == 'ec'
Puppet.info _("Creating a new EC SSL key for %{name} using curve %{curve}") % { name: certname, curve: Puppet[:named_curve] }
OpenSSL::PKey::EC.generate(Puppet[:named_curve])
else
Puppet.info _("Creating a new SSL key for %{name}") % { name: certname }
OpenSSL::PKey::RSA.new(Puppet[:keylength].to_i)
end
end
end
8 changes: 6 additions & 2 deletions lib/puppet/x509/cert_provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -376,13 +376,17 @@ def load_request_from_pem(pem)
OpenSSL::X509::Request.new(pem)
end

private

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

private

def permissions_for_setting(name)
setting = Puppet.settings.setting(name)
perm = { mode: setting.mode.to_i(8) }
Expand Down
4 changes: 4 additions & 0 deletions man/man8/puppet-ssl.8
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ submit_request
Generate a certificate signing request (CSR) and submit it to the CA\. If a private and public key pair already exist, they will be used to generate the CSR\. Otherwise a new key pair will be generated\. If a CSR has already been submitted with the given \fBcertname\fR, then the operation will fail\.
.
.TP
generate_request
Generate a certificate signing request (CSR)\. If a private and public key pair already exist, they will be used to generate the CSR\. Otherwise a new key pair will be generated\.
.
.TP
download_cert
Download a certificate for this host\. If the current private key matches the downloaded certificate, then the certificate will be saved and used for subsequent requests\. If there is already an existing certificate, it will be overwritten\.
.
Expand Down
44 changes: 44 additions & 0 deletions spec/unit/application/ssl_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,50 @@ def expects_command_to_fail(message)
end
end

context 'when generating a CSR' do
let(:csr_path) { Puppet[:hostcsr] }
let(:requestdir) { Puppet[:requestdir] }

before do
ssl.command_line.args << 'generate_request'
end

it 'generates an RSA private key' do
File.unlink(Puppet[:hostprivkey])

expects_command_to_pass(%r{Generated certificate request in '#{csr_path}'})
end

it 'generates an EC private key' do
Puppet[:key_type] = 'ec'
File.unlink(Puppet[:hostprivkey])

expects_command_to_pass(%r{Generated certificate request in '#{csr_path}'})
end

it 'registers OIDs' do
expect(Puppet::SSL::Oids).to receive(:register_puppet_oids)

expects_command_to_pass(%r{Generated certificate request in '#{csr_path}'})
end

it 'saves the CSR locally' do
expects_command_to_pass(%r{Generated certificate request in '#{csr_path}'})

expect(Puppet::FileSystem).to be_exist(csr_path)
end

it 'accepts dns alt names' do
Puppet[:dns_alt_names] = 'majortom'

expects_command_to_pass

csr = Puppet::SSL::CertificateRequest.new(name)
csr.read(csr_path)
expect(csr.subject_alt_names).to include('DNS:majortom')
end
end

context 'when downloading a certificate' do
before do
ssl.command_line.args << 'download_cert'
Expand Down
17 changes: 17 additions & 0 deletions spec/unit/ssl/certificate_signer_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
require 'spec_helper'

describe Puppet::SSL::CertificateSigner do
include PuppetSpec::Files

let(:wrong_key) { OpenSSL::PKey::RSA.new(512) }
let(:client_cert) { cert_fixture('signed.pem') }

# jruby-openssl >= 0.13.0 (JRuby >= 9.3.5.0) raises an error when signing a
# certificate when there is a discrepancy between the certificate and key.
it 'raises if client cert signature is invalid', if: Puppet::Util::Platform.jruby? && RUBY_VERSION.to_f >= 2.6 do
expect {
client_cert.sign(wrong_key, OpenSSL::Digest::SHA256.new)
}.to raise_error(OpenSSL::X509::CertificateError,
'invalid public key data')
end
end
2 changes: 1 addition & 1 deletion spec/unit/ssl/ssl_provider_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@
end
end

it 'raises if intermediate CA signature is invalid' do
it 'raises if intermediate CA signature is invalid', unless: Puppet::Util::Platform.jruby? && RUBY_VERSION.to_f >= 2.6 do
int = global_cacerts.last
int.public_key = wrong_key.public_key if Puppet::Util::Platform.jruby?
int.sign(wrong_key, OpenSSL::Digest::SHA256.new)
Expand Down

0 comments on commit 63230e6

Please sign in to comment.