# # Author:: Steven Murawski ( 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! # Knife seems to ignore the return value of this method, # so we exit to force the process exit code for this # subcommand if returns is set exit @exit_code if @exit_code && @exit_code != 0 0 end def relay_winrm_command(command) Chef::Log.debug(command) @winrm_sessions.each do |s| begin 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 @exit_code = 401 else raise e end end end end private 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! @winrm_sessions.each do |session| session_exit_code = session.exit_code unless success_return_codes.include? session_exit_code.to_i @exit_code = session_exit_code.to_i 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 } 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_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 end end end