diff --git a/Gemfile b/Gemfile index b1f8b9b5..b3566e31 100644 --- a/Gemfile +++ b/Gemfile @@ -8,9 +8,9 @@ if ENV["GEMFILE_MOD"] puts "GEMFILE_MOD: #{ENV["GEMFILE_MOD"]}" instance_eval(ENV["GEMFILE_MOD"]) else - gem "chef", git: "https://github.com/chef/chef", branch: "main" - gem "ohai", git: "https://github.com/chef/ohai", branch: "main" - gem "knife" + gem "chef", "~> 17" + gem "ohai", "~> 17" + gem "knife", "~> 17" end group :test do diff --git a/README.md b/README.md index e594129c..566387ef 100644 --- a/README.md +++ b/README.md @@ -302,6 +302,10 @@ transport as follows using the `-t` (or `--winrm-transport`) option with the knife winrm -m web1.cloudapp.net -t ssl -x "proddomain\webuser" -P "super_secret_password" -f ~/mycert.crt knife winrm -m db1.cloudapp.net -t ssl -x "localadmin" -P "super_secret_password" ~/mycert.crt +Client certificates can be used for authentication in lieu of username/password credentials: + + knife winrm -m web1.cloudapp.net -t ssl --winrm-authentication-protocol cert --winrm-client-cert ~/myclient.crt --winrm-client-key ~/myclient.key -f ~/mycert.crt + ### Troubleshooting authentication Unencrypted traffic with Basic authentication should only be used for low level wire protocol debugging. The configuration for plain text connectivity to @@ -323,7 +327,7 @@ authentication; an account local to the remote system must be used. ### Platform WinRM authentication support -`knife-windows` supports `Kerberos`, `Negotiate`, and `Basic` authentication +`knife-windows` supports `Kerberos`, `Negotiate`, `Certificate`, and `Basic` authentication for WinRM communication. The following table shows the authentication protocols that can be used with diff --git a/lib/chef/knife/helpers/winrm_base.rb b/lib/chef/knife/helpers/winrm_base.rb index 3218f4af..ec82474b 100644 --- a/lib/chef/knife/helpers/winrm_base.rb +++ b/lib/chef/knife/helpers/winrm_base.rb @@ -23,7 +23,7 @@ class Knife module WinrmBase # It includes supported WinRM authentication protocol. - WINRM_AUTH_PROTOCOL_LIST ||= %w{basic negotiate kerberos}.freeze + WINRM_AUTH_PROTOCOL_LIST ||= %w{basic negotiate kerberos cert}.freeze # :nodoc: # Would prefer to do this in a rational way, but can't be done b/c of @@ -86,6 +86,14 @@ def self.included(includer) long: "--ca-trust-file CA_TRUST_FILE", description: "The Certificate Authority (CA) trust file used for SSL transport" + option :winrm_client_cert, + long: "--winrm-client-cert CERT_FILE", + description: "Certificate to use when --winrm-authentication-protocol is set to 'cert'" + + option :winrm_client_key, + long: "--winrm-client-key KEY_FILE", + description: "Certificate to use when --winrm-authentication-protocol is set to 'cert'" + option :winrm_ssl_verify_mode, long: "--winrm-ssl-verify-mode SSL_VERIFY_MODE", description: "The WinRM peer verification mode. Valid choices are [verify_peer, verify_none]", diff --git a/lib/chef/knife/helpers/winrm_knife_base.rb b/lib/chef/knife/helpers/winrm_knife_base.rb index 8f7ff153..4234b08f 100644 --- a/lib/chef/knife/helpers/winrm_knife_base.rb +++ b/lib/chef/knife/helpers/winrm_knife_base.rb @@ -110,6 +110,7 @@ def extract_nested_value(data, nested_value_spec) def run_command(command = "") relay_winrm_command(command) + @exit_code = 0 check_for_errors! @exit_code end @@ -204,8 +205,6 @@ def resolve_session_options config[:winrm_port] ||= ( config[:winrm_transport] == "ssl" ) ? "5986" : "5985" @session_opts = { - user: resolve_winrm_user, - password: config[:winrm_password], port: config[:winrm_port], operation_timeout: resolve_winrm_session_timeout, basic_auth_only: resolve_winrm_basic_auth, @@ -217,8 +216,18 @@ def resolve_session_options codepage: config[:winrm_codepage], } - if @session_opts[:user] && (not @session_opts[:password]) - @session_opts[:password] = config[:winrm_password] = get_password + if cert_auth? + @session_opts[:winrm_client_cert] = config[:winrm_client_cert] if config[:winrm_client_cert] + @session_opts[:winrm_client_key] = config[:winrm_client_key] if config[:winrm_client_key] + @session_opts[:transport] = :ssl + @session_opts.delete(:user) + @session_opts.delete(:password) + else + @session_opts[:user] = resolve_winrm_user + @session_opts[:password] = config[:winrm_password] + if @session_opts[:user] && (not @session_opts[:password]) + @session_opts[:password] = config[:winrm_password] = get_password + end end if @session_opts[:transport] == :kerberos @@ -287,6 +296,10 @@ def get_password @password ||= ui.ask("Enter your password: ", echo: false) end + def cert_auth? + config[:winrm_authentication_protocol] == "cert" + end + def negotiate_auth? config[:winrm_authentication_protocol] == "negotiate" end diff --git a/lib/chef/knife/helpers/winrm_session.rb b/lib/chef/knife/helpers/winrm_session.rb index e88cc846..3670c627 100644 --- a/lib/chef/knife/helpers/winrm_session.rb +++ b/lib/chef/knife/helpers/winrm_session.rb @@ -33,14 +33,18 @@ def initialize(options) @user = options[:user] @shell_args = [ options[:shell] ] @shell_args << { codepage: options[:codepage] } if options[:shell] == :cmd - url = "#{options[:host]}:#{options[:port]}/wsman" - scheme = options[:transport] == :ssl ? "https" : "http" + if options[:transport] == :ssl + scheme = "https" + @port ||= 5986 + else + scheme = "http" + @port ||= 5985 + end + url = "#{@host}:#{@port}/wsman" @endpoint = "#{scheme}://#{url}" opts = {} opts = { - user: @user, - password: options[:password], basic_auth_only: options[:basic_auth_only], disable_sspi: options[:disable_sspi], no_ssl_peer_verification: options[:no_ssl_peer_verification], @@ -50,6 +54,15 @@ def initialize(options) } options[:transport] == :kerberos ? opts.merge!({ service: options[:service], realm: options[:realm] }) : opts.merge!({ ca_trust_path: options[:ca_trust_path] }) opts[:operation_timeout] = options[:operation_timeout] if options[:operation_timeout] + if options[:winrm_client_cert] && options[:winrm_client_key] + opts[:client_cert] = options[:winrm_client_cert] + opts[:client_key] = options[:winrm_client_key] + opts.delete(:user) + opts.delete(:password) + else + opts[:user] = @user + opts[:password] = options[:password] + end Chef::Log.debug("WinRM::WinRMWebService options: #{opts}") Chef::Log.debug("Endpoint: #{endpoint}") Chef::Log.debug("Transport: #{options[:transport]}")