diff options
author | John McCrae <john.mccrae@progress.com> | 2022-05-26 11:49:46 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-05-26 11:49:46 +0000 |
commit | 24c570d5723e013eb885bb21c467e84a40d3fa2d (patch) | |
tree | ff617337b9afcd61f320f0c6d29fe2a906ef2a05 | |
parent | 2ea88376925bac73cbeb77c53af23be4a5da7c13 (diff) | |
parent | 0af044a6ccc717d4905778648c9fdeaaab696bbd (diff) | |
download | chef-24c570d5723e013eb885bb21c467e84a40d3fa2d.tar.gz |
Merge pull request #12910 from chef/jfm/client_pem_update2
-rw-r--r-- | cspell.json | 1 | ||||
-rw-r--r-- | lib/chef/client.rb | 64 | ||||
-rw-r--r-- | lib/chef/http/authenticator.rb | 22 | ||||
-rw-r--r-- | spec/unit/http/authenticator_spec.rb | 3 |
4 files changed, 86 insertions, 4 deletions
diff --git a/cspell.json b/cspell.json index c83ca74074..9d19afa372 100644 --- a/cspell.json +++ b/cspell.json @@ -589,6 +589,7 @@ "keyivgen", "KEYNAME", "keyname", + "keypair", "keyscan", "keysign", "KEYTAB", diff --git a/lib/chef/client.rb b/lib/chef/client.rb index 07798798b9..e5fcd56eb0 100644 --- a/lib/chef/client.rb +++ b/lib/chef/client.rb @@ -715,6 +715,70 @@ class Chef pfx end + def update_key_and_register(cert_name) + self.class.update_key_and_register(cert_name) + end + + def self.update_key_and_register(cert_name, expiring_cert = nil) + # Chef client and node objects exist on Chef Server already + # Create a new public/private keypair in secure storage + # and register the new public cert with Chef Server + require "time" unless defined?(Time) + autoload :URI, "uri" + + node = Chef::Config[:node_name] + end_date = Time.new + (3600 * 24 * 90) + end_date = end_date.utc.iso8601 + + new_cert_name = Time.now.utc.iso8601 + payload = { + name: new_cert_name, + clientname: node, + public_key: "", + expiration_date: end_date, + } + + 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]}" + + @tmpdir = Dir.mktmpdir + file_path = File.join(@tmpdir, "#{node}.pem") + + # The pfx files expire every 90 days. + # We check them in /http/authenticator to see if they are expiring when we extract the private key + # If they are, we come here to update Chef Server with a new public key + if expiring_cert + File.open(file_path, "w") { |f| f.write expiring_cert.key.to_pem } + signing_cert = file_path + client = Chef::ServerAPI.new(base_url, client_name: Chef::Config[:node_name], signing_key_filename: signing_cert ) + File.delete(file_path) + else + client = Chef::ServerAPI.new(base_url, client_name: Chef::Config[:node_name], signing_key_filename: Chef::Config[:client_key] ) + end + + # Get the list of keys for this client + # Then add the new key we just created + # Then we delete the old one. + cert_list = client.get(base_url + "/clients/#{node}/keys") + client.post(base_url + "/clients/#{node}/keys", payload) + + # We want to remove the old key for various reasons + # In the case where more than 1 certificate is returned we assume + # there is some special condition applied to the client so we won't delete the old + # certificates + if cert_list.count < 2 + cert_hash = cert_list.reduce({}, :merge!) + old_cert_name = cert_hash["name"] + new_key = new_pfx.key.to_pem + File.open(file_path, "w") { |f| f.write new_key } + client = Chef::ServerAPI.new(base_url, client_name: Chef::Config[:node_name], signing_key_filename: file_path) + client.delete(base_url + "/clients/#{node}/keys/#{old_cert_name}") + File.delete(file_path) + end + import_pfx_to_store(new_pfx) + end + def create_new_key_and_register(cert_name) require "time" unless defined?(Time) autoload :URI, "uri" diff --git a/lib/chef/http/authenticator.rb b/lib/chef/http/authenticator.rb index 98d331d4f0..6fbc2a74ba 100644 --- a/lib/chef/http/authenticator.rb +++ b/lib/chef/http/authenticator.rb @@ -191,7 +191,7 @@ class Chef @win32registry.create_key(new_path, true) end size = 14 - password = (0...size).map { SOME_CHARS.sample(1) }.join + password = (0...size).map { SOME_CHARS[rand(SOME_CHARS.size)] }.join encrypted_pass = encrypt_pfx_pass(password) values = { name: "PfxPass", type: :string, data: encrypted_pass } @win32registry.set_value(new_path, values) @@ -228,12 +228,16 @@ class Chef file_path = ps_blob["PSPath"].split("::")[1] pkcs = OpenSSL::PKCS12.new(File.binread(file_path), password) - # We test the pfx we just extracted the private key from + # We check the pfx we just extracted the private key from # if that cert is expiring in 7 days or less we generate a new pfx/p12 object # then we post the new public key from that to the client endpoint on # chef server. - # is_certificate_expiring(pkcs) File.delete(file_path) + key_expiring = is_certificate_expiring?(pkcs) + if key_expiring + powershell_exec!(delete_old_key_ps(client_name)) + ::Chef::Client.update_key_and_register(Chef::Config[:client_name], pkcs) + end return pkcs.key.private_to_pem end @@ -242,6 +246,12 @@ class Chef false end + def self.is_certificate_expiring?(pkcs) + today = Date.parse(Time.now.utc.iso8601) + future = Date.parse(pkcs.certificate.not_after.iso8601) + future.mjd - today.mjd <= 7 + end + def self.get_the_key_ps(client_name, password) powershell_code = <<~CODE Try { @@ -256,6 +266,12 @@ class Chef CODE end + def self.delete_old_key_ps(client_name) + powershell_code = <<~CODE + Get-ChildItem -path cert:\\LocalMachine\\My -Recurse | Where-Object { $_.Subject -match "chef-#{client_name}$" } | Remove-Item -ErrorAction Stop; + CODE + end + def authentication_headers(method, url, json_body = nil, headers = nil) request_params = { http_method: method, diff --git a/spec/unit/http/authenticator_spec.rb b/spec/unit/http/authenticator_spec.rb index a1b67610c5..bbd952b436 100644 --- a/spec/unit/http/authenticator_spec.rb +++ b/spec/unit/http/authenticator_spec.rb @@ -32,7 +32,8 @@ describe Chef::HTTP::Authenticator, :windows_only 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 + end_date = Time.new + (3600 * 24 * 90) + end_date = end_date.utc.iso8601 my_client = Chef::Client.new pfx = my_client.generate_pfx_package(cert_name, end_date) |