Chris Roberts c8f431cf44 Prepend computer name to user when created scheduled tasks
When running a shell provisioner elevated with winrm a scheduled
task is created to bypass permissions issues. If the name of the
computer has changed this may no longer work. To prevent errors
this PR updates the implementation to fetch the computer name
and prepends it to the username before creating the task.
2018-11-08 14:21:20 -08:00

234 lines
8.0 KiB
Ruby

require File.expand_path("../../../../base", __FILE__)
require Vagrant.source_root.join("plugins/communicators/winrm/shell")
require Vagrant.source_root.join("plugins/communicators/winrm/config")
describe VagrantPlugins::CommunicatorWinRM::WinRMShell do
include_context "unit"
let(:connection) { double("winrm_connection") }
let(:shell) { double("winrm_shell") }
let(:port) { config.transport == :ssl ? 5986 : 5985 }
let(:config) {
VagrantPlugins::CommunicatorWinRM::Config.new.tap do |c|
c.username = 'username'
c.password = 'password'
c.max_tries = 3
c.retry_delay = 0
c.basic_auth_only = false
c.retry_delay = 1
c.max_tries = 2
c.finalize!
end
}
let(:output) { WinRM::Output.new.tap { |out| out.exitcode = 0 } }
before { allow(connection).to receive(:shell).and_yield(shell) }
subject do
described_class.new('localhost', port, config).tap do |comm|
allow(comm).to receive(:new_connection).and_return(connection)
end
end
describe "#upload" do
let(:fm) { double("file_manager") }
it "should call file_manager.upload for each passed in path" do
from = ["/path", "/path/folder", "/path/folder/file.py"]
to = "/destination"
size = 80
allow(WinRM::FS::FileManager).to receive(:new).with(connection)
.and_return(fm)
allow(fm).to receive(:upload).and_return(size)
expect(fm).to receive(:upload).exactly(from.size).times
expect(subject.upload(from, to)).to eq(size*from.size)
end
it "should call file_manager.upload once for a single path" do
from = "/path/folder/file.py"
to = "/destination"
size = 80
allow(WinRM::FS::FileManager).to receive(:new).with(connection)
.and_return(fm)
allow(fm).to receive(:upload).and_return(size)
expect(fm).to receive(:upload).exactly(1).times
expect(subject.upload(from, to)).to eq(size)
end
end
describe ".powershell" do
it "should call winrm powershell" do
expect(shell).to receive(:run).with("dir").and_return(output)
expect(subject.powershell("dir").exitcode).to eq(0)
end
it "should raise an execution error when an exception occurs" do
expect(shell).to receive(:run).with("dir").and_raise(
StandardError.new("Oh no! a 500 SOAP error!"))
expect { subject.powershell("dir") }.to raise_error(
VagrantPlugins::CommunicatorWinRM::Errors::ExecutionError)
end
end
describe ".elevated" do
let(:username) { double("username") }
before do
allow(subject).to receive(:elevated_username).and_return(username)
allow(shell).to receive(:username).and_return(username)
allow(shell).to receive(:username=)
end
it "should call winrm elevated" do
expect(shell).to receive(:run).with("dir").and_return(output)
expect(shell).to receive(:interactive_logon=).with(false)
expect(subject.elevated("dir").exitcode).to eq(0)
end
it "should set interactive_logon when interactive is true" do
expect(shell).to receive(:run).with("dir").and_return(output)
expect(shell).to receive(:interactive_logon=).with(true)
expect(subject.elevated("dir", { interactive: true }).exitcode).to eq(0)
end
it "should raise an execution error when an exception occurs" do
expect(shell).to receive(:run).with("dir").and_raise(
StandardError.new("Oh no! a 500 SOAP error!"))
expect { subject.powershell("dir") }.to raise_error(
VagrantPlugins::CommunicatorWinRM::Errors::ExecutionError)
end
it "should use elevated username" do
expect(subject).to receive(:elevated_username).and_return(username)
expect(shell).to receive(:run).with("dir").and_return(output)
expect(shell).to receive(:interactive_logon=).with(false)
expect(subject.elevated("dir").exitcode).to eq(0)
end
end
describe ".cmd" do
it "should call winrm cmd" do
expect(connection).to receive(:shell).with(:cmd, { })
expect(shell).to receive(:run).with("dir").and_return(output)
expect(subject.cmd("dir").exitcode).to eq(0)
end
context "when codepage is given" do
let(:config) {
VagrantPlugins::CommunicatorWinRM::Config.new.tap do |c|
c.codepage = 800
c.finalize!
end
}
it "creates shell with the given codepage when set" do
expect(connection).to receive(:shell).with(:cmd, { codepage: 800 })
expect(shell).to receive(:run).with("dir").and_return(output)
expect(subject.cmd("dir").exitcode).to eq(0)
end
end
end
describe ".wql" do
it "should call winrm wql" do
expect(connection).to receive(:run_wql).with("select * from Win32_OperatingSystem")
subject.wql("select * from Win32_OperatingSystem")
end
it "should retry when a WinRMAuthorizationError is received" do
expect(connection).to receive(:run_wql).with("select * from Win32_OperatingSystem").exactly(2).times.and_raise(
# Note: The initialize for WinRMAuthorizationError may require a status_code as
# the second argument in a future WinRM release. Currently it doesn't track the
# status code.
WinRM::WinRMAuthorizationError.new("Oh no!! Unauthorized")
)
expect { subject.wql("select * from Win32_OperatingSystem") }.to raise_error(
VagrantPlugins::CommunicatorWinRM::Errors::AuthenticationFailed)
end
end
describe ".endpoint" do
context 'when transport is :ssl' do
let(:config) {
VagrantPlugins::CommunicatorWinRM::Config.new.tap do |c|
c.transport = :ssl
c.finalize!
end
}
it "should create winrm endpoint address using https" do
expect(subject.send(:endpoint)).to eq("https://localhost:5986/wsman")
end
end
context "when transport is :negotiate" do
it "should create winrm endpoint address using http" do
expect(subject.send(:endpoint)).to eq("http://localhost:5985/wsman")
end
end
context "when transport is :plaintext" do
let(:config) {
VagrantPlugins::CommunicatorWinRM::Config.new.tap do |c|
c.transport = :plaintext
c.finalize!
end
}
it "should create winrm endpoint address using http" do
expect(subject.send(:endpoint)).to eq("http://localhost:5985/wsman")
end
end
end
describe ".endpoint_options" do
it "should create endpoint options" do
expect(subject.send(:endpoint_options)).to eq(
{ endpoint: "http://localhost:5985/wsman", operation_timeout: 1800,
user: "username", password: "password", host: "localhost", port: 5985,
basic_auth_only: false, no_ssl_peer_verification: false,
retry_delay: 1, retry_limit: 2, transport: :negotiate })
end
end
describe "#elevated_username" do
let(:username) { "username" }
before do
allow(subject).to receive(:username).and_return(username)
allow(subject).to receive(:powershell)
end
it "should return username" do
expect(subject.send(:elevated_username)).to eq(username)
end
it "should attempt to get computer name" do
expect(subject).to receive(:powershell).with(/computername/)
subject.send(:elevated_username)
end
it "should prepend computer name when available" do
expect(subject).to receive(:powershell).with(/computername/).and_yield(:stdout, "COMPUTERNAME")
expect(subject.send(:elevated_username)).to eq("COMPUTERNAME\\#{username}")
end
it "should only compute elevated username once" do
expect(subject).to receive(:powershell).once.with(/computername/).and_yield(:stdout, "COMPUTERNAME")
expect(subject.send(:elevated_username)).to eq("COMPUTERNAME\\#{username}")
expect(subject.send(:elevated_username)).to eq("COMPUTERNAME\\#{username}")
end
context "when username includes computer/domain name" do
let(:username) { "machine\\username" }
it "should not attempt to get computer name" do
expect(subject).not_to receive(:powershell)
expect(subject.send(:elevated_username)).to eq(username)
end
end
end
end