diff options
-rw-r--r-- | lib/chef/client.rb | 74 | ||||
-rw-r--r-- | lib/chef/http/authenticator.rb | 88 |
2 files changed, 149 insertions, 13 deletions
diff --git a/lib/chef/client.rb b/lib/chef/client.rb index 3a312701d2..dafbfac797 100644 --- a/lib/chef/client.rb +++ b/lib/chef/client.rb @@ -20,6 +20,8 @@ require_relative "config" require_relative "mixin/params_validate" +require "chef/mixin/powershell_exec" +require "chef/mixin/shell_out" require "chef-utils/dsl/default_paths" unless defined?(ChefUtils::DSL::DefaultPaths) require_relative "log" require_relative "deprecated" @@ -65,6 +67,8 @@ class Chef # syncs cookbooks if necessary, and triggers convergence. class Client extend Chef::Mixin::Deprecation + include Chef::Mixin::PowershellExec + include Chef::Mixin::ShellOut extend Forwardable # @@ -638,13 +642,21 @@ class Chef # @api private # def register(client_name = node_name, config = Chef::Config) + puts "\n cli.rb - This is the client name I am using : #{client_name}" + puts "\n cli.rb - This is my client key : #{config[:client_key]}" if !config[:client_key] events.skipping_registration(client_name, config) logger.trace("Client key is unspecified - skipping registration") - elsif File.exists?(config[:client_key]) + elsif detect_certificate_key(client_name) + events.skipping_registration(client_name, config) + logger.trace("Client key #{client_name} is present in certificate repository - skipping registration") + puts "\n cli.rb - Did I correctly detect a client key in the certstore or keychain : #{detect_certificate_key(client_name)}" + puts "\n cli.rb - Hey, I found an appropriate key in the keychain or Certstore!" + elsif File.exist?(config[:client_key]) events.skipping_registration(client_name, config) logger.trace("Client key #{config[:client_key]} is present - skipping registration") else + puts "\n cli.rb - Did I correctly detect a client key in the certstore or keychain : #{detect_certificate_key(client_name)}" events.registration_start(node_name, config) logger.info("Client key #{config[:client_key]} is not present - registering") Chef::ApiClient::Registration.new(node_name, config[:client_key]).run @@ -660,6 +672,60 @@ class Chef end # + # Detects if a private key exists in a certificate repository like Keychain (macOS) or Certificate Store (Windows) + # + # @param client_name - we're using the node name to store and retrieve any keys + # Returns true if a key is found, false if not. False will trigger a registration event which will lead to a certificate based key being created + # + # + def detect_certificate_key(client_name) + if ChefUtils.windows? + check_certstore_for_key(client_name) + elsif ChefUtils.macos? + puts "Made it to checking the macos key" + check_keychain_for_key(client_name) + else # generic return for Linux systemss + false + end + end + + def check_certstore_for_key(client_name) + powershell_code = <<~CODE + $cert = Get-ChildItem -path cert:\\LocalMachine\\My -Recurse -Force | Where-Object { $_.Subject -Match "#{client_name}" } -ErrorAction Stop + if ($cert) { + return $true + } + else{ + return $false + } + CODE + powershell_exec!(powershell_code).result + end + + # below we're checking Keychain for the presence of a cert. + # We compare the output we get because the code throws an error (127) if the cert isn't present + def check_keychain_for_key(client_name) + mine = shell_out! <<~EOH + getcert() { + CERTSTATUS=$(security find-certificate -c #{client_name} >/dev/null) + SUB='#{client_name}' + if [[ "$CERTSTATUS" == *"$SUB"* ]]; then + true + else + false + fi + } + getcert + getcertresult=$? + echo $getcertresult + EOH + + # The bash script above checks for a cert in Keychain and returns a 127 if not present, 0 if present. + output = mine.run_command + output == '0' ? true : false + end + + # # Converges all compiled resources. # # Fires the converge_start, converge_complete and converge_failed events. @@ -863,12 +929,6 @@ class Chef end def start_profiling - if Chef::Config[:slow_report] - require_relative "handler/slow_report" - - Chef::Config.report_handlers << Chef::Handler::SlowReport.new(Chef::Config[:slow_report]) - end - return unless Chef::Config[:profile_ruby] profiling_prereqs! diff --git a/lib/chef/http/authenticator.rb b/lib/chef/http/authenticator.rb index 8e45423de9..eda722b255 100644 --- a/lib/chef/http/authenticator.rb +++ b/lib/chef/http/authenticator.rb @@ -16,20 +16,19 @@ # limitations under the License. # +require "chef/mixin/powershell_exec" require_relative "auth_credentials" require_relative "../exceptions" autoload :OpenSSL, "openssl" -autoload :ChefUtils, "chef-utils" - -require "chef/mixin/powershell_exec" class Chef class HTTP class Authenticator - include Chef::Mixin::PowershellExec DEFAULT_SERVER_API_VERSION = "2".freeze + include Chef::Mixin::PowershellExec + attr_reader :signing_key_filename attr_reader :raw_key attr_reader :attr_names @@ -87,10 +86,42 @@ class Chef @auth_credentials.client_name end + # def load_signing_key(key_file, raw_key = nil) + # if !!key_file + # @raw_key = IO.read(key_file).strip + # elsif !!raw_key + # @raw_key = raw_key.strip + # else + # return nil + # end + # # Pass in '' as the passphrase to avoid OpenSSL prompting on the TTY if + # # given an encrypted key. This also helps if using a single file for + # # both the public and private key with ssh-agent mode. + # @key = OpenSSL::PKey::RSA.new(@raw_key, "") + # rescue SystemCallError, IOError => e + # Chef::Log.warn "Failed to read the private key #{key_file}: #{e.inspect}" + # raise Chef::Exceptions::PrivateKeyMissing, "I cannot read #{key_file}, which you told me to use to sign requests!" + # rescue OpenSSL::PKey::RSAError + # msg = "The file #{key_file} or :raw_key option does not contain a correctly formatted private key or the key is encrypted.\n" + # msg << "The key file should begin with '-----BEGIN RSA PRIVATE KEY-----' and end with '-----END RSA PRIVATE KEY-----'" + # raise Chef::Exceptions::InvalidPrivateKey, msg + # end + def load_signing_key(key_file, raw_key = nil) - puts "This is the key file name I am trying to load : #{@signing_key_filename}" + results = retrieve_certificate_key(Chef::Config[:node_name]) + puts "\n" + puts " auth.rb - This is the key file name I am trying to load : #{key_file}" + puts " auth.rb - Is there a raw_key name? : #{raw_key ? raw_key : false}" + puts " auth.rb - Chef Config thinks this is my node name : #{Chef::Config[:node_name]}" + puts " auth.rb - Is the Global @node_name available here? : #{@node_Name ? @node_name : false}" + puts "\n" if key_file == nil? && raw_key == nil? - puts "No key detected" + puts "\nNo key detected\n" + elsif results != false + # results variable holds 2 values - "False" or the contents of a key. + @raw_key = results + puts "\n" + puts "Hey. I think I got a key back! #{results}" # first time chef-client runs, generate node-name and burn that to the client.rb # use that node name to create a p12/pfx on the fly # store that @@ -131,6 +162,51 @@ class Chef raise Chef::Exceptions::InvalidPrivateKey, msg end + def retrieve_certificate_key(client_name) + if ChefUtils.windows? + check_and_retrieve_certstore_for_key(client_name) + elsif ChefUtils.macos? + check_keychain_for_key(client_name) + else # generic return for Linux systemss + false + end + end + + def check_and_retrieve_certstore_for_key(client_name) + # This code block assumes a certificate with a subject name like "Chef-<node-name>" is in the \LocalMachine\My store and + # that here is a password stored in the registry to be used to export the pfx with. + + require "openssl" unless defined?(OpenSSL) + + powershell_password_code = <<~CODE + Try { + Get-ItemPropertyValue -Path "HKLM:\\Software\\Progress\\Authenticator" -Name "PfxPass" -ErrorAction Stop; + } + Catch { + return $false + } + CODE + password = powershell_exec!(powershell_password_code).result + + powershell_code = <<~CODE + Try { + $pfspass = "#{password}" + $my_pwd = ConvertTo-SecureString -String $pfspass -Force -AsPlainText; + $cert = Get-ChildItem -path cert:\\LocalMachine\\My -Recurse | Where-Object { $_.Subject -match "#{client_name}" } -ErrorAction Stop; + $tempfile = [System.IO.Path]::GetTempPath() + "exportpfx.pfx"; + Export-PfxCertificate -Cert $cert -Password $my_pwd -FilePath $tempfile; + } + Catch { + return $false + } + CODE + myresult = powershell_exec!(powershell_code).result + + pkcs = OpenSSL::PKCS12.new(File.binread(myresult["PSPath"].split("::")[1]), password) + ::File.delete(myresult["PSPath"].split("::")[1]) + return OpenSSL::PKey::RSA.new(pkcs.key.to_pem) + end + def authentication_headers(method, url, json_body = nil, headers = nil) request_params = { http_method: method, |