From 2fcd7c776838ca45897502db434e25a4be18b573 Mon Sep 17 00:00:00 2001 From: Michael Hashizume Date: Wed, 12 Jul 2023 10:32:10 -0700 Subject: [PATCH 1/6] (PUP-11935) Update JRuby in tests to 9.3.y.z Puppetserver upgraded from the JRuby 9.2.y.z series to JRuby 9.3.y.z as part of SERVER-3133. This commit updates the JRuby version used in Rspec tests to match the most recent JRuby version used in the Puppetserver 7.x series. --- .github/workflows/rspec_tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rspec_tests.yaml b/.github/workflows/rspec_tests.yaml index 7f2dba5efb3..b6e6c2efe05 100644 --- a/.github/workflows/rspec_tests.yaml +++ b/.github/workflows/rspec_tests.yaml @@ -20,7 +20,7 @@ jobs: - {os: ubuntu-latest, ruby: '2.6'} - {os: ubuntu-latest, ruby: '2.7'} - {os: ubuntu-latest, ruby: '3.0'} - - {os: ubuntu-latest, ruby: 'jruby-9.2.21.0'} + - {os: ubuntu-latest, ruby: 'jruby-9.3.9.0'} - {os: windows-2019, ruby: '2.5'} - {os: windows-2019, ruby: '2.6'} - {os: windows-2019, ruby: '2.7'} From 54aead0258962d46381f758fec0fecf093b0d097 Mon Sep 17 00:00:00 2001 From: Nick Germany Date: Sat, 29 Jul 2023 16:03:28 -0400 Subject: [PATCH 2/6] (PUP-10589) Add a generate_request option to puppet ssl --- lib/puppet/application/ssl.rb | 27 +++++++++++++++++++ spec/unit/application/ssl_spec.rb | 44 +++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/lib/puppet/application/ssl.rb b/lib/puppet/application/ssl.rb index 6c31428c254..c9d79bb7b18 100644 --- a/lib/puppet/application/ssl.rb +++ b/lib/puppet/application/ssl.rb @@ -59,6 +59,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 @@ -136,6 +141,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' @@ -187,6 +194,26 @@ 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 + if Puppet[:key_type] == 'ec' + Puppet.info _("Creating a new EC SSL key for %{name} using curve %{curve}") % { name: certname, curve: Puppet[:named_curve] } + key = OpenSSL::PKey::EC.generate(Puppet[:named_curve]) + else + Puppet.info _("Creating a new SSL key for %{name}") % { name: certname } + key = OpenSSL::PKey::RSA.new(Puppet[:keylength].to_i) + end + @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 for '%{name}' at %{requestdir}") % { name: certname, requestdir: Puppet[:requestdir] } + 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]) diff --git a/spec/unit/application/ssl_spec.rb b/spec/unit/application/ssl_spec.rb index 4b6d1c8d6b1..9506cb64b3f 100644 --- a/spec/unit/application/ssl_spec.rb +++ b/spec/unit/application/ssl_spec.rb @@ -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 for '#{name}' at #{requestdir}}) + end + + it 'generates an EC private key' do + Puppet[:key_type] = 'ec' + File.unlink(Puppet[:hostprivkey]) + + expects_command_to_pass(%r{Generated certificate request for '#{name}' at #{requestdir}}) + end + + it 'registers OIDs' do + expect(Puppet::SSL::Oids).to receive(:register_puppet_oids) + + expects_command_to_pass(%r{Generated certificate request for '#{name}' at #{requestdir}}) + end + + it 'saves the CSR locally' do + expects_command_to_pass(%r{Generated certificate request for '#{name}' at #{requestdir}}) + + 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' From bc29de8bf247382550d4d51fc97dd99992b6d735 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Tue, 12 Sep 2023 11:21:27 -0700 Subject: [PATCH 3/6] (PUP-10589) Print location of CSR --- lib/puppet/application/ssl.rb | 2 +- lib/puppet/x509/cert_provider.rb | 8 ++++++-- spec/unit/application/ssl_spec.rb | 8 ++++---- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/puppet/application/ssl.rb b/lib/puppet/application/ssl.rb index c9d79bb7b18..7e2cdd3a933 100644 --- a/lib/puppet/application/ssl.rb +++ b/lib/puppet/application/ssl.rb @@ -209,7 +209,7 @@ def generate_request(certname) csr = @cert_provider.create_request(certname, key) @cert_provider.save_request(certname, csr) - Puppet.notice _("Generated certificate request for '%{name}' at %{requestdir}") % { name: certname, requestdir: Puppet[:requestdir] } + 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 diff --git a/lib/puppet/x509/cert_provider.rb b/lib/puppet/x509/cert_provider.rb index 811c9ec13b9..9789888fb72 100644 --- a/lib/puppet/x509/cert_provider.rb +++ b/lib/puppet/x509/cert_provider.rb @@ -346,13 +346,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) } diff --git a/spec/unit/application/ssl_spec.rb b/spec/unit/application/ssl_spec.rb index 9506cb64b3f..b790e70d065 100644 --- a/spec/unit/application/ssl_spec.rb +++ b/spec/unit/application/ssl_spec.rb @@ -182,24 +182,24 @@ def expects_command_to_fail(message) it 'generates an RSA private key' do File.unlink(Puppet[:hostprivkey]) - expects_command_to_pass(%r{Generated certificate request for '#{name}' at #{requestdir}}) + 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 for '#{name}' at #{requestdir}}) + 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 for '#{name}' at #{requestdir}}) + 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 for '#{name}' at #{requestdir}}) + expects_command_to_pass(%r{Generated certificate request in '#{csr_path}'}) expect(Puppet::FileSystem).to be_exist(csr_path) end From ebef1efdf01f6d9b7e7d2b41191d3400f920784c Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Tue, 12 Sep 2023 13:00:31 -0700 Subject: [PATCH 4/6] (PUP-10589) Refactor private key creation Refactor code into a private method. --- lib/puppet/application/ssl.rb | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/lib/puppet/application/ssl.rb b/lib/puppet/application/ssl.rb index 7e2cdd3a933..5f39a2cfea0 100644 --- a/lib/puppet/application/ssl.rb +++ b/lib/puppet/application/ssl.rb @@ -169,13 +169,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 @@ -197,13 +191,7 @@ def submit_request(ssl_context) def generate_request(certname) key = @cert_provider.load_private_key(certname) unless key - if Puppet[:key_type] == 'ec' - Puppet.info _("Creating a new EC SSL key for %{name} using curve %{curve}") % { name: certname, curve: Puppet[:named_curve] } - key = OpenSSL::PKey::EC.generate(Puppet[:named_curve]) - else - Puppet.info _("Creating a new SSL key for %{name}") % { name: certname } - key = OpenSSL::PKey::RSA.new(Puppet[:keylength].to_i) - end + key = create_key(certname) @cert_provider.save_private_key(certname, key) end @@ -312,4 +300,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 From 431cd4f5bf447fcd9bd400c406ca16d995b265e3 Mon Sep 17 00:00:00 2001 From: Jenkins CI Date: Fri, 15 Sep 2023 01:06:09 +0000 Subject: [PATCH 5/6] (packaging) Updating manpage file for 7.x --- man/man8/puppet-ssl.8 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/man/man8/puppet-ssl.8 b/man/man8/puppet-ssl.8 index 3ec444ea957..a78ac6a7409 100644 --- a/man/man8/puppet-ssl.8 +++ b/man/man8/puppet-ssl.8 @@ -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\. . From 760ec827428e76c2ad158662b34b109a27b2baa9 Mon Sep 17 00:00:00 2001 From: Michael Hashizume Date: Fri, 15 Sep 2023 15:06:52 -0700 Subject: [PATCH 6/6] (PUP-11935) Handle JRuby OpenSSL behavior Starting with jruby-openssl 0.13.0[1] (which is included in JRuby >= 9.3.5.0), certificate signing raises an error when there is a discrepancy between the certificate and key. This behavior in JRuby differs from MRI OpenSSL. This commit adds a test for this JRuby-specific behavior and updates existing tests to skip when running on affected versions of JRuby. [1] https://github.com/jruby/jruby-openssl/commit/4b2968b3fd2ee9e5f91d34548e6b4faa270a3eb9 --- spec/unit/ssl/certificate_signer_spec.rb | 17 +++++++++++++++++ spec/unit/ssl/ssl_provider_spec.rb | 4 ++-- 2 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 spec/unit/ssl/certificate_signer_spec.rb diff --git a/spec/unit/ssl/certificate_signer_spec.rb b/spec/unit/ssl/certificate_signer_spec.rb new file mode 100644 index 00000000000..45f0108da15 --- /dev/null +++ b/spec/unit/ssl/certificate_signer_spec.rb @@ -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 diff --git a/spec/unit/ssl/ssl_provider_spec.rb b/spec/unit/ssl/ssl_provider_spec.rb index 8a84fb8fafb..13321207dd8 100644 --- a/spec/unit/ssl/ssl_provider_spec.rb +++ b/spec/unit/ssl/ssl_provider_spec.rb @@ -298,7 +298,7 @@ ).to eq(['CN=signed', 'CN=Test CA Subauthority', 'CN=Test CA']) end - it 'raises if client cert signature is invalid' do + it 'raises if client cert signature is invalid', unless: Puppet::Util::Platform.jruby? && RUBY_VERSION.to_f >= 2.6 do client_cert.sign(wrong_key, OpenSSL::Digest::SHA256.new) expect { subject.create_context(**config.merge(client_cert: client_cert)) @@ -337,7 +337,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.sign(wrong_key, OpenSSL::Digest::SHA256.new)