module Chef::Knife::WinrmCommandCommon
Public Class Methods
included(includer)
click to toggle source
# File lib/chef/knife/winops_winrm_knife_base.rb, line 36 def self.included(includer) includer.class_eval do @@ssl_warning_given = false include Chef::Knife::WinrmCore include Chef::Knife::WinrmOptions include Chef::Knife::KnifeWindowsCore def validate_winrm_options! winrm_auth_protocol = locate_config_value(:winrm_authentication_protocol) if ! Chef::Knife::WinrmCore::WINRM_AUTH_PROTOCOL_LIST.include?(winrm_auth_protocol) ui.error "Invalid value '#{winrm_auth_protocol}' for --winrm-authentication-protocol option." ui.info "Valid values are #{Chef::Knife::WinrmCore::WINRM_AUTH_PROTOCOL_LIST.join(",")}." exit 1 end warn_no_ssl_peer_verification if resolve_no_ssl_peer_verification end #Overrides Chef::Knife#configure_session, as that code is tied to the SSH implementation #Tracked by Issue # 3042 / https://github.com/chef/chef/issues/3042 def configure_session validate_winrm_options! resolve_session_options resolve_target_nodes session_from_list end def resolve_target_nodes @list = case config[:manual] when true @name_args[0].split(" ") when false r = Array.new q = Chef::Search::Query.new @action_nodes = q.search(:node, @name_args[0])[0] @action_nodes.each do |item| i = extract_nested_value(item, config[:attribute]) r.push(i) unless i.nil? end r end if @list.length == 0 if @action_nodes.length == 0 ui.fatal("No nodes returned from search!") else ui.fatal("#{@action_nodes.length} #{@action_nodes.length > 1 ? "nodes":"node"} found, " + "but does not have the required attribute (#{config[:attribute]}) to establish the connection. " + "Try setting another attribute to open the connection using --attribute.") end exit 10 end end # TODO: Copied from Knife::Core:GenericPresenter. Should be extracted def extract_nested_value(data, nested_value_spec) nested_value_spec.split(".").each do |attr| if data.nil? nil # don't get no method error on nil elsif data.respond_to?(attr.to_sym) data = data.send(attr.to_sym) elsif data.respond_to?(:[]) data = data[attr] else data = begin data.send(attr.to_sym) rescue NoMethodError nil end end end ( !data.kind_of?(Array) && data.respond_to?(:to_hash) ) ? data.to_hash : data end def run_command(command = '') relay_winrm_command(command) check_for_errors! @exit_code end def relay_winrm_command(command) Chef::Log.debug(command) @session_results = [] queue = Queue.new @winrm_sessions.each { |s| queue << s } # These nils will kill the Threads once no more sessions are left locate_config_value(:concurrency).times { queue << nil } threads = [] locate_config_value(:concurrency).times do threads << Thread.new do while session = queue.pop run_command_in_thread(session, command) end end end threads.map(&:join) @session_results end private def run_command_in_thread(s, command) @session_results << s.relay_command(command) rescue WinRM::WinRMHTTPTransportError, WinRM::WinRMAuthorizationError => e if authorization_error?(e) if ! config[:suppress_auth_failure] # Display errors if the caller hasn't opted to retry ui.error "Failed to authenticate to #{s.host} as #{locate_config_value(:winrm_user)}" ui.info "Response: #{e.message}" ui.info get_failed_authentication_hint raise e end else raise e end end def get_failed_authentication_hint if @session_opts[:basic_auth_only] FAILED_BASIC_HINT else FAILED_NOT_BASIC_HINT end end def authorization_error?(exception) exception.is_a?(WinRM::WinRMAuthorizationError) || exception.message =~ /401/ end def check_for_errors! @exit_code ||= 0 @winrm_sessions.each do |session| session_exit_code = session.exit_code unless success_return_codes.include? session_exit_code.to_i @exit_code = [@exit_code, session_exit_code.to_i].max ui.error "Failed to execute command on #{session.host} return code #{session_exit_code}" end end end def success_return_codes #Redundant if the CLI options parsing occurs return [0] unless config[:returns] return @success_return_codes ||= config[:returns].split(',').collect {|item| item.to_i} end def session_from_list @list.each do |item| Chef::Log.debug("Adding #{item}") @session_opts[:host] = item create_winrm_session(@session_opts) end end def create_winrm_session(options={}) session = Chef::Knife::WinrmSession.new(options) @winrm_sessions ||= [] @winrm_sessions.push(session) end 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, disable_sspi: resolve_winrm_disable_sspi, transport: resolve_winrm_transport, no_ssl_peer_verification: resolve_no_ssl_peer_verification, ssl_peer_fingerprint: resolve_ssl_peer_fingerprint, shell: locate_config_value(:winrm_shell), 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 end if @session_opts[:transport] == :kerberos @session_opts.merge!(resolve_winrm_kerberos_options) end @session_opts[:ca_trust_path] = locate_config_value(:ca_trust_file) if locate_config_value(:ca_trust_file) end def resolve_winrm_user user = locate_config_value(:winrm_user) # Prefixing with '.\' when using negotiate # to auth user against local machine domain if resolve_winrm_basic_auth || resolve_winrm_transport == :kerberos || user.include?("\\") || user.include?("@") user else ".\\#{user}" end end def resolve_winrm_session_timeout #30 min (Default) OperationTimeout for long bootstraps fix for KNIFE_WINDOWS-8 locate_config_value(:session_timeout).to_i * 60 if locate_config_value(:session_timeout) end def resolve_winrm_basic_auth locate_config_value(:winrm_authentication_protocol) == "basic" end def resolve_winrm_kerberos_options kerberos_opts = {} kerberos_opts[:keytab] = locate_config_value(:kerberos_keytab_file) if locate_config_value(:kerberos_keytab_file) kerberos_opts[:realm] = locate_config_value(:kerberos_realm) if locate_config_value(:kerberos_realm) kerberos_opts[:service] = locate_config_value(:kerberos_service) if locate_config_value(:kerberos_service) kerberos_opts end def resolve_winrm_transport transport = locate_config_value(:winrm_transport).to_sym if config.any? {|k,v| k.to_s =~ /kerberos/ && !v.nil? } transport = :kerberos elsif transport != :ssl && negotiate_auth? transport = :negotiate end transport end def resolve_no_ssl_peer_verification locate_config_value(:ca_trust_file).nil? && config[:winrm_ssl_verify_mode] == :verify_none && resolve_winrm_transport == :ssl end def resolve_ssl_peer_fingerprint locate_config_value(:ssl_peer_fingerprint) end def resolve_winrm_disable_sspi resolve_winrm_transport != :negotiate end def get_password @password ||= ui.ask("Enter your password: ") { |q| q.echo = false } end def negotiate_auth? locate_config_value(:winrm_authentication_protocol) == "negotiate" end def warn_no_ssl_peer_verification if ! @@ssl_warning_given @@ssl_warning_given = true ui.warn(<<-WARN) * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * SSL validation of HTTPS requests for the WinRM transport is disabled. HTTPS WinRM connections are still encrypted, but knife is not able to detect forged replies or spoofing attacks. To fix this issue add an entry like this to your knife configuration file: ``` # Verify all WinRM HTTPS connections (default, recommended) knife[:winrm_ssl_verify_mode] = :verify_peer ``` * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * WARN end end end end
Public Instance Methods
check_for_errors!()
click to toggle source
# File lib/chef/knife/winops_winrm_knife_base.rb, line 171 def check_for_errors! @exit_code ||= 0 @winrm_sessions.each do |session| session_exit_code = session.exit_code unless success_return_codes.include? session_exit_code.to_i @exit_code = [@exit_code, session_exit_code.to_i].max ui.error "Failed to execute command on #{session.host} return code #{session_exit_code}" end end end
configure_session()
click to toggle source
Overrides Chef::Knife#configure_session, as that code is tied to the SSH implementation Tracked by Issue # 3042 / github.com/chef/chef/issues/3042
# File lib/chef/knife/winops_winrm_knife_base.rb, line 59 def configure_session validate_winrm_options! resolve_session_options resolve_target_nodes session_from_list end
create_winrm_session(options={})
click to toggle source
# File lib/chef/knife/winops_winrm_knife_base.rb, line 196 def create_winrm_session(options={}) session = Chef::Knife::WinrmSession.new(options) @winrm_sessions ||= [] @winrm_sessions.push(session) end
extract_nested_value(data, nested_value_spec)
click to toggle source
TODO: Copied from Knife::Core:GenericPresenter. Should be extracted
# File lib/chef/knife/winops_winrm_knife_base.rb, line 94 def extract_nested_value(data, nested_value_spec) nested_value_spec.split(".").each do |attr| if data.nil? nil # don't get no method error on nil elsif data.respond_to?(attr.to_sym) data = data.send(attr.to_sym) elsif data.respond_to?(:[]) data = data[attr] else data = begin data.send(attr.to_sym) rescue NoMethodError nil end end end ( !data.kind_of?(Array) && data.respond_to?(:to_hash) ) ? data.to_hash : data end
get_failed_authentication_hint()
click to toggle source
# File lib/chef/knife/winops_winrm_knife_base.rb, line 158 def get_failed_authentication_hint if @session_opts[:basic_auth_only] FAILED_BASIC_HINT else FAILED_NOT_BASIC_HINT end end
get_password()
click to toggle source
# File lib/chef/knife/winops_winrm_knife_base.rb, line 283 def get_password @password ||= ui.ask("Enter your password: ") { |q| q.echo = false } end
negotiate_auth?()
click to toggle source
# File lib/chef/knife/winops_winrm_knife_base.rb, line 287 def negotiate_auth? locate_config_value(:winrm_authentication_protocol) == "negotiate" end
relay_winrm_command(command)
click to toggle source
# File lib/chef/knife/winops_winrm_knife_base.rb, line 119 def relay_winrm_command(command) Chef::Log.debug(command) @session_results = [] queue = Queue.new @winrm_sessions.each { |s| queue << s } # These nils will kill the Threads once no more sessions are left locate_config_value(:concurrency).times { queue << nil } threads = [] locate_config_value(:concurrency).times do threads << Thread.new do while session = queue.pop run_command_in_thread(session, command) end end end threads.map(&:join) @session_results end
resolve_no_ssl_peer_verification()
click to toggle source
# File lib/chef/knife/winops_winrm_knife_base.rb, line 271 def resolve_no_ssl_peer_verification locate_config_value(:ca_trust_file).nil? && config[:winrm_ssl_verify_mode] == :verify_none && resolve_winrm_transport == :ssl end
resolve_session_options()
click to toggle source
# File lib/chef/knife/winops_winrm_knife_base.rb, line 202 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, disable_sspi: resolve_winrm_disable_sspi, transport: resolve_winrm_transport, no_ssl_peer_verification: resolve_no_ssl_peer_verification, ssl_peer_fingerprint: resolve_ssl_peer_fingerprint, shell: locate_config_value(:winrm_shell), 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 end if @session_opts[:transport] == :kerberos @session_opts.merge!(resolve_winrm_kerberos_options) end @session_opts[:ca_trust_path] = locate_config_value(:ca_trust_file) if locate_config_value(:ca_trust_file) end
resolve_ssl_peer_fingerprint()
click to toggle source
# File lib/chef/knife/winops_winrm_knife_base.rb, line 275 def resolve_ssl_peer_fingerprint locate_config_value(:ssl_peer_fingerprint) end
resolve_target_nodes()
click to toggle source
# File lib/chef/knife/winops_winrm_knife_base.rb, line 66 def resolve_target_nodes @list = case config[:manual] when true @name_args[0].split(" ") when false r = Array.new q = Chef::Search::Query.new @action_nodes = q.search(:node, @name_args[0])[0] @action_nodes.each do |item| i = extract_nested_value(item, config[:attribute]) r.push(i) unless i.nil? end r end if @list.length == 0 if @action_nodes.length == 0 ui.fatal("No nodes returned from search!") else ui.fatal("#{@action_nodes.length} #{@action_nodes.length > 1 ? "nodes":"node"} found, " + "but does not have the required attribute (#{config[:attribute]}) to establish the connection. " + "Try setting another attribute to open the connection using --attribute.") end exit 10 end end
resolve_winrm_basic_auth()
click to toggle source
# File lib/chef/knife/winops_winrm_knife_base.rb, line 248 def resolve_winrm_basic_auth locate_config_value(:winrm_authentication_protocol) == "basic" end
resolve_winrm_disable_sspi()
click to toggle source
# File lib/chef/knife/winops_winrm_knife_base.rb, line 279 def resolve_winrm_disable_sspi resolve_winrm_transport != :negotiate end
resolve_winrm_kerberos_options()
click to toggle source
# File lib/chef/knife/winops_winrm_knife_base.rb, line 252 def resolve_winrm_kerberos_options kerberos_opts = {} kerberos_opts[:keytab] = locate_config_value(:kerberos_keytab_file) if locate_config_value(:kerberos_keytab_file) kerberos_opts[:realm] = locate_config_value(:kerberos_realm) if locate_config_value(:kerberos_realm) kerberos_opts[:service] = locate_config_value(:kerberos_service) if locate_config_value(:kerberos_service) kerberos_opts end
resolve_winrm_session_timeout()
click to toggle source
# File lib/chef/knife/winops_winrm_knife_base.rb, line 243 def resolve_winrm_session_timeout #30 min (Default) OperationTimeout for long bootstraps fix for KNIFE_WINDOWS-8 locate_config_value(:session_timeout).to_i * 60 if locate_config_value(:session_timeout) end
resolve_winrm_transport()
click to toggle source
# File lib/chef/knife/winops_winrm_knife_base.rb, line 260 def resolve_winrm_transport transport = locate_config_value(:winrm_transport).to_sym if config.any? {|k,v| k.to_s =~ /kerberos/ && !v.nil? } transport = :kerberos elsif transport != :ssl && negotiate_auth? transport = :negotiate end transport end
resolve_winrm_user()
click to toggle source
# File lib/chef/knife/winops_winrm_knife_base.rb, line 228 def resolve_winrm_user user = locate_config_value(:winrm_user) # Prefixing with '.\' when using negotiate # to auth user against local machine domain if resolve_winrm_basic_auth || resolve_winrm_transport == :kerberos || user.include?("\\") || user.include?("@") user else ".\\#{user}" end end
run_command(command = '')
click to toggle source
# File lib/chef/knife/winops_winrm_knife_base.rb, line 113 def run_command(command = '') relay_winrm_command(command) check_for_errors! @exit_code end
run_command_in_thread(s, command)
click to toggle source
# File lib/chef/knife/winops_winrm_knife_base.rb, line 142 def run_command_in_thread(s, command) @session_results << s.relay_command(command) rescue WinRM::WinRMHTTPTransportError, WinRM::WinRMAuthorizationError => e if authorization_error?(e) if ! config[:suppress_auth_failure] # Display errors if the caller hasn't opted to retry ui.error "Failed to authenticate to #{s.host} as #{locate_config_value(:winrm_user)}" ui.info "Response: #{e.message}" ui.info get_failed_authentication_hint raise e end else raise e end end
session_from_list()
click to toggle source
# File lib/chef/knife/winops_winrm_knife_base.rb, line 188 def session_from_list @list.each do |item| Chef::Log.debug("Adding #{item}") @session_opts[:host] = item create_winrm_session(@session_opts) end end
success_return_codes()
click to toggle source
# File lib/chef/knife/winops_winrm_knife_base.rb, line 182 def success_return_codes #Redundant if the CLI options parsing occurs return [0] unless config[:returns] return @success_return_codes ||= config[:returns].split(',').collect {|item| item.to_i} end
validate_winrm_options!()
click to toggle source
# File lib/chef/knife/winops_winrm_knife_base.rb, line 45 def validate_winrm_options! winrm_auth_protocol = locate_config_value(:winrm_authentication_protocol) if ! Chef::Knife::WinrmCore::WINRM_AUTH_PROTOCOL_LIST.include?(winrm_auth_protocol) ui.error "Invalid value '#{winrm_auth_protocol}' for --winrm-authentication-protocol option." ui.info "Valid values are #{Chef::Knife::WinrmCore::WINRM_AUTH_PROTOCOL_LIST.join(",")}." exit 1 end warn_no_ssl_peer_verification if resolve_no_ssl_peer_verification end
warn_no_ssl_peer_verification()
click to toggle source
# File lib/chef/knife/winops_winrm_knife_base.rb, line 291 def warn_no_ssl_peer_verification if ! @@ssl_warning_given @@ssl_warning_given = true ui.warn(<<-WARN) * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * SSL validation of HTTPS requests for the WinRM transport is disabled. HTTPS WinRM connections are still encrypted, but knife is not able to detect forged replies or spoofing attacks. To fix this issue add an entry like this to your knife configuration file: ``` # Verify all WinRM HTTPS connections (default, recommended) knife[:winrm_ssl_verify_mode] = :verify_peer ``` * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * WARN end end