diff options
author | John McCrae <john.mccrae@progress.com> | 2022-03-23 13:17:44 -0700 |
---|---|---|
committer | John McCrae <john.mccrae@progress.com> | 2022-03-23 13:17:44 -0700 |
commit | 25545606b7990b713dd8072e8cf3403f9a14a62a (patch) | |
tree | d1ae9d5c8eb25f2a1cbc2b0cbac30fac518c84fb | |
parent | 7621812b7e829fd53ad65430c61cb01a572d623b (diff) | |
download | chef-25545606b7990b713dd8072e8cf3403f9a14a62a.tar.gz |
updating gemlock files and updating code from feedback
Signed-off-by: John McCrae <john.mccrae@progress.com>
-rw-r--r-- | Gemfile.lock | 40 | ||||
-rw-r--r-- | lib/chef/client.rb | 110 | ||||
-rw-r--r-- | lib/chef/http/authenticator.rb | 10 | ||||
-rw-r--r-- | spec/unit/client_spec.rb | 13 | ||||
-rw-r--r-- | spec/unit/http/authenticator_spec.rb | 22 |
5 files changed, 91 insertions, 104 deletions
diff --git a/Gemfile.lock b/Gemfile.lock index d937910dfd..175c6de329 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -8,10 +8,10 @@ GIT GIT remote: https://github.com/chef/ohai.git - revision: 996b52b8017f1378e4c29b4faeb9ed90db1e9579 + revision: db6480f47f37954b9d3ac1fbb7837fbdb474646a branch: main specs: - ohai (18.0.8) + ohai (18.0.9) chef-config (>= 14.12, < 19) chef-utils (>= 16.0, < 19) ffi (~> 1.9) @@ -188,7 +188,7 @@ GEM chef-zero (>= 14.0) net-ssh coderay (1.1.3) - concurrent-ruby (1.1.9) + concurrent-ruby (1.1.10) corefoundation (0.3.13) ffi (>= 1.15.0) crack (0.4.5) @@ -240,7 +240,7 @@ GEM domain_name (~> 0.5) httpclient (2.8.3) iniparse (1.5.0) - inspec-core (4.52.9) + inspec-core (4.56.17) addressable (~> 2.4) chef-telemetry (~> 1.0, >= 1.0.8) faraday (>= 0.9.0, < 1.5) @@ -253,7 +253,7 @@ GEM parallel (~> 1.9) parslet (>= 1.5, < 2.0) pry (~> 0.13) - rspec (>= 3.9, < 3.11) + rspec (>= 3.9, <= 3.11) rspec-its (~> 1.2) rubyzip (>= 1.2.2, < 3.0) semverse (~> 3.0) @@ -263,8 +263,8 @@ GEM train-core (~> 3.0) tty-prompt (~> 0.17) tty-table (~> 0.10) - inspec-core-bin (4.52.9) - inspec-core (= 4.52.9) + inspec-core-bin (4.56.17) + inspec-core (= 4.56.17) io-wait (0.2.1) ipaddress (0.8.3) iso8601 (0.13.0) @@ -315,7 +315,7 @@ GEM net-ssh (6.1.0) netrc (0.11.0) nori (2.6.0) - parallel (1.21.0) + parallel (1.22.0) parser (3.1.1.0) ast (~> 2.4.1) parslet (1.8.2) @@ -356,22 +356,22 @@ GEM mime-types (>= 1.16, < 4.0) netrc (~> 0.8) rexml (3.2.5) - rspec (3.10.0) - rspec-core (~> 3.10.0) - rspec-expectations (~> 3.10.0) - rspec-mocks (~> 3.10.0) - rspec-core (3.10.2) - rspec-support (~> 3.10.0) - rspec-expectations (3.10.2) + rspec (3.11.0) + rspec-core (~> 3.11.0) + rspec-expectations (~> 3.11.0) + rspec-mocks (~> 3.11.0) + rspec-core (3.11.0) + rspec-support (~> 3.11.0) + rspec-expectations (3.11.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.10.0) + rspec-support (~> 3.11.0) rspec-its (1.3.0) rspec-core (>= 3.0.0) rspec-expectations (>= 3.0.0) - rspec-mocks (3.10.3) + rspec-mocks (3.11.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.10.0) - rspec-support (3.10.3) + rspec-support (~> 3.11.0) + rspec-support (3.11.0) rubocop (1.25.1) parallel (~> 1.10) parser (>= 3.1.0.0) @@ -401,7 +401,7 @@ GEM date timeout (0.2.0) tomlrb (1.3.0) - train-core (3.8.7) + train-core (3.8.9) addressable (~> 2.5) ffi (!= 1.13.0) json (>= 1.8, < 3.0) diff --git a/lib/chef/client.rb b/lib/chef/client.rb index c3b62a60c3..d50b8c6704 100644 --- a/lib/chef/client.rb +++ b/lib/chef/client.rb @@ -65,15 +65,7 @@ class Chef # The main object in a Chef run. Preps a Chef::Node and Chef::RunContext, # syncs cookbooks if necessary, and triggers convergence. class Client - class KeyMigration - include Singleton - attr_accessor :key_migrated - attr_accessor :old_priv_key - def initialize - @key_migrated = false - @old_priv_key = nil - end - end + CRYPT_EXPORTABLE = 0x00000001 attr_reader :local_context @@ -661,8 +653,8 @@ class Chef if result.rassoc("#{cert_name}") logger.trace("Client key #{config[:client_key]} is present in Certificate Store - skipping registration") else - move_key_and_register(cert_name) - logger.trace("Client key #{config[:client_key]} moved to the Certificate Store - skipping registration") + create_new_key_and_register(cert_name) + logger.trace("New client keys created in the Certificate Store - skipping registration") end events.skipping_registration(client_name, config) elsif File.exists?(config[:client_key]) @@ -683,7 +675,6 @@ class Chef raise end - # # In the brave new world of No Certs On Disk, we want to put the pem file into Keychain or the Certstore # But is it already there? def check_certstore_for_key(cert_name) @@ -692,35 +683,47 @@ class Chef win32certstore.search("#{cert_name}") end - def generate_pfx_package(cert_name, date = nil) - require_relative "mixin/powershell_exec" - extend Chef::Mixin::PowershellExec - ::Chef::HTTP::Authenticator.get_cert_password - powershell_code = <<~EOH - - $date = "#{date}" - - $certSplat = @{ - Subject = "#{cert_name}" - KeyExportPolicy = 'Exportable' - KeyUsage = @('KeyEncipherment','DigitalSignature') - CertStoreLocation = 'Cert:\\LocalMachine\\My' - TextExtension = @("2.5.29.37={text}1.3.6.1.5.5.7.3.2,1.3.6.1.5.5.7.3.1") - }; - if ([string]$date -as [DateTime]){ - $certSplat.add('NotAfter', $date) - } - - New-SelfSignedCertificate @certSplat; - EOH - powershell_exec!(powershell_code) + def generate_pfx_package(cert_name, date) + self.class.generate_pfx_package(cert_name, date) + end + + def self.generate_pfx_package(cert_name, date) + require "openssl" + + key = OpenSSL::PKey::RSA.new(2048) + public_key = key.public_key + + subject = "CN=#{cert_name}" + + cert = OpenSSL::X509::Certificate.new + cert.subject = cert.issuer = OpenSSL::X509::Name.parse(subject) + cert.not_before = Time.now + cert.not_after = Time.parse(date) + cert.public_key = public_key + cert.serial = 0x0 + cert.version = 2 + + ef = OpenSSL::X509::ExtensionFactory.new + ef.subject_certificate = cert + ef.issuer_certificate = cert + cert.extensions = [ + ef.create_extension("subjectKeyIdentifier", "hash"), + ef.create_extension("keyUsage", "digitalSignature,keyEncipherment", true), + ] + cert.add_extension(ef.create_ext_from_string("extendedKeyUsage=critical,serverAuth,clientAuth")) + + cert.sign key, OpenSSL::Digest::SHA256.new + password = ::Chef::HTTP::Authenticator.get_cert_password + pfx = OpenSSL::PKCS12.create(password, subject, key, cert) + pfx end - def move_key_and_register(cert_name) + def create_new_key_and_register(cert_name) + require "pry" require "time" unless defined?(Time) autoload :URI, "uri" - KeyMigration.instance.key_migrated = true + # KeyMigration.instance.key_migrated = true node = Chef::Config[:node_name] d = Time.now @@ -733,31 +736,29 @@ class Chef expiration_date: end_date, } - generate_pfx_package(cert_name, end_date) - payload[:public_key] = get_public_key(cert_name) + new_pfx = generate_pfx_package(cert_name, end_date) + payload[:public_key] = new_pfx.certificate.public_key.to_pem base_url = "#{Chef::Config[:chef_server_url]}" client = Chef::ServerAPI.new(base_url, client_name: Chef::Config[:validation_client_name], signing_key_filename: Chef::Config[:validation_key]) client.post(base_url + "/clients", payload) - KeyMigration.instance.key_migrated = false + # KeyMigration.instance.key_migrated = false Chef::Log.trace("Updated client data: #{client.inspect}") + import_pfx_to_store(new_pfx) + end + + def import_pfx_to_store(new_pfx) + self.class.import_pfx_to_store(new_pfx) end - def get_public_key(cert_name) + def self.import_pfx_to_store(new_pfx) password = ::Chef::HTTP::Authenticator.get_cert_password - require_relative "mixin/powershell_exec" - extend Chef::Mixin::PowershellExec - powershell_code = <<~EOH - $my_pwd = ConvertTo-SecureString -String "#{password}" -Force -AsPlainText; - $tempfile = $([System.IO.Path]::GetTempPath()) + $([System.IO.Path]::GetRandomFileName()); - $cert = Get-ChildItem -path cert:\\LocalMachine\\My -Recurse | Where-Object { $_.Subject -match "#{cert_name}$" } -ErrorAction Stop; - Export-PFXCertificate -Cert $cert -Password $my_pwd -FilePath $tempfile; - return $tempfile; - EOH - cert_file = powershell_exec!(powershell_code).result - path = cert_file[1] - p12 = OpenSSL::PKCS12.new(File.binread(path), password) - File.delete(path) - p12.key.public_to_pem + require "win32-certstore" + tempfile = Tempfile.new("#{Chef::Config[:node_name]}.pfx") + File.open(tempfile, "wb") { |f| f.print new_pfx.to_der } + + store = ::Win32::Certstore.open("MY") + store.add_pfx(tempfile, password, CRYPT_EXPORTABLE) + tempfile.unlink end # @@ -1024,3 +1025,4 @@ end require_relative "cookbook_loader" require_relative "cookbook_version" require_relative "cookbook/synchronizer" + diff --git a/lib/chef/http/authenticator.rb b/lib/chef/http/authenticator.rb index 641599972a..79e462c5fc 100644 --- a/lib/chef/http/authenticator.rb +++ b/lib/chef/http/authenticator.rb @@ -119,6 +119,7 @@ class Chef # def self.detect_certificate_key(client_name) if ChefUtils.windows? + require "pry" check_certstore_for_key(client_name) else # generic return for Mac and LInux clients false @@ -141,9 +142,7 @@ class Chef def load_signing_key(key_file, raw_key = nil) results = retrieve_certificate_key(Chef::Config[:node_name]) - if ::Chef::Config[:migrate_key_to_keystore] == true && ::Chef::Client::KeyMigration.instance.key_migrated == true - @raw_key = IO.read(Chef::Config[:validation_key]).strip - elsif !!results + if !!results @raw_key = results elsif key_file == nil? && raw_key == nil? puts "\nNo key detected\n" @@ -192,7 +191,8 @@ class Chef unless @win32registry.key_exists?(new_path) @win32registry.create_key(new_path, true) end - password = SOME_CHARS.sample(1 + rand(SOME_CHARS.count)).join[0...14] + size = 14 + password = SOME_CHARS.sample(size).join encrypted_pass = encrypt_pfx_pass(password) values = { name: "PfxPass", type: :string, data: encrypted_pass } @win32registry.set_value(new_path, values) @@ -247,7 +247,7 @@ class Chef powershell_code = <<~CODE Try { $my_pwd = ConvertTo-SecureString -String "#{password}" -Force -AsPlainText; - $cert = Get-ChildItem -path cert:\\LocalMachine\\My -Recurse | Where-Object { $_.Subject -match "#{client_name}$" } -ErrorAction Stop; + $cert = Get-ChildItem -path cert:\\LocalMachine\\My -Recurse | Where-Object { $_.Subject -match "chef-#{client_name}$" } -ErrorAction Stop; $tempfile = [System.IO.Path]::GetTempPath() + "export_pfx.pfx"; Export-PfxCertificate -Cert $cert -Password $my_pwd -FilePath $tempfile; } diff --git a/spec/unit/client_spec.rb b/spec/unit/client_spec.rb index 1b8775e0d8..199ca7e9b6 100644 --- a/spec/unit/client_spec.rb +++ b/spec/unit/client_spec.rb @@ -291,6 +291,7 @@ describe Chef::Client, :windows_only do let(:hostname) { "test" } let(:my_client) { Chef::Client.new } let(:cert_name) { "chef-#{hostname}" } + let(:node_name) { "#{hostname}" } let(:end_date) do d = Time.now end_date = Time.new(d.year, d.month + 3, d.day, d.hour, d.min, d.sec).utc.iso8601 @@ -298,6 +299,7 @@ describe Chef::Client, :windows_only do # include_context "client" before(:each) do Chef::Config[:migrate_key_to_keystore] = true + Chef::Config[:node_name] = node_name end after(:each) do @@ -305,19 +307,14 @@ describe Chef::Client, :windows_only do end context "when the client intially boots the first time" do - it "created a new pfx object" do - expect(my_client.generate_pfx_package(cert_name, end_date)).to be_truthy - end - - it "verfies that a certificate correctly exists in the Cert Store" do + it "verfies that a certificate was correctly created and exists in the Cert Store" do my_client.generate_pfx_package(cert_name, end_date) expect(my_client.check_certstore_for_key(cert_name)).not_to be false end it "correctly returns a new Publc Key" do - my_client.generate_pfx_package(cert_name, end_date) - public_key = my_client.get_public_key(cert_name) - cert_object = OpenSSL::PKey::RSA.new(public_key) + new_pfx = my_client.generate_pfx_package(cert_name, end_date) + cert_object = new_pfx.certificate.public_key.to_pem expect(cert_object.to_s).to match(/PUBLIC KEY/) end diff --git a/spec/unit/http/authenticator_spec.rb b/spec/unit/http/authenticator_spec.rb index 0bb81b8ca5..0ce5448d1a 100644 --- a/spec/unit/http/authenticator_spec.rb +++ b/spec/unit/http/authenticator_spec.rb @@ -28,14 +28,15 @@ describe Chef::HTTP::Authenticator, :windows_only do let(:node_name) { "test" } let(:passwrd) { "some_insecure_password" } - before(:each) do - ::Chef::Config[:node_name] = "test" + before do + Chef::Config[:node_name] = node_name cert_name = "chef-#{node_name}" d = Time.now end_date = Time.new(d.year, d.month + 3, d.day, d.hour, d.min, d.sec).utc.iso8601 my_client = Chef::Client.new - my_client.generate_pfx_package(cert_name, end_date) + pfx = my_client.generate_pfx_package(cert_name, end_date) + my_client.import_pfx_to_store(pfx) end after(:each) do @@ -57,25 +58,16 @@ describe Chef::HTTP::Authenticator, :windows_only do end it "retrieves a certificate password from the registry when the hive exists" do - set_registry_hive + class_instance.get_cert_password expect { class_instance.get_cert_password }.not_to raise_error end - it "correctly retrieves a private key from the certstore" do - cert_name = "chef-#{node_name}" - expect { class_instance.retrieve_certificate_key(cert_name) }.not_to raise_error - end - it "correctly retrieves a valid certificate in pem format from the certstore" do require "openssl" certificate = class_instance.retrieve_certificate_key(node_name) cert_object = OpenSSL::PKey::RSA.new(certificate) expect(cert_object.to_s).to match(/BEGIN RSA PRIVATE KEY/) end - - # does retrieving a cert work - # is the password at least 14 characters - # is the pem a proper cert object end def delete_certificate(cert_name) @@ -93,10 +85,6 @@ describe Chef::HTTP::Authenticator, :windows_only do @win32registry.delete_key(path, true) end end - - def set_registry_hive - class_instance.get_cert_password - end end describe Chef::HTTP::Authenticator do |