From 42a9f698a7e62b868e93191a05486c734b00249b Mon Sep 17 00:00:00 2001 From: Mu Master Date: Tue, 10 Oct 2017 10:07:01 -0400 Subject: [PATCH 1/4] add glue to take advantage WinRM gem's certificate authentication support --- README.md | 6 +++++- lib/chef/knife/winrm_base.rb | 12 +++++++++++- lib/chef/knife/winrm_knife_base.rb | 24 ++++++++++++++++++------ lib/chef/knife/winrm_session.rb | 22 +++++++++++++++++----- 4 files changed, 51 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index bdc68720..b84bfe32 100644 --- a/README.md +++ b/README.md @@ -325,6 +325,10 @@ transport as follows using the `-t` (or `--winrm-transport`) option with the knife bootstrap windows winrm -t ssl web1.cloudapp.net -r "server::web" -x "proddomain\webuser" -P "super_secret_password" -f ~/mycert.crt knife bootstrap windows winrm -t ssl db1.cloudapp.net -r "server::db" -x "localadmin" -P "super_secret_password" ~/mycert.crt +Client certificates can be used for authentication in lieu username/password credentials: + + knife bootstrap windows winrm -t ssl web1.cloudapp.net --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 @@ -346,7 +350,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/winrm_base.rb b/lib/chef/knife/winrm_base.rb index b192cd82..1a093619 100644 --- a/lib/chef/knife/winrm_base.rb +++ b/lib/chef/knife/winrm_base.rb @@ -25,7 +25,7 @@ class Knife module WinrmBase # It includes supported WinRM authentication protocol. - WINRM_AUTH_PROTOCOL_LIST ||= %w{basic negotiate kerberos} + WINRM_AUTH_PROTOCOL_LIST ||= %w{basic negotiate kerberos cert} # :nodoc: # Would prefer to do this in a rational way, but can't be done b/c of @@ -96,6 +96,16 @@ def self.included(includer) :description => "The Certificate Authority (CA) trust file used for SSL transport", :proc => Proc.new { |trust| Chef::Config[:knife][:ca_trust_file] = trust } + option :winrm_client_cert, + :long => "--winrm-client-cert CERT_FILE", + :description => "Certificate to use when --winrm-authentication-protocol is set to 'cert'", + :proc => Proc.new { |crt| Chef::Config[:knife][:winrm_client_cert] = crt } + + option :winrm_client_key, + :long => "--winrm-client-key KEY_FILE", + :description => "Certificate to use when --winrm-authentication-protocol is set to 'key'", + :proc => Proc.new { |key| Chef::Config[:knife][:winrm_client_key] = key } + 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/winrm_knife_base.rb b/lib/chef/knife/winrm_knife_base.rb index 7e1c0d96..3d3cf791 100644 --- a/lib/chef/knife/winrm_knife_base.rb +++ b/lib/chef/knife/winrm_knife_base.rb @@ -45,7 +45,7 @@ def self.included(includer) def validate_winrm_options! winrm_auth_protocol = locate_config_value(:winrm_authentication_protocol) - if ! Chef::Knife::WinrmBase::WINRM_AUTH_PROTOCOL_LIST.include?(winrm_auth_protocol) + if ! Chef::Knife::WinrmBase::WINRM_AUTH_PROTOCOL_LIST.include?(winrm_auth_protocol.to_s) ui.error "Invalid value '#{winrm_auth_protocol}' for --winrm-authentication-protocol option." ui.info "Valid values are #{Chef::Knife::WinrmBase::WINRM_AUTH_PROTOCOL_LIST.join(",")}." exit 1 @@ -201,8 +201,6 @@ def create_winrm_session(options={}) def resolve_session_options @session_opts = { - user: resolve_winrm_user, - password: locate_config_value(:winrm_password), port: locate_config_value(:winrm_port), operation_timeout: resolve_winrm_session_timeout, basic_auth_only: resolve_winrm_basic_auth, @@ -214,8 +212,18 @@ def resolve_session_options codepage: locate_config_value(:winrm_codepage) } - if @session_opts[:user] and (not @session_opts[:password]) - @session_opts[:password] = Chef::Config[:knife][:winrm_password] = config[:winrm_password] = get_password + if cert_auth? + @session_opts[:winrm_client_cert] = locate_config_value(:winrm_client_cert) if locate_config_value(:winrm_client_cert) + @session_opts[:winrm_client_key] = locate_config_value(:winrm_client_key) if locate_config_value(: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] = locate_config_value(:winrm_password) + if @session_opts[:user] and (not @session_opts[:password]) + @session_opts[:password] = Chef::Config[:knife][:winrm_password] = config[:winrm_password] = get_password + end end if @session_opts[:transport] == :kerberos @@ -284,8 +292,12 @@ def get_password @password ||= ui.ask("Enter your password: ") { |q| q.echo = false } end + def cert_auth? + locate_config_value(:winrm_authentication_protocol).to_s == "cert" + end + def negotiate_auth? - locate_config_value(:winrm_authentication_protocol) == "negotiate" + locate_config_value(:winrm_authentication_protocol).to_s == "negotiate" end def warn_no_ssl_peer_verification diff --git a/lib/chef/knife/winrm_session.rb b/lib/chef/knife/winrm_session.rb index f66c0d9d..2d0f7887 100644 --- a/lib/chef/knife/winrm_session.rb +++ b/lib/chef/knife/winrm_session.rb @@ -27,20 +27,23 @@ class WinrmSession def initialize(options) configure_proxy - @host = options[:host] @port = options[:port] @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 = Hash.new 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 +53,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] and 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]}") From f0d9c41b0c0d628466a47aa8cb56e5c67ecdfb00 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Tue, 10 Oct 2017 10:09:05 -0400 Subject: [PATCH 2/4] typo in online help for --winrm-client-key --- lib/chef/knife/winrm_base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/chef/knife/winrm_base.rb b/lib/chef/knife/winrm_base.rb index 1a093619..c1b754b6 100644 --- a/lib/chef/knife/winrm_base.rb +++ b/lib/chef/knife/winrm_base.rb @@ -103,7 +103,7 @@ def self.included(includer) option :winrm_client_key, :long => "--winrm-client-key KEY_FILE", - :description => "Certificate to use when --winrm-authentication-protocol is set to 'key'", + :description => "Certificate to use when --winrm-authentication-protocol is set to 'cert'", :proc => Proc.new { |key| Chef::Config[:knife][:winrm_client_key] = key } option :winrm_ssl_verify_mode, From 824063601c59a81702e31f6a69223e68e96e4e06 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Tue, 10 Oct 2017 12:31:37 -0400 Subject: [PATCH 3/4] fix run_command in winrm_knife_base.rb getting stuck on stale exit codes --- lib/chef/knife/winrm_knife_base.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/chef/knife/winrm_knife_base.rb b/lib/chef/knife/winrm_knife_base.rb index 3d3cf791..ad8f79b1 100644 --- a/lib/chef/knife/winrm_knife_base.rb +++ b/lib/chef/knife/winrm_knife_base.rb @@ -112,6 +112,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 @@ -134,6 +135,7 @@ def relay_winrm_command(command) end end threads.map(&:join) +pp @session_results @session_results end From 0deb5ce9bba6718ebac6bb6c48a1a4b7bdc4b15b Mon Sep 17 00:00:00 2001 From: Mu Master Date: Fri, 13 Oct 2017 13:16:02 -0400 Subject: [PATCH 4/4] drop rogue debug statement --- lib/chef/knife/winrm_knife_base.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/chef/knife/winrm_knife_base.rb b/lib/chef/knife/winrm_knife_base.rb index ad8f79b1..a927a2d1 100644 --- a/lib/chef/knife/winrm_knife_base.rb +++ b/lib/chef/knife/winrm_knife_base.rb @@ -135,7 +135,6 @@ def relay_winrm_command(command) end end threads.map(&:join) -pp @session_results @session_results end