Merge pull request #6185 from marc-ta/elevated_interactive

Elevated interactive
This commit is contained in:
Mitchell Hashimoto 2015-11-18 12:46:39 -08:00
commit 37940e7350
9 changed files with 67 additions and 27 deletions

9
plugins/communicators/winrm/communicator.rb Normal file → Executable file
View File

@ -136,10 +136,11 @@ module VagrantPlugins
error_key: nil, # use the error_class message key error_key: nil, # use the error_class message key
good_exit: 0, good_exit: 0,
shell: :powershell, shell: :powershell,
interactive: false,
}.merge(opts || {}) }.merge(opts || {})
opts[:good_exit] = Array(opts[:good_exit]) opts[:good_exit] = Array(opts[:good_exit])
command = wrap_in_scheduled_task(command) if opts[:elevated] command = wrap_in_scheduled_task(command, opts[:interactive]) if opts[:elevated]
output = shell.send(opts[:shell], command, &block) output = shell.send(opts[:shell], command, &block)
execution_output(output, opts) execution_output(output, opts)
end end
@ -193,9 +194,11 @@ module VagrantPlugins
# in place. # in place.
# #
# @return The wrapper command to execute # @return The wrapper command to execute
def wrap_in_scheduled_task(command) def wrap_in_scheduled_task(command, interactive)
path = File.expand_path("../scripts/elevated_shell.ps1", __FILE__) path = File.expand_path("../scripts/elevated_shell.ps1", __FILE__)
script = Vagrant::Util::TemplateRenderer.render(path) script = Vagrant::Util::TemplateRenderer.render(path, options: {
interactive: interactive,
})
guest_script_path = "c:/tmp/vagrant-elevated-shell.ps1" guest_script_path = "c:/tmp/vagrant-elevated-shell.ps1"
file = Tempfile.new(["vagrant-elevated-shell", "ps1"]) file = Tempfile.new(["vagrant-elevated-shell", "ps1"])
begin begin

View File

@ -13,7 +13,7 @@ $task_xml = @'
<Principals> <Principals>
<Principal id="Author"> <Principal id="Author">
<UserId>{username}</UserId> <UserId>{username}</UserId>
<LogonType>Password</LogonType> <LogonType><%= options[:interactive] ? 'InteractiveTokenOrPassword' : 'Password' %></LogonType>
<RunLevel>HighestAvailable</RunLevel> <RunLevel>HighestAvailable</RunLevel>
</Principal> </Principal>
</Principals> </Principals>
@ -55,7 +55,7 @@ $schedule.Connect()
$task = $schedule.NewTask($null) $task = $schedule.NewTask($null)
$task.XmlText = $task_xml $task.XmlText = $task_xml
$folder = $schedule.GetFolder("\") $folder = $schedule.GetFolder("\")
$folder.RegisterTaskDefinition($task_name, $task, 6, $username, $password, 1, $null) | Out-Null $folder.RegisterTaskDefinition($task_name, $task, 6, $username, $password, <%= options[:interactive] ? 3 : 1 %>, $null) | Out-Null
$registered_task = $folder.GetTask("\$task_name") $registered_task = $folder.GetTask("\$task_name")
$registered_task.Run($null) | Out-Null $registered_task.Run($null) | Out-Null
@ -71,7 +71,7 @@ function SlurpOutput($out_file, $cur_line) {
if (Test-Path $out_file) { if (Test-Path $out_file) {
get-content $out_file | select -skip $cur_line | ForEach { get-content $out_file | select -skip $cur_line | ForEach {
$cur_line += 1 $cur_line += 1
Write-Host "$_" Write-Host "$_"
} }
} }
return $cur_line return $cur_line

1
plugins/communicators/winrm/shell.rb Normal file → Executable file
View File

@ -23,6 +23,7 @@ module VagrantPlugins
HTTPClient::KeepAliveDisconnected, HTTPClient::KeepAliveDisconnected,
WinRM::WinRMHTTPTransportError, WinRM::WinRMHTTPTransportError,
WinRM::WinRMAuthorizationError, WinRM::WinRMAuthorizationError,
WinRM::WinRMWSManFault,
Errno::EACCES, Errno::EACCES,
Errno::EADDRINUSE, Errno::EADDRINUSE,
Errno::ECONNREFUSED, Errno::ECONNREFUSED,

43
plugins/provisioners/shell/config.rb Normal file → Executable file
View File

@ -12,29 +12,32 @@ module VagrantPlugins
attr_accessor :keep_color attr_accessor :keep_color
attr_accessor :name attr_accessor :name
attr_accessor :powershell_args attr_accessor :powershell_args
attr_accessor :elevated_interactive
def initialize def initialize
@args = UNSET_VALUE @args = UNSET_VALUE
@inline = UNSET_VALUE @inline = UNSET_VALUE
@path = UNSET_VALUE @path = UNSET_VALUE
@upload_path = UNSET_VALUE @upload_path = UNSET_VALUE
@privileged = UNSET_VALUE @privileged = UNSET_VALUE
@binary = UNSET_VALUE @binary = UNSET_VALUE
@keep_color = UNSET_VALUE @keep_color = UNSET_VALUE
@name = UNSET_VALUE @name = UNSET_VALUE
@powershell_args = UNSET_VALUE @powershell_args = UNSET_VALUE
@elevated_interactive = UNSET_VALUE
end end
def finalize! def finalize!
@args = nil if @args == UNSET_VALUE @args = nil if @args == UNSET_VALUE
@inline = nil if @inline == UNSET_VALUE @inline = nil if @inline == UNSET_VALUE
@path = nil if @path == UNSET_VALUE @path = nil if @path == UNSET_VALUE
@upload_path = "/tmp/vagrant-shell" if @upload_path == UNSET_VALUE @upload_path = "/tmp/vagrant-shell" if @upload_path == UNSET_VALUE
@privileged = true if @privileged == UNSET_VALUE @privileged = true if @privileged == UNSET_VALUE
@binary = false if @binary == UNSET_VALUE @binary = false if @binary == UNSET_VALUE
@keep_color = false if @keep_color == UNSET_VALUE @keep_color = false if @keep_color == UNSET_VALUE
@name = nil if @name == UNSET_VALUE @name = nil if @name == UNSET_VALUE
@powershell_args = "-ExecutionPolicy Bypass" if @powershell_args == UNSET_VALUE @powershell_args = "-ExecutionPolicy Bypass" if @powershell_args == UNSET_VALUE
@elevated_interactive = false if @elevated_interactive == UNSET_VALUE
if @args && args_valid? if @args && args_valid?
@args = @args.is_a?(Array) ? @args.map { |a| a.to_s } : @args.to_s @args = @args.is_a?(Array) ? @args.map { |a| a.to_s } : @args.to_s
@ -78,6 +81,10 @@ module VagrantPlugins
errors << I18n.t("vagrant.provisioners.shell.args_bad_type") errors << I18n.t("vagrant.provisioners.shell.args_bad_type")
end end
if elevated_interactive && !privileged
errors << I18n.t("vagrant.provisioners.shell.interactive_not_elevated")
end
{ "shell provisioner" => errors } { "shell provisioner" => errors }
end end

2
plugins/provisioners/shell/provisioner.rb Normal file → Executable file
View File

@ -137,7 +137,7 @@ module VagrantPlugins
end end
# Execute it with sudo # Execute it with sudo
comm.sudo(command, elevated: config.privileged) do |type, data| comm.sudo(command, { elevated: config.privileged, interactive: config.elevated_interactive }) do |type, data|
handle_comm(type, data) handle_comm(type, data)
end end
end end

1
templates/locales/en.yml Normal file → Executable file
View File

@ -2037,6 +2037,7 @@ en:
running: "Running: %{script}" running: "Running: %{script}"
runningas: "Running: %{local} as %{remote}" runningas: "Running: %{local} as %{remote}"
upload_path_not_set: "`upload_path` must be set for the shell provisioner." upload_path_not_set: "`upload_path` must be set for the shell provisioner."
interactive_not_elevated: "To be interactive, it must also be privileged."
ansible: ansible:
errors: errors:

View File

@ -93,6 +93,15 @@ describe VagrantPlugins::CommunicatorWinRM::Communicator do
expect(subject.execute("dir", { elevated: true })).to eq(0) expect(subject.execute("dir", { elevated: true })).to eq(0)
end end
it "wraps command in elevated and interactive shell script when elevated and interactive are true" do
expect(shell).to receive(:upload).with(kind_of(String), "c:/tmp/vagrant-elevated-shell.ps1")
expect(shell).to receive(:powershell) do |cmd|
expect(cmd).to eq("powershell -executionpolicy bypass -file \"c:/tmp/vagrant-elevated-shell.ps1\" " +
"-username \"vagrant\" -password \"password\" -encoded_command \"ZABpAHIAOwAgAGUAeABpAHQAIAAkAEwAQQBTAFQARQBYAEkAVABDAE8ARABFAA==\"")
end.and_return({ exitcode: 0 })
expect(subject.execute("dir", { elevated: true, interactive: true })).to eq(0)
end
it "can use cmd shell" do it "can use cmd shell" do
expect(shell).to receive(:cmd).with(kind_of(String)).and_return({ exitcode: 0 }) expect(shell).to receive(:cmd).with(kind_of(String)).and_return({ exitcode: 0 })
expect(subject.execute("dir", { shell: :cmd })).to eq(0) expect(subject.execute("dir", { shell: :cmd })).to eq(0)

View File

@ -85,6 +85,19 @@ describe "VagrantPlugins::Shell::Config" do
I18n.t("vagrant.provisioners.shell.args_bad_type") I18n.t("vagrant.provisioners.shell.args_bad_type")
]) ])
end end
it "returns an error if elevated_interactive is true but privileged is false" do
subject.path = file_that_exists
subject.elevated_interactive = true
subject.privileged = false
subject.finalize!
result = subject.validate(machine)
expect(result["shell provisioner"]).to eq([
I18n.t("vagrant.provisioners.shell.interactive_not_elevated")
])
end
end end
describe 'finalize!' do describe 'finalize!' do

10
website/docs/source/v2/provisioning/shell.html.md Normal file → Executable file
View File

@ -45,8 +45,9 @@ The remainder of the available options are optional:
defaults to "true". defaults to "true".
* `privileged` (boolean) - Specifies whether to execute the shell script * `privileged` (boolean) - Specifies whether to execute the shell script
as a privileged user or not (`sudo`). By default this is "true". This has as a privileged user or not (`sudo`). By default this is "true". Windows
no effect for Windows guests. guests use a scheduled task to run as a true administrator without the
WinRM limitations.
* `upload_path` (string) - Is the remote path where the shell script will * `upload_path` (string) - Is the remote path where the shell script will
be uploaded to. The script is uploaded as the SSH user over SCP, so this be uploaded to. The script is uploaded as the SSH user over SCP, so this
@ -65,6 +66,11 @@ The remainder of the available options are optional:
* `powershell_args` (string) - Extra arguments to pass to `PowerShell` * `powershell_args` (string) - Extra arguments to pass to `PowerShell`
if you're provisioning with PowerShell on Windows. if you're provisioning with PowerShell on Windows.
* `elevated_interactive` (boolean) - Run an elevated script in interactive mode
on Windows. By default this is "false". Must also be `privileged`. Be sure to
enable auto-login for Windows as the user must be logged in for interactive
mode to work.
<a name="inline-scripts"></a> <a name="inline-scripts"></a>
## Inline Scripts ## Inline Scripts