summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn McCrae <john.mccrae@progress.com>2022-05-26 11:49:46 +0000
committerGitHub <noreply@github.com>2022-05-26 11:49:46 +0000
commit24c570d5723e013eb885bb21c467e84a40d3fa2d (patch)
treeff617337b9afcd61f320f0c6d29fe2a906ef2a05
parent2ea88376925bac73cbeb77c53af23be4a5da7c13 (diff)
parent0af044a6ccc717d4905778648c9fdeaaab696bbd (diff)
downloadchef-24c570d5723e013eb885bb21c467e84a40d3fa2d.tar.gz
Merge pull request #12910 from chef/jfm/client_pem_update2
-rw-r--r--cspell.json1
-rw-r--r--lib/chef/client.rb64
-rw-r--r--lib/chef/http/authenticator.rb22
-rw-r--r--spec/unit/http/authenticator_spec.rb3
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)