Only modify elevated username under specific conditions

Elevated commands can fail via winrm under certain conditions like
the machine name being changed. Detect this by checking for a known
exit code combined with known output included within stderr. If found,
attempt to re-execute the command using a machine prefixed username
if possible.
This commit is contained in:
Chris Roberts 2018-12-07 12:11:17 -08:00
parent f7757b58d9
commit 24cd988d39
2 changed files with 41 additions and 17 deletions

View File

@ -17,6 +17,10 @@ module VagrantPlugins
class WinRMShell class WinRMShell
include Vagrant::Util::Retryable include Vagrant::Util::Retryable
# Exit code generated when user is invalid. Can occur
# after a hostname update
INVALID_USERID_EXITCODE = -196608
# These are the exceptions that we retry because they represent # These are the exceptions that we retry because they represent
# errors that are generally fixed from a retry and don't # errors that are generally fixed from a retry and don't
# necessarily represent immediate failure cases. # necessarily represent immediate failure cases.
@ -71,13 +75,22 @@ module VagrantPlugins
def elevated(command, opts = {}, &block) def elevated(command, opts = {}, &block)
connection.shell(:elevated) do |shell| connection.shell(:elevated) do |shell|
shell.interactive_logon = opts[:interactive] || false shell.interactive_logon = opts[:interactive] || false
uname = shell.username result = execute_with_rescue(shell, command, &block)
begin if result.exitcode == INVALID_USERID_EXITCODE && result.stderr.include?(":UserId:")
shell.username = elevated_username uname = shell.username
execute_with_rescue(shell, command, &block) ename = elevated_username
ensure if uname != ename
shell.username = uname @logger.warn("elevated command failed due to username error")
@logger.warn("retrying command using machine prefixed username - #{ename}")
begin
shell.username = ename
result = execute_with_rescue(shell, command, &block)
ensure
shell.username = uname
end
end
end end
result
end end
end end
@ -224,11 +237,8 @@ module VagrantPlugins
end end
def elevated_username def elevated_username
if @elevated_username
return @elevated_username
end
if username.include?("\\") if username.include?("\\")
return @elevated_username = username return username
end end
computername = "" computername = ""
powershell("Write-Output $env:computername") do |type, data| powershell("Write-Output $env:computername") do |type, data|
@ -236,9 +246,9 @@ module VagrantPlugins
end end
computername.strip! computername.strip!
if computername.empty? if computername.empty?
return @elevated_username = username return username
end end
@elevated_username = "#{computername}\\#{username}" "#{computername}\\#{username}"
end end
end #WinShell class end #WinShell class
end end

View File

@ -75,10 +75,16 @@ describe VagrantPlugins::CommunicatorWinRM::WinRMShell do
end end
describe ".elevated" do describe ".elevated" do
let(:eusername) { double("elevatedusername") }
let(:username) { double("username") } let(:username) { double("username") }
let(:failed_output) { WinRM::Output.new.tap { |out|
out.exitcode = -196608
out << {stderr: "(10,8):UserId:"}
out << {stderr: "At line:72 char:1"}
} }
before do before do
allow(subject).to receive(:elevated_username).and_return(username) allow(subject).to receive(:elevated_username).and_return(eusername)
allow(shell).to receive(:username).and_return(username) allow(shell).to receive(:username).and_return(username)
allow(shell).to receive(:username=) allow(shell).to receive(:username=)
end end
@ -102,12 +108,20 @@ describe VagrantPlugins::CommunicatorWinRM::WinRMShell do
VagrantPlugins::CommunicatorWinRM::Errors::ExecutionError) VagrantPlugins::CommunicatorWinRM::Errors::ExecutionError)
end end
it "should use elevated username" do it "should use elevated username and retry on username failure" do
expect(subject).to receive(:elevated_username).and_return(username) expect(subject).to receive(:elevated_username).and_return(eusername)
expect(shell).to receive(:run).with("dir").and_return(failed_output)
expect(shell).to receive(:run).with("dir").and_return(output) expect(shell).to receive(:run).with("dir").and_return(output)
expect(shell).to receive(:interactive_logon=).with(false) expect(shell).to receive(:interactive_logon=).with(false)
expect(subject.elevated("dir").exitcode).to eq(0) expect(subject.elevated("dir").exitcode).to eq(0)
end end
it "should not retry on username failure if elevated username is the same" do
expect(subject).to receive(:elevated_username).and_return(username)
expect(shell).to receive(:run).with("dir").and_return(failed_output)
expect(shell).to receive(:interactive_logon=).with(false)
expect(subject.elevated("dir").exitcode).to eq(failed_output.exitcode)
end
end end
describe ".cmd" do describe ".cmd" do
@ -215,8 +229,8 @@ describe VagrantPlugins::CommunicatorWinRM::WinRMShell do
expect(subject.send(:elevated_username)).to eq("COMPUTERNAME\\#{username}") expect(subject.send(:elevated_username)).to eq("COMPUTERNAME\\#{username}")
end end
it "should only compute elevated username once" do it "should compute elevated username every time" do
expect(subject).to receive(:powershell).once.with(/computername/).and_yield(:stdout, "COMPUTERNAME") expect(subject).to receive(:powershell).twice.with(/computername/).and_yield(:stdout, "COMPUTERNAME")
expect(subject.send(:elevated_username)).to eq("COMPUTERNAME\\#{username}") expect(subject.send(:elevated_username)).to eq("COMPUTERNAME\\#{username}")
expect(subject.send(:elevated_username)).to eq("COMPUTERNAME\\#{username}") expect(subject.send(:elevated_username)).to eq("COMPUTERNAME\\#{username}")
end end